From 35e4f8f677c5c6c75f62cec4784252c297b72dcf Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 2 Jan 2026 12:06:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E6=8F=90=E4=BA=A4=EF=BC=9ADe?= =?UTF-8?q?signHub=E8=AE=BE=E8=AE=A1=E5=B8=88=E4=BB=BB=E5=8A=A1=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 68 + DEPLOYMENT_GUIDE.md | 850 +++ DEVELOPMENT_GUIDE.md | 418 ++ Nginx缓存配置说明.md | 459 ++ PROJECT_REQUIREMENTS.md | 6335 +++++++++++++++++ README.md | 362 + S3文件存储系统实现说明.md | 331 + S3部署说明.md | 195 + backend/backend-src.tar.gz | Bin 0 -> 125095 bytes backend/backend_src.tar.gz | Bin 0 -> 125281 bytes backend/pom-s3-dependency.xml | 15 + backend/pom.xml | 205 + .../com/designhub/DesignHubApplication.java | 36 + .../annotation/RequirePermission.java | 30 + .../com/designhub/annotation/RequireRole.java | 30 + .../java/com/designhub/common/Result.java | 110 + .../java/com/designhub/common/ResultCode.java | 59 + .../java/com/designhub/config/CorsConfig.java | 40 + .../com/designhub/config/Knife4jConfig.java | 32 + .../designhub/config/MybatisPlusConfig.java | 29 + .../config/SystemConfigInitializer.java | 31 + .../java/com/designhub/config/WebConfig.java | 38 + .../controller/AchievementController.java | 53 + .../controller/AdminTaskController.java | 155 + .../controller/AnalyticsController.java | 44 + .../designhub/controller/AuthController.java | 130 + .../controller/CheckInController.java | 84 + .../controller/ConfigController.java | 271 + .../controller/ContestController.java | 122 + .../controller/DelayPenaltyController.java | 72 + .../controller/DepartmentController.java | 114 + .../controller/DesignerController.java | 54 + .../controller/DingTalkController.java | 174 + .../controller/ExportController.java | 55 + .../controller/FileUploadController.java | 184 + .../designhub/controller/HelloController.java | 50 + .../controller/NotificationController.java | 70 + .../controller/PermissionController.java | 73 + .../controller/RewardRuleController.java | 92 + .../designhub/controller/RoleController.java | 79 + .../controller/S3ConfigController.java | 132 + .../controller/ScorePoolController.java | 102 + .../controller/SystemConfigController.java | 87 + .../controller/SystemHealthController.java | 30 + .../controller/TaskApprovalController.java | 93 + .../designhub/controller/TaskController.java | 400 ++ .../controller/TaskItemController.java | 174 + .../controller/TaskSubTypeController.java | 81 + .../controller/TeamPKController.java | 85 + .../ThirdPartyCallbackController.java | 72 + .../controller/UploadController.java | 171 + .../designhub/controller/UserController.java | 129 + .../controller/UserGoalController.java | 117 + .../dto/DepartmentCreateRequest.java | 86 + .../dto/DepartmentLimitSettingsRequest.java | 37 + .../dto/DepartmentScoreAdjustRequest.java | 21 + .../dto/DepartmentUpdateRequest.java | 53 + .../main/java/com/designhub/dto/LoginDTO.java | 34 + .../com/designhub/dto/PipelineTaskDTO.java | 78 + .../java/com/designhub/dto/RegisterDTO.java | 52 + .../com/designhub/dto/TaskApprovalDTO.java | 36 + .../com/designhub/dto/TaskAssignRequest.java | 33 + .../com/designhub/dto/TaskAuditRequest.java | 19 + .../java/com/designhub/dto/TaskUpdateDTO.java | 116 + .../com/designhub/entity/Achievement.java | 95 + .../main/java/com/designhub/entity/Brand.java | 69 + .../java/com/designhub/entity/Category.java | 84 + .../java/com/designhub/entity/CheckIn.java | 68 + .../java/com/designhub/entity/Contest.java | 110 + .../designhub/entity/ContestParticipant.java | 107 + .../designhub/entity/DelayPenaltyRecord.java | 106 + .../designhub/entity/DelayPenaltyRule.java | 86 + .../java/com/designhub/entity/DemandType.java | 75 + .../entity/DemandTypeDepartment.java | 38 + .../java/com/designhub/entity/Department.java | 235 + .../com/designhub/entity/DingTalkConfig.java | 88 + .../java/com/designhub/entity/FileUpload.java | 200 + .../com/designhub/entity/Notification.java | 268 + .../java/com/designhub/entity/Permission.java | 96 + .../java/com/designhub/entity/Platform.java | 69 + .../java/com/designhub/entity/RewardRule.java | 109 + .../main/java/com/designhub/entity/Role.java | 103 + .../com/designhub/entity/RolePermission.java | 39 + .../java/com/designhub/entity/S3Config.java | 187 + .../java/com/designhub/entity/ScorePool.java | 148 + .../com/designhub/entity/ScorePoolRecord.java | 76 + .../com/designhub/entity/SystemConfig.java | 81 + .../main/java/com/designhub/entity/Task.java | 517 ++ .../com/designhub/entity/TaskApprovalLog.java | 136 + .../com/designhub/entity/TaskAssignLog.java | 53 + .../com/designhub/entity/TaskAuditLog.java | 53 + .../java/com/designhub/entity/TaskItem.java | 197 + .../entity/TaskProgressUpdateLog.java | 99 + .../java/com/designhub/entity/TaskReview.java | 65 + .../com/designhub/entity/TaskSubType.java | 124 + .../java/com/designhub/entity/TeamPK.java | 359 + .../com/designhub/entity/TeamScorePool.java | 135 + .../designhub/entity/ThirdPartyCallback.java | 76 + .../main/java/com/designhub/entity/User.java | 101 + .../com/designhub/entity/UserAchievement.java | 72 + .../java/com/designhub/entity/UserGoal.java | 108 + .../java/com/designhub/entity/UserRole.java | 39 + .../exception/BusinessException.java | 50 + .../exception/GlobalExceptionHandler.java | 84 + .../interceptor/PermissionInterceptor.java | 166 + .../designhub/mapper/AchievementMapper.java | 13 + .../com/designhub/mapper/BrandMapper.java | 13 + .../com/designhub/mapper/CategoryMapper.java | 13 + .../com/designhub/mapper/CheckInMapper.java | 29 + .../com/designhub/mapper/ContestMapper.java | 12 + .../mapper/ContestParticipantMapper.java | 28 + .../mapper/DelayPenaltyRecordMapper.java | 35 + .../mapper/DelayPenaltyRuleMapper.java | 22 + .../mapper/DemandTypeDepartmentMapper.java | 28 + .../designhub/mapper/DemandTypeMapper.java | 13 + .../designhub/mapper/DepartmentMapper.java | 22 + .../mapper/DingTalkConfigMapper.java | 12 + .../designhub/mapper/FileUploadMapper.java | 12 + .../designhub/mapper/NotificationMapper.java | 26 + .../designhub/mapper/PermissionMapper.java | 33 + .../com/designhub/mapper/PlatformMapper.java | 13 + .../designhub/mapper/RewardRuleMapper.java | 12 + .../java/com/designhub/mapper/RoleMapper.java | 25 + .../mapper/RolePermissionMapper.java | 13 + .../com/designhub/mapper/S3ConfigMapper.java | 19 + .../com/designhub/mapper/ScorePoolMapper.java | 12 + .../mapper/ScorePoolRecordMapper.java | 12 + .../designhub/mapper/SystemConfigMapper.java | 13 + .../mapper/TaskApprovalLogMapper.java | 12 + .../designhub/mapper/TaskAssignLogMapper.java | 12 + .../designhub/mapper/TaskAuditLogMapper.java | 12 + .../com/designhub/mapper/TaskItemMapper.java | 177 + .../java/com/designhub/mapper/TaskMapper.java | 206 + .../mapper/TaskProgressUpdateLogMapper.java | 12 + .../designhub/mapper/TaskReviewMapper.java | 13 + .../designhub/mapper/TaskSubTypeMapper.java | 12 + .../com/designhub/mapper/TeamPKMapper.java | 12 + .../designhub/mapper/TeamScorePoolMapper.java | 13 + .../mapper/ThirdPartyCallbackMapper.java | 12 + .../mapper/UserAchievementMapper.java | 13 + .../com/designhub/mapper/UserGoalMapper.java | 12 + .../java/com/designhub/mapper/UserMapper.java | 12 + .../com/designhub/mapper/UserRoleMapper.java | 13 + .../designhub/service/AchievementService.java | 208 + .../designhub/service/AdminTaskService.java | 425 ++ .../designhub/service/AnalyticsService.java | 514 ++ .../com/designhub/service/CheckInService.java | 164 + .../com/designhub/service/ConfigService.java | 266 + .../com/designhub/service/ContestService.java | 483 ++ .../service/DelayPenaltyService.java | 183 + .../service/DemandTypeDepartmentService.java | 74 + .../designhub/service/DepartmentService.java | 459 ++ .../designhub/service/DesignerService.java | 417 ++ .../designhub/service/DingTalkService.java | 507 ++ .../com/designhub/service/ExportService.java | 156 + .../service/NotificationService.java | 240 + .../designhub/service/PermissionService.java | 190 + .../designhub/service/RewardRuleService.java | 471 ++ .../com/designhub/service/RoleService.java | 198 + .../designhub/service/S3ConfigService.java | 120 + .../java/com/designhub/service/S3Service.java | 146 + .../designhub/service/ScorePoolService.java | 419 ++ .../service/SystemConfigService.java | 257 + .../service/SystemHealthService.java | 63 + .../service/TaskApprovalService.java | 518 ++ .../designhub/service/TaskItemService.java | 365 + .../service/TaskProgressCheckService.java | 186 + .../com/designhub/service/TaskService.java | 1723 +++++ .../designhub/service/TaskSubTypeService.java | 145 + .../com/designhub/service/TeamPKService.java | 455 ++ .../service/TeamScorePoolService.java | 307 + .../service/ThirdPartyCallbackService.java | 173 + .../designhub/service/UserGoalService.java | 358 + .../com/designhub/service/UserService.java | 857 +++ .../com/designhub/util/ExcelExportUtil.java | 190 + .../main/java/com/designhub/util/JwtUtil.java | 184 + .../com/designhub/vo/DashboardStatsVO.java | 67 + .../com/designhub/vo/DemandStatisticsVO.java | 141 + .../designhub/vo/DepartmentOverviewVO.java | 36 + .../com/designhub/vo/DesignerStatsVO.java | 138 + .../main/java/com/designhub/vo/LoginVO.java | 64 + .../java/com/designhub/vo/SystemHealthVO.java | 73 + .../java/com/designhub/vo/UserInfoVO.java | 79 + .../src/main/resources/application-dev.yml | 94 + .../src/main/resources/application-prod.yml | 101 + backend/src/main/resources/application.yml | 32 + .../db/migration/V8__add_incentive_tables.sql | 235 + database/.gitkeep_if_needed | 0 database/README_DATABASE_SYNC.md | 170 + database/init-data.sql | 162 + database/schema.sql | 432 ++ database/sync_database.sh | 154 + database/sync_database_structure.sql | 203 + deploy_to_public_server.sh | 505 ++ frontend/.env.development | 18 + frontend/.env.production | 3 + frontend/.eslintrc-auto-import.json | 91 + frontend/dist.tar.gz | Bin 0 -> 863638 bytes frontend/index.html | 15 + frontend/package-lock.json | 4793 +++++++++++++ frontend/package.json | 43 + frontend/pnpm-lock.yaml | 3030 ++++++++ frontend/src/App.vue | 46 + frontend/src/api/admin.ts | 73 + frontend/src/api/analytics.ts | 131 + frontend/src/api/auth.ts | 96 + frontend/src/api/config.ts | 313 + frontend/src/api/contest.ts | 153 + frontend/src/api/delayPenalty.ts | 61 + frontend/src/api/department.ts | 99 + frontend/src/api/designer.ts | 63 + frontend/src/api/dingtalk.ts | 81 + frontend/src/api/file.ts | 176 + frontend/src/api/goal.ts | 127 + frontend/src/api/incentive.ts | 116 + frontend/src/api/notification.ts | 81 + frontend/src/api/permission.ts | 87 + frontend/src/api/rewardRule.ts | 142 + frontend/src/api/role.ts | 85 + frontend/src/api/s3.ts | 70 + frontend/src/api/scorePool.ts | 181 + frontend/src/api/system.ts | 99 + frontend/src/api/task.ts | 516 ++ frontend/src/api/teamPK.ts | 114 + frontend/src/api/types.ts | 29 + frontend/src/api/upload.ts | 63 + frontend/src/api/user.ts | 141 + .../src/components/DailyProgressReminder.vue | 391 + frontend/src/components/Icon.vue | 219 + frontend/src/components/IncentiveModal.vue | 281 + frontend/src/components/MilestoneCard.vue | 390 + frontend/src/components/Navbar.vue | 438 ++ frontend/src/components/NotificationBell.vue | 410 ++ frontend/src/components/RankingCard.vue | 293 + frontend/src/components/TaskCard.vue | 442 ++ frontend/src/layouts/DefaultLayout.vue | 25 + frontend/src/main.ts | 17 + frontend/src/router/index.ts | 248 + frontend/src/stores/auth.ts | 167 + frontend/src/stores/config.ts | 132 + frontend/src/stores/navbarConfig.ts | 144 + frontend/src/stores/siteSettings.ts | 126 + frontend/src/styles/index.scss | 1440 ++++ frontend/src/styles/variables.scss | 126 + frontend/src/types/auto-imports.d.ts | 87 + frontend/src/types/components.d.ts | 67 + frontend/src/utils/request.ts | 116 + frontend/src/views/Achievements.vue | 533 ++ frontend/src/views/Analytics.vue | 887 +++ frontend/src/views/CheckIn.vue | 559 ++ frontend/src/views/Contests.vue | 734 ++ frontend/src/views/CreateTask.vue | 1482 ++++ frontend/src/views/DesignerHall.vue | 944 +++ frontend/src/views/Goals.vue | 650 ++ frontend/src/views/IncentiveCenter.vue | 507 ++ frontend/src/views/LeaderWorkbench.vue | 697 ++ frontend/src/views/Login.vue | 846 +++ frontend/src/views/MyTasks.vue | 1785 +++++ frontend/src/views/NotFound.vue | 283 + frontend/src/views/Notifications.vue | 491 ++ frontend/src/views/RoleManagement.vue | 280 + frontend/src/views/Settings.vue | 3715 ++++++++++ frontend/src/views/TaskHall.vue | 3626 ++++++++++ frontend/src/views/TaskHall.vue.backup | 2821 ++++++++ frontend/src/views/TeamPK.vue | 675 ++ frontend/src/views/UserManagement.vue | 624 ++ frontend/src/views/admin/RewardRules.vue | 543 ++ .../src/views/admin/ScorePoolManagement.vue | 797 +++ frontend/src/views/admin/TaskManagement.vue | 472 ++ frontend/tsconfig.json | 37 + frontend/tsconfig.node.json | 11 + frontend/vite.config.ts | 63 + nginx.conf | 47 + scripts/health-check.sh | 30 + 公网服务器部署指南.md | 407 ++ 域名和HTTPS配置指南.md | 533 ++ 服务器和数据库配置清单.md | 849 +++ 配置SSL证书说明.md | 113 + 钉钉登录完整配置指南.md | 881 +++ 279 files changed, 77455 insertions(+) create mode 100644 .gitignore create mode 100644 DEPLOYMENT_GUIDE.md create mode 100644 DEVELOPMENT_GUIDE.md create mode 100644 Nginx缓存配置说明.md create mode 100644 PROJECT_REQUIREMENTS.md create mode 100644 README.md create mode 100644 S3文件存储系统实现说明.md create mode 100644 S3部署说明.md create mode 100644 backend/backend-src.tar.gz create mode 100644 backend/backend_src.tar.gz create mode 100644 backend/pom-s3-dependency.xml create mode 100644 backend/pom.xml create mode 100644 backend/src/main/java/com/designhub/DesignHubApplication.java create mode 100644 backend/src/main/java/com/designhub/annotation/RequirePermission.java create mode 100644 backend/src/main/java/com/designhub/annotation/RequireRole.java create mode 100644 backend/src/main/java/com/designhub/common/Result.java create mode 100644 backend/src/main/java/com/designhub/common/ResultCode.java create mode 100644 backend/src/main/java/com/designhub/config/CorsConfig.java create mode 100644 backend/src/main/java/com/designhub/config/Knife4jConfig.java create mode 100644 backend/src/main/java/com/designhub/config/MybatisPlusConfig.java create mode 100644 backend/src/main/java/com/designhub/config/SystemConfigInitializer.java create mode 100644 backend/src/main/java/com/designhub/config/WebConfig.java create mode 100644 backend/src/main/java/com/designhub/controller/AchievementController.java create mode 100644 backend/src/main/java/com/designhub/controller/AdminTaskController.java create mode 100644 backend/src/main/java/com/designhub/controller/AnalyticsController.java create mode 100644 backend/src/main/java/com/designhub/controller/AuthController.java create mode 100644 backend/src/main/java/com/designhub/controller/CheckInController.java create mode 100644 backend/src/main/java/com/designhub/controller/ConfigController.java create mode 100644 backend/src/main/java/com/designhub/controller/ContestController.java create mode 100644 backend/src/main/java/com/designhub/controller/DelayPenaltyController.java create mode 100644 backend/src/main/java/com/designhub/controller/DepartmentController.java create mode 100644 backend/src/main/java/com/designhub/controller/DesignerController.java create mode 100644 backend/src/main/java/com/designhub/controller/DingTalkController.java create mode 100644 backend/src/main/java/com/designhub/controller/ExportController.java create mode 100644 backend/src/main/java/com/designhub/controller/FileUploadController.java create mode 100644 backend/src/main/java/com/designhub/controller/HelloController.java create mode 100644 backend/src/main/java/com/designhub/controller/NotificationController.java create mode 100644 backend/src/main/java/com/designhub/controller/PermissionController.java create mode 100644 backend/src/main/java/com/designhub/controller/RewardRuleController.java create mode 100644 backend/src/main/java/com/designhub/controller/RoleController.java create mode 100644 backend/src/main/java/com/designhub/controller/S3ConfigController.java create mode 100644 backend/src/main/java/com/designhub/controller/ScorePoolController.java create mode 100644 backend/src/main/java/com/designhub/controller/SystemConfigController.java create mode 100644 backend/src/main/java/com/designhub/controller/SystemHealthController.java create mode 100644 backend/src/main/java/com/designhub/controller/TaskApprovalController.java create mode 100644 backend/src/main/java/com/designhub/controller/TaskController.java create mode 100644 backend/src/main/java/com/designhub/controller/TaskItemController.java create mode 100644 backend/src/main/java/com/designhub/controller/TaskSubTypeController.java create mode 100644 backend/src/main/java/com/designhub/controller/TeamPKController.java create mode 100644 backend/src/main/java/com/designhub/controller/ThirdPartyCallbackController.java create mode 100644 backend/src/main/java/com/designhub/controller/UploadController.java create mode 100644 backend/src/main/java/com/designhub/controller/UserController.java create mode 100644 backend/src/main/java/com/designhub/controller/UserGoalController.java create mode 100644 backend/src/main/java/com/designhub/dto/DepartmentCreateRequest.java create mode 100644 backend/src/main/java/com/designhub/dto/DepartmentLimitSettingsRequest.java create mode 100644 backend/src/main/java/com/designhub/dto/DepartmentScoreAdjustRequest.java create mode 100644 backend/src/main/java/com/designhub/dto/DepartmentUpdateRequest.java create mode 100644 backend/src/main/java/com/designhub/dto/LoginDTO.java create mode 100644 backend/src/main/java/com/designhub/dto/PipelineTaskDTO.java create mode 100644 backend/src/main/java/com/designhub/dto/RegisterDTO.java create mode 100644 backend/src/main/java/com/designhub/dto/TaskApprovalDTO.java create mode 100644 backend/src/main/java/com/designhub/dto/TaskAssignRequest.java create mode 100644 backend/src/main/java/com/designhub/dto/TaskAuditRequest.java create mode 100644 backend/src/main/java/com/designhub/dto/TaskUpdateDTO.java create mode 100644 backend/src/main/java/com/designhub/entity/Achievement.java create mode 100644 backend/src/main/java/com/designhub/entity/Brand.java create mode 100644 backend/src/main/java/com/designhub/entity/Category.java create mode 100644 backend/src/main/java/com/designhub/entity/CheckIn.java create mode 100644 backend/src/main/java/com/designhub/entity/Contest.java create mode 100644 backend/src/main/java/com/designhub/entity/ContestParticipant.java create mode 100644 backend/src/main/java/com/designhub/entity/DelayPenaltyRecord.java create mode 100644 backend/src/main/java/com/designhub/entity/DelayPenaltyRule.java create mode 100644 backend/src/main/java/com/designhub/entity/DemandType.java create mode 100644 backend/src/main/java/com/designhub/entity/DemandTypeDepartment.java create mode 100644 backend/src/main/java/com/designhub/entity/Department.java create mode 100644 backend/src/main/java/com/designhub/entity/DingTalkConfig.java create mode 100644 backend/src/main/java/com/designhub/entity/FileUpload.java create mode 100644 backend/src/main/java/com/designhub/entity/Notification.java create mode 100644 backend/src/main/java/com/designhub/entity/Permission.java create mode 100644 backend/src/main/java/com/designhub/entity/Platform.java create mode 100644 backend/src/main/java/com/designhub/entity/RewardRule.java create mode 100644 backend/src/main/java/com/designhub/entity/Role.java create mode 100644 backend/src/main/java/com/designhub/entity/RolePermission.java create mode 100644 backend/src/main/java/com/designhub/entity/S3Config.java create mode 100644 backend/src/main/java/com/designhub/entity/ScorePool.java create mode 100644 backend/src/main/java/com/designhub/entity/ScorePoolRecord.java create mode 100644 backend/src/main/java/com/designhub/entity/SystemConfig.java create mode 100644 backend/src/main/java/com/designhub/entity/Task.java create mode 100644 backend/src/main/java/com/designhub/entity/TaskApprovalLog.java create mode 100644 backend/src/main/java/com/designhub/entity/TaskAssignLog.java create mode 100644 backend/src/main/java/com/designhub/entity/TaskAuditLog.java create mode 100644 backend/src/main/java/com/designhub/entity/TaskItem.java create mode 100644 backend/src/main/java/com/designhub/entity/TaskProgressUpdateLog.java create mode 100644 backend/src/main/java/com/designhub/entity/TaskReview.java create mode 100644 backend/src/main/java/com/designhub/entity/TaskSubType.java create mode 100644 backend/src/main/java/com/designhub/entity/TeamPK.java create mode 100644 backend/src/main/java/com/designhub/entity/TeamScorePool.java create mode 100644 backend/src/main/java/com/designhub/entity/ThirdPartyCallback.java create mode 100644 backend/src/main/java/com/designhub/entity/User.java create mode 100644 backend/src/main/java/com/designhub/entity/UserAchievement.java create mode 100644 backend/src/main/java/com/designhub/entity/UserGoal.java create mode 100644 backend/src/main/java/com/designhub/entity/UserRole.java create mode 100644 backend/src/main/java/com/designhub/exception/BusinessException.java create mode 100644 backend/src/main/java/com/designhub/exception/GlobalExceptionHandler.java create mode 100644 backend/src/main/java/com/designhub/interceptor/PermissionInterceptor.java create mode 100644 backend/src/main/java/com/designhub/mapper/AchievementMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/BrandMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/CategoryMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/CheckInMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/ContestMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/ContestParticipantMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/DelayPenaltyRecordMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/DelayPenaltyRuleMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/DemandTypeDepartmentMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/DemandTypeMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/DepartmentMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/DingTalkConfigMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/FileUploadMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/NotificationMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/PermissionMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/PlatformMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/RewardRuleMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/RoleMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/RolePermissionMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/S3ConfigMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/ScorePoolMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/ScorePoolRecordMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/SystemConfigMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/TaskApprovalLogMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/TaskAssignLogMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/TaskAuditLogMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/TaskItemMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/TaskMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/TaskProgressUpdateLogMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/TaskReviewMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/TaskSubTypeMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/TeamPKMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/TeamScorePoolMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/ThirdPartyCallbackMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/UserAchievementMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/UserGoalMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/UserMapper.java create mode 100644 backend/src/main/java/com/designhub/mapper/UserRoleMapper.java create mode 100644 backend/src/main/java/com/designhub/service/AchievementService.java create mode 100644 backend/src/main/java/com/designhub/service/AdminTaskService.java create mode 100644 backend/src/main/java/com/designhub/service/AnalyticsService.java create mode 100644 backend/src/main/java/com/designhub/service/CheckInService.java create mode 100644 backend/src/main/java/com/designhub/service/ConfigService.java create mode 100644 backend/src/main/java/com/designhub/service/ContestService.java create mode 100644 backend/src/main/java/com/designhub/service/DelayPenaltyService.java create mode 100644 backend/src/main/java/com/designhub/service/DemandTypeDepartmentService.java create mode 100644 backend/src/main/java/com/designhub/service/DepartmentService.java create mode 100644 backend/src/main/java/com/designhub/service/DesignerService.java create mode 100644 backend/src/main/java/com/designhub/service/DingTalkService.java create mode 100644 backend/src/main/java/com/designhub/service/ExportService.java create mode 100644 backend/src/main/java/com/designhub/service/NotificationService.java create mode 100644 backend/src/main/java/com/designhub/service/PermissionService.java create mode 100644 backend/src/main/java/com/designhub/service/RewardRuleService.java create mode 100644 backend/src/main/java/com/designhub/service/RoleService.java create mode 100644 backend/src/main/java/com/designhub/service/S3ConfigService.java create mode 100644 backend/src/main/java/com/designhub/service/S3Service.java create mode 100644 backend/src/main/java/com/designhub/service/ScorePoolService.java create mode 100644 backend/src/main/java/com/designhub/service/SystemConfigService.java create mode 100644 backend/src/main/java/com/designhub/service/SystemHealthService.java create mode 100644 backend/src/main/java/com/designhub/service/TaskApprovalService.java create mode 100644 backend/src/main/java/com/designhub/service/TaskItemService.java create mode 100644 backend/src/main/java/com/designhub/service/TaskProgressCheckService.java create mode 100644 backend/src/main/java/com/designhub/service/TaskService.java create mode 100644 backend/src/main/java/com/designhub/service/TaskSubTypeService.java create mode 100644 backend/src/main/java/com/designhub/service/TeamPKService.java create mode 100644 backend/src/main/java/com/designhub/service/TeamScorePoolService.java create mode 100644 backend/src/main/java/com/designhub/service/ThirdPartyCallbackService.java create mode 100644 backend/src/main/java/com/designhub/service/UserGoalService.java create mode 100644 backend/src/main/java/com/designhub/service/UserService.java create mode 100644 backend/src/main/java/com/designhub/util/ExcelExportUtil.java create mode 100644 backend/src/main/java/com/designhub/util/JwtUtil.java create mode 100644 backend/src/main/java/com/designhub/vo/DashboardStatsVO.java create mode 100644 backend/src/main/java/com/designhub/vo/DemandStatisticsVO.java create mode 100644 backend/src/main/java/com/designhub/vo/DepartmentOverviewVO.java create mode 100644 backend/src/main/java/com/designhub/vo/DesignerStatsVO.java create mode 100644 backend/src/main/java/com/designhub/vo/LoginVO.java create mode 100644 backend/src/main/java/com/designhub/vo/SystemHealthVO.java create mode 100644 backend/src/main/java/com/designhub/vo/UserInfoVO.java create mode 100644 backend/src/main/resources/application-dev.yml create mode 100644 backend/src/main/resources/application-prod.yml create mode 100644 backend/src/main/resources/application.yml create mode 100644 backend/src/main/resources/db/migration/V8__add_incentive_tables.sql create mode 100644 database/.gitkeep_if_needed create mode 100644 database/README_DATABASE_SYNC.md create mode 100644 database/init-data.sql create mode 100644 database/schema.sql create mode 100755 database/sync_database.sh create mode 100644 database/sync_database_structure.sql create mode 100755 deploy_to_public_server.sh create mode 100644 frontend/.env.development create mode 100644 frontend/.env.production create mode 100644 frontend/.eslintrc-auto-import.json create mode 100644 frontend/dist.tar.gz create mode 100644 frontend/index.html create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/pnpm-lock.yaml create mode 100644 frontend/src/App.vue create mode 100644 frontend/src/api/admin.ts create mode 100644 frontend/src/api/analytics.ts create mode 100644 frontend/src/api/auth.ts create mode 100644 frontend/src/api/config.ts create mode 100644 frontend/src/api/contest.ts create mode 100644 frontend/src/api/delayPenalty.ts create mode 100644 frontend/src/api/department.ts create mode 100644 frontend/src/api/designer.ts create mode 100644 frontend/src/api/dingtalk.ts create mode 100644 frontend/src/api/file.ts create mode 100644 frontend/src/api/goal.ts create mode 100644 frontend/src/api/incentive.ts create mode 100644 frontend/src/api/notification.ts create mode 100644 frontend/src/api/permission.ts create mode 100644 frontend/src/api/rewardRule.ts create mode 100644 frontend/src/api/role.ts create mode 100644 frontend/src/api/s3.ts create mode 100644 frontend/src/api/scorePool.ts create mode 100644 frontend/src/api/system.ts create mode 100644 frontend/src/api/task.ts create mode 100644 frontend/src/api/teamPK.ts create mode 100644 frontend/src/api/types.ts create mode 100644 frontend/src/api/upload.ts create mode 100644 frontend/src/api/user.ts create mode 100644 frontend/src/components/DailyProgressReminder.vue create mode 100644 frontend/src/components/Icon.vue create mode 100644 frontend/src/components/IncentiveModal.vue create mode 100644 frontend/src/components/MilestoneCard.vue create mode 100644 frontend/src/components/Navbar.vue create mode 100644 frontend/src/components/NotificationBell.vue create mode 100644 frontend/src/components/RankingCard.vue create mode 100644 frontend/src/components/TaskCard.vue create mode 100644 frontend/src/layouts/DefaultLayout.vue create mode 100644 frontend/src/main.ts create mode 100644 frontend/src/router/index.ts create mode 100644 frontend/src/stores/auth.ts create mode 100644 frontend/src/stores/config.ts create mode 100644 frontend/src/stores/navbarConfig.ts create mode 100644 frontend/src/stores/siteSettings.ts create mode 100644 frontend/src/styles/index.scss create mode 100644 frontend/src/styles/variables.scss create mode 100644 frontend/src/types/auto-imports.d.ts create mode 100644 frontend/src/types/components.d.ts create mode 100644 frontend/src/utils/request.ts create mode 100644 frontend/src/views/Achievements.vue create mode 100644 frontend/src/views/Analytics.vue create mode 100644 frontend/src/views/CheckIn.vue create mode 100644 frontend/src/views/Contests.vue create mode 100644 frontend/src/views/CreateTask.vue create mode 100644 frontend/src/views/DesignerHall.vue create mode 100644 frontend/src/views/Goals.vue create mode 100644 frontend/src/views/IncentiveCenter.vue create mode 100644 frontend/src/views/LeaderWorkbench.vue create mode 100644 frontend/src/views/Login.vue create mode 100644 frontend/src/views/MyTasks.vue create mode 100644 frontend/src/views/NotFound.vue create mode 100644 frontend/src/views/Notifications.vue create mode 100644 frontend/src/views/RoleManagement.vue create mode 100644 frontend/src/views/Settings.vue create mode 100644 frontend/src/views/TaskHall.vue create mode 100644 frontend/src/views/TaskHall.vue.backup create mode 100644 frontend/src/views/TeamPK.vue create mode 100644 frontend/src/views/UserManagement.vue create mode 100644 frontend/src/views/admin/RewardRules.vue create mode 100644 frontend/src/views/admin/ScorePoolManagement.vue create mode 100644 frontend/src/views/admin/TaskManagement.vue create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts create mode 100644 nginx.conf create mode 100644 scripts/health-check.sh create mode 100644 公网服务器部署指南.md create mode 100644 域名和HTTPS配置指南.md create mode 100644 服务器和数据库配置清单.md create mode 100644 配置SSL证书说明.md create mode 100644 钉钉登录完整配置指南.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e2fd6ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Build outputs +dist/ +dist-ssr/ +build/ +target/ +*.jar +*.war +*.ear + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store + +# Environment files +.env +.env.local +.env.*.local +*.log + +# Database +*.sql~ +*.db + +# OS +Thumbs.db +ehthumbs.db + +# Java +*.class +*.log +*.ctxt +.mtj.tmp/ +hs_err_pid* + +# Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +# Spring Boot +.springBeans +.sts4-cache/ + +# Redis +dump.rdb + +# Logs +logs/ +*.log + diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md new file mode 100644 index 0000000..17e16a8 --- /dev/null +++ b/DEPLOYMENT_GUIDE.md @@ -0,0 +1,850 @@ +# 🚀 DesignHub 部署指南 + +本文档详细说明如何在本地开发环境和生产环境中部署 DesignHub 系统。 + +--- + +## 📋 目录 + +1. [环境要求](#环境要求) +2. [本地开发环境部署](#本地开发环境部署) +3. [生产环境部署](#生产环境部署) +4. [Docker部署](#docker部署) +5. [常见问题](#常见问题) +6. [维护与监控](#维护与监控) + +--- + +## 🔧 环境要求 + +### 开发环境 +| 组件 | 版本要求 | 说明 | +|------|---------|------| +| JDK | 17+ | 建议使用 OpenJDK 17 | +| Node.js | 18+ | 建议使用 LTS 版本 | +| MySQL | 8.0+ | 必须 | +| Redis | 6.0+ | 建议 7.0+ | +| Maven | 3.6+ | 后端构建工具 | +| pnpm | 8.0+ | 前端包管理器(可选npm/yarn) | + +### 生产环境 +| 组件 | 版本要求 | 配置建议 | +|------|---------|---------| +| 服务器 | Linux | CentOS 7+, Ubuntu 20.04+ | +| CPU | 4核+ | 推荐 8核 | +| 内存 | 8GB+ | 推荐 16GB | +| 磁盘 | 50GB+ | SSD推荐 | +| 带宽 | 10Mbps+ | 推荐 100Mbps | + +--- + +## 💻 本地开发环境部署 + +### 1. 克隆项目 + +```bash +# 克隆代码仓库 +cd /Users/lanmu/Desktop/qiangdanxitong +# 或使用 Git +# git clone +``` + +### 2. 配置数据库 + +#### 2.1 安装 MySQL 8.0 + +**macOS**: +```bash +brew install mysql@8.0 +brew services start mysql@8.0 +``` + +**Linux**: +```bash +sudo apt update +sudo apt install mysql-server +sudo systemctl start mysql +sudo systemctl enable mysql +``` + +**Windows**: +- 下载MySQL 8.0安装包 +- 运行安装向导 + +#### 2.2 创建数据库 + +```sql +-- 登录MySQL +mysql -u root -p + +-- 创建数据库 +CREATE DATABASE design_hub CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- 创建用户(可选) +CREATE USER 'designhub'@'localhost' IDENTIFIED BY 'your_password'; +GRANT ALL PRIVILEGES ON design_hub.* TO 'designhub'@'localhost'; +FLUSH PRIVILEGES; +``` + +#### 2.3 导入数据库表 + +```bash +# 执行数据库脚本(如果有SQL文件) +mysql -u root -p design_hub < database/schema.sql +``` + +**注意**:项目使用MyBatis Plus,实体类会自动映射到数据库表。确保在启动前,数据库连接配置正确。 + +### 3. 配置 Redis + +#### 3.1 安装 Redis + +**macOS**: +```bash +brew install redis +brew services start redis +``` + +**Linux**: +```bash +sudo apt install redis-server +sudo systemctl start redis +sudo systemctl enable redis +``` + +**Windows**: +- 下载Redis for Windows +- 解压并运行 `redis-server.exe` + +#### 3.2 验证Redis +```bash +redis-cli ping +# 应返回:PONG +``` + +### 4. 配置后端 + +#### 4.1 编辑配置文件 + +编辑 `/backend/src/main/resources/application.yml`: + +```yaml +spring: + datasource: + url: jdbc:mysql://localhost:3306/design_hub?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai + username: root # 修改为你的MySQL用户名 + password: your_password # 修改为你的MySQL密码 + driver-class-name: com.mysql.cj.jdbc.Driver + + redis: + host: localhost + port: 6379 + password: # 如果Redis没有密码,留空 + database: 0 + timeout: 3000 + +jwt: + secret: your-secret-key-change-this-in-production # 生产环境请修改 + access-token-expiration: 7200000 # 2小时 + refresh-token-expiration: 604800000 # 7天 + +server: + port: 8080 +``` + +#### 4.2 构建并运行后端 + +```bash +cd backend + +# 使用 Maven 构建 +mvn clean install -DskipTests + +# 运行后端 +mvn spring-boot:run + +# 或者使用 JAR 文件 +java -jar target/designhub-backend-1.0.0.jar +``` + +**后端启动成功标志**: +- 控制台输出 `Started DesignHubApplication` +- 访问 http://localhost:8080/swagger-ui.html 查看API文档 + +### 5. 配置前端 + +#### 5.1 安装依赖 + +```bash +cd frontend + +# 使用 pnpm(推荐) +pnpm install + +# 或使用 npm +npm install + +# 或使用 yarn +yarn install +``` + +#### 5.2 配置环境变量 + +创建 `/frontend/.env.development`: + +```env +# 开发环境配置 +VITE_APP_BASE_API=http://localhost:8080/api +VITE_APP_TITLE=DesignHub +VITE_APP_VERSION=6.0 +``` + +#### 5.3 运行前端 + +```bash +# 开发模式 +pnpm dev + +# 或 +npm run dev +``` + +**前端启动成功标志**: +- 控制台输出本地访问地址(默认:http://localhost:5173) +- 浏览器自动打开页面 + +### 6. 访问系统 + +打开浏览器,访问 `http://localhost:5173` + +**默认测试账号**: +- 用户名:`admin` +- 密码:`123456` + +--- + +## 🌐 生产环境部署 + +### 1. 服务器准备 + +#### 1.1 更新系统 +```bash +# CentOS/RHEL +sudo yum update -y + +# Ubuntu/Debian +sudo apt update && sudo apt upgrade -y +``` + +#### 1.2 安装必要软件 +```bash +# 安装 JDK 17 +sudo yum install java-17-openjdk -y + +# 安装 Nginx +sudo yum install nginx -y + +# 安装 Git(如需) +sudo yum install git -y +``` + +### 2. 部署数据库 + +#### 2.1 安装 MySQL 8.0 + +```bash +# 下载MySQL Yum Repository +wget https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm + +# 安装Repository +sudo rpm -ivh mysql80-community-release-el7-3.noarch.rpm + +# 安装MySQL +sudo yum install mysql-server -y + +# 启动MySQL +sudo systemctl start mysqld +sudo systemctl enable mysqld + +# 获取临时密码 +sudo grep 'temporary password' /var/log/mysqld.log + +# 修改root密码 +mysql_secure_installation +``` + +#### 2.2 创建数据库和用户 + +```sql +CREATE DATABASE design_hub CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE USER 'designhub'@'%' IDENTIFIED BY 'StrongPassword123!'; +GRANT ALL PRIVILEGES ON design_hub.* TO 'designhub'@'%'; +FLUSH PRIVILEGES; +``` + +#### 2.3 导入数据 + +```bash +mysql -u designhub -p design_hub < /path/to/schema.sql +``` + +### 3. 部署 Redis + +```bash +# 安装Redis +sudo yum install redis -y + +# 配置Redis(可选设置密码) +sudo vi /etc/redis.conf +# 找到 requirepass,设置密码 +requirepass YourStrongPassword + +# 启动Redis +sudo systemctl start redis +sudo systemctl enable redis +``` + +### 4. 部署后端 + +#### 4.1 上传代码 + +```bash +# 创建应用目录 +sudo mkdir -p /opt/designhub/backend +sudo chown -R $USER:$USER /opt/designhub + +# 上传代码(使用scp、rsync或git) +# scp方式 +scp -r backend/* user@server:/opt/designhub/backend/ + +# 或使用git +cd /opt/designhub +git clone +``` + +#### 4.2 配置生产环境 + +编辑 `/opt/designhub/backend/src/main/resources/application-prod.yml`: + +```yaml +spring: + datasource: + url: jdbc:mysql://localhost:3306/design_hub?useSSL=true + username: designhub + password: StrongPassword123! + + redis: + host: localhost + port: 6379 + password: YourStrongPassword + +jwt: + secret: change-this-to-a-very-strong-secret-key-in-production + +logging: + level: + root: INFO + com.designhub: INFO + file: + name: /opt/designhub/logs/application.log +``` + +#### 4.3 构建后端 + +```bash +cd /opt/designhub/backend + +# 构建 +mvn clean package -DskipTests -Pprod + +# 移动JAR文件 +mv target/designhub-backend-1.0.0.jar /opt/designhub/ +``` + +#### 4.4 创建系统服务 + +创建 `/etc/systemd/system/designhub-backend.service`: + +```ini +[Unit] +Description=DesignHub Backend Service +After=network.target + +[Service] +Type=simple +User=designhub +WorkingDirectory=/opt/designhub +ExecStart=/usr/bin/java -jar /opt/designhub/designhub-backend-1.0.0.jar --spring.profiles.active=prod +Restart=on-failure +RestartSec=10 + +[Install] +WantedBy=multi-user.target +``` + +#### 4.5 启动后端服务 + +```bash +# 重新加载systemd +sudo systemctl daemon-reload + +# 启动服务 +sudo systemctl start designhub-backend + +# 设置开机自启 +sudo systemctl enable designhub-backend + +# 查看状态 +sudo systemctl status designhub-backend + +# 查看日志 +sudo journalctl -u designhub-backend -f +``` + +### 5. 部署前端 + +#### 5.1 构建前端 + +```bash +cd /opt/designhub/frontend + +# 安装依赖 +pnpm install --production + +# 创建生产环境配置 +cat > .env.production << EOF +VITE_APP_BASE_API=https://api.yourdomain.com/api +VITE_APP_TITLE=DesignHub +VITE_APP_VERSION=6.0 +EOF + +# 构建 +pnpm build + +# 构建产物在 dist/ 目录 +``` + +#### 5.2 配置 Nginx + +创建 `/etc/nginx/conf.d/designhub.conf`: + +```nginx +server { + listen 80; + server_name yourdomain.com www.yourdomain.com; + + # 前端静态资源 + root /opt/designhub/frontend/dist; + index index.html; + + # 前端路由 + location / { + try_files $uri $uri/ /index.html; + } + + # API代理 + location /api/ { + proxy_pass http://localhost:8080/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket支持(如需) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # Gzip压缩 + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + # 静态资源缓存 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 30d; + add_header Cache-Control "public, immutable"; + } +} +``` + +#### 5.3 配置 HTTPS (Let's Encrypt) + +```bash +# 安装certbot +sudo yum install certbot python3-certbot-nginx -y + +# 获取SSL证书 +sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com + +# 自动续期测试 +sudo certbot renew --dry-run +``` + +#### 5.4 启动 Nginx + +```bash +# 测试配置 +sudo nginx -t + +# 启动Nginx +sudo systemctl start nginx +sudo systemctl enable nginx + +# 重新加载配置 +sudo systemctl reload nginx +``` + +### 6. 防火墙配置 + +```bash +# CentOS/RHEL (firewalld) +sudo firewall-cmd --permanent --add-service=http +sudo firewall-cmd --permanent --add-service=https +sudo firewall-cmd --reload + +# Ubuntu (ufw) +sudo ufw allow 80/tcp +sudo ufw allow 443/tcp +sudo ufw enable +``` + +--- + +## 🐳 Docker 部署 + +### 1. 后端 Dockerfile + +创建 `/backend/Dockerfile`: + +```dockerfile +FROM openjdk:17-slim + +WORKDIR /app + +# 复制JAR文件 +COPY target/designhub-backend-1.0.0.jar app.jar + +# 设置时区 +ENV TZ=Asia/Shanghai +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +# 暴露端口 +EXPOSE 8080 + +# 启动命令 +ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=prod"] +``` + +### 2. 前端 Dockerfile + +创建 `/frontend/Dockerfile`: + +```dockerfile +FROM node:18-alpine AS builder + +WORKDIR /app + +# 复制package文件 +COPY package*.json ./ +COPY pnpm-lock.yaml ./ + +# 安装依赖 +RUN npm install -g pnpm +RUN pnpm install + +# 复制源代码 +COPY . . + +# 构建 +RUN pnpm build + +# 生产环境 +FROM nginx:alpine + +# 复制构建产物 +COPY --from=builder /app/dist /usr/share/nginx/html + +# 复制Nginx配置 +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] +``` + +### 3. Docker Compose + +创建 `/docker-compose.yml`: + +```yaml +version: '3.8' + +services: + mysql: + image: mysql:8.0 + container_name: designhub-mysql + environment: + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_DATABASE: design_hub + MYSQL_USER: designhub + MYSQL_PASSWORD: designhubpass + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + networks: + - designhub-network + + redis: + image: redis:7-alpine + container_name: designhub-redis + command: redis-server --requirepass redispassword + ports: + - "6379:6379" + volumes: + - redis_data:/data + networks: + - designhub-network + + backend: + build: ./backend + container_name: designhub-backend + environment: + SPRING_PROFILES_ACTIVE: prod + SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/design_hub + SPRING_DATASOURCE_USERNAME: designhub + SPRING_DATASOURCE_PASSWORD: designhubpass + SPRING_REDIS_HOST: redis + SPRING_REDIS_PASSWORD: redispassword + ports: + - "8080:8080" + depends_on: + - mysql + - redis + networks: + - designhub-network + + frontend: + build: ./frontend + container_name: designhub-frontend + ports: + - "80:80" + depends_on: + - backend + networks: + - designhub-network + +volumes: + mysql_data: + redis_data: + +networks: + designhub-network: + driver: bridge +``` + +### 4. 启动 Docker Compose + +```bash +# 构建并启动所有服务 +docker-compose up -d + +# 查看日志 +docker-compose logs -f + +# 停止所有服务 +docker-compose down + +# 重启服务 +docker-compose restart +``` + +--- + +## ❓ 常见问题 + +### 1. 后端无法连接数据库 + +**问题**:`java.sql.SQLException: Access denied for user` + +**解决**: +```sql +-- 检查用户权限 +SELECT User, Host FROM mysql.user; + +-- 重新授权 +GRANT ALL PRIVILEGES ON design_hub.* TO 'designhub'@'%' IDENTIFIED BY 'password'; +FLUSH PRIVILEGES; +``` + +### 2. Redis连接失败 + +**问题**:`Could not connect to Redis at localhost:6379` + +**解决**: +```bash +# 检查Redis是否运行 +sudo systemctl status redis + +# 检查端口 +netstat -tuln | grep 6379 + +# 测试连接 +redis-cli -h localhost -p 6379 -a password ping +``` + +### 3. 前端API请求跨域 + +**问题**:`CORS policy: No 'Access-Control-Allow-Origin' header` + +**解决**:后端添加CORS配置(已在项目中配置) + +### 4. JWT Token过期 + +**问题**:频繁退出登录 + +**解决**:调整Token过期时间或实现自动刷新机制(已实现) + +### 5. 文件上传失败 + +**问题**:上传大文件失败 + +**解决**: +```yaml +# application.yml +spring: + servlet: + multipart: + max-file-size: 100MB + max-request-size: 100MB +``` + +--- + +## 🛠️ 维护与监控 + +### 1. 日志管理 + +#### 后端日志 +```bash +# 查看实时日志 +tail -f /opt/designhub/logs/application.log + +# 按日期查看 +less /opt/designhub/logs/application-2025-11-07.log + +# 日志清理(保留30天) +find /opt/designhub/logs -name "*.log" -mtime +30 -delete +``` + +#### Nginx日志 +```bash +# 访问日志 +tail -f /var/log/nginx/access.log + +# 错误日志 +tail -f /var/log/nginx/error.log +``` + +### 2. 数据库备份 + +#### 自动备份脚本 + +创建 `/opt/designhub/scripts/backup_db.sh`: + +```bash +#!/bin/bash + +# 配置 +DB_NAME="design_hub" +DB_USER="designhub" +DB_PASS="password" +BACKUP_DIR="/opt/designhub/backups" +DATE=$(date +%Y%m%d_%H%M%S) + +# 创建备份目录 +mkdir -p $BACKUP_DIR + +# 备份数据库 +mysqldump -u$DB_USER -p$DB_PASS $DB_NAME > $BACKUP_DIR/backup_$DATE.sql + +# 压缩备份 +gzip $BACKUP_DIR/backup_$DATE.sql + +# 删除7天前的备份 +find $BACKUP_DIR -name "backup_*.sql.gz" -mtime +7 -delete + +echo "Backup completed: backup_$DATE.sql.gz" +``` + +#### 设置定时任务 + +```bash +# 编辑crontab +crontab -e + +# 每天凌晨2点备份 +0 2 * * * /opt/designhub/scripts/backup_db.sh +``` + +### 3. 性能监控 + +#### 系统资源监控 +```bash +# CPU和内存 +top + +# 磁盘使用 +df -h + +# 网络连接 +netstat -tuln + +# 进程监控 +ps aux | grep java +``` + +#### 应用监控 + +**Spring Boot Actuator**(已集成): +- 访问 http://yourdomain.com/actuator/health +- 访问 http://yourdomain.com/actuator/metrics + +### 4. 安全加固 + +#### 系统安全 +```bash +# 关闭不必要的服务 +sudo systemctl disable bluetooth +sudo systemctl disable cups + +# 更新安全补丁 +sudo yum update --security -y + +# 配置fail2ban +sudo yum install fail2ban -y +sudo systemctl enable fail2ban +sudo systemctl start fail2ban +``` + +#### 应用安全 +- 定期更新依赖版本 +- 使用强密码 +- 启用HTTPS +- 配置防火墙 +- 定期安全审计 + +--- + +## 📞 技术支持 + +如遇到部署问题,请: +1. 查看本文档"常见问题"部分 +2. 检查日志文件 +3. 联系技术支持团队 + +--- + +**祝您部署顺利!** 🎉 + diff --git a/DEVELOPMENT_GUIDE.md b/DEVELOPMENT_GUIDE.md new file mode 100644 index 0000000..8290836 --- /dev/null +++ b/DEVELOPMENT_GUIDE.md @@ -0,0 +1,418 @@ +# DesignHub 开发启动指南 + +## 📋 目录 + +- [环境准备](#环境准备) +- [项目初始化](#项目初始化) +- [数据库配置](#数据库配置) +- [后端启动](#后端启动) +- [前端启动](#前端启动) +- [开发规范](#开发规范) +- [常见问题](#常见问题) + +## 🛠️ 环境准备 + +### 必备软件 + +确保您的开发环境已安装以下软件: + +| 软件 | 版本要求 | 说明 | +|------|---------|------| +| Java | >= 17 | 后端运行环境 | +| Node.js | >= 18.0 | 前端运行环境 | +| MySQL | >= 8.0 | 数据库 | +| Redis | >= 7.0 | 缓存服务(可选,开发阶段可不启用) | +| Maven | >= 3.8 | Java构建工具 | +| Git | 最新版本 | 版本控制 | + +### 推荐开发工具 + +- **后端开发**: IntelliJ IDEA / Eclipse +- **前端开发**: VS Code +- **数据库管理**: Navicat / DBeaver / MySQL Workbench +- **API测试**: Postman / Apifox +- **Redis管理**: RedisInsight / Another Redis Desktop Manager + +## 🚀 项目初始化 + +### 1. 克隆项目(如果从远程仓库) + +```bash +git clone +cd qiangdanxitong +``` + +### 2. 项目结构说明 + +``` +qiangdanxitong/ +├── frontend/ # 前端项目(Vue 3) +├── backend/ # 后端项目(Spring Boot) +├── database/ # 数据库脚本 +├── docs/ # 文档 +├── PROJECT_REQUIREMENTS.md # 需求文档 +└── README.md # 项目说明 +``` + +## 💾 数据库配置 + +### 1. 创建数据库 + +使用 MySQL 客户端或管理工具执行以下命令: + +```bash +# 方式1:使用命令行 +mysql -u root -p < database/schema.sql +mysql -u root -p < database/init-data.sql + +# 方式2:使用 MySQL 客户端 +mysql -u root -p +source /Users/lanmu/Desktop/qiangdanxitong/database/schema.sql; +source /Users/lanmu/Desktop/qiangdanxitong/database/init-data.sql; +``` + +### 2. 验证数据库 + +```sql +-- 登录MySQL +mysql -u root -p + +-- 切换到designhub数据库 +USE designhub; + +-- 查看所有表 +SHOW TABLES; + +-- 查看部门数据 +SELECT * FROM sys_dept; + +-- 查看任务类型 +SELECT * FROM task_type; +``` + +### 3. 配置数据库连接 + +编辑 `backend/src/main/resources/application-dev.yml`: + +```yaml +spring: + datasource: + url: jdbc:mysql://localhost:3306/designhub?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai + username: root + password: 你的MySQL密码 +``` + +## 🔧 后端启动 + +### 方式1:使用IDE启动(推荐) + +1. 用 IntelliJ IDEA 打开 `backend` 目录 +2. 等待 Maven 自动下载依赖 +3. 找到 `DesignHubApplication.java` 主类 +4. 右键 -> Run 'DesignHubApplication' + +### 方式2:使用命令行启动 + +```bash +cd backend + +# 首次启动需要先编译 +mvn clean install + +# 启动应用 +mvn spring-boot:run + +# 或者直接运行jar包 +java -jar target/designhub-backend-1.0.0.jar +``` + +### 验证后端启动 + +启动成功后,访问以下地址: + +- **API文档**: http://localhost:8080/api/doc.html +- **健康检查**: http://localhost:8080/api/actuator/health(如果配置了actuator) + +控制台应该输出: + +``` +╔═══════════════════════════════════════════════════════╗ +║ ║ +║ 🎨 DesignHub Backend Service Started 🎨 ║ +║ ║ +║ Port: 8080 ║ +║ Swagger UI: http://localhost:8080/api/doc.html ║ +║ Version: 1.0.0 ║ +║ ║ +╚═══════════════════════════════════════════════════════╝ +``` + +## 🎨 前端启动 + +### 1. 安装依赖 + +```bash +cd frontend + +# 使用npm +npm install + +# 或使用pnpm(推荐,更快) +pnpm install + +# 或使用yarn +yarn install +``` + +### 2. 启动开发服务器 + +```bash +# 使用npm +npm run dev + +# 使用pnpm +pnpm dev + +# 使用yarn +yarn dev +``` + +### 3. 访问前端 + +浏览器自动打开或手动访问: + +- **开发地址**: http://localhost:3000 + +控制台应该输出: + +``` +VITE v5.0.0 ready in 500 ms + +➜ Local: http://localhost:3000/ +➜ Network: use --host to expose +➜ press h + enter to show help +``` + +## 📝 开发规范 + +### Git提交规范 + +使用 Conventional Commits 规范: + +```bash +# 功能开发 +git commit -m "feat: 添加任务大厅页面" + +# Bug修复 +git commit -m "fix: 修复任务列表显示问题" + +# 文档更新 +git commit -m "docs: 更新README文档" + +# 样式调整 +git commit -m "style: 调整按钮样式" + +# 代码重构 +git commit -m "refactor: 重构任务服务层代码" + +# 性能优化 +git commit -m "perf: 优化数据库查询性能" + +# 测试相关 +git commit -m "test: 添加任务模块单元测试" +``` + +### 分支策略 + +``` +main/master -> 生产环境分支 +develop -> 开发分支 +feature/* -> 功能分支 +hotfix/* -> 紧急修复分支 +release/* -> 发布分支 +``` + +### 代码风格 + +#### 前端(TypeScript/Vue) + +- 使用 ESLint + Prettier 格式化代码 +- 组件命名使用 PascalCase +- 文件命名使用 kebab-case +- 使用 Composition API + +```bash +# 格式化代码 +npm run format + +# 检查代码 +npm run lint +``` + +#### 后端(Java/Spring Boot) + +- 遵循 阿里巴巴 Java 开发手册 +- 类名使用 PascalCase +- 方法名使用 camelCase +- 常量使用 UPPER_SNAKE_CASE + +## 🔍 常见问题 + +### Q1: 后端启动失败,提示数据库连接错误 + +**A**: 检查以下几点: +1. MySQL 是否已启动 +2. 数据库密码是否正确配置在 `application-dev.yml` +3. 数据库 `designhub` 是否已创建 +4. 数据库端口是否为 3306 + +### Q2: 前端启动后页面空白 + +**A**: +1. 检查浏览器控制台是否有报错 +2. 确认后端API是否正常运行(访问 http://localhost:8080/api/doc.html) +3. 检查前端代理配置(vite.config.ts) + +### Q3: Maven 依赖下载缓慢 + +**A**: 配置国内Maven镜像(阿里云) + +编辑 `~/.m2/settings.xml`: + +```xml + + aliyun + central + Aliyun Maven Mirror + https://maven.aliyun.com/repository/public + +``` + +### Q4: npm 安装依赖失败 + +**A**: 切换npm镜像源 + +```bash +# 临时使用淘宝镜像 +npm install --registry=https://registry.npmmirror.com + +# 永久设置 +npm config set registry https://registry.npmmirror.com + +# 使用 nrm 管理镜像源(推荐) +npm install -g nrm +nrm use taobao +``` + +### Q5: 钉钉登录如何配置 + +**A**: +1. 登录钉钉开放平台 https://open.dingtalk.com +2. 创建应用,获取 AppKey 和 AppSecret +3. 配置到 `application.yml` 的 `dingtalk` 部分 +4. 配置回调地址和权限 + +```yaml +dingtalk: + app-key: your-app-key + app-secret: your-app-secret + agent-id: your-agent-id + corp-id: your-corp-id +``` + +### Q6: Redis 连接失败 + +**A**: 开发阶段可以暂时禁用 Redis + +在 `application-dev.yml` 中注释掉 Redis 配置,或确保 Redis 已启动: + +```bash +# 启动 Redis(Mac) +brew services start redis + +# 启动 Redis(Linux) +redis-server + +# 启动 Redis(Windows) +redis-server.exe + +# 测试连接 +redis-cli ping +# 应该返回 PONG +``` + +## 📚 下一步 + +### 开发优先级 + +1. ✅ **基础架构** - 已完成 + - 项目结构 + - 数据库设计 + - 前后端框架搭建 + +2. 🔄 **核心功能开发** - 进行中 + - [ ] 用户认证模块(Casdoor + 钉钉登录) + - [ ] 任务管理模块 + - [ ] 积分管理模块 + - [ ] 通知系统 + +3. ⏳ **进阶功能** - 待开发 + - [ ] 激励系统 + - [ ] 数据分析 + - [ ] 管理后台 + +### 推荐开发顺序 + +``` +1. 用户认证 → 2. 组织架构 → 3. 任务发起 → 4. 任务大厅 → +5. 我的任务 → 6. 设计师大厅 → 7. 积分系统 → 8. 数据分析 → +9. 激励系统 → 10. 系统配置 → 11. 通知系统 +``` + +## 🤝 团队协作 + +### 每日开发流程 + +1. **早上**:拉取最新代码 + ```bash + git checkout develop + git pull origin develop + ``` + +2. **开发**:创建功能分支 + ```bash + git checkout -b feature/task-hall + # 编码... + git add . + git commit -m "feat: 完成任务大厅页面" + ``` + +3. **提交**:推送到远程并创建PR + ```bash + git push origin feature/task-hall + # 在 GitLab/GitHub 创建 Merge Request / Pull Request + ``` + +4. **审查**:代码审查通过后合并到 develop + +### 代码审查清单 + +- [ ] 代码符合规范 +- [ ] 没有明显的 bug +- [ ] 功能完整 +- [ ] 已添加必要的注释 +- [ ] 通过单元测试 +- [ ] UI 符合 OpenAI 设计规范 + +## 📞 技术支持 + +如有问题,请联系: + +- **技术负责人**: [姓名] - [邮箱] +- **项目文档**: PROJECT_REQUIREMENTS.md +- **API文档**: http://localhost:8080/api/doc.html + +--- + +**祝开发愉快!** 🎉 + diff --git a/Nginx缓存配置说明.md b/Nginx缓存配置说明.md new file mode 100644 index 0000000..1008362 --- /dev/null +++ b/Nginx缓存配置说明.md @@ -0,0 +1,459 @@ +# Nginx缓存配置说明 + +## 📝 配置概述 + +**更新时间**: 2025-12-23 +**服务器**: 公网服务器 156.225.27.52:50232 +**配置文件**: /www/server/panel/vhost/nginx/qd.yydd.ai.conf +**状态**: ✅ 已禁用所有缓存(开发模式) + +--- + +## 🚫 当前缓存设置 + +### 已禁用的缓存功能 + +```nginx +location / { + # 反向代理配置 + proxy_pass http://127.0.0.1:5246; + + # 🚫 禁用缓存 - 开发模式 + proxy_buffering off; # 禁用代理缓冲 + proxy_cache off; # 禁用代理缓存 + proxy_no_cache 1; # 不缓存响应 + proxy_cache_bypass 1; # 绕过缓存检查 + + # 添加缓存控制头 + add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; + add_header Pragma "no-cache"; + add_header Expires "0"; +} +``` + +--- + +## 📊 缓存控制说明 + +### Nginx层面缓存控制 + +| 指令 | 作用 | 值 | +|------|------|-----| +| `proxy_buffering` | 代理缓冲 | off(禁用) | +| `proxy_cache` | 代理缓存 | off(禁用) | +| `proxy_no_cache` | 不缓存响应 | 1(启用) | +| `proxy_cache_bypass` | 绕过缓存 | 1(启用) | + +### HTTP响应头 + +| 响应头 | 作用 | 值 | +|--------|------|-----| +| `Cache-Control` | 缓存策略 | no-store, no-cache, must-revalidate | +| `Pragma` | HTTP/1.0兼容 | no-cache | +| `Expires` | 过期时间 | 0(立即过期) | + +--- + +## 🎯 效果说明 + +### 对开发的好处 + +1. **✅ 实时更新** + - 前端代码更新立即生效 + - 无需手动清除浏览器缓存 + - API响应不会被缓存 + +2. **✅ 调试便利** + - 每次请求都是最新内容 + - 便于测试新功能 + - 避免缓存导致的问题 + +3. **✅ 快速迭代** + - 修改代码后立即看到效果 + - 减少"看不到更新"的困惑 + - 提高开发效率 + +--- + +## 🔄 切换到生产模式 + +### 当系统正式上线后 + +**建议**:启用缓存以提高性能和减少服务器负载 + +```nginx +location / { + proxy_pass http://127.0.0.1:5246; + + # 启用缓存(生产模式) + proxy_buffering on; + + # 静态资源缓存(仅示例,根据实际调整) + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { + proxy_pass http://127.0.0.1:5246; + proxy_cache my_cache; + proxy_cache_valid 200 30d; + proxy_cache_valid 404 1m; + add_header Cache-Control "public, max-age=2592000"; + } + + # API响应不缓存 + location /api/ { + proxy_pass http://127.0.0.1:5246; + proxy_buffering off; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + } +} +``` + +--- + +## 📝 切换步骤 + +### 从开发模式切换到生产模式 + +```bash +# 1. SSH登录公网服务器 +ssh -p 50232 root@156.225.27.52 + +# 2. 编辑Nginx配置 +vi /www/server/panel/vhost/nginx/qd.yydd.ai.conf + +# 3. 修改缓存配置(根据需要) +# 移除或注释 proxy_buffering off 等指令 +# 添加适当的缓存配置 + +# 4. 测试配置 +nginx -t + +# 5. 重载Nginx +nginx -s reload +``` + +--- + +## 🧪 测试缓存配置 + +### 验证缓存已禁用 + +```bash +# 方式1:查看HTTP响应头 +curl -I https://qd.yydd.ai + +# 应该看到: +# Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0 +# Pragma: no-cache +# Expires: 0 + +# 方式2:在浏览器开发者工具中 +# Network → Headers → Response Headers +# 检查 Cache-Control 等头部 +``` + +### 测试更新是否实时 + +1. **前端更新测试** + ```bash + # 修改前端代码 + cd /Users/lanmu/Desktop/qiangdanxitong/frontend + # 编辑任意文件 + + # 构建并上传 + pnpm build + scp -P 5204 -r dist/* root@156.225.27.52:/opt/designhub/frontend/ + + # 立即刷新浏览器(Ctrl+R,无需Ctrl+Shift+R强制刷新) + # 应该看到更新 + ``` + +2. **API更新测试** + ```bash + # 修改后端代码 + # 重启后端服务 + ssh -p 5204 root@156.225.27.52 + systemctl restart designhub-backend + + # 立即在浏览器中调用API + # 应该返回最新结果 + ``` + +--- + +## ⚠️ 注意事项 + +### 开发模式的影响 + +**优点**: +- ✅ 开发调试方便 +- ✅ 代码更新实时生效 +- ✅ 避免缓存问题 + +**缺点**: +- ⚠️ 服务器负载略高(每次请求都处理) +- ⚠️ 用户体验略差(无缓存加速) +- ⚠️ 带宽消耗略大 + +**建议**: +- 开发阶段:使用当前配置(无缓存) +- 生产阶段:启用适当缓存(性能优化) + +--- + +## 🎯 完整配置文件 + +### 当前配置(开发模式) + +```nginx +# /www/server/panel/vhost/nginx/qd.yydd.ai.conf + +# HTTP自动跳转HTTPS +server { + listen 80; + server_name qd.yydd.ai; + + location /.well-known/acme-challenge/ { + root /www/wwwroot/qd.yydd.ai; + } + + location / { + return 301 https://$server_name$request_uri; + } +} + +# HTTPS配置 +server { + listen 443 ssl http2; + server_name qd.yydd.ai; + + # SSL证书 + ssl_certificate /etc/letsencrypt/live/qd.yydd.ai/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/qd.yydd.ai/privkey.pem; + + # SSL优化 + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + client_max_body_size 100M; + + # 反向代理 - 无缓存模式 + location / { + proxy_pass http://127.0.0.1:5246; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket支持 + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # 超时设置 + proxy_connect_timeout 300; + proxy_send_timeout 300; + proxy_read_timeout 300; + + # 🚫 禁用缓存 - 开发模式 + proxy_buffering off; + proxy_cache off; + proxy_no_cache 1; + proxy_cache_bypass 1; + + # 添加缓存控制头 + add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; + add_header Pragma "no-cache"; + add_header Expires "0"; + } + + access_log /www/wwwlogs/qd.yydd.ai.https.access.log; + error_log /www/wwwlogs/qd.yydd.ai.https.error.log; +} +``` + +--- + +## 🔧 常用命令 + +### 修改配置后重载 + +```bash +# SSH登录 +ssh -p 50232 root@156.225.27.52 + +# 编辑配置 +vi /www/server/panel/vhost/nginx/qd.yydd.ai.conf + +# 测试配置 +nginx -t + +# 重载Nginx(不中断服务) +nginx -s reload +``` + +### 查看配置 + +```bash +# 查看当前配置 +ssh -p 50232 root@156.225.27.52 +cat /www/server/panel/vhost/nginx/qd.yydd.ai.conf +``` + +### 验证缓存状态 + +```bash +# 方式1:curl查看响应头 +curl -I https://qd.yydd.ai + +# 方式2:详细查看 +curl -v https://qd.yydd.ai 2>&1 | grep -i cache +``` + +--- + +## 📊 浏览器缓存说明 + +### 浏览器本地缓存 + +**注意**: 即使Nginx禁用了缓存,浏览器仍可能有自己的缓存 + +**开发时的刷新方式**: + +| 刷新方式 | 快捷键 | 效果 | +|----------|--------|------| +| 普通刷新 | F5 或 Ctrl+R | 可能使用缓存 | +| 硬刷新 | Ctrl+Shift+R | 强制不使用缓存 | +| 清空缓存 | DevTools → Network → Disable cache | 开发时建议勾选 | + +**建议**: +- 开发时打开浏览器开发者工具(F12) +- 勾选 Network → Disable cache +- 这样可以完全禁用浏览器缓存 + +--- + +## 🚀 性能对比 + +### 开发模式 vs 生产模式 + +| 指标 | 开发模式(无缓存) | 生产模式(有缓存) | +|------|-------------------|-------------------| +| 首次加载 | 正常 | 正常 | +| 重复访问 | 每次都请求服务器 | 使用缓存,快速 | +| 服务器负载 | 较高 | 较低 | +| 带宽消耗 | 较高 | 较低 | +| 开发便利性 | ⭐⭐⭐⭐⭐ | ⭐⭐ | +| 用户体验 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | + +--- + +## 💡 最佳实践 + +### 开发阶段(当前) + +✅ **已实施**: +- Nginx层面禁用所有缓存 +- 添加 no-cache 响应头 +- 便于快速迭代开发 + +### 测试阶段(即将上线) + +建议: +- 保持当前配置 +- 测试功能完整性 +- 验证无缓存相关bug + +### 生产阶段(正式上线后) + +建议: +- 启用静态资源缓存(JS/CSS/图片) +- API响应保持不缓存 +- 设置合理的缓存时间 +- 使用CDN加速 + +--- + +## 🔄 快速切换配置 + +### 配置模板 + +**开发模式配置**(当前): +```nginx +# 完全禁用缓存 +proxy_buffering off; +proxy_cache off; +proxy_no_cache 1; +proxy_cache_bypass 1; +add_header Cache-Control "no-store, no-cache, must-revalidate"; +``` + +**生产模式配置**(未来): +```nginx +# 启用适度缓存 +proxy_buffering on; + +# 静态资源缓存7天 +location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + add_header Cache-Control "public, max-age=604800"; +} + +# API不缓存 +location /api/ { + add_header Cache-Control "no-cache"; +} +``` + +--- + +## 📞 技术支持 + +### 修改缓存配置 + +如需修改缓存配置,请: + +1. SSH登录公网服务器 +2. 编辑 `/www/server/panel/vhost/nginx/qd.yydd.ai.conf` +3. 测试配置:`nginx -t` +4. 重载Nginx:`nginx -s reload` + +### 验证配置生效 + +```bash +# 查看HTTP响应头 +curl -I https://qd.yydd.ai | grep -i cache + +# 应该看到当前设置的缓存策略 +``` + +--- + +## 📝 更新记录 + +| 日期 | 操作 | 说明 | +|------|------|------| +| 2025-12-23 | 禁用缓存 | 开发模式,便于测试 | +| 待定 | 启用缓存 | 生产模式,性能优化 | + +--- + +## 🎯 总结 + +✅ **当前状态**: 所有缓存已禁用 +✅ **适用场景**: 开发测试阶段 +✅ **效果**: 代码更新立即生效 +✅ **访问地址**: https://qd.yydd.ai + +**开发体验**: +- 修改前端代码 → 构建上传 → 普通刷新(F5)即可看到更新 +- 修改后端代码 → 重启服务 → 立即生效 +- 无需清除浏览器缓存或强制刷新 + +🎉 **现在可以愉快地开发测试了!** + +--- + +**创建时间**: 2025-12-23 +**配置状态**: ✅ 已生效 +**验证方式**: 访问 https://qd.yydd.ai 并查看响应头 diff --git a/PROJECT_REQUIREMENTS.md b/PROJECT_REQUIREMENTS.md new file mode 100644 index 0000000..847b385 --- /dev/null +++ b/PROJECT_REQUIREMENTS.md @@ -0,0 +1,6335 @@ +# 设计师任务抢单管理系统 - 项目需求文档 + +## 📋 项目概述 + +### 1.1 项目背景 +本系统旨在建立一个高效的设计师任务分配与管理平台,通过积分制度和任务大厅机制,实现设计任务的智能分配、进度跟踪和绩效评估。 + +### 1.2 项目目标 +- 提高设计任务分配效率 +- 实现任务进度可视化管理 +- 建立公平的积分激励机制 +- 提供多维度的数据分析和绩效评估 +- 增强团队协作和透明度 + +### 1.3 核心价值 +- **效率提升**:自动化任务分配,减少人工协调成本 +- **透明化管理**:实时查看任务进度和设计师工作负荷 +- **激励机制**:积分制度激发设计师积极性 +- **数据驱动**:多维度数据分析支持决策优化 + +--- + +## 🏗️ 系统架构设计 + +### 2.1 技术栈建议 + +#### 前端技术栈 +- **框架**:Vue 3 + TypeScript / React 18 + TypeScript +- **UI组件库**:Ant Design Vue / Ant Design / Element Plus +- **状态管理**:Pinia / Zustand / Redux Toolkit +- **路由管理**:Vue Router / React Router +- **图表库**:ECharts / Apache ECharts +- **日历组件**:FullCalendar / Vue-Calendar +- **文件上传**:Vue-Upload-Component / react-dropzone +- **HTTP客户端**:Axios + +#### 后端技术栈 +- **主框架**:Spring Boot 3.x / Node.js (NestJS / Express) +- **数据库**:MySQL 8.0 + Redis +- **ORM框架**:MyBatis Plus / Hibernate / TypeORM +- **认证授权**: + - OAuth 2.0 / OpenID Connect + - 推荐开源方案:Keycloak / Auth0 / Casdoor + - JWT Token +- **钉钉集成**:DingTalk Open API SDK +- **文件存储**:阿里云OSS / MinIO / 七牛云 +- **任务调度**:Quartz / XXL-JOB +- **API文档**:Swagger / OpenAPI + +#### 数据库设计 +- **主数据库**:MySQL(业务数据) +- **缓存层**:Redis(会话、热点数据) +- **搜索引擎**:Elasticsearch(可选,用于高级搜索) + +#### 部署方案 +- **容器化**:Docker + Docker Compose +- **CI/CD**:Jenkins / GitLab CI / GitHub Actions +- **服务器**:Nginx(反向代理)+ 应用服务器 + +### 2.2 系统模块划分 + +``` +├── 用户认证模块 +│ ├── 开源认证系统集成 +│ ├── 钉钉登录集成 +│ └── 权限管理 +├── 组织架构模块 +│ ├── 组织架构同步 +│ ├── 部门类型管理(需求部门/接单部门) +│ ├── 团队管理 +│ ├── 部门负责人管理 +│ └── 积分池管理 +├── 任务管理模块 +│ ├── 任务发起 +│ ├── 任务分配 +│ ├── 任务接收 +│ ├── 任务审核 +│ └── 任务调配(管理员) +├── 任务大厅模块 +│ ├── 任务列表展示 +│ ├── 任务搜索筛选 +│ └── 任务抢单 +├── 进度管理模块 +│ ├── 我的任务进度 +│ ├── 日历视图 +│ └── 任务状态追踪 +├── 设计师大厅模块 +│ ├── 设计师列表 +│ ├── 实时进度看板 +│ └── 积分统计 +├── 数据分析模块 +│ ├── 绩效统计 +│ ├── 需求统计展板 +│ ├── 多维度报表 +│ └── 排行榜 +├── 系统配置模块 +│ ├── 需求类型配置 +│ ├── 品牌配置 +│ ├── 类目配置 +│ ├── 平台配置 +│ └── 激励系统配置 +├── 激励系统模块 +│ ├── 成就勋章系统 +│ ├── 实时排名系统 +│ ├── 里程碑提醒 +│ ├── 目标管理 +│ ├── 每日签到 +│ ├── 竞赛活动 +│ ├── 团队PK +│ └── 智能推荐 +├── 激励配置管理模块(管理员) +│ ├── 激励游戏开关 +│ ├── 成就勋章配置 +│ ├── 自动奖励规则配置 +│ ├── 竞赛活动管理 +│ ├── 团队PK管理 +│ ├── 排名规则配置 +│ └── 激励积分池管理 +└── 通知提醒模块 + ├── 站内消息 + ├── 钉钉推送 + └── 邮件通知 +``` + +--- + +## 📝 功能需求详细说明 + +## 一、用户认证与授权模块 + +### 1.1 开源认证系统集成 + +#### 推荐方案 +**Casdoor** - 开源的身份访问管理平台 +- 优势: + - 支持多种身份提供商(钉钉、企业微信等) + - 提供完整的UI界面 + - 易于集成和定制 + - 支持RBAC权限模型 + - 开源免费 + +#### 功能要点 +- 用户注册与登录 +- 单点登录(SSO) +- 会话管理 +- 密码策略管理 +- 安全日志记录 + +### 1.2 钉钉登录集成 + +#### 实现方式 +``` +流程: +1. 用户点击"钉钉登录" +2. 跳转到钉钉授权页面 +3. 用户授权后回调系统 +4. 系统获取钉钉用户信息 +5. 查询并同步组织架构 +6. 匹配或创建系统用户 +7. 生成JWT Token +8. 跳转到系统首页 +``` + +#### 组织架构同步 +- **同步频率**:定时任务(每天凌晨)+ 手动触发 +- **同步内容**: + - 部门层级结构 + - 用户基本信息(姓名、工号、头像) + - 用户部门关系 + - 团队积分池配置 + +#### 权限角色设计 +| 角色 | 权限说明 | +|------|----------| +| 超级管理员 | 系统全部权限,配置管理,任务全局调配 | +| 管理员 | 任务调配、任务转移、查看全部任务数据 | +| 需求部门负责人 | 发起任务、查看本部门数据、设置积分消耗限制、管理部门成员 | +| 接单部门主管 | 任务分配、审核、查看所属团队数据 | +| 设计师(接单部门) | 接收任务、提交交付、查看个人数据 | +| 需求方(需求部门) | 发起任务、审核交付、查看自己发起的任务 | +| 观察者 | 只读权限,查看数据分析 | + +--- + +## 一之二、组织架构与部门管理模块 + +### 1.3 功能概述 +组织架构管理是系统的基础模块,通过部门分类(需求部门/接单部门)实现职责明确的任务流转。需求部门负责发起任务并提供积分,接单部门负责接收和完成任务。 + +### 1.4 部门类型管理 + +#### 1.4.1 部门类型定义 + +**两种部门类型** + +| 部门类型 | 职责 | 权限 | 积分关系 | +|---------|------|------|---------| +| 需求部门 | 发起设计需求 | 只能发布任务,不能接单 | 拥有积分池,发布任务消耗积分 | +| 接单部门 | 接收并完成任务 | 只能接单,不能发起任务 | 完成任务获得积分奖励 | + +**典型场景示例** +``` +需求部门: +- 产品部 +- 市场部 +- 运营部 +- 电商部 + +接单部门: +- 设计部 +- 视觉设计组 +- UI设计组 +- 视频制作组 +``` + +#### 1.4.2 部门类型配置界面 + +**部门列表管理** +``` +┌──────────────────────────────────────────────────┐ +│ 组织架构 - 部门管理 │ +├──────────────────────────────────────────────────┤ +│ 🔄 [同步钉钉组织架构] [+ 新增部门] │ +│ ──────────────────────────────────────────── │ +│ │ +│ 🏢 公司总部 │ +│ │ │ +│ ├─ 📋 产品部 [需求部门] │ +│ │ 负责人:张三 │ +│ │ 成员:15人 | 积分池:5,000 │ +│ │ 消耗限制:500分/周 │ +│ │ [编辑] [查看详情] [设置负责人] │ +│ │ │ +│ ├─ 📋 市场部 [需求部门] │ +│ │ 负责人:李四 │ +│ │ 成员:12人 | 积分池:3,500 │ +│ │ 消耗限制:无限制 │ +│ │ [编辑] [查看详情] [设置负责人] │ +│ │ │ +│ ├─ 🎨 设计部 [接单部门] │ +│ │ 主管:王五 │ +│ │ 成员:25人 | 本月完成:156个任务 │ +│ │ [编辑] [查看详情] [管理成员] │ +│ │ │ +│ └─ 📺 视频制作组 [接单部门] │ +│ 主管:赵六 │ +│ 成员:8人 | 本月完成:45个任务 │ +│ [编辑] [查看详情] [管理成员] │ +│ │ +└──────────────────────────────────────────────────┘ +``` + +**编辑部门表单** +``` +┌───────────────────────────────────┐ +│ 编辑部门 │ +├───────────────────────────────────┤ +│ 部门名称: │ +│ [产品部] │ +│ │ +│ [必填] 部门类型: │ +│ ● 需求部门 │ +│ ○ 接单部门 │ +│ │ +│ 上级部门: │ +│ [公司总部 ▼] │ +│ │ +│ 部门描述: │ +│ ┌─────────────────────────────┐ │ +│ │ 负责产品设计需求... │ │ +│ └─────────────────────────────┘ │ +│ │ +│ === 需求部门专属配置 === │ +│ │ +│ 部门负责人: │ +│ [张三 ▼] │ +│ │ +│ 积分池余额: │ +│ [5000] 分 │ +│ │ +│ 积分消耗限制: │ +│ ☑ 启用消耗限制 │ +│ 周期:[每周 ▼] │ +│ 限制:[500] 分 │ +│ │ +│ 预警设置: │ +│ ☑ 剩余20%时预警 │ +│ ☑ 超过限制时预警 │ +│ │ +│ === 接单部门专属配置 === │ +│ (部门类型为需求部门时隐藏) │ +│ │ +│ 部门主管: │ +│ [王五 ▼] │ +│ │ +│ 可接单的需求类型: │ +│ ☑ 主图设计 │ +│ ☑ 详情页设计 │ +│ ☑ 视频剪辑 │ +│ ☐ 图片修图 │ +│ │ +│ [取消] [保存] │ +└───────────────────────────────────┘ +``` + +#### 1.4.3 部门类型权限控制 + +**权限验证规则** +```javascript +// 发起任务权限验证 +function canPublishTask(user) { + // 1. 检查用户所属部门 + const userDept = user.department; + + // 2. 验证部门类型 + if (userDept.type !== 'DEMAND') { + return { + allowed: false, + message: '您所在的部门为接单部门,无权发起任务' + }; + } + + // 3. 检查积分余额 + if (userDept.scoreBalance < minScore) { + return { + allowed: false, + message: '部门积分余额不足,请联系部门负责人充值' + }; + } + + // 4. 检查消耗限制 + if (userDept.hasLimit && userDept.periodUsed >= userDept.periodLimit) { + return { + allowed: false, + message: `已达到本${userDept.limitPeriod}消耗上限,请等待下一周期或联系负责人调整` + }; + } + + return { allowed: true }; +} + +// 接单权限验证 +function canReceiveTask(user, task) { + // 1. 检查用户所属部门 + const userDept = user.department; + + // 2. 验证部门类型 + if (userDept.type !== 'RECEIVER') { + return { + allowed: false, + message: '您所在的部门为需求部门,无法接收任务' + }; + } + + // 3. 检查技能匹配(可选) + if (!userDept.allowedDemandTypes.includes(task.demandType)) { + return { + allowed: false, + message: '您所在部门不支持该类型的任务' + }; + } + + return { allowed: true }; +} +``` + +**前端权限控制** +``` +需求部门用户登录后: +✓ 显示"发起任务"按钮 +✗ 隐藏"任务大厅"(抢单) +✓ 显示"我发起的任务" +✗ 隐藏"我接收的任务" + +接单部门用户登录后: +✗ 隐藏"发起任务"按钮 +✓ 显示"任务大厅"(抢单) +✗ 隐藏"我发起的任务" +✓ 显示"我接收的任务" +``` + +### 1.5 需求部门负责人管理 + +#### 1.5.1 负责人设置 + +**设置负责人** +``` +┌───────────────────────────────────┐ +│ 设置部门负责人 │ +├───────────────────────────────────┤ +│ 部门:产品部(需求部门) │ +│ 当前负责人:张三 │ +│ │ +│ 更改负责人: │ +│ [搜索部门成员] │ +│ ○ 张三(当前) │ +│ ○ 李四 │ +│ ● 王五 │ +│ ○ 赵六 │ +│ │ +│ 授权权限: │ +│ ☑ 查看部门所有任务 │ +│ ☑ 管理部门积分池 │ +│ ☑ 设置积分消耗限制 │ +│ ☑ 审批部门成员发起的任务(可选)│ +│ ☑ 查看部门数据统计 │ +│ ☑ 管理部门成员 │ +│ │ +│ 生效时间: │ +│ ● 立即生效 │ +│ ○ 指定时间 [日期选择器] │ +│ │ +│ 通知方式: │ +│ ☑ 站内消息 │ +│ ☑ 钉钉通知 │ +│ ☑ 邮件通知 │ +│ │ +│ [取消] [确认] │ +└───────────────────────────────────┘ +``` + +#### 1.5.2 积分消耗限制管理 + +**限制设置界面** +``` +┌──────────────────────────────────────────────┐ +│ 积分消耗限制管理 - 产品部 │ +├──────────────────────────────────────────────┤ +│ 部门负责人:张三 │ +│ 积分池余额:5,000 分 │ +│ ──────────────────────────────────────── │ +│ │ +│ 消耗限制设置: │ +│ ☑ 启用积分消耗限制 │ +│ │ +│ 限制周期: │ +│ ○ 每天 │ +│ ● 每周 │ +│ ○ 每月 │ +│ │ +│ 限制额度: │ +│ [500] 分 / 周 │ +│ │ +│ 本周期统计(2025.11.04 - 2025.11.10): │ +│ ┌────────────────────────────────────┐ │ +│ │ 已消耗:320 分(64%) │ │ +│ │ 剩余额度:180 分 │ │ +│ │ 进度:████████░░░ 64% │ │ +│ │ │ │ +│ │ 本周发起任务:18 个 │ │ +│ │ 待审核:2 个 │ │ +│ │ 进行中:12 个 │ │ +│ │ 已完成:4 个 │ │ +│ └────────────────────────────────────┘ │ +│ │ +│ 预警设置: │ +│ ☑ 达到 80% 时预警(400分) │ +│ ☑ 达到 100% 时禁止发起新任务 │ +│ ☑ 每周一发送消耗报告 │ +│ │ +│ 超限处理: │ +│ ● 禁止发起新任务 │ +│ ○ 仅提醒,不限制 │ +│ ○ 需要管理员审批 │ +│ │ +│ 历史记录: │ +│ ┌────────────────────────────────────┐ │ +│ │ 2025-11-04周:已用420/500(84%) │ │ +│ │ 2025-10-28周:已用480/500(96%) │ │ +│ │ 2025-10-21周:已用350/500(70%) │ │ +│ │ [查看更多] │ │ +│ └────────────────────────────────────┘ │ +│ │ +│ 临时调整: │ +│ [本周期临时增加额度] [暂停限制1周] │ +│ │ +│ [重置为默认] [保存配置] │ +└──────────────────────────────────────────────┘ +``` + +**消耗限制算法** +```javascript +// 检查是否可以发起任务 +function checkScoreLimit(dept, taskScore) { + // 1. 检查是否启用限制 + if (!dept.limitEnabled) { + return { allowed: true }; + } + + // 2. 获取当前周期 + const currentPeriod = getCurrentPeriod(dept.limitPeriod); + + // 3. 计算当前周期已使用 + const periodUsed = calculatePeriodUsage(dept.id, currentPeriod); + + // 4. 计算使用后的总额 + const afterUse = periodUsed + taskScore; + + // 5. 判断是否超限 + if (afterUse > dept.periodLimit) { + return { + allowed: false, + message: `超过本${periodName}消耗限制`, + details: { + limit: dept.periodLimit, + used: periodUsed, + required: taskScore, + remaining: dept.periodLimit - periodUsed + } + }; + } + + // 6. 预警提示 + const usageRate = afterUse / dept.periodLimit; + if (usageRate >= 0.8) { + return { + allowed: true, + warning: `将达到本${periodName}消耗限制的${(usageRate * 100).toFixed(0)}%` + }; + } + + return { allowed: true }; +} + +// 周期计算 +function getCurrentPeriod(periodType) { + const now = new Date(); + + if (periodType === 'DAILY') { + return now.toISOString().split('T')[0]; // 2025-11-07 + } else if (periodType === 'WEEKLY') { + const weekStart = getWeekStart(now); // 本周一 + return weekStart.toISOString().split('T')[0]; + } else if (periodType === 'MONTHLY') { + return `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}`; + } +} +``` + +**超限提示** +``` +┌─────────────────────────────────────┐ +│ ⚠️ 积分消耗限制提醒 │ +├─────────────────────────────────────┤ +│ 您的部门本周积分消耗即将达到上限 │ +│ │ +│ 部门:产品部 │ +│ 本周限制:500 分 │ +│ 已使用:420 分(84%) │ +│ 剩余额度:80 分 │ +│ │ +│ 本次任务需要:100 分 │ +│ │ +│ ❌ 无法发起任务 │ +│ │ +│ 建议: │ +│ 1. 等待下周一重置(还剩2天) │ +│ 2. 联系部门负责人临时增加额度 │ +│ 3. 降低任务规模减少积分消耗 │ +│ │ +│ [联系负责人] [知道了] │ +└─────────────────────────────────────┘ +``` + +### 1.6 部门数据权限 + +**数据可见性规则** +``` +需求部门负责人可见: +✓ 本部门所有成员发起的任务 +✓ 本部门积分使用明细 +✓ 本部门消耗统计和趋势 +✓ 本部门成员需求发起排名 +✗ 其他部门的任务和数据 + +接单部门主管可见: +✓ 本部门所有设计师接收的任务 +✓ 本部门设计师的进度和绩效 +✓ 本部门获得的积分统计 +✓ 本部门设计师排行榜 +✗ 其他部门的设计师数据 + +超级管理员/管理员: +✓ 全部部门的所有数据 +``` + +--- + +## 二、任务发起模块 + +### 2.1 功能流程图 + +``` +开始 → 检查用户权限 → 查询团队积分 → 填写任务信息 → 计算所需积分 +→ 积分验证 → 选择发布类型 → 提交审核 → 发送通知 → 结束 +``` + +### 2.2 详细功能点 + +#### 2.2.1 需求类型选择 + +**配置管理(系统设置)** +- 需求类型名称(如:主图设计、详情页设计、视频剪辑等) +- 单价分值(每个类型对应的积分单价) +- 预估耗时(小时/天) +- 类型状态(启用/禁用) +- 类型描述和要求 + +**前端交互** +- 下拉选择框展示已启用的需求类型 +- 快速新增按钮(需管理员权限) +- 显示类型详细信息(单价、耗时) + +**数据示例** +| 需求类型 | 单价分值 | 预估耗时 | 状态 | +|----------|----------|----------|------| +| 主图设计 | 50 | 4小时 | 启用 | +| 详情页设计 | 200 | 2天 | 启用 | +| 视频剪辑 | 150 | 1天 | 启用 | +| 图片修图 | 30 | 2小时 | 启用 | + +#### 2.2.2 数量输入与积分计算 + +**实时计算逻辑** +```javascript +总积分 = 需求类型单价 × 数量 +预估完成时间 = 需求类型耗时 × 数量 +``` + +**积分验证机制** +``` +1. 实时查询用户所属团队的积分池余额 +2. 对比:总积分 vs 团队余额 +3. 不足时: + - 显示红色警告 + - 显示当前余额和所需积分 + - 提示联系管理员充值 + - 禁用提交按钮 +4. 充足时: + - 显示绿色提示 + - 显示扣除后余额 + - 允许提交 +``` + +**优化建议** +- 添加积分预算预警功能(余额<阈值时提前通知) +- 团队积分充值审批流程 +- 积分使用历史记录和统计 + +#### 2.2.3 任务类型选择 +- **新品**:全新产品的设计需求 +- **优化**:对已有设计进行优化改进 +- **改图**:对现有图片进行修改 +- **其它**:自定义说明的其他需求 + +#### 2.2.4 品牌选择 + +**配置管理(系统设置)** +- 品牌名称 +- 品牌LOGO +- 品牌说明 +- 品牌风格指南(可选) +- 状态(启用/禁用) + +**前端展示** +- 下拉选择框 +- 支持搜索过滤 +- 显示品牌LOGO和名称 + +#### 2.2.5 叶子类目选择 + +**配置管理(系统设置)** +- 多级类目树形结构 +- 类目名称 +- 类目层级(一级/二级/三级/叶子类目) +- 关联品牌(可选) +- 状态管理 + +**前端交互** +- 级联选择器 +- 支持搜索 +- 只能选择叶子类目 + +**数据结构示例** +``` +服装 +├── 女装 +│ ├── 连衣裙 [叶子] +│ ├── T恤 [叶子] +│ └── 裤子 [叶子] +└── 男装 + ├── 衬衫 [叶子] + └── 牛仔裤 [叶子] +``` + +#### 2.2.6 SPU信息输入 +- 文本输入框 +- 支持多行 +- 字数限制(如500字) +- 支持复制粘贴 + +#### 2.2.7 平台选择 + +**配置管理(系统设置)** +- 平台名称(淘宝、天猫、京东、拼多多、抖音等) +- 平台图标 +- 平台设计规范说明 +- 状态管理 + +**前端交互** +- 下拉选择或标签选择 +- 支持多选(一个任务可发布到多个平台) + +#### 2.2.8 需求附件上传 + +**⚠️ 重要说明** +- **发布需求时**:只需上传需求表格文件(Excel/Word),无需上传设计源文件(PSD、AI等) +- **设计师交付时**:只需填写素材存储路径,无需上传文件到系统 + +**文件类型支持(仅需求表格)** +- 需求表格:.xls, .xlsx, .doc, .docx +- 参考文档:.pdf +- 压缩包:.zip(如需多个表格时) + +**文件限制** +- 单文件大小:≤ 50MB +- 文件数量:≤ 5个(通常1-2个表格即可) +- 总大小:≤ 100MB + +**功能要求** +- 拖拽上传 +- 上传进度显示 +- 文件预览(支持Excel/Word在线预览) +- 删除功能 +- 下载功能 + +**设计说明** +``` +┌────────────────────────────────────────┐ +│ 上传需求表格 │ +├────────────────────────────────────────┤ +│ │ +│ 📎 已上传文件: │ +│ ┌──────────────────────────────────┐ │ +│ │ 📄 11月主图设计需求表.xlsx │ │ +│ │ 大小:2.3MB 上传时间:10:30 │ │ +│ │ [预览] [下载] [删除] │ │ +│ └──────────────────────────────────┘ │ +│ │ +│ [+ 点击上传] 或 拖拽文件到此处 │ +│ │ +│ 💡 提示: │ +│ • 仅需上传需求说明表格 │ +│ • 支持格式:Excel (.xlsx/.xls) │ +│ • 无需上传设计源文件(PSD/AI等) │ +│ • 设计师交付时仅需填写素材路径 │ +│ │ +└────────────────────────────────────────┘ +``` + +#### 2.2.9 任务发布类型 + +##### A. 指定任务 +- 从设计师列表中选择指定人员 +- 显示该设计师当前任务负荷 +- 支持选择多人(任务拆分) +- 任务直接进入指定设计师的待办列表 + +##### B. 招募任务(任务大厅) +- 任务发布到任务大厅 +- 所有设计师可见 +- 设计师主动抢单 +- 遵循"先到先得"或"评分优先"规则 + +##### C. 默认模式(主管分配) +- 任务进入主管待分配池 +- 主管根据设计师负荷和专长分配 +- 主管可设置任务优先级 +- 分配后通知设计师 + +**优化建议:智能推荐** +- 根据设计师历史任务类型匹配度推荐 +- 根据设计师当前负荷推荐 +- 根据设计师评分推荐 +- AI自动分配模式(可选) + +#### 2.2.10 交付地址信息(素材路径) + +**⚠️ 重要说明** +设计师交付时**无需上传文件**到系统,只需填写或粘贴素材的存储路径即可。 + +**信息收集(由接收任务人填写)** +- 共享盘路径(如:\\\\server\\design\\2025-11\\project01\\) +- 云盘链接(如:https://pan.baidu.com/s/xxxxx) +- 本地服务器路径(如:/mnt/design/2025-11/) +- Git仓库地址(如:https://github.com/xxx/project.git) +- 其他说明(文件夹结构、命名规范等) + +**填写时机** +- 任务接收时可选填写(预计交付位置) +- 任务进行中可修改 +- 任务完成提交时必须填写(实际交付位置) + +**交付路径输入界面** +``` +┌────────────────────────────────────────┐ +│ 填写素材交付路径 │ +├────────────────────────────────────────┤ +│ │ +│ 素材存储路径:* │ +│ ┌──────────────────────────────────┐ │ +│ │ \\server\design\2025-11\main-img│ │ +│ └──────────────────────────────────┘ │ +│ │ +│ 路径类型: │ +│ ● 共享盘路径 ○ 云盘链接 ○ 其他 │ +│ │ +│ 文件夹结构说明: │ +│ ┌──────────────────────────────────┐ │ +│ │ /源文件(PSD、AI等) │ │ +│ │ /导出图(JPG、PNG等) │ │ +│ │ /参考素材 │ │ +│ └──────────────────────────────────┘ │ +│ │ +│ 补充说明: │ +│ ┌──────────────────────────────────┐ │ +│ │ 已按需求表格顺序命名,请查收 │ │ +│ └──────────────────────────────────┘ │ +│ │ +│ 💡 提示: │ +│ • 请确保路径可访问 │ +│ • 建议使用共享盘或云盘链接 │ +│ • 无需上传文件到系统 │ +│ • 确保文件夹权限已设置 │ +│ │ +│ [取消] [确认提交] │ +└────────────────────────────────────────┘ +``` + +**路径验证规则** +- 必填项:至少填写一个有效路径 +- 格式检查:UNC路径、HTTP链接等格式验证 +- 长度限制:单个路径≤500字符 +- 支持多行输入:可填写多个路径 + +### 2.3 任务发起表单设计 + +**表单布局建议** + +``` +┌─────────────────────────────────────┐ +│ 发起任务需求 │ +├─────────────────────────────────────┤ +│ │ +│ 团队积分余额:1,000 分 │ +│ │ +│ [必填] 需求类型: │ +│ [下拉选择 ▼] → [+ 新增] │ +│ 显示:单价50分/个,预估4小时 │ +│ │ +│ [必填] 数量: │ +│ [___] 个 │ +│ ✓ 需消耗:250分 │ +│ ✓ 剩余:750分 │ +│ ✓ 预估完成时间:20小时 │ +│ │ +│ [必填] 任务类型: │ +│ ○ 新品 ○ 优化 ○ 改图 ○ 其它 │ +│ │ +│ [必填] 品牌: │ +│ [下拉选择 ▼] │ +│ │ +│ [必填] 叶子类目: │ +│ [级联选择 ▼] │ +│ │ +│ [必填] SPU信息: │ +│ [多行文本框] │ +│ 0/500 字 │ +│ │ +│ [必填] 平台: │ +│ [多选下拉 ▼] │ +│ │ +│ [必填] 需求附件: │ +│ [拖拽上传区域或点击上传] │ +│ 已上传:design.psd (5.2MB) [×] │ +│ │ +│ [必填] 发布类型: │ +│ ○ 指定任务 [选择设计师 ▼] │ +│ ○ 招募任务(发布到任务大厅) │ +│ ● 默认模式(由主管分配) │ +│ │ +│ 期望完成时间: │ +│ [日期选择器] │ +│ │ +│ 备注说明: │ +│ [文本框] │ +│ │ +│ [取消] [保存草稿] [提交] │ +│ │ +└─────────────────────────────────────┘ +``` + +--- + +## 三、任务大厅模块 + +### 3.1 功能概述 +任务大厅是招募任务的展示平台,设计师可以浏览、筛选和抢单。 + +### 3.2 任务卡片展示 + +**卡片信息结构** +``` +┌──────────────────────────────────────┐ +│ [主图设计] 🔥热门 │ +│ ────────────────────────────────── │ +│ 发布人:张三(产品部) │ +│ 发布时间:2025-11-07 10:30 │ +│ 任务类型:新品 │ +│ 预估积分:250分 │ +│ 任务时效:2天 │ +│ ────────────────────────────────── │ +│ 📦 品牌:Nike │ +│ 📑 类目:服装 > 女装 > 连衣裙 │ +│ 🏷️ SPU:夏季新款连衣裙系列 │ +│ 🌐 平台:淘宝、天猫 │ +│ ────────────────────────────────── │ +│ 数量:5个 | 还剩:2个可抢 │ +│ │ +│ [查看详情] [立即抢单] │ +└──────────────────────────────────────┘ +``` + +**任务状态标识** +- 🔥 热门任务(抢单人数多) +- ⚡ 紧急任务(时效短) +- 💎 高分任务(积分高) +- 🆕 新发布任务(1小时内) + +### 3.3 筛选和搜索功能 + +**筛选条件** +- 任务类型(新品/优化/改图/其它) +- 需求类型(下拉多选) +- 品牌(下拉多选) +- 类目(树形选择) +- 平台(多选) +- 积分范围(滑块) +- 时效范围(今天/3天内/一周内/自定义) +- 发布时间(今天/本周/本月/自定义) + +**搜索功能** +- 关键词搜索(SPU信息、任务描述) +- 发布人搜索 + +**排序方式** +- 最新发布 +- 积分最高 +- 时效最短 +- 最热门 + +### 3.4 抢单机制 + +#### 抢单规则 +1. **资格校验** + - 用户角色为设计师 + - 账号状态正常 + - 当前任务负荷未超限(可配置) + +2. **抢单冲突处理** + - 并发控制:乐观锁/悲观锁 + - 先到先得原则 + - 数量限制(一个任务可被多人抢,每人抢部分数量) + +3. **抢单确认** + - 显示任务详细信息 + - 确认接收数量 + - 填写预计完成时间 + - 填写交付地址信息 + +#### 抢单后流程 +``` +抢单成功 → 任务进入"我的任务" → 开始计时 → 设计师进度大厅更新 +``` + +### 3.5 任务详情页 + +**展示内容** +- 任务基本信息(扩展版卡片信息) +- 需求附件下载 +- 发布人联系方式 +- 任务历史记录(如被抢单记录) +- 相关任务推荐 + +--- + +## 四、我的任务进度模块 + +### 4.1 功能概述 +为发布人和接收人提供任务进度的可视化管理界面。 + +### 4.2 双视角设计 + +#### 视角一:发布人视角 +- 查看自己发起的所有任务 +- 查看每个任务的接收人和进度 +- 审核任务交付 +- 评价任务完成情况 + +#### 视角二:接收人视角 +- 查看自己接收的所有任务 +- 更新任务进度 +- 提交任务交付 +- 查看任务评价 + +### 4.3 任务卡片(增强版) + +**卡片信息结构** +``` +┌──────────────────────────────────────────┐ +│ [主图设计] #T20251107001 │ +│ ──────────────────────────────────── │ +│ 发布人:张三 接收人:李四 │ +│ 发布时间:11-07 10:30 开始:11-07 11:00│ +│ 任务类型:新品 预期:11-09 │ +│ 预估积分:250分 │ +│ ──────────────────────────────────── │ +│ 📦 品牌:Nike │ +│ 📑 类目:服装 > 女装 > 连衣裙 │ +│ 🏷️ SPU:夏季新款连衣裙系列 │ +│ 🌐 平台:淘宝、天猫 │ +│ ──────────────────────────────────── │ +│ 进度:███████████░░░░░ 75% │ +│ 状态:⚙️ 进行中 │ +│ ──────────────────────────────────── │ +│ [查看详情] [更新进度] [提交交付] │ +│ ──────────────────────────────────── │ +│ 💬 备注:需要增加模特图... │ +└──────────────────────────────────────────┘ +``` + +**任务状态流转** +``` +待接收 → 进行中 → 待审核 → [审核通过 / 打回修改] → 已完成 +``` + +| 状态 | 说明 | 可操作 | +|------|------|--------| +| 待接收 | 已分配但未开始 | 接收人:接受/拒绝 | +| 进行中 | 设计师正在处理 | 接收人:更新进度、提交交付 | +| 待审核 | 已提交交付 | 发布人:审核通过、打回修改 | +| 打回修改 | 审核未通过 | 接收人:重新提交 | +| 已完成 | 审核通过 | 发布人:评价 | +| 已取消 | 任务取消 | - | + +### 4.4 审核功能 + +#### 审核通过 +1. 点击"审核通过"按钮 +2. 填写五星评价 + - 质量评分(1-5星) + - 时效评分(1-5星) + - 沟通评分(1-5星) + - 综合评分(自动计算平均值) + - 评价内容(文字,可选) +3. 确认提交 +4. 积分奖励发放给设计师 +5. 任务状态更新为"已完成" + +**评价维度建议** +``` +┌──────────────────────────┐ +│ 任务完成评价 │ +├──────────────────────────┤ +│ 质量评分:★★★★★ │ +│ 时效评分:★★★★☆ │ +│ 沟通评分:★★★★★ │ +│ ────────────────────── │ +│ 综合评分:4.7分 │ +│ ────────────────────── │ +│ 评价内容: │ +│ [文本框] │ +│ 设计质量很高,符合要求... │ +│ │ +│ [取消] [提交] │ +└──────────────────────────┘ +``` + +#### 打回修改 +1. 点击"打回修改"按钮 +2. 填写打回原因(必填) +3. 上传标注图片(可选) +4. 设置最晚交付时间(必填) +5. 确认提交 +6. 发送通知给设计师 +7. 任务状态更新为"打回修改" + +**打回记录** +- 记录每次打回的原因和时间 +- 显示打回次数(过多可能影响评分) +- 保留完整的沟通记录 + +### 4.5 日历视图 + +**功能描述** +以日历的形式展示任务的时间线和进度,类似项目甘特图。 + +**视图类型** +- 月视图 +- 周视图 +- 日视图 + +**展示元素** +``` + 周一 周二 周三 周四 周五 + ┌─────┬─────┬─────┬─────┬─────┐ + 张三 │█████│█████│ │ │ │ 主图设计 + │ │ │░░░░░│░░░░░│ │ 详情页优化 + ├─────┼─────┼─────┼─────┼─────┤ + 李四 │ │█████│█████│█████│ │ 视频剪辑 + │ │ │ │░░░░░│░░░░░│ 新品设计 + └─────┴─────┴─────┴─────┴─────┘ + +图例: +█ 任务执行时间 +░ 任务预留时间 +红色边框:延期任务 +黄色标记:即将到期 +``` + +**交互功能** +- 点击任务条查看详情 +- 拖拽调整任务时间(主管权限) +- 悬浮显示任务摘要 +- 筛选显示(按人员、状态、类型) + +**优化建议** +- 添加时间轴缩放功能 +- 支持导出PDF/图片 +- 支持打印功能 +- 与钉钉日历同步 + +### 4.6 任务详情抽屉/弹窗 + +**详细信息展示** +- 任务完整信息 +- 附件下载 +- 交付地址信息 +- 进度更新记录 +- 沟通留言板 +- 操作日志 + +--- + +## 四之二、管理员任务调配模块 + +### 4.7 功能概述 +管理员任务调配模块是系统的核心管理功能之一,允许管理员对系统中的任务进行灵活调配,包括对进行中的任务进行转移、重新分配,以及对任务池(任务大厅)中的招募任务进行管理和调配。这个功能可以有效应对突发情况、优化资源分配、平衡设计师工作负荷。 + +### 4.8 任务调配权限 + +**权限要求** +- **超级管理员**:可以调配所有任务,无限制 +- **管理员**:可以调配指定部门或团队的任务 +- **主管**:只能调配自己团队内的任务(可选功能) + +**权限控制** +``` +调配权限 = 角色权限 + 部门权限 + 特殊授权 +``` + +### 4.9 进行中任务调配 + +#### 4.9.1 功能入口 + +**调配面板位置** +- 我的任务进度页面(管理员视图) +- 设计师进度大厅页面 +- 任务详情页面(管理员权限可见) + +**展示方式** +``` +┌──────────────────────────────────────────┐ +│ 进行中的任务管理 │ +├──────────────────────────────────────────┤ +│ 🔍 筛选:[部门▼] [设计师▼] [任务状态▼] │ +│ [批量调配] [导出] │ +├──────────────────────────────────────────┤ +│ │ +│ ☑ [进行中] #T20251107001 - 主图设计 │ +│ 接收人:张三 | 进度:60% | 剩余:1天 │ +│ 发布人:产品部-王经理 │ +│ [查看详情] [转移任务] [调整优先级] │ +│ ──────────────────────────────────── │ +│ ☑ [进行中] #T20251107002 - 详情页优化 │ +│ 接收人:李四 | 进度:30% | 剩余:3天 │ +│ 发布人:市场部-刘总监 │ +│ [查看详情] [转移任务] [调整优先级] │ +│ ──────────────────────────────────── │ +│ ☑ [延期] #T20251107003 - 视频剪辑 │ +│ 接收人:王五 | 进度:45% | 已延期2天 │ +│ 发布人:运营部-陈主管 │ +│ [查看详情] [转移任务] [调整优先级] │ +│ │ +│ [1] 2 3 ... 10 │ +└──────────────────────────────────────────┘ +``` + +#### 4.9.2 任务转移功能 + +**转移场景** +1. **设计师请假/离职**:紧急转移任务给其他设计师 +2. **负荷不均衡**:从繁忙设计师转移任务给空闲设计师 +3. **技能匹配**:转移给更擅长该类型的设计师 +4. **任务延期**:转移给效率更高的设计师加急处理 +5. **质量问题**:转移给经验更丰富的设计师 + +**转移操作流程** + +**步骤1:选择任务** +``` +支持单个转移和批量转移 +- 单个:点击任务卡片的"转移任务"按钮 +- 批量:勾选多个任务,点击"批量调配" +``` + +**步骤2:填写转移信息** +``` +┌─────────────────────────────────────┐ +│ 任务转移 │ +├─────────────────────────────────────┤ +│ 当前任务:#T20251107001 - 主图设计 │ +│ 当前接收人:张三(UI设计师) │ +│ 当前进度:60% │ +│ ───────────────────────────────── │ +│ [必填] 转移给: │ +│ [选择设计师 ▼] │ +│ 智能推荐: │ +│ ✓ 李四(负荷:40%,评分4.8) │ +│ ✓ 赵六(负荷:35%,评分4.7) │ +│ │ +│ [必填] 转移原因: │ +│ ○ 原设计师请假/离职 │ +│ ○ 负荷调整 │ +│ ○ 技能不匹配 │ +│ ○ 任务延期 │ +│ ● 其他(请说明) │ +│ │ +│ 详细说明: │ +│ ┌─────────────────────────────┐ │ +│ │ [文本框] │ │ +│ │ 张三因病请假,任务紧急... │ │ +│ └─────────────────────────────┘ │ +│ │ +│ 是否保留原进度: │ +│ ☑ 是(新接收人继续当前进度) │ +│ ☐ 否(任务重置为0%,重新开始) │ +│ │ +│ 调整交付时间: │ +│ 原定:2025-11-09 │ +│ 新定:[日期选择器] │ +│ │ +│ 积分处理: │ +│ ○ 按进度分配(张三60%,李四40%) │ +│ ○ 全部给新接收人 │ +│ ● 由管理员最终确定 │ +│ │ +│ 通知设置: │ +│ ☑ 通知原接收人 │ +│ ☑ 通知新接收人 │ +│ ☑ 通知发布人 │ +│ ☑ 通知主管 │ +│ │ +│ [取消] [确认转移] │ +└─────────────────────────────────────┘ +``` + +**步骤3:系统处理** +``` +1. 权限校验(管理员权限验证) +2. 检查新接收人状态(是否在职、负荷是否过高) +3. 计算原设计师的延误情况和扣分 + - 判断是否存在延误(当前时间 > 原定交付时间) + - 如果延误,计算延误天数/小时 + - 根据系统扣分规则计算扣分值 + - 记录延误数据到原设计师档案 + - 执行积分扣除操作 + - 标注"因任务转移扣分" +4. 重新计算任务时效 + - 评估剩余工作量(根据当前进度) + - 计算新的预计完成时间 + - 考虑新设计师的当前负荷 + - 设置新的交付期限 +5. 更新任务接收人信息 +6. 记录转移日志(原因、操作人、时间、延误扣分情况) +7. 处理积分分配(按进度分配奖励积分,扣除延误扣分) +8. 发送通知给相关人员(包含延误扣分信息) +9. 更新任务状态和时间线 +10. 在任务详情中显示转移记录和延误记录 +``` + +**步骤4:转移记录** +``` +任务操作历史: +──────────────────────────────────── +2025-11-07 14:30 [任务转移] +操作人:管理员-陈经理 +转移:张三 → 李四 +原因:原设计师请假 +进度保留:是(60%) + +⚠️ 延误情况: +- 原定交付时间:2025-11-07 12:00 +- 转移时间:2025-11-07 14:30 +- 延误时长:2小时30分钟 +- 扣分:-5分(按延误扣分规则) +- 延误记录已记入张三档案 + +积分处理: +- 张三获得进度积分:150分(60%) +- 张三延误扣分:-5分 +- 张三实际获得:145分 +- 李四将获得:100分(40%) + +时效重新计算: +- 原定交付时间:2025-11-07 12:00(已延误) +- 剩余工作量:40% +- 预估需要时间:8小时 +- 新交付时间:2025-11-08 10:00 +- 新接收人:李四(当前负荷:40%) + +说明:张三因病请假,任务紧急转移给李四继续完成 +──────────────────────────────────── +2025-11-07 10:00 [任务开始] +接收人:张三 +原定交付时间:2025-11-07 12:00 +──────────────────────────────────── +``` + +#### 4.9.3 延误扣分机制 + +**延误判定标准** +``` +延误判定 = 当前时间 > 原定交付时间 + +延误分类: +- 轻微延误:< 2小时 +- 一般延误:2-8小时 +- 严重延误:8-24小时 +- 重大延误:> 24小时 +``` + +**扣分规则(在系统配置中可调整)** + +| 延误程度 | 延误时长 | 扣分规则 | 示例 | +|---------|---------|---------|------| +| 轻微延误 | < 2小时 | 每小时扣2分 | 延误1.5小时 → 扣3分 | +| 一般延误 | 2-8小时 | 每小时扣3分 | 延误5小时 → 扣15分 | +| 严重延误 | 8-24小时 | 每小时扣5分 | 延误10小时 → 扣50分 | +| 重大延误 | > 24小时 | 基础50分+每天10分 | 延误2天 → 扣70分 | + +**扣分计算公式** +```javascript +function calculateDelayPenalty(delayHours) { + if (delayHours < 0) return 0; // 未延误 + + if (delayHours < 2) { + // 轻微延误 + return Math.ceil(delayHours * 2); + } else if (delayHours < 8) { + // 一般延误 + return Math.ceil(delayHours * 3); + } else if (delayHours < 24) { + // 严重延误 + return Math.ceil(delayHours * 5); + } else { + // 重大延误 + const days = Math.floor(delayHours / 24); + return 50 + days * 10; + } +} + +// 示例 +延误2.5小时 → 扣分 = 2.5 × 3 = 8分 +延误10小时 → 扣分 = 10 × 5 = 50分 +延误48小时 → 扣分 = 50 + 2 × 10 = 70分 +``` + +**延误数据留存** + +**设计师延误档案** +``` +张三 - 延误记录: + +┌──────────────────────────────────────┐ +│ 延误记录 #1 │ +├──────────────────────────────────────┤ +│ 任务:#T20251107001 - 主图设计 │ +│ 原定交付:2025-11-07 12:00 │ +│ 实际情况:任务转移时延误 │ +│ 延误时长:2小时30分钟 │ +│ 延误程度:一般延误 │ +│ 扣分:-5分 │ +│ 原因标注:因病请假导致任务转移 │ +│ 进度完成:60% │ +│ 记录时间:2025-11-07 14:30 │ +│ 操作人:管理员-陈经理 │ +└──────────────────────────────────────┘ + +影响统计: +- 本月延误次数:1次 +- 本月延误扣分:-5分 +- 延误率:4.2%(1/24) +- 影响平均评分:-0.1分 +``` + +**延误记录字段** +- 任务ID和名称 +- 原定交付时间 +- 延误发生时间 +- 延误时长(精确到分钟) +- 延误程度分类 +- 扣分值 +- 原因标注(任务转移/正常延误) +- 当时进度完成情况 +- 是否因特殊原因(病假/事假等) +- 管理员备注 +- 是否申诉 + +**延误扣分通知** + +**通知原设计师** +``` +┌─────────────────────────────────────┐ +│ ⚠️ 延误扣分通知 │ +├─────────────────────────────────────┤ +│ 您的任务因延误产生扣分 │ +│ │ +│ 任务:#T20251107001 - 主图设计 │ +│ 原定交付:2025-11-07 12:00 │ +│ 转移时间:2025-11-07 14:30 │ +│ 延误时长:2小时30分钟 │ +│ │ +│ 扣分详情: │ +│ - 延误扣分:-5分 │ +│ - 进度积分:+150分(60%完成) │ +│ - 实际获得:+145分 │ +│ │ +│ 原因:因病请假导致任务转移 │ +│ 备注:管理员已标注特殊原因 │ +│ │ +│ 您的延误记录: │ +│ - 本月延误次数:1次 │ +│ - 本月延误扣分:-5分 │ +│ │ +│ [查看详情] [申诉] │ +└─────────────────────────────────────┘ +``` + +**延误申诉机制** + +设计师如果认为延误有正当理由,可以提出申诉: +1. 填写申诉原因 +2. 上传证明材料(如病假条、邮件截图等) +3. 提交给管理员审核 +4. 管理员审核通过后可撤销扣分或减少扣分 +5. 记录申诉历史 + +**时效重新计算算法** + +```javascript +function recalculateDeadline(task, newDesigner) { + // 1. 计算剩余工作量 + const remainingWork = 1 - task.progress; // 40% = 0.4 + + // 2. 获取原始预估时长 + const originalEstimatedHours = task.demandType.estimateTime; // 20小时 + + // 3. 计算剩余工作时长 + const remainingHours = originalEstimatedHours * remainingWork; // 8小时 + + // 4. 考虑新设计师的熟练度系数(可选) + const proficiencyFactor = calculateProficiency(newDesigner, task.demandType); + // 0.8 = 很熟练,1.0 = 正常,1.2 = 不太熟练 + + // 5. 考虑新设计师的当前负荷 + const workloadFactor = calculateWorkloadFactor(newDesigner.currentWorkload); + // 负荷高则需要更多时间 + // 负荷<50%: 1.0, 50-70%: 1.1, 70-90%: 1.2, >90%: 1.3 + + // 6. 计算实际需要时间 + const actualHours = remainingHours * proficiencyFactor * workloadFactor; + // 8 × 1.0 × 1.1 = 8.8小时 + + // 7. 计算新的交付时间(考虑工作日历) + const newDeadline = calculateWorkingDeadline( + new Date(), // 从现在开始 + actualHours, // 需要的小时数 + '9:00-18:00' // 工作时间 + ); + + return { + newDeadline: newDeadline, + estimatedHours: actualHours, + proficiencyFactor: proficiencyFactor, + workloadFactor: workloadFactor, + reason: '任务转移,时效重新计算' + }; +} + +// 示例输出 +{ + newDeadline: '2025-11-08 10:00', + estimatedHours: 8.8, + proficiencyFactor: 1.0, + workloadFactor: 1.1, + reason: '任务转移,时效重新计算' +} +``` + +**时效重新计算展示** +``` +┌─────────────────────────────────────┐ +│ 时效重新计算 │ +├─────────────────────────────────────┤ +│ 原定交付时间:2025-11-07 12:00(已过)│ +│ 当前时间:2025-11-07 14:30 │ +│ 已延误:2小时30分钟 │ +│ │ +│ 剩余工作量:40% │ +│ 预估工作时长:8小时 │ +│ │ +│ 新接收人:李四 │ +│ - 熟练度系数:1.0(熟练) │ +│ - 负荷系数:1.1(负荷适中) │ +│ │ +│ 实际需要时间:8.8小时 │ +│ 新交付时间:2025-11-08 10:00 │ +│ │ +│ [确认] [调整] │ +└─────────────────────────────────────┘ +``` + +#### 4.9.4 智能推荐算法 + +**推荐设计师的评分算法** +```javascript +推荐分数 = + 负荷权重 × (1 - 当前负荷率) × 30% + + 技能权重 × 技能匹配度 × 25% + + 评分权重 × 历史平均评分 × 20% + + 效率权重 × 按时完成率 × 15% + + 经验权重 × 同类型任务完成数 × 10% + +按推荐分数降序排列,展示Top 5 +``` + +**推荐标签** +- 🔥 最佳匹配:综合评分最高 +- ⚡ 效率之选:历史效率最高 +- 💎 质量保证:历史评分最高 +- 🆓 空闲优先:当前负荷最低 +- 🎯 经验丰富:同类型任务完成最多 + +#### 4.9.4 批量转移 + +**批量转移场景** +- 设计师长期离职,转移所有在手任务 +- 部门调整,批量转移任务归属 +- 紧急情况,批量分散任务 + +**批量操作界面** +``` +┌─────────────────────────────────────┐ +│ 批量任务转移 (已选择 5 个任务) │ +├─────────────────────────────────────┤ +│ 任务列表: │ +│ ☑ #T001 - 主图设计 (张三, 60%) │ +│ ☑ #T002 - 详情页优化 (张三, 30%) │ +│ ☑ #T005 - 视频剪辑 (张三, 45%) │ +│ ☑ #T008 - 改图 (张三, 80%) │ +│ ☑ #T012 - 新品设计 (张三, 15%) │ +│ ───────────────────────────────── │ +│ 转移策略: │ +│ ○ 统一转移给单人 │ +│ ● 智能分配给多人(推荐) │ +│ ○ 手动指定每个任务的接收人 │ +│ │ +│ 智能分配预览: │ +│ 李四:接收2个任务 (#T001, #T002) │ +│ 王五:接收2个任务 (#T005, #T008) │ +│ 赵六:接收1个任务 (#T012) │ +│ │ +│ 转移原因:[必填] │ +│ 积分处理:[选择策略] │ +│ 通知设置:[勾选] │ +│ │ +│ [取消] [执行转移] │ +└─────────────────────────────────────┘ +``` + +#### 4.9.5 任务优先级调整 + +**优先级管理** +``` +优先级级别: +P0 - 特急(红色):需立即处理,1天内完成 +P1 - 紧急(橙色):优先处理,3天内完成 +P2 - 普通(蓝色):正常处理,1周内完成 +P3 - 低优(灰色):可延后处理,2周内完成 +``` + +**优先级调整界面** +``` +┌───────────────────────────────┐ +│ 调整任务优先级 │ +├───────────────────────────────┤ +│ 任务:#T20251107001 │ +│ 标题:主图设计 │ +│ 当前优先级:P2 (普通) │ +│ ─────────────────────────── │ +│ 调整为: │ +│ ○ P0 - 特急 │ +│ ● P1 - 紧急 │ +│ ○ P2 - 普通 │ +│ ○ P3 - 低优 │ +│ │ +│ 调整原因:[必填] │ +│ ┌─────────────────────────┐ │ +│ │ 客户要求提前上线... │ │ +│ └─────────────────────────┘ │ +│ │ +│ 是否通知接收人: │ +│ ☑ 站内消息 │ +│ ☑ 钉钉推送 │ +│ ☑ 短信提醒(紧急时) │ +│ │ +│ [取消] [确认] │ +└───────────────────────────────┘ +``` + +### 4.10 任务池(任务大厅)调配 + +#### 4.10.1 功能概述 +管理员可以对任务大厅中的招募任务进行管理,包括: +- 查看所有招募任务的状态 +- 指定接收人(将招募任务转为指定任务) +- 撤回任务到待分配池 +- 调整任务信息 +- 关闭/取消任务 + +#### 4.10.2 任务池管理界面 + +**管理入口** +``` +任务大厅 → 管理员视图(切换按钮) +``` + +**界面布局** +``` +┌──────────────────────────────────────────┐ +│ 任务池管理(管理员) │ +├──────────────────────────────────────────┤ +│ 📊 统计: │ +│ 招募中:23个 | 无人抢单:5个 | 今日新增:8个 │ +│ ──────────────────────────────────── │ +│ 🔍 筛选:[状态▼] [部门▼] [发布时间▼] │ +│ [高级筛选] [批量操作▼] [刷新] │ +│ ──────────────────────────────────── │ +│ │ +│ ┌────────────────────────────────────┐ │ +│ │ 🔥 [招募中] #T20251107010 │ │ +│ │ 主图设计 | 250分 | 2天 │ │ +│ │ 发布人:产品部-王经理 │ │ +│ │ 发布时间:2小时前 │ │ +│ │ 浏览:15次 | 收藏:3人 | 抢单:0人│ │ +│ │ ⚠️ 提示:发布2小时无人抢单 │ │ +│ │ │ │ +│ │ [查看详情] [指定接收人] [撤回] │ │ +│ │ [调整] [推广] [关闭] │ │ +│ └────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────┐ │ +│ │ ✅ [已抢单] #T20251107011 │ │ +│ │ 详情页优化 | 200分 | 1天 │ │ +│ │ 发布人:市场部-刘总监 │ │ +│ │ 接收人:李四 │ │ +│ │ 抢单时间:30分钟前 │ │ +│ │ │ │ +│ │ [查看详情] [转移] [调整] │ │ +│ └────────────────────────────────────┘ │ +│ │ +└──────────────────────────────────────────┘ +``` + +#### 4.10.3 指定接收人功能 + +**使用场景** +- 招募任务长时间无人抢单 +- 任务紧急需要快速分配 +- 特定任务需要特定设计师处理 + +**操作流程** +``` +┌─────────────────────────────────────┐ +│ 为任务指定接收人 │ +├─────────────────────────────────────┤ +│ 任务:#T20251107010 - 主图设计 │ +│ 当前状态:招募中(无人抢单) │ +│ 发布时间:2小时前 │ +│ ───────────────────────────────── │ +│ 选择接收人: │ +│ [搜索设计师] │ +│ │ +│ 智能推荐: │ +│ ┌─────────────────────────────┐ │ +│ │ ○ 李四 🔥 最佳匹配 │ │ +│ │ UI设计师 | 负荷40% | 评分4.8 │ │ +│ │ 同类型完成:28个 │ │ +│ ├─────────────────────────────┤ │ +│ │ ● 王五 ⚡ 效率之选 │ │ +│ │ 视觉设计师 | 负荷35% | 评分4.6│ │ +│ │ 同类型完成:22个 │ │ +│ ├─────────────────────────────┤ │ +│ │ ○ 赵六 🆓 空闲优先 │ │ +│ │ UI设计师 | 负荷20% | 评分4.7 │ │ +│ │ 同类型完成:15个 │ │ +│ └─────────────────────────────┘ │ +│ │ +│ 指定原因: │ +│ ○ 任务紧急 │ +│ ● 长时间无人抢单 │ +│ ○ 需要特定设计师 │ +│ ○ 其他 │ +│ │ +│ 说明:[选填] │ +│ ┌─────────────────────────────┐ │ +│ │ 任务已发布2小时无人抢单... │ │ +│ └─────────────────────────────┘ │ +│ │ +│ 是否通知: │ +│ ☑ 接收人(钉钉+站内) │ +│ ☑ 发布人(站内) │ +│ │ +│ 备注:指定后任务状态将变为"进行中" │ +│ │ +│ [取消] [确认指定] │ +└─────────────────────────────────────┘ +``` + +#### 4.10.4 任务撤回功能 + +**撤回场景** +- 任务信息有误需要修改 +- 任务需求变更 +- 任务取消或暂停 +- 重新评估任务优先级 + +**撤回操作** +``` +┌───────────────────────────────┐ +│ 撤回任务 │ +├───────────────────────────────┤ +│ 任务:#T20251107010 │ +│ 当前状态:招募中 │ +│ ─────────────────────────── │ +│ 撤回后状态: │ +│ ● 待分配(进入主管待办) │ +│ ○ 草稿箱(暂时保存) │ +│ ○ 取消任务(积分退回) │ +│ │ +│ 撤回原因:[必填] │ +│ ┌─────────────────────────┐ │ +│ │ 任务信息需要修改... │ │ +│ └─────────────────────────┘ │ +│ │ +│ 积分处理: │ +│ ☑ 退回到发布人团队积分池 │ +│ │ +│ 通知设置: │ +│ ☑ 通知发布人 │ +│ ☑ 通知关注的设计师 │ +│ │ +│ [取消] [确认撤回] │ +└───────────────────────────────┘ +``` + +#### 4.10.5 任务推广功能 + +**推广场景** +- 重要任务长时间无人接单 +- 紧急任务需要快速响应 +- 高分任务需要更多曝光 + +**推广方式** +``` +┌───────────────────────────────┐ +│ 任务推广 │ +├───────────────────────────────┤ +│ 任务:#T20251107010 │ +│ ─────────────────────────── │ +│ 推广方式: │ +│ ☑ 置顶显示(任务大厅顶部) │ +│ ☑ 标记为热门任务 │ +│ ☑ 钉钉群发通知 │ +│ ☑ 站内消息推送(全体设计师)│ +│ ☐ 短信通知(谨慎使用) │ +│ │ +│ 置顶时长: │ +│ ○ 1小时 ● 3小时 ○ 6小时 │ +│ ○ 12小时 ○ 24小时 │ +│ │ +│ 推广理由: │ +│ ┌─────────────────────────┐ │ +│ │ 紧急任务,需尽快接单... │ │ +│ └─────────────────────────┘ │ +│ │ +│ [取消] [开始推广] │ +└───────────────────────────────┘ +``` + +#### 4.10.6 批量调配任务池 + +**批量操作** +``` +支持的批量操作: +- 批量指定接收人(选择多个任务,分配给不同设计师) +- 批量撤回(将多个任务从大厅撤回) +- 批量调整优先级 +- 批量推广 +- 批量关闭/取消 +``` + +**批量指定界面** +``` +┌─────────────────────────────────────┐ +│ 批量指定接收人 (已选择 3 个任务) │ +├─────────────────────────────────────┤ +│ 任务列表: │ +│ ☑ #T010 - 主图设计 (250分, 2天) │ +│ ☑ #T011 - 详情页优化 (200分, 1天) │ +│ ☑ #T015 - 视频剪辑 (150分, 1天) │ +│ ───────────────────────────────── │ +│ 分配策略: │ +│ ○ 统一分配给单人 │ +│ ● 智能分配(根据技能和负荷) │ +│ ○ 手动指定每个任务 │ +│ │ +│ 智能分配预览: │ +│ ┌─────────────────────────────┐ │ +│ │ #T010 → 李四 │ │ +│ │ 匹配度:95% | 当前负荷:40% │ │ +│ ├─────────────────────────────┤ │ +│ │ #T011 → 王五 │ │ +│ │ 匹配度:88% | 当前负荷:35% │ │ +│ ├─────────────────────────────┤ │ +│ │ #T015 → 赵六 │ │ +│ │ 匹配度:92% | 当前负荷:30% │ │ +│ └─────────────────────────────┘ │ +│ │ +│ [调整分配] [取消] [确认批量指定] │ +└─────────────────────────────────────┘ +``` + +### 4.11 调配监控与统计 + +#### 4.11.1 调配日志 + +**完整记录** +``` +所有调配操作都会记录详细日志: +- 操作时间 +- 操作人 +- 操作类型(转移/指定/撤回/调整等) +- 任务信息 +- 变更详情(从谁到谁) +- 操作原因 +- 处理结果 +``` + +**日志查询界面** +``` +┌──────────────────────────────────────────┐ +│ 任务调配日志 │ +├──────────────────────────────────────────┤ +│ 🔍 [时间范围▼] [操作人▼] [操作类型▼] │ +│ [任务编号] [搜索] │ +│ ──────────────────────────────────── │ +│ 2025-11-07 14:30:25 │ +│ [任务转移] 管理员-陈经理 │ +│ #T20251107001 - 主图设计 │ +│ 张三 → 李四 │ +│ 原因:原设计师请假 │ +│ 积分处理:按进度分配(60%:40%) │ +│ [查看详情] │ +│ ──────────────────────────────────── │ +│ 2025-11-07 13:15:10 │ +│ [指定接收人] 管理员-陈经理 │ +│ #T20251107010 - 主图设计 │ +│ 招募中 → 李四 │ +│ 原因:长时间无人抢单 │ +│ [查看详情] │ +│ ──────────────────────────────────── │ +└──────────────────────────────────────────┘ +``` + +#### 4.11.2 调配统计 + +**统计维度** +``` +管理员工作台 - 任务调配统计 + +本月调配概况: +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ 调配次数 │ │ 转移任务 │ │ 指定任务 │ +│ 156次 │ │ 89个 │ │ 67个 │ +│ ↑ 较上月+12│ │ ↑ 较上月+8 │ │ ↑ 较上月+4 │ +└─────────────┘ └─────────────┘ └─────────────┘ + +调配原因分布(饼图): +- 负荷调整:35% +- 设计师请假/离职:25% +- 任务延期:20% +- 技能不匹配:12% +- 无人抢单:8% + +调配效果分析: +- 调配后按时完成率:94.5% +- 调配后平均评分:4.7⭐ +- 调配后延误率:5.5% +``` + +### 4.12 调配权限审计 + +**审计功能** +``` +系统自动审计调配操作: +1. 记录所有调配行为 +2. 监控异常调配(如频繁转移同一任务) +3. 权限使用统计 +4. 定期生成审计报告 +5. 支持审计日志导出 +``` + +**异常告警** +``` +触发告警的场景: +- 同一任务被转移超过3次 +- 管理员单日调配超过50次 +- 非授权调配尝试 +- 批量调配超过20个任务 +- 积分异常变动 +``` + +### 4.13 移动端调配支持 + +**移动端功能** +``` +管理员可在钉钉工作台或移动App进行: +✓ 查看待调配任务列表 +✓ 快速转移任务 +✓ 指定接收人 +✓ 调整优先级 +✓ 接收调配提醒 +✓ 审批调配申请(可选流程) +``` + +--- + +## 五、设计师进度大厅模块 + +### 5.1 功能概述 +为管理层提供全局视角,实时查看所有设计师的工作状态、负荷和绩效。 + +### 5.2 设计师看板 + +**看板布局** +``` +┌────────────────────────────────────────────────┐ +│ 🔍 筛选:[部门▼] [在职状态▼] [排序:积分▼] │ +├────────────────────────────────────────────────┤ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ 👤 张三 │ │ 👤 李四 │ │ 👤 王五 │ │ +│ │ UI设计师 │ │ 视觉设计师│ │ 视频剪辑师│ │ +│ ├──────────┤ ├──────────┤ ├──────────┤ │ +│ │📊 负荷:●●│ │📊 负荷:●●│ │📊 负荷:●○│ │ +│ │ ●●●○○ │ │ ●●○○○ │ │ ●○○○○ │ │ +│ │ │ │ │ │ │ │ +│ │⚙️ 进行中:│ │⚙️ 进行中:│ │⚙️ 进行中:│ │ +│ │ 3个 │ │ 2个 │ │ 1个 │ │ +│ │ │ │ │ │ │ │ +│ │📅 排队: │ │📅 排队: │ │📅 排队: │ │ +│ │ 2个 │ │ 1个 │ │ 0个 │ │ +│ │ │ │ │ │ │ │ +│ │💎 累计积分│ │💎 累计积分│ │💎 累计积分│ │ +│ │ 1,250 │ │ 980 │ │ 650 │ │ +│ │ │ │ │ │ │ │ +│ │⭐ 平均评分│ │⭐ 平均评分│ │⭐ 平均评分│ │ +│ │ 4.8⭐ │ │ 4.5⭐ │ │ 4.9⭐ │ │ +│ │ │ │ │ │ │ │ +│ │⚠️ 延误: │ │⚠️ 延误: │ │⚠️ 延误: │ │ +│ │ 0次 │ │ 1次 │ │ 0次 │ │ +│ │ │ │ │ │ │ │ +│ │✅ 完成: │ │✅ 完成: │ │✅ 完成: │ │ +│ │ 25个 │ │ 18个 │ │ 12个 │ │ +│ │ │ │ │ │ │ │ +│ │[查看详情]│ │[查看详情]│ │[查看详情]│ │ +│ └──────────┘ └──────────┘ └──────────┘ │ +└────────────────────────────────────────────────┘ +``` + +**卡片信息项** +| 指标 | 说明 | +|------|------| +| 负荷状态 | 用圆点数量表示当前任务饱和度 | +| 进行中任务 | 当前正在执行的任务数量 | +| 排队任务 | 已接收但未开始的任务数量 | +| 累计积分 | 本月/本季度/累计获得的积分 | +| 平均评分 | 历史任务的平均评分 | +| 延误次数 | 本月/累计延误交付的次数 | +| 完成数量 | 本月/累计完成的任务数量 | + +**负荷状态算法** +``` +负荷值 = (进行中任务数 × 1.5 + 排队任务数) / 设计师容量 +负荷等级: +- 0-2分:空闲(○○○○○) +- 3-4分:轻度(●○○○○) +- 5-6分:适中(●●○○○) +- 7-8分:繁忙(●●●○○) +- 9-10分:饱和(●●●●○) +- 10+分:超负荷(●●●●●)红色警告 +``` + +### 5.3 激励提示系统 + +#### 5.3.1 功能概述 +通过实时排名、成就系统、目标追踪等多种激励手段,激发设计师的工作积极性和竞争意识,营造良性竞争氛围。 + +#### 5.3.2 实时排名提示 + +**个人排名卡片** +``` +┌─────────────────────────────────────┐ +│ 🏆 您的实时排名 │ +├─────────────────────────────────────┤ +│ 本月积分排名:#3 / 28 │ +│ ▲ 较上月上升 2 名 │ +│ │ +│ 💪 再完成 2 个任务即可超越李四! │ +│ (当前:980分,李四:1,050分) │ +│ │ +│ 🎯 本月目标:进入前 3 名 │ +│ 进度:██████░░░░ 60% │ +│ 还差:150 分 │ +│ │ +│ [查看详细排名] [设定新目标] │ +└─────────────────────────────────────┘ +``` + +**排名变化实时提醒** +``` +📢 排名变动通知: +"恭喜!您在本月积分榜上升至第3名,超越了赵六!继续加油💪" + +📢 被超越提醒: +"王五刚刚超过了你,当前排名第4。只差50分就能反超!🔥" + +📢 接近目标提醒: +"距离您的目标(前3名)只差150分了,加油!还剩15天⏰" +``` + +#### 5.3.3 成就勋章系统 + +**成就类别** + +| 成就类型 | 成就名称 | 获得条件 | 奖励 | +|---------|---------|---------|------| +| 数量类 | 🥉 百战老兵 | 累计完成100个任务 | +100积分 | +| 数量类 | 🥈 千锤百炼 | 累计完成500个任务 | +500积分 | +| 数量类 | 🥇 万事通 | 累计完成1000个任务 | +1000积分 | +| 质量类 | ⭐ 完美主义 | 连续10个5星好评 | +200积分 | +| 质量类 | 💎 品质保证 | 月均评分≥4.8分 | +150积分 | +| 质量类 | 👑 质量之王 | 季度评分第一 | +500积分 | +| 效率类 | ⚡ 闪电侠 | 单日完成5个任务 | +50积分 | +| 效率类 | 🚀 极速传说 | 连续7天按时交付 | +200积分 | +| 效率类 | ⏰ 守时达人 | 连续30天零延误 | +500积分 | +| 特殊类 | 🌟 新星 | 新人首月完成20个 | +100积分 | +| 特殊类 | 🔥 月度之星 | 月度积分第一 | +300积分 | +| 特殊类 | 🎖️ 全能战士 | 完成所有类型任务 | +200积分 | + +**成就展示** +``` +┌─────────────────────────────────────┐ +│ 🏅 我的成就勋章 │ +├─────────────────────────────────────┤ +│ 已获得:8个 | 总积分奖励:1,500 │ +│ ───────────────────────────────── │ +│ │ +│ ✨ 最新获得 │ +│ 🥉 百战老兵 │ +│ 2025-11-07 获得 │ +│ 完成第100个任务! │ +│ │ +│ 📋 我的勋章墙 │ +│ 🥇🥈🥉 ⭐💎 ⚡🚀⏰ 🌟🔥🎖️ │ +│ 已解锁:●●●●●●●● ○○○○ │ +│ │ +│ 🎯 即将获得(还差一点) │ +│ ⏰ 守时达人 │ +│ 连续按时交付:21/30天 │ +│ ███████░░░ 70% │ +│ │ +│ 💎 品质保证 │ +│ 月均评分:4.75/4.8分 │ +│ ████████░░ 80% │ +│ │ +│ [查看全部成就] [成就排行榜] │ +└─────────────────────────────────────┘ +``` + +**成就获得通知** +``` +🎉 恭喜!您获得新成就! + +┌─────────────────────────────────────┐ +│ 🥉 百战老兵 │ +│ │ +│ 您已累计完成 100 个任务! │ +│ │ +│ 奖励积分:+100 分 │ +│ 获得时间:2025-11-07 │ +│ │ +│ "每一次交付都是成长的印记" │ +│ │ +│ [分享成就] [继续努力] │ +└─────────────────────────────────────┘ +``` + +#### 5.3.4 里程碑提醒 + +**个人里程碑** +``` +📍 里程碑提醒: + +✅ 已达成 +• 本月完成第10个任务 🎯 +• 本月获得500积分 💎 +• 连续7天按时交付 ⚡ + +🎯 即将达成 +• 本月完成20个任务(当前:18个) + 还差:2个 | 预计:2天后达成 + +• 本月获得1000积分(当前:980分) + 还差:20分 | 下个任务即可达成 + +• 累计完成100个任务(当前:98个) + 还差:2个 | 历史性时刻即将到来! + +💪 挑战目标 +• 本月进入积分榜前3(当前:第4) + 还差:70分 | 完成1个大任务即可 + +• 月均评分达到4.8⭐(当前:4.75⭐) + 需要:接下来3个任务都拿5星 +``` + +#### 5.3.5 积分冲刺提示 + +**月末冲刺** +``` +┌─────────────────────────────────────┐ +│ 🔥 本月积分冲刺 │ +├─────────────────────────────────────┤ +│ 距离月底还有:5天 │ +│ ───────────────────────────────── │ +│ │ +│ 您当前排名:#4 │ +│ 本月积分:980 分 │ +│ │ +│ 🎯 冲刺目标:进入前3名 │ +│ 需要超越:李四(1,050分) │ +│ 还需积分:70+ 分 │ +│ │ +│ 💪 冲刺方案建议: │ +│ • 接1个详情页任务(200分)✅ │ +│ • 或接2个主图任务(100分)✅ │ +│ • 或接3个改图任务(90分)✅ │ +│ │ +│ 📊 成功率预测:78% │ +│ (基于您的历史效率和剩余时间) │ +│ │ +│ [查看可接任务] [设定提醒] │ +└─────────────────────────────────────┘ +``` + +**周冲刺提醒** +``` +📢 周冲刺提醒: +"本周还剩2天,再完成1个任务即可达成本周目标(5个任务)! +任务大厅有3个适合您的主图设计任务,快去抢单吧!🚀" +``` + +#### 5.3.6 同事对比(友好竞争) + +**与同事对比卡片** +``` +┌─────────────────────────────────────┐ +│ 📊 与同事对比 │ +├─────────────────────────────────────┤ +│ 对比对象:李四(同部门/同职级) │ +│ ───────────────────────────────── │ +│ │ +│ 本月任务完成数: │ +│ 你:18个 ████████░░ 90% │ +│ 他:20个 ██████████ 100% │ +│ 差距:-2个 │ +│ │ +│ 本月积分: │ +│ 你:980分 ████████░░ 93% │ +│ 他:1,050分 ██████████ 100% │ +│ 差距:-70分 │ +│ │ +│ 平均评分: │ +│ 你:4.75⭐ █████████░ 95% │ +│ 他:4.8⭐ ██████████ 100% │ +│ 差距:-0.05⭐ │ +│ │ +│ 按时完成率: │ +│ 你:95% ██████████░ 95% │ +│ 他:92% █████████░░ 92% │ +│ 领先:+3% 💪 │ +│ │ +│ 💡 超越建议: │ +│ "再完成2个任务并保持5星好评, │ +│ 本月你将在综合排名上超越李四!" │ +│ │ +│ [换个对比对象] [查看详细数据] │ +└─────────────────────────────────────┘ +``` + +#### 5.3.7 每日激励卡片 + +**每日签到激励** +``` +┌─────────────────────────────────────┐ +│ 🌅 早安,张三! │ +├─────────────────────────────────────┤ +│ 今天是 2025-11-07 周四 │ +│ ───────────────────────────────── │ +│ │ +│ 📅 连续签到:21天 🔥 │ +│ 本月签到:21/30天 │ +│ (连续签到30天可获得勋章+50积分) │ +│ │ +│ 📋 今日待办: │ +│ • 主图设计 #T001(截止今天18:00)⚠️ │ +│ • 详情页优化 #T015(截止明天) │ +│ • 改图 #T028(排队中) │ +│ │ +│ 🎯 今日目标建议: │ +│ • 完成1个任务(当前进度:2/5) │ +│ • 保持5星好评(连续5星:4个) │ +│ • 按时交付(连续按时:21天) │ +│ │ +│ 💪 加油语录: │ +│ "每一次精心设计,都是向卓越迈进!" │ +│ │ +│ [开始工作] [查看任务] │ +└─────────────────────────────────────┘ +``` + +#### 5.3.8 即时激励弹窗 + +**完成任务时** +``` +✨ 任务完成! + +┌─────────────────────────────────────┐ +│ 🎉 恭喜完成任务! │ +├─────────────────────────────────────┤ +│ 任务:主图设计 #T001 │ +│ 用时:1.5天(提前0.5天)✅ │ +│ ───────────────────────────────── │ +│ │ +│ 获得积分:+50分 💎 │ +│ 累计积分:1,030分 │ +│ ───────────────────────────────── │ +│ │ +│ 📊 排名变化 │ +│ 本月积分排名:#4 → #3 ▲ │ +│ 超越了:李四 │ +│ │ +│ 🎯 里程碑 │ +│ • 本月第19个任务 ✅ │ +│ • 连续22天按时交付 🔥 │ +│ • 距离"守时达人"勋章还差8天 │ +│ │ +│ 💪 激励 │ +│ "太棒了!你的速度快人一步!" │ +│ "继续保持,月度之星就是你!" │ +│ │ +│ [分享成果] [接下一个任务] │ +└─────────────────────────────────────┘ +``` + +**获得好评时** +``` +⭐ 获得5星好评! + +┌─────────────────────────────────────┐ +│ 🌟 客户给了你5星好评! │ +├─────────────────────────────────────┤ +│ 任务:详情页优化 #T015 │ +│ 评价:"设计质量非常高,超出预期!" │ +│ ───────────────────────────────── │ +│ │ +│ 📊 评分统计 │ +│ • 连续5星好评:5个 🔥 │ +│ • 本月平均评分:4.8⭐ │ +│ • 好评榜排名:#2 ▲ │ +│ │ +│ 🎯 成就进度 │ +│ • "完美主义"勋章:5/10 个5星 │ +│ █████░░░░░ 50% │ +│ │ +│ 💡 提示 │ +│ 再获得5个5星好评即可解锁"完美主义" │ +│ 勋章,奖励200积分! │ +│ │ +│ [查看评价详情] │ +└─────────────────────────────────────┘ +``` + +#### 5.3.9 竞赛活动提示 + +**月度竞赛** +``` +┌─────────────────────────────────────┐ +│ 🏆 本月竞赛活动 │ +├─────────────────────────────────────┤ +│ 活动主题:"设计马拉松" │ +│ 时间:11月1日-11月30日 │ +│ ───────────────────────────────── │ +│ │ +│ 🎯 竞赛规则 │ +│ • 完成任务数量排名 │ +│ • 平均评分≥4.5分 │ +│ • 零延误记录 │ +│ │ +│ 🏅 奖励设置 │ +│ 🥇 第一名:2000积分 + 荣誉证书 │ +│ 🥈 第二名:1500积分 │ +│ 🥉 第三名:1000积分 │ +│ 🎖️ 4-10名:500积分 │ +│ │ +│ 📊 您的当前排名:#5 │ +│ 完成任务:18个 │ +│ 平均评分:4.75⭐ │ +│ 距离前3名:2个任务的差距 │ +│ │ +│ 💪 冲刺建议 │ +│ "距离活动结束还有5天,加油冲刺!" │ +│ "再完成3个高质量任务,进前3有望!" │ +│ │ +│ [查看排行榜] [活动规则] │ +└─────────────────────────────────────┘ +``` + +#### 5.3.10 团队PK提示 + +**部门团队PK** +``` +┌─────────────────────────────────────┐ +│ ⚔️ 部门PK战 │ +├─────────────────────────────────────┤ +│ 设计部 UI组 vs 视觉组 │ +│ ───────────────────────────────── │ +│ │ +│ 📊 实时战况 │ +│ UI组: │ +│ • 完成任务:45个 ████████░░ 82% │ +│ • 团队积分:2,250分 │ +│ • 平均评分:4.7⭐ │ +│ │ +│ 视觉组: │ +│ • 完成任务:55个 ██████████ 100% │ +│ • 团队积分:2,750分 │ +│ • 平均评分:4.8⭐ │ +│ │ +│ 📈 差距分析 │ +│ • 任务数:落后10个 │ +│ • 积分:落后500分 │ +│ • 每人需完成:2个任务 │ +│ │ +│ 💪 团队激励 │ +│ "UI组加油!我们能行!" │ +│ "张三、李四刚刚各完成1个任务!" │ +│ "王五正在冲刺,距离完成还有2小时!" │ +│ │ +│ 🎁 获胜奖励 │ +│ • 团队成员各+300积分 │ +│ • 团队荣誉墙展示 │ +│ • 团队聚餐基金 │ +│ │ +│ [为团队加油] [查看详情] │ +└─────────────────────────────────────┘ +``` + +#### 5.3.11 智能提醒和建议 + +**任务推荐** +``` +💡 智能推荐 + +基于你的历史表现,以下任务很适合你: + +┌─────────────────────────────────────┐ +│ 🎯 推荐任务 #T088 │ +│ 类型:主图设计 │ +│ 积分:50分 | 时效:1天 │ +│ ───────────────────────────────── │ +│ 推荐原因: │ +│ ✓ 你在该类型任务上平均用时0.8天 │ +│ ✓ 该类型任务的平均评分4.9⭐ │ +│ ✓ 完成后可超越李四,升至第3名! │ +│ ✓ 客户是你的老客户(5次合作) │ +│ │ +│ 预期收益: │ +│ • 积分:+50分 │ +│ • 排名:#4 → #3 │ +│ • 里程碑:本月第20个任务达成 │ +│ │ +│ 成功率预测:95% 💪 │ +│ │ +│ [立即抢单] [查看详情] │ +└─────────────────────────────────────┘ +``` + +**休息提醒** +``` +⏰ 适度休息提醒 + +张三,你今天已经连续工作5小时了, +完成了3个任务,表现非常出色!👍 + +建议休息15-20分钟,劳逸结合效率更高! + +你的今日工作数据: +• 完成任务:3个 ✅ +• 获得积分:150分 💎 +• 工作时长:5小时 +• 效率评分:优秀 ⭐ + +"休息是为了走更远的路" 😊 + +[休息一下] [继续工作] +``` + +#### 5.3.12 可视化激励看板 + +**个人激励看板** +``` +┌─────────────────────────────────────────────────┐ +│ ✨ 我的激励看板 │ +├─────────────────────────────────────────────────┤ +│ │ +│ 🏆 本月成就 │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ 积分排名 │ │ 完成任务 │ │ 好评数 │ │ +│ │ #3 │ │ 18个 │ │ 15个 │ │ +│ │ ▲ +2 │ │ ▲ +5 │ │ ▲ +3 │ │ +│ └─────────┘ └─────────┘ └─────────┘ │ +│ │ +│ 📈 趋势曲线(近30天) │ +│ 积分 │ ╱ │ +│ 1000│ ╱ │ +│ 800│╱ │ +│ 600└───────────────── │ +│ 1周 2周 3周 4周 │ +│ │ +│ 🎯 目标进度 │ +│ 月度目标:1200积分 │ +│ 当前进度:980分 ████████░░ 82% │ +│ 还需:220分(预计3天可达成) │ +│ │ +│ 🔥 连续记录 │ +│ • 连续签到:21天 🔥🔥🔥 │ +│ • 连续按时:22天 ⚡⚡⚡ │ +│ • 连续5星:5个 ⭐⭐⭐ │ +│ │ +│ 💎 本月收获 │ +│ • 获得积分:980分 │ +│ • 解锁成就:2个 │ +│ • 获得好评:15个 │ +│ • 被感谢:8次 │ +│ │ +└─────────────────────────────────────────────────┘ +``` + +### 5.4 实时进度跟踪 + +**甘特图视图** +- 横轴:时间线(按天/周显示) +- 纵轴:设计师列表 +- 任务条:显示任务名称、进度、状态 +- 颜色标识: + - 绿色:正常进行 + - 黄色:即将到期 + - 红色:已延期 + - 灰色:已完成 + +**时间线视图示例** +``` +设计师 11/7 11/8 11/9 11/10 11/11 +──────────────────────────────────────────── +张三 [████主图设计█████] + [░░░░详情页░░░░░░░░░░░░] +──────────────────────────────────────────── +李四 [██████视频剪辑████] + [░新品░░░] +──────────────────────────────────────────── +王五 [█改图█] + [░░░░░优化设计░░░░░░░] +``` + +### 5.4 数据分析看板 + +#### 5.4.1 总览卡片 + +**设计师团队总览** +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ 👥 团队人数 │ │ 📊 总任务量 │ │ ✅ 完成率 │ +│ │ │ │ │ │ +│ 28人 │ │ 156个 │ │ 92.3% │ +│ │ │ │ │ │ +│ ↑ 较上月+2 │ │ ↑ 较上月+8 │ │ ↓ 较上月-1%│ +└─────────────┘ └─────────────┘ └─────────────┘ + +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ ⭐ 平均评分 │ │ ⚠️ 延误率 │ │ 💎 总积分 │ +│ │ │ │ │ │ +│ 4.6 ⭐ │ │ 5.2% │ │ 28,500 │ +│ │ │ │ │ │ +│ ↑ 较上月+0.2│ │ ↓ 较上月-2%│ │ ↑ 较上月+3K│ +└─────────────┘ └─────────────┘ └─────────────┘ +``` + +#### 5.4.2 多维度分析图表 + +**A. 任务完成趋势图(折线图)** +- X轴:时间(周/月) +- Y轴:任务数量 +- 多条线:发起、完成、延误 + +**B. 需求类型分布(饼图)** +- 各类型需求占比 +- 点击可查看详细数据 + +**C. 设计师积分排名(柱状图)** +- 显示Top 20 +- 对比上月排名变化 + +**D. 任务时效分析(雷达图)** +- 多维度:质量、时效、沟通、复用率、创新度 + +**E. 品牌/类目热力图** +- 哪些品牌/类目的任务最多 +- 用于资源分配参考 + +**F. 设计师负荷热力图** +- 日历视图 +- 颜色深浅代表负荷程度 + +#### 5.4.3 排行榜 + +**好评榜(五星好评榜)** +``` +排名 设计师 五星数 平均评分 完成任务数 +───────────────────────────────────────── +🥇 张三 45 4.9⭐ 48 +🥈 王五 38 4.8⭐ 42 +🥉 李四 35 4.7⭐ 45 +4 赵六 32 4.6⭐ 40 +5 孙七 30 4.7⭐ 38 +``` + +**积分榜** +``` +排名 设计师 本月积分 累计积分 环比增长 +───────────────────────────────────────── +🥇 李四 1,250 12,500 ↑15% +🥈 张三 1,180 15,200 ↑8% +🥉 王五 980 9,800 ↑12% +``` + +**效率榜(按时完成率)** +``` +排名 设计师 完成任务 按时完成 按时率 +───────────────────────────────────────── +🥇 王五 42 42 100% +🥈 张三 48 47 97.9% +🥉 李四 45 43 95.6% +``` + +**勤奋榜(任务数量)** +``` +排名 设计师 本月完成 累计完成 月均完成 +───────────────────────────────────────── +🥇 张三 48 520 43 +🥈 李四 45 480 40 +🥉 王五 42 450 38 +``` + +#### 5.4.4 高级筛选 + +**筛选维度** +- 时间范围:本周/本月/本季度/本年/自定义 +- 部门/团队 +- 设计师 +- 任务类型 +- 需求类型 +- 品牌 +- 状态 + +**导出功能** +- 导出Excel报表 +- 导出PDF报告 +- 定时邮件推送(周报/月报) + +--- + +## 五之二、需求统计展板模块 + +### 5.5 功能概述 +需求统计展板为管理层和各部门提供全局的需求发起数据分析,通过用户维度和部门维度的多角度统计,帮助了解需求分布、消耗趋势和团队协作情况。 + +### 5.6 统计展板首页 + +**整体布局** +``` +┌──────────────────────────────────────────────────┐ +│ 需求统计展板 │ +├──────────────────────────────────────────────────┤ +│ 筛选:[本月▼] [全部部门▼] [导出报表] │ +│ ────────────────────────────────────────────── │ +│ │ +│ 📊 总览数据卡片 │ +│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ +│ │发起总数 │ │消耗积分 │ │完成率 │ │平均时效 │ │ +│ │ 256个 │ │ 12,500 │ │ 92.3% │ │ 1.8天 │ │ +│ │↑ +15% │ │↑ +8% │ │↓ -1.2% │ │↓ -0.3天│ │ +│ └────────┘ └────────┘ └────────┘ └────────┘ │ +│ │ +│ ────────────────────────────────────────────── │ +│ [部门维度] [用户维度] [趋势分析] [类型分析] │ +│ ────────────────────────────────────────────── │ +│ │ +│ (下方显示对应的统计内容) │ +│ │ +└──────────────────────────────────────────────────┘ +``` + +### 5.7 部门维度统计 + +**部门积分余额消耗提醒看板** +``` +┌──────────────────────────────────────────────────┐ +│ 💰 部门积分消耗月报(激励消耗) │ +├──────────────────────────────────────────────────┤ +│ 当前月份:2025年11月 | 剩余天数:⏰ 5天 │ +│ ────────────────────────────────────────────── │ +│ │ +│ 🔥 消耗提醒:月底将至,抓紧消耗您的积分额度! │ +│ │ +│ ┌─────────────────────────────────────────┐ │ +│ │ 产品部 - 负责人:张三 │ │ +│ │ ───────────────────────────────────── │ │ +│ │ 月度积分池:10,000 分 │ │ +│ │ 已使用:4,250 分(42.5%) │ │ +│ │ 剩余可用:5,750 分(57.5%) │ │ +│ │ ▓▓▓▓▓▓▓▓░░░░░░░░░░░░ │ │ +│ │ │ │ +│ │ 💡 建议:还有5,750分可用,可发起约115个 │ │ +│ │ 主图设计任务(50分/个) │ │ +│ │ │ │ +│ │ ⚠️ 提醒:月底剩余积分将清零,请合理利用! │ │ +│ │ [立即发起任务] │ │ +│ └─────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────┐ │ +│ │ 市场部 - 负责人:李四 │ │ +│ │ ───────────────────────────────────── │ │ +│ │ 月度积分池:8,000 分 │ │ +│ │ 已使用:3,400 分(42.5%) │ │ +│ │ 剩余可用:4,600 分(57.5%) │ │ +│ │ ▓▓▓▓▓▓▓▓░░░░░░░░░░░░ │ │ +│ │ │ │ +│ │ 💡 建议:还有4,600分可用 │ │ +│ │ [立即发起任务] │ │ +│ └─────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────┐ │ +│ │ 运营部 - 负责人:王五 │ │ +│ │ ───────────────────────────────────── │ │ +│ │ 月度积分池:5,000 分 │ │ +│ │ 已使用:4,800 分(96%) │ │ +│ │ 剩余可用:200 分(4%)🎉 快消耗完了! │ │ +│ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░ │ │ +│ │ │ │ +│ │ ✅ 优秀:积分利用率高达96%! │ │ +│ │ [立即发起任务] │ │ +│ └─────────────────────────────────────────┘ │ +│ │ +│ 🏆 本月积分消耗率排行榜 │ +│ 1. 运营部 96% (4,800/5,000) 🥇 第一名 │ +│ 2. 电商部 85% (3,400/4,000) 🥈 第二名 │ +│ 3. 产品部 42% (4,250/10,000) 🥉 第三名 │ +│ 4. 市场部 42% (3,400/8,000) │ +│ │ +│ ⏰ 月底倒计时:还有 5 天 │ +│ 📢 温馨提示:月底未使用的积分将清零,请各部门 │ +│ 负责人合理安排任务发起,充分利用积分资源! │ +│ │ +│ [查看历史消耗] [导出消耗报告] [钉钉提醒] │ +└──────────────────────────────────────────────────┘ +``` + +**积分消耗提醒规则** +- **绿色区域(0-50%)**:🟢 消耗率偏低,提醒"还有XX分可用,请合理安排任务" +- **黄色区域(51-80%)**:🟡 消耗率适中,提醒"进度良好,还可继续使用" +- **橙色区域(81-95%)**:🟠 消耗率较高,提醒"积分利用率高,再接再厉" +- **红色区域(96-100%)**:🔴 即将用完,祝贺"太棒了!积分利用率极高" + +**月底倒计时提醒** +- 距离月底30天:无特殊提醒 +- 距离月底15天:显示"还有15天,请注意积分使用进度" +- 距离月底7天:显示"⚠️ 还有7天,抓紧时间消耗积分" +- 距离月底3天:显示"🔥 紧急提醒:还有3天!快发起任务" +- 距离月底1天:显示"⚡ 最后1天!积分即将清零" + +**智能建议功能** +系统根据剩余积分和常用任务类型,自动计算并推荐可发起的任务数量: +```javascript +// 智能建议算法 +function calculateSuggestion(remainingScore, deptHistory) { + // 获取部门最常发起的任务类型 + const topTaskTypes = getTopTaskTypes(deptHistory, 3); + + const suggestions = topTaskTypes.map(taskType => { + const count = Math.floor(remainingScore / taskType.unitScore); + return { + taskTypeName: taskType.name, + unitScore: taskType.unitScore, + suggestedCount: count, + totalScore: count * taskType.unitScore + }; + }); + + return suggestions; +} +``` + +**部门排行榜** +``` +┌──────────────────────────────────────────────────┐ +│ 需求部门统计排行(本月) │ +├──────────────────────────────────────────────────┤ +│ │ +│ 排名 部门 发起数 消耗积分 完成率 平均评分│ +│ ──────────────────────────────────────────── │ +│ 🥇 产品部 85个 4,250 94.1% 4.8⭐ │ +│ 负责人:张三 │ +│ 本月积分池:10000分 | 已用:4250分(42%) │ +│ 💰 剩余:5750分 🔥 抓紧消耗 │ +│ [查看详情] [查看明细] [立即发起任务] │ +│ ──────────────────────────────────────────── │ +│ 🥈 市场部 68个 3,400 91.2% 4.7⭐ │ +│ 负责人:李四 │ +│ 本月积分池:8000分 | 已用:3400分(42%) │ +│ 💰 剩余:4600分 🔥 抓紧消耗 │ +│ [查看详情] [查看明细] [立即发起任务] │ +│ ──────────────────────────────────────────── │ +│ 🥉 运营部 52个 4,800 89.8% 4.6⭐ │ +│ 负责人:王五 │ +│ 本月积分池:5000分 | 已用:4800分(96%) │ +│ ✅ 剩余:200分 优秀利用率 │ +│ [查看详情] [查看明细] [立即发起任务] │ +│ ──────────────────────────────────────────── │ +│ 4 电商部 41个 3,400 93.7% 4.9⭐ │ +│ 积分池:4000分 | 已用:3400分(85%) 🟠 │ +│ 5 设计部 10个 500 95.0% 4.8⭐ │ +│ 积分池:6000分 | 已用:500分(8%) 🟢 │ +│ │ +│ [查看全部部门] │ +└──────────────────────────────────────────────────┘ +``` + +**部门详情卡片** +``` +┌──────────────────────────────────────────────────┐ +│ 产品部 - 需求统计详情 │ +├──────────────────────────────────────────────────┤ +│ 负责人:张三 | 成员:15人 │ +│ ────────────────────────────────────────────── │ +│ │ +│ 📈 本月概况(2025-11) │ +│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ +│ │发起任务 │ │消耗积分 │ │完成任务 │ │进行中 │ │ +│ │ 85个 │ │ 4,250 │ │ 71个 │ │ 12个 │ │ +│ └────────┘ └────────┘ └────────┘ └────────┘ │ +│ │ +│ 💰 积分使用情况 │ +│ 本月限制:2,000 分 │ +│ 实际消耗:4,250 分 ⚠️(超限2,250分) │ +│ 进度:████████████████████░ 212% │ +│ │ +│ ⚠️ 提示:已超出本月限制,请控制需求发起 │ +│ │ +│ 📊 需求类型分布 │ +│ ┌────────────────────────────────────────┐ │ +│ │ ████████ 主图设计 35个 (41%) │ │ +│ │ ██████ 详情页设计 25个 (29%) │ │ +│ │ ████ 视频剪辑 15个 (18%) │ │ +│ │ ██ 改图 10个 (12%) │ │ +│ └────────────────────────────────────────┘ │ +│ │ +│ 📅 每周发起趋势 │ +│ ┌────────────────────────────────────────┐ │ +│ │ 25│ ● │ │ +│ │ 20│ ● ● │ │ +│ │ 15│ ● ● │ │ +│ │ 10│ ● ● │ │ +│ │ 0└───────────────────── │ │ +│ │ W1 W2 W3 W4 W5 │ │ +│ └────────────────────────────────────────┘ │ +│ │ +│ 👥 部门成员发起排名(Top 5) │ +│ 1. 张三 28个 1,400分 ⭐4.9 │ +│ 2. 李四 22个 1,100分 ⭐4.8 │ +│ 3. 王五 18个 900分 ⭐4.7 │ +│ 4. 赵六 12个 600分 ⭐4.6 │ +│ 5. 孙七 5个 250分 ⭐4.8 │ +│ │ +│ [导出明细] [设置消耗限制] [积分充值] │ +└──────────────────────────────────────────────────┘ +``` + +### 5.8 用户维度统计 + +**用户发起排行榜** +``` +┌──────────────────────────────────────────────────┐ +│ 需求发起用户排行(本月) │ +├──────────────────────────────────────────────────┤ +│ 🔍 搜索:[姓名/工号] 筛选:[部门▼] [排序▼] │ +│ ────────────────────────────────────────────── │ +│ │ +│ 排名 用户 部门 发起数 消耗积分 完成率│ +│ ──────────────────────────────────────────── │ +│ 🥇 张三 产品部 28个 1,400 96.4% │ +│ 最常发起:主图设计(12个) │ +│ 平均评分:4.9⭐ │ +│ [查看详情] │ +│ ──────────────────────────────────────────── │ +│ 🥈 李四 市场部 25个 1,250 92.0% │ +│ 最常发起:详情页设计(10个) │ +│ 平均评分:4.8⭐ │ +│ [查看详情] │ +│ ──────────────────────────────────────────── │ +│ 🥉 王五 产品部 22个 1,100 90.9% │ +│ 最常发起:视频剪辑(8个) │ +│ 平均评分:4.7⭐ │ +│ [查看详情] │ +│ ──────────────────────────────────────────── │ +│ 4 赵六 运营部 20个 1,000 95.0% │ +│ 5 孙七 市场部 18个 900 88.9% │ +│ ... │ +│ │ +│ [查看全部] [导出列表] │ +└──────────────────────────────────────────────────┘ +``` + +**用户详情分析** +``` +┌──────────────────────────────────────────────────┐ +│ 张三 - 需求发起详情 │ +├──────────────────────────────────────────────────┤ +│ 👤 产品部 - 产品经理 | 工号:EP001 │ +│ ────────────────────────────────────────────── │ +│ │ +│ 📊 统计概览 │ +│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ +│ │本月发起 │ │消耗积分 │ │完成率 │ │平均评分 │ │ +│ │ 28个 │ │ 1,400 │ │ 96.4% │ │ 4.9⭐ │ │ +│ │↑ +5个 │ │↑ +250 │ │↑ +2.1% │ │持平 │ │ +│ └────────┘ └────────┘ └────────┘ └────────┘ │ +│ │ +│ 📈 需求类型偏好 │ +│ ┌────────────────────────────────────────┐ │ +│ │ ████████████ 主图设计 12个 (43%) │ │ +│ │ ████████ 详情页设计 8个 (29%) │ │ +│ │ ████ 视频剪辑 5个 (18%) │ │ +│ │ ██ 改图 3个 (11%) │ │ +│ └────────────────────────────────────────┘ │ +│ │ +│ 🎯 品牌分布 │ +│ Nike: 10个 | Adidas: 8个 | Puma: 6个 | 其他: 4个│ +│ │ +│ 📅 发起时间分布 │ +│ ┌────────────────────────────────────────┐ │ +│ │ 周一: ███ 5个 │ │ +│ │ 周二: ████ 6个 │ │ +│ │ 周三: ████ 6个 │ │ +│ │ 周四: ███ 5个 │ │ +│ │ 周五: ██ 4个 │ │ +│ │ 周六: █ 1个 │ │ +│ │ 周日: █ 1个 │ │ +│ └────────────────────────────────────────┘ │ +│ │ +│ ⏱️ 平均任务时效:1.6天 │ +│ 📍 最常合作设计师:李四(接收8个任务) │ +│ │ +│ 📋 最近发起的任务(Top 5) │ +│ 1. #T001 主图设计 已完成 ⭐5.0 2天 │ +│ 2. #T015 详情页优化 进行中 - - │ +│ 3. #T028 视频剪辑 已完成 ⭐4.8 1.5天 │ +│ 4. #T042 改图 已完成 ⭐5.0 0.5天 │ +│ 5. #T055 新品设计 待接收 - - │ +│ │ +│ [查看全部任务] [导出数据] │ +└──────────────────────────────────────────────────┘ +``` + +### 5.9 趋势分析 + +**时间趋势图** +``` +┌──────────────────────────────────────────────────┐ +│ 需求发起趋势分析 │ +├──────────────────────────────────────────────────┤ +│ 时间范围:[近6个月▼] 对比:[同比▼] │ +│ ────────────────────────────────────────────── │ +│ │ +│ 📈 需求发起数量趋势 │ +│ 300│ │ +│ │ ●● │ +│ 250│ ●● │ +│ │ ●● │ +│ 200│ ●● │ +│ │ ●● │ +│ 150│ ●● │ +│ │ ●● │ +│ 100│ ●● │ +│ └───────────────────────────────── │ +│ 6月 7月 8月 9月 10月 11月 │ +│ │ +│ 💰 积分消耗趋势 │ +│ 15K│ │ +│ │ ●● │ +│ 12K│ ●● │ +│ │ ●● │ +│ 9K│ ●● │ +│ │ ●● │ +│ 6K│ ●● │ +│ │ ●● │ +│ 3K│ ●● │ +│ └───────────────────────────────── │ +│ 6月 7月 8月 9月 10月 11月 │ +│ │ +│ 📊 环比数据 │ +│ 本月 vs 上月: │ +│ - 发起数:256个 vs 223个 (↑14.8%) │ +│ - 消耗积分:12,500 vs 11,580 (↑7.9%) │ +│ - 完成率:92.3% vs 93.5% (↓1.3%) │ +│ │ +└──────────────────────────────────────────────────┘ +``` + +### 5.10 类型分析 + +**需求类型统计** +``` +┌──────────────────────────────────────────────────┐ +│ 需求类型分析 │ +├──────────────────────────────────────────────────┤ +│ │ +│ 📊 类型分布(本月) │ +│ ┌────────────────────────────────────────┐ │ +│ │ │ │ +│ │ ██████████ 主图设计 40% │ │ +│ │ ███████ 详情页设计 28% │ │ +│ │ █████ 视频剪辑 20% │ │ +│ │ ███ 改图 12% │ │ +│ │ │ │ +│ └────────────────────────────────────────┘ │ +│ │ +│ 类型 数量 占比 平均积分 平均时效 完成率 │ +│ ──────────────────────────────────────────── │ +│ 主图 102 40% 50分 1.5天 94% │ +│ 详情 72 28% 200分 2.5天 91% │ +│ 视频 51 20% 150分 2.0天 89% │ +│ 改图 31 12% 30分 0.8天 98% │ +│ │ +│ 📈 类型趋势对比 │ +│ (折线图显示各类型近6个月的变化) │ +│ │ +│ 🏆 最受欢迎:主图设计(102个,4,590分) │ +│ 💎 最高价值:详情页设计(平均200分/个) │ +│ ⚡ 最快完成:改图(平均0.8天) │ +│ │ +└──────────────────────────────────────────────────┘ +``` + +### 5.11 导出与报表 + +**导出功能** +``` +支持导出格式: +✓ Excel(详细明细) +✓ PDF(可视化报表) +✓ CSV(原始数据) +✓ 图片(图表截图) + +导出内容: +✓ 部门统计数据 +✓ 用户发起明细 +✓ 任务完成情况 +✓ 积分消耗记录 +✓ 趋势分析图表 + +定时报表: +✓ 每周一早9:00发送上周统计 +✓ 每月1号早9:00发送上月统计 +✓ 季度/年度报告 +``` + +**报表订阅** +``` +┌───────────────────────────────┐ +│ 统计报表订阅 │ +├───────────────────────────────┤ +│ 订阅类型: │ +│ ☑ 周报(每周一) │ +│ ☑ 月报(每月1号) │ +│ ☐ 季报(每季度首日) │ +│ │ +│ 接收人: │ +│ ☑ 自己 │ +│ ☑ 部门负责人 │ +│ ☑ 上级领导 │ +│ ☐ 其他人员 [添加] │ +│ │ +│ 发送方式: │ +│ ☑ 邮件 │ +│ ☑ 钉钉 │ +│ ☐ 短信 │ +│ │ +│ [取消] [保存] │ +└───────────────────────────────┘ +``` + +--- + +## 六、系统配置模块 + +### 6.1 功能清单 + +#### 6.1.1 需求类型配置 +``` +列表视图: +ID 类型名称 单价分值 预估耗时 状态 操作 +─────────────────────────────────────────────── +1 主图设计 50 4小时 启用 [编辑] [禁用] +2 详情页设计 200 2天 启用 [编辑] [禁用] +3 视频剪辑 150 1天 启用 [编辑] [禁用] +4 图片修图 30 2小时 禁用 [编辑] [启用] + +[+ 新增需求类型] +``` + +**表单字段** +- 类型名称(必填) +- 单价分值(必填,数字) +- 预估耗时(必填,小时/天) +- 类型描述(选填,富文本) +- 交付要求(选填,富文本) +- 排序(数字,用于前端显示顺序) +- 状态(启用/禁用) + +#### 6.1.2 品牌配置 +``` +列表视图(卡片或表格): + +┌──────────┐ ┌──────────┐ ┌──────────┐ +│ [LOGO] │ │ [LOGO] │ │ [LOGO] │ +│ Nike │ │ Adidas │ │ Puma │ +│ │ │ │ │ │ +│ 任务数:25│ │ 任务数:18│ │ 任务数:12│ +│ │ │ │ │ │ +│[编辑][删] │ │[编辑][删] │ │[编辑][删] │ +└──────────┘ └──────────┘ └──────────┘ + +[+ 新增品牌] +``` + +**表单字段** +- 品牌名称(必填) +- 品牌LOGO(图片上传) +- 品牌官网(选填) +- 品牌说明(选填) +- 设计风格指南(选填,富文本或附件) +- 排序 +- 状态 + +#### 6.1.3 类目配置 +``` +树形结构视图: + ++ 服装 + + 女装 + - 连衣裙 [编辑] [删除] [添加子类目] + - T恤 [编辑] [删除] + - 裤子 [编辑] [删除] + + 男装 + - 衬衫 [编辑] [删除] + - 牛仔裤 [编辑] [删除] ++ 鞋类 + + 运动鞋 + - 跑步鞋 [编辑] [删除] + - 篮球鞋 [编辑] [删除] ++ 配饰 + +[+ 新增一级类目] +``` + +**表单字段** +- 类目名称(必填) +- 父级类目(选择,为空则为一级类目) +- 类目描述(选填) +- 是否叶子类目(系统自动判断) +- 排序 +- 状态 + +**功能要求** +- 支持拖拽排序 +- 支持批量导入(Excel) +- 支持批量操作(启用/禁用/删除) +- 删除校验(有关联任务时不允许删除) + +#### 6.1.4 平台配置 +``` +列表视图: + +┌──────────┐ ┌──────────┐ ┌──────────┐ +│ [图标] │ │ [图标] │ │ [图标] │ +│ 淘宝 │ │ 天猫 │ │ 京东 │ +│ │ │ │ │ │ +│ 任务数:88│ │ 任务数:56│ │ 任务数:34│ +│ │ │ │ │ │ +│[编辑][删] │ │[编辑][删] │ │[编辑][删] │ +└──────────┘ └──────────┘ └──────────┘ + +[+ 新增平台] +``` + +**表单字段** +- 平台名称(必填) +- 平台图标(图片上传) +- 平台官网(选填) +- 设计规范说明(选填,富文本或附件) +- 排序 +- 状态 + +#### 6.1.5 积分管理 + +**团队积分池配置** +``` +团队列表: +团队名称 当前积分 已使用 总充值 操作 +───────────────────────────────────────────── +产品一部 2,500 7,500 10,000 [充值] [明细] +产品二部 1,200 3,800 5,000 [充值] [明细] +市场部 800 1,200 2,000 [充值] [明细] + +充值记录:[查看所有充值记录] +``` + +**充值表单** +- 选择团队 +- 充值积分数量 +- 充值原因/说明 +- 审批人(可选) + +**积分明细** +- 时间 +- 操作类型(充值/消耗/退回) +- 积分变动 +- 关联任务(如果是消耗) +- 操作人 +- 余额 + +#### 6.1.6 延误扣分规则配置 + +**延误扣分规则管理** +``` +┌──────────────────────────────────────────────┐ +│ 延误扣分规则配置 │ +├──────────────────────────────────────────────┤ +│ 规则状态:● 启用 ○ 禁用 │ +│ ──────────────────────────────────────── │ +│ 延误等级配置: │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 轻微延误 │ │ +│ │ 延误时长:< 2 小时 │ │ +│ │ 扣分规则:每小时 [2] 分 │ │ +│ │ 状态:启用 [编辑] [删除] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 一般延误 │ │ +│ │ 延误时长:2-8 小时 │ │ +│ │ 扣分规则:每小时 [3] 分 │ │ +│ │ 状态:启用 [编辑] [删除] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 严重延误 │ │ +│ │ 延误时长:8-24 小时 │ │ +│ │ 扣分规则:每小时 [5] 分 │ │ +│ │ 状态:启用 [编辑] [删除] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 重大延误 │ │ +│ │ 延误时长:> 24 小时 │ │ +│ │ 扣分规则:基础 [50] 分 + 每天 [10] 分│ │ +│ │ 状态:启用 [编辑] [删除] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ [+ 新增延误等级] │ +│ ──────────────────────────────────────── │ +│ 其他设置: │ +│ ☑ 任务转移时自动计算延误扣分 │ +│ ☑ 允许设计师申诉延误扣分 │ +│ ☑ 显示延误预警提醒 │ +│ □ 延误扣分翻倍(重复延误) │ +│ │ +│ 申诉审核: │ +│ 审核人:[主管] ▼ │ +│ 审核时限:[48小时] │ +│ │ +│ [重置为默认] [保存配置] │ +└──────────────────────────────────────────────┘ +``` + +**编辑延误等级表单** +``` +┌───────────────────────────────┐ +│ 编辑延误等级 │ +├───────────────────────────────┤ +│ 等级名称: │ +│ [一般延误] │ +│ │ +│ 延误时长范围: │ +│ 最小:[2] 小时 │ +│ 最大:[8] 小时 │ +│ │ +│ 扣分规则: │ +│ ○ 固定扣分 │ +│ ● 按时长扣分 │ +│ ○ 阶梯扣分 │ +│ │ +│ 按时长扣分参数: │ +│ 每小时扣:[3] 分 │ +│ 最高扣分:[24] 分(可选) │ +│ │ +│ 说明: │ +│ ┌─────────────────────────┐ │ +│ │ 一般程度的延误... │ │ +│ └─────────────────────────┘ │ +│ │ +│ 状态:● 启用 ○ 禁用 │ +│ │ +│ [取消] [保存] │ +└───────────────────────────────┘ +``` + +**延误扣分预览测试** +``` +输入延误时长:[10] 小时 + +计算结果: +- 匹配规则:严重延误(8-24小时) +- 扣分规则:每小时 5 分 +- 计算过程:10 × 5 = 50 分 +- 扣分结果:-50 分 + +[重新计算] +``` + +#### 6.1.7 系统参数配置 + +**通用配置** +``` +参数名称 当前值 操作 +─────────────────────────────────────────────── +设计师最大任务并发数 5 [修改] +任务抢单超时时间(分钟) 30 [修改] +任务延期预警提前时间(小时) 24 [修改] +积分预警阈值 500 [修改] +文件上传大小限制(MB) 100 [修改] +任务自动关闭时间(天) 30 [修改] +时效重新计算默认系数 1.0 [修改] +设计师熟练度系数范围 0.8-1.2 [修改] +``` + +**通知配置** +- 站内消息开关 +- 钉钉通知开关 +- 邮件通知开关 +- 延误扣分通知开关 +- 通知模板配置 + +#### 6.1.8 激励系统配置 + +**功能概述** +管理员可以在后台对激励系统进行全面配置,包括: +- **激励游戏开关**:控制各类激励功能的启用/禁用 +- **奖励积分设置**:为不同激励行为配置奖励积分 +- **自动奖励周期**:设置按周/月自动发放奖励 +- **激励规则定制**:自定义成就条件、排名规则等 + +##### 6.1.8.1 激励游戏总开关 + +``` +┌──────────────────────────────────────────────┐ +│ 激励系统总控制面板 │ +├──────────────────────────────────────────────┤ +│ 系统状态:● 全局启用 ○ 全局禁用 │ +│ ──────────────────────────────────────── │ +│ 功能模块开关: │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 📊 实时排名系统 │ │ +│ │ 状态:● 启用 ○ 禁用 │ │ +│ │ 更新频率:[实时] ▼ │ │ +│ │ 排名范围:[全体] ▼ │ │ +│ │ [高级设置] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 🏅 成就勋章系统 │ │ +│ │ 状态:● 启用 ○ 禁用 │ │ +│ │ 弹窗提醒:☑ 启用 │ │ +│ │ 勋章墙展示:☑ 启用 │ │ +│ │ [成就管理] [奖励设置] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 🎯 里程碑提醒系统 │ │ +│ │ 状态:● 启用 ○ 禁用 │ │ +│ │ 提醒时机:☑ 达成时 ☑ 接近时 │ │ +│ │ 奖励发放:● 自动 ○ 手动 │ │ +│ │ [里程碑管理] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 🚀 积分冲刺提示 │ │ +│ │ 状态:● 启用 ○ 禁用 │ │ +│ │ 提醒周期:[每周一] ▼ │ │ +│ │ 冲刺奖励:● 启用 ○ 禁用 │ │ +│ │ [冲刺规则设置] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 👥 同事对比(友好竞争) │ │ +│ │ 状态:● 启用 ○ 禁用 │ │ +│ │ 对比维度:☑ 任务数 ☑ 积分 ☑ 评分 │ │ +│ │ 显示模式:[匿名对比] ▼ │ │ +│ │ [对比规则] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 📅 每日激励卡片 │ │ +│ │ 状态:● 启用 ○ 禁用 │ │ +│ │ 展示时间:[登录时] ▼ │ │ +│ │ 签到奖励:● 启用 ○ 禁用 │ │ +│ │ [卡片模板] [签到规则] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ ⚡ 即时激励弹窗 │ │ +│ │ 状态:● 启用 ○ 禁用 │ │ +│ │ 触发场景:☑ 任务完成 ☑ 好评收到 │ │ +│ │ 弹窗频率:[适中] ▼ │ │ +│ │ [弹窗模板] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 🏆 竞赛活动系统 │ │ +│ │ 状态:● 启用 ○ 禁用 │ │ +│ │ 当前竞赛:[月度设计之星] [管理] │ │ +│ │ 下期竞赛:[未设置] [创建] │ │ +│ │ [竞赛历史] [规则模板] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ ⚔️ 团队PK系统 │ │ +│ │ 状态:● 启用 ○ 禁用 │ │ +│ │ PK周期:[每月] ▼ │ │ +│ │ 奖励分配:[胜方额外20%积分] ▼ │ │ +│ │ [PK管理] [历史战况] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 🤖 智能提醒和建议 │ │ +│ │ 状态:● 启用 ○ 禁用 │ │ +│ │ 任务推荐:☑ 启用 │ │ +│ │ 休息提醒:☑ 启用(连续4小时触发) │ │ +│ │ [推荐算法调优] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 📊 可视化激励看板 │ │ +│ │ 状态:● 启用 ○ 禁用 │ │ +│ │ 默认视图:[综合视图] ▼ │ │ +│ │ 数据范围:[近6个月] ▼ │ │ +│ │ [看板布局] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ [全部启用] [全部禁用] [保存设置] │ +└──────────────────────────────────────────────┘ +``` + +##### 6.1.8.2 成就勋章配置 + +**成就勋章列表** +``` +┌──────────────────────────────────────────────────────────┐ +│ 成就勋章管理 │ +├──────────────────────────────────────────────────────────┤ +│ 分类:[全部] [数量类] [质量类] [效率类] [特殊类] │ +│ 状态:[全部] [启用] [禁用] [+ 新增成就] │ +│ ────────────────────────────────────────────────────── │ +│ │ +│ 勋章 名称 类别 触发条件 奖励 状态 │ +│ ──────────────────────────────────────────────────── │ +│ 🏆 新手上路 数量 完成首个任务 +10 启用 [编辑]│ +│ 💯 百任达人 数量 累计完成100任务 +100 启用 [编辑]│ +│ ⭐ 五星之光 质量 连续10个五星好评 +50 启用 [编辑]│ +│ 🚀 效率之王 效率 10次提前完成任务 +80 启用 [编辑]│ +│ 🎯 精准狙击 效率 单日完成5个任务 +60 启用 [编辑]│ +│ 🏅 月度冠军 特殊 月度排名第一 +200 启用 [编辑]│ +│ 💪 连续奋进 特殊 连续签到30天 +30 启用 [编辑]│ +│ 🎨 创意大师 质量 获得20个客户表扬 +150 启用 [编辑]│ +│ ⏰ 守时模范 效率 30天零延误 +100 启用 [编辑]│ +│ 🌟 全能选手 综合 各类任务都完成 +120 启用 [编辑]│ +│ 📈 进步之星 特殊 月度排名提升10位 +50 禁用 [编辑]│ +│ │ +└──────────────────────────────────────────────────────────┘ +``` + +**成就编辑表单** +``` +┌───────────────────────────────────────┐ +│ 编辑成就勋章 │ +├───────────────────────────────────────┤ +│ 成就名称: │ +│ [五星之光] │ +│ │ +│ 成就图标: │ +│ [⭐] [选择图标] [上传自定义] │ +│ │ +│ 成就类别: │ +│ ○ 数量类 ● 质量类 ○ 效率类 │ +│ ○ 特殊类 ○ 综合类 │ +│ │ +│ 触发条件类型: │ +│ [连续好评次数] ▼ │ +│ │ +│ 条件参数设置: │ +│ 连续获得:[10] 次 │ +│ 评分要求:[5] 星 │ +│ 时间限制:[不限] ▼ │ +│ │ +│ 成就描述: │ +│ ┌─────────────────────────────────┐ │ +│ │ 连续获得10个五星好评,你就是客户│ │ +│ │ 心中的设计之光! │ │ +│ └─────────────────────────────────┘ │ +│ │ +│ 奖励设置: │ +│ 奖励积分:[50] 分 │ +│ 额外奖励:[称号:五星设计师] │ +│ 奖励有效期:[永久] ▼ │ +│ │ +│ 通知设置: │ +│ ☑ 弹窗通知(即时) │ +│ ☑ 站内消息 │ +│ ☑ 钉钉推送 │ +│ ☑ 勋章墙展示 │ +│ │ +│ 成就等级:(多级成就) │ +│ ○ 单级成就 │ +│ ● 多级成就: │ +│ - 铜牌:5次五星 奖励20分 │ +│ - 银牌:10次五星 奖励50分 │ +│ - 金牌:20次五星 奖励100分 │ +│ [+ 添加等级] │ +│ │ +│ 成就状态: │ +│ ● 启用 ○ 禁用 │ +│ │ +│ 排序:[5] │ +│ │ +│ [取消] [保存] │ +└───────────────────────────────────────┘ +``` + +##### 6.1.8.3 自动奖励规则配置 + +**自动奖励周期设置** +``` +┌──────────────────────────────────────────────┐ +│ 自动奖励规则配置 │ +├──────────────────────────────────────────────┤ +│ 功能状态:● 启用自动奖励 ○ 禁用 │ +│ ──────────────────────────────────────── │ +│ 奖励周期配置: │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 📅 每日奖励(按天) │ │ +│ │ 状态:● 启用 │ │ +│ │ ───────────────────────────────── │ │ +│ │ • 每日签到奖励:+5 分 │ │ +│ │ • 连续签到3天:+10 分 │ │ +│ │ • 连续签到7天:+30 分 │ │ +│ │ • 连续签到30天:+150 分 │ │ +│ │ • 完成每日目标:+20 分 │ │ +│ │ │ │ +│ │ 发放时间:[自动,完成时即时发放] │ │ +│ │ [编辑规则] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 📊 每周奖励(按周) │ │ +│ │ 状态:● 启用 │ │ +│ │ 发放时间:[每周一 09:00] │ │ +│ │ ───────────────────────────────── │ │ +│ │ 周度排名奖励: │ │ +│ │ • 第1名:+300 分 │ │ +│ │ • 第2名:+200 分 │ │ +│ │ • 第3名:+150 分 │ │ +│ │ • 第4-10名:+100 分 │ │ +│ │ │ │ +│ │ 周度任务量奖励: │ │ +│ │ • 完成≥20个任务:+200 分 │ │ +│ │ • 完成15-19个任务:+150 分 │ │ +│ │ • 完成10-14个任务:+100 分 │ │ +│ │ • 完成5-9个任务:+50 分 │ │ +│ │ │ │ +│ │ 周度好评奖励: │ │ +│ │ • 五星好评率≥95%:+150 分 │ │ +│ │ • 五星好评率≥90%:+100 分 │ │ +│ │ • 五星好评率≥85%:+50 分 │ │ +│ │ │ │ +│ │ 周度零延误奖励: │ │ +│ │ • 全周无延误:+100 分 │ │ +│ │ • 延误≤1次:+50 分 │ │ +│ │ │ │ +│ │ [编辑规则] [测试发放] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 📆 每月奖励(按月) │ │ +│ │ 状态:● 启用 │ │ +│ │ 发放时间:[每月1日 10:00] │ │ +│ │ ───────────────────────────────── │ │ +│ │ 月度排名奖励: │ │ +│ │ • 冠军(第1名):+1000 分 + 🏆 │ │ +│ │ • 亚军(第2名):+800 分 + 🥈 │ │ +│ │ • 季军(第3名):+600 分 + 🥉 │ │ +│ │ • 第4-10名:+400 分 │ │ +│ │ • 第11-20名:+200 分 │ │ +│ │ │ │ +│ │ 月度任务量奖励: │ │ +│ │ • 完成≥80个任务:+800 分 │ │ +│ │ • 完成60-79个任务:+600 分 │ │ +│ │ • 完成40-59个任务:+400 分 │ │ +│ │ • 完成20-39个任务:+200 分 │ │ +│ │ │ │ +│ │ 月度质量奖励: │ │ +│ │ • 五星好评率≥98%:+500 分 │ │ +│ │ • 五星好评率≥95%:+300 分 │ │ +│ │ • 五星好评率≥90%:+200 分 │ │ +│ │ │ │ +│ │ 月度效率奖励: │ │ +│ │ • 平均提前完成≥2天:+400 分 │ │ +│ │ • 平均提前完成≥1天:+300 分 │ │ +│ │ • 准时完成率100%:+200 分 │ │ +│ │ │ │ +│ │ 月度进步奖: │ │ +│ │ • 排名提升≥20位:+300 分 │ │ +│ │ • 排名提升≥10位:+200 分 │ │ +│ │ • 排名提升≥5位:+100 分 │ │ +│ │ │ │ +│ │ 月度全勤奖: │ │ +│ │ • 全月签到:+200 分 │ │ +│ │ • 签到≥25天:+100 分 │ │ +│ │ │ │ +│ │ [编辑规则] [测试发放] [历史记录] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 🎊 季度/年度特殊奖励 │ │ +│ │ 状态:● 启用 │ │ +│ │ ───────────────────────────────── │ │ +│ │ 季度MVP:+3000 分 + 专属称号 │ │ +│ │ 年度最佳设计师:+10000 分 + 实物奖励 │ │ +│ │ 年度进步之星:+5000 分 │ │ +│ │ │ │ +│ │ [编辑规则] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ────────────────────────────────────── │ +│ 奖励发放设置: │ +│ ☑ 自动计算并发放 │ +│ ☑ 发放前管理员审核 │ +│ ☑ 发放后通知接收人 │ +│ ☑ 发放记录留存归档 │ +│ │ +│ 积分来源: │ +│ ● 系统积分池(无限) │ +│ ○ 指定部门积分池:[运营部] ▼ │ +│ │ +│ [重置为默认] [测试奖励计算] [保存配置] │ +└──────────────────────────────────────────────┘ +``` + +**自动奖励发放日志** +``` +┌──────────────────────────────────────────────────────┐ +│ 自动奖励发放记录 │ +├──────────────────────────────────────────────────────┤ +│ 筛选:[本周] [本月] [本季度] [导出记录] │ +│ ──────────────────────────────────────────────── │ +│ │ +│ 发放时间 周期 规则名称 发放人数 总积分│ +│ ────────────────────────────────────────────────── │ +│ 2025-11-04 09:00 每周 周度排名奖励 10人 1,500│ +│ 2025-11-04 09:00 每周 周度任务量奖励 15人 1,800│ +│ 2025-11-04 09:00 每周 周度好评奖励 12人 1,200│ +│ 2025-11-04 09:00 每周 周度零延误奖励 8人 800 │ +│ 2025-11-01 10:00 每月 月度排名奖励 20人 8,000│ +│ 2025-11-01 10:00 每月 月度质量奖励 25人 7,500│ +│ 2025-11-01 10:00 每月 月度效率奖励 18人 5,400│ +│ │ +│ [查看详情] [重新发放] [撤销发放] │ +└──────────────────────────────────────────────────────┘ +``` + +##### 6.1.8.4 竞赛活动配置 + +**竞赛活动管理** +``` +┌──────────────────────────────────────────────┐ +│ 竞赛活动管理 │ +├──────────────────────────────────────────────┤ +│ [+ 创建新竞赛] [竞赛模板] │ +│ ──────────────────────────────────────── │ +│ 当前竞赛: │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 🏆 11月设计之星月度挑战赛 │ │ +│ │ 状态:● 进行中 │ │ +│ │ 时间:2025-11-01 至 2025-11-30 │ │ +│ │ 参与人数:28人 │ │ +│ │ ───────────────────────────────── │ │ +│ │ 竞赛规则: │ │ +│ │ • 完成任务数 × 2分 │ │ +│ │ • 五星好评 × 10分 │ │ +│ │ • 提前完成 × 5分 │ │ +│ │ • 被表扬 × 15分 │ │ +│ │ │ │ +│ │ 奖励设置: │ │ +│ │ • 第1名:+1500分 + 月度冠军勋章 │ │ +│ │ • 第2名:+1000分 + 月度亚军勋章 │ │ +│ │ • 第3名:+800分 + 月度季军勋章 │ │ +│ │ • 参与奖:完成≥10任务 +100分 │ │ +│ │ │ │ +│ │ 实时排名:[查看] [导出] │ │ +│ │ │ │ +│ │ [编辑] [暂停] [提前结束] [结算奖励] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ 即将开始: │ +│ • 无 [创建下期] │ +│ │ +│ 历史竞赛: │ +│ • 10月设计之星(已结束) [查看] │ +│ • 9月效率挑战赛(已结束) [查看] │ +│ • 8月质量月(已结束) [查看] │ +│ │ +└──────────────────────────────────────────────┘ +``` + +**创建竞赛表单** +``` +┌───────────────────────────────────────┐ +│ 创建竞赛活动 │ +├───────────────────────────────────────┤ +│ 竞赛名称: │ +│ [12月年终冲刺赛] │ +│ │ +│ 竞赛类型: │ +│ ● 月度竞赛 ○ 周度竞赛 ○ 专项竞赛 │ +│ │ +│ 竞赛时间: │ +│ 开始:[2025-12-01 00:00] │ +│ 结束:[2025-12-31 23:59] │ +│ │ +│ 参与对象: │ +│ ● 全体设计师 │ +│ ○ 指定部门:[选择部门] ▼ │ +│ ○ 指定人员:[选择人员] │ +│ │ +│ 竞赛维度与权重: │ +│ 任务完成数:[2] 分/个 权重30% │ +│ 五星好评数:[10] 分/个 权重25% │ +│ 提前完成次数:[5] 分/次 权重20% │ +│ 被客户表扬:[15] 分/次 权重15% │ +│ 无延误天数:[2] 分/天 权重10% │ +│ [+ 添加维度] │ +│ │ +│ 排名计算方式: │ +│ ● 综合得分排名 │ +│ ○ 单项排名 │ +│ ○ 混合排名 │ +│ │ +│ 奖励设置: │ +│ ┌─────────────────────────────────┐ │ +│ │ 排名 奖励积分 额外奖励 │ │ +│ │ ───────────────────────────────│ │ +│ │ 第1名 2000 年终冠军勋章 │ │ +│ │ 第2名 1500 年终亚军勋章 │ │ +│ │ 第3名 1000 年终季军勋章 │ │ +│ │ 4-10名 500 优秀设计师称号 │ │ +│ │ [+ 添加奖励档位] │ │ +│ └─────────────────────────────────┘ │ +│ │ +│ 参与奖: │ +│ ☑ 完成≥15个任务:+150分 │ +│ ☑ 参与满30天:+100分 │ +│ │ +│ 通知设置: │ +│ ☑ 竞赛开始通知(钉钉+站内) │ +│ ☑ 每日进度提醒(站内) │ +│ ☑ 排名变化提醒(站内) │ +│ ☑ 竞赛结束通知(钉钉+站内) │ +│ ☑ 奖励发放通知(钉钉+站内) │ +│ │ +│ 竞赛说明: │ +│ ┌─────────────────────────────────┐ │ +│ │ 年终冲刺,冲击年度最佳设计师!│ │ +│ │ 让我们一起用实力说话... │ │ +│ └─────────────────────────────────┘ │ +│ │ +│ 状态: │ +│ ○ 草稿 ● 立即发布 ○ 定时发布 │ +│ │ +│ [保存为模板] [取消] [创建] │ +└───────────────────────────────────────┘ +``` + +##### 6.1.8.5 团队PK配置 + +**团队PK管理** +``` +┌──────────────────────────────────────────────┐ +│ 团队PK管理 │ +├──────────────────────────────────────────────┤ +│ [+ 创建新PK] │ +│ ──────────────────────────────────────── │ +│ 当前PK: │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ ⚔️ 11月部门对抗赛 │ │ +│ │ 状态:● 激战中 │ │ +│ │ 时间:2025-11-01 至 2025-11-30 │ │ +│ │ ───────────────────────────────── │ │ +│ │ 🔵 设计一部(12人)VS 🔴 设计二部(15人)│ │ +│ │ │ │ +│ │ 当前比分: │ │ +│ │ 设计一部:15,200 分 🔥 领先 │ │ +│ │ 设计二部:14,800 分 ⚡ 追赶 │ │ +│ │ 差距:400 分 │ │ +│ │ │ │ +│ │ PK维度: │ │ +│ │ • 任务完成总数(当前:68 vs 72) │ │ +│ │ • 五星好评总数(当前:45 vs 48) │ │ +│ │ • 准时完成率(当前:92% vs 89%) │ │ +│ │ • 平均任务评分(当前:4.7 vs 4.6)│ │ +│ │ │ │ +│ │ 奖励设置: │ │ +│ │ • 胜方团队:每人+200分 │ │ +│ │ • 败方团队:每人+100分(参与奖) │ │ +│ │ • MVP(胜方最高贡献):额外+300分 │ │ +│ │ │ │ +│ │ 实时战况:[查看详情] │ │ +│ │ │ │ +│ │ [编辑] [暂停] [提前结束] [结算] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ 历史PK: │ +│ • 10月部门对抗赛(设计一部胜) [查看] │ +│ • 9月新老对决(老将队胜) [查看] │ +│ │ +└──────────────────────────────────────────────┘ +``` + +##### 6.1.8.6 排名规则配置 + +**排名系统配置** +``` +┌──────────────────────────────────────────────┐ +│ 排名系统规则配置 │ +├──────────────────────────────────────────────┤ +│ 排名维度与权重设置: │ +│ │ +│ 综合排名算法: │ +│ ┌─────────────────────────────────────┐ │ +│ │ 维度 权重 计分方式 │ │ +│ │ ───────────────────────────────── │ │ +│ │ 任务完成数 30% 1任务=2分 │ │ +│ │ 五星好评数 25% 1好评=10分 │ │ +│ │ 累计获得积分 20% 1积分=0.1分 │ │ +│ │ 准时完成率 15% 比例×100分 │ │ +│ │ 平均任务评分 10% 评分×20分 │ │ +│ │ [+ 添加维度] [重置权重] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ 排名更新频率: │ +│ ● 实时更新(每次任务变化) │ +│ ○ 定时更新:[每小时] ▼ │ +│ ○ 手动更新 │ +│ │ +│ 排名范围设置: │ +│ 显示范围:[全体] [前50名] [前100名] ▼ │ +│ 分组排名:☑ 启用部门内排名 │ +│ ☑ 启用等级内排名 │ +│ │ +│ 排名变化通知: │ +│ ☑ 排名上升≥5位时通知 │ +│ ☑ 进入前10名时通知 │ +│ ☑ 被他人超越时通知 │ +│ ☑ 掉出前10名时通知 │ +│ │ +│ 排名保护机制: │ +│ ☑ 新人保护期:入职[30]天内不显示排名 │ +│ ☑ 休假保护:休假期间排名冻结 │ +│ ☑ 异常保护:任务量<3时不参与排名 │ +│ │ +│ [预览排名结果] [保存配置] │ +└──────────────────────────────────────────────┘ +``` + +##### 6.1.8.7 激励积分池管理 + +**激励专用积分池** +``` +┌──────────────────────────────────────────────┐ +│ 激励系统积分池 │ +├──────────────────────────────────────────────┤ +│ 积分来源: │ +│ ● 系统独立积分池(推荐,不占用团队积分) │ +│ ○ 从指定部门扣除 │ +│ │ +│ ──────────────────────────────────────── │ +│ 系统积分池(当前月): │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 本月预算:50,000 分 │ │ +│ │ 已发放: 28,500 分(57%) │ │ +│ │ 剩余: 21,500 分(43%) │ │ +│ │ │ │ +│ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░ │ │ +│ │ │ │ +│ │ 预计本月总需求:45,000 分 │ │ +│ │ 状态:✅ 充足 │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ 月度预算设置: │ +│ 每月预算:[50000] 分 │ +│ 预警阈值:剩余 [20]% 时预警 │ +│ 超支处理: │ +│ ● 自动扩容 │ +│ ○ 暂停发放 │ +│ ○ 降低奖励 │ +│ │ +│ 分项预算分配(参考): │ +│ • 每日签到奖励:约 4,200 分/月 │ +│ • 每周奖励:约 8,000 分/月 │ +│ • 每月奖励:约 15,000 分/月 │ +│ • 成就奖励:约 8,000 分/月 │ +│ • 竞赛奖励:约 10,000 分/月 │ +│ • 团队PK奖励:约 5,000 分/月 │ +│ • 其他奖励:约 3,000 分/月 │ +│ │ +│ 历史统计: │ +│ • 10月发放:48,500 分 │ +│ • 9月发放:46,200 分 │ +│ • 8月发放:51,800 分 │ +│ 平均:48,833 分/月 │ +│ │ +│ [查看详细记录] [导出报表] [调整预算] │ +└──────────────────────────────────────────────┘ +``` + +--- + +## 七、通知提醒模块 + +### 7.1 通知场景 + +| 场景 | 接收人 | 通知方式 | +|------|--------|----------| +| 任务发布(指定) | 指定设计师 | 站内+钉钉 | +| 任务发布(招募) | 所有设计师 | 站内 | +| 任务被抢单 | 发布人 | 站内+钉钉 | +| 任务分配(主管) | 接收设计师 | 站内+钉钉 | +| **任务被转移(原接收人)** | **原接收设计师** | **站内+钉钉** | +| **任务被转移(新接收人)** | **新接收设计师** | **站内+钉钉** | +| **任务被指定接收** | **指定设计师、发布人** | **站内+钉钉** | +| **任务被撤回** | **发布人、关注者** | **站内** | +| **任务优先级调整** | **接收设计师** | **站内+钉钉** | +| **任务被推广** | **全体设计师** | **站内+钉钉** | +| **任务转移延误扣分** | **原接收设计师** | **站内+钉钉** | +| **延误申诉提交** | **管理员、主管** | **站内** | +| **延误申诉审核结果** | **申诉设计师** | **站内+钉钉** | +| **部门消耗限制预警** | **部门负责人、部门成员** | **站内+钉钉** | +| **部门消耗超限** | **部门负责人、发起人** | **站内+钉钉+邮件** | +| **部门消耗周报/月报** | **部门负责人** | **邮件** | +| **部门负责人变更** | **新负责人、旧负责人** | **站内+钉钉** | +| **获得成就勋章** | **设计师本人** | **站内+弹窗** | +| **排名上升** | **设计师本人** | **站内** | +| **排名下降/被超越** | **设计师本人** | **站内** | +| **里程碑达成** | **设计师本人** | **站内+弹窗** | +| **冲刺提醒** | **设计师本人** | **站内** | +| **目标达成** | **设计师本人** | **站内+弹窗** | +| **竞赛开始/结束** | **参赛设计师** | **站内+钉钉** | +| **团队PK进度** | **团队成员** | **站内** | +| **连续记录提醒** | **设计师本人** | **站内** | +| **自动奖励发放(每日/每周/每月)** | **获奖设计师** | **站内+弹窗** | +| **竞赛奖励发放** | **获奖设计师** | **站内+钉钉+弹窗** | +| **团队PK奖励发放** | **团队成员** | **站内+钉钉** | +| **激励积分池预警** | **超级管理员** | **站内+邮件** | +| **激励规则变更通知** | **全体设计师** | **站内** | +| 任务即将到期 | 接收设计师、发布人 | 站内+钉钉 | +| 任务延期 | 接收设计师、发布人、主管 | 站内+钉钉+邮件 | +| 任务提交交付 | 发布人 | 站内+钉钉 | +| 任务审核通过 | 接收设计师 | 站内+钉钉 | +| 任务被打回 | 接收设计师 | 站内+钉钉 | +| 积分不足 | 发布人、主管 | 站内+钉钉 | +| 积分充值 | 团队成员 | 站内 | +| 排行榜更新 | 相关设计师 | 站内(周/月) | + +### 7.2 站内消息 + +**消息中心** +``` +┌──────────────────────────────────────┐ +│ 🔔 消息中心 (5) [全部已读] │ +├──────────────────────────────────────┤ +│ ● [系统通知] │ +│ 您有一个新任务 #T20251107001 │ +│ 2分钟前 │ +│ │ +│ ○ [任务提醒] │ +│ 任务"主图设计"将在2小时后到期 │ +│ 1小时前 │ +│ │ +│ ○ [审核通知] │ +│ 您的任务"详情页优化"审核通过 │ +│ 3小时前 │ +└──────────────────────────────────────┘ +``` + +**消息类型** +- 系统通知(红点) +- 任务提醒(黄点) +- 审核通知(蓝点) +- 积分变动(绿点) +- 调配通知(紫点) + +### 7.3 钉钉通知集成 + +**⚠️ 重要:钉钉通知链接预留** +系统所有站内通知都需预留与钉钉通知的集成接口,确保重要通知能够同步推送到钉钉。 + +**工作通知消息卡片** +``` +┌─────────────────────────────┐ +│ 【新任务通知】 │ +│ │ +│ 任务:主图设计 │ +│ 发布人:张三 │ +│ 预估积分:250分 │ +│ 期望完成:2025-11-09 │ +│ │ +│ [查看详情] [接受任务] │ +└─────────────────────────────┘ +``` + +**钉钉集成技术实现** + +**1. 通知数据结构设计** +所有站内通知消息必须包含钉钉推送所需字段: +```javascript +// 通知消息统一数据结构 +{ + // 基础信息 + id: "notify_20251107_001", + type: "task_assign", // 通知类型 + title: "新任务通知", + content: "您有一个新任务需要处理", + + // 接收人信息 + receiverUserId: "U123456", + receiverDingUserId: "ding_abc123", // 钉钉用户ID(关键) + + // 跳转链接(H5页面或小程序) + link: "https://xxx.com/task/detail?id=T001", + mobileLink: "dingtalk://dingtalkclient/page/link?url=xxx", + + // 时间信息 + createTime: "2025-11-07 10:30:00", + sendTime: "2025-11-07 10:30:05", // 实际发送时间 + + // 钉钉推送相关 + dingNotification: { + enabled: true, // 是否推送钉钉 + messageType: "oa", // 消息类型:text/oa/markdown/actionCard + priority: "normal", // 优先级:normal/urgent + pushed: false, // 是否已推送 + pushTime: null, // 推送时间 + pushResult: null // 推送结果 + }, + + // 业务数据(可选) + businessData: { + taskId: "T001", + taskType: "主图设计", + publisher: "张三", + score: 250 + } +} +``` + +**2. 钉钉消息推送接口封装** +```javascript +// 钉钉通知推送服务 +class DingTalkNotificationService { + + /** + * 发送工作通知消息 + * @param {Object} notification 通知对象 + * @returns {Promise} + */ + async sendWorkNotification(notification) { + // 1. 检查是否需要推送钉钉 + if (!notification.dingNotification.enabled) { + return { success: true, skipped: true }; + } + + // 2. 获取钉钉access_token + const accessToken = await this.getAccessToken(); + + // 3. 构建钉钉消息体 + const message = this.buildDingMessage(notification); + + // 4. 调用钉钉API + const result = await this.callDingTalkAPI({ + url: 'https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2', + accessToken, + body: { + agent_id: process.env.DING_AGENT_ID, + userid_list: notification.receiverDingUserId, + msg: message + } + }); + + // 5. 更新推送状态 + await this.updatePushStatus(notification.id, result); + + return result; + } + + /** + * 构建不同类型的钉钉消息 + */ + buildDingMessage(notification) { + const { type, title, content, link, businessData } = notification; + + switch (notification.dingNotification.messageType) { + case 'oa': + // OA消息(推荐,样式美观) + return { + msgtype: 'oa', + oa: { + head: { + text: title, + bgcolor: 'FFBBBBBB' + }, + body: { + title: title, + form: [ + { key: '任务类型:', value: businessData.taskType }, + { key: '发布人:', value: businessData.publisher }, + { key: '积分:', value: businessData.score + '分' } + ] + }, + message_url: link, + pc_message_url: link + } + }; + + case 'actionCard': + // 卡片消息(支持多按钮) + return { + msgtype: 'action_card', + action_card: { + title: title, + markdown: content, + single_title: '查看详情', + single_url: link + } + }; + + case 'markdown': + // Markdown消息 + return { + msgtype: 'markdown', + markdown: { + title: title, + text: this.formatMarkdown(notification) + } + }; + + default: + // 文本消息(默认) + return { + msgtype: 'text', + text: { + content: `${title}\n${content}` + } + }; + } + } + + /** + * 格式化Markdown消息 + */ + formatMarkdown(notification) { + const { title, businessData } = notification; + return `### ${title}\n` + + `- **任务类型**:${businessData.taskType}\n` + + `- **发布人**:${businessData.publisher}\n` + + `- **积分**:${businessData.score}分\n` + + `\n[点击查看详情](${notification.link})`; + } +} +``` + +**3. 通知发送流程** +``` +站内消息生成 → 保存到数据库 → 检查是否需要钉钉推送 +→ 异步推送到钉钉 → 更新推送状态 → 记录推送日志 +``` + +**4. 通知队列机制(推荐)** +```javascript +// 使用消息队列异步处理钉钉推送 +// 避免同步推送影响主业务性能 + +// 发送通知时 +async function sendNotification(notification) { + // 1. 保存站内消息 + await saveNotification(notification); + + // 2. 如需钉钉推送,加入队列 + if (notification.dingNotification.enabled) { + await notificationQueue.add('ding-push', { + notificationId: notification.id, + priority: notification.dingNotification.priority + }); + } +} + +// 队列处理器 +notificationQueue.process('ding-push', async (job) => { + const { notificationId } = job.data; + const notification = await getNotificationById(notificationId); + + // 调用钉钉推送服务 + await dingTalkService.sendWorkNotification(notification); +}); +``` + +**5. 数据库表设计(通知表扩展字段)** +```sql +-- 通知消息表 +CREATE TABLE `sys_notification` ( + `id` bigint PRIMARY KEY, + `type` varchar(50) COMMENT '通知类型', + `title` varchar(200) COMMENT '标题', + `content` text COMMENT '内容', + `receiver_user_id` bigint COMMENT '接收人用户ID', + `receiver_ding_user_id` varchar(100) COMMENT '接收人钉钉UserID', + `link_url` varchar(500) COMMENT '跳转链接', + `mobile_link_url` varchar(500) COMMENT '移动端跳转链接', + + -- 钉钉推送相关字段 + `ding_enabled` tinyint DEFAULT 0 COMMENT '是否推送钉钉: 0-否, 1-是', + `ding_message_type` varchar(20) COMMENT '钉钉消息类型: text/oa/markdown/actionCard', + `ding_priority` varchar(20) DEFAULT 'normal' COMMENT '优先级: normal/urgent', + `ding_pushed` tinyint DEFAULT 0 COMMENT '是否已推送: 0-未推送, 1-已推送', + `ding_push_time` datetime COMMENT '推送时间', + `ding_task_id` varchar(100) COMMENT '钉钉任务ID', + `ding_push_result` text COMMENT '推送结果JSON', + + `business_type` varchar(50) COMMENT '业务类型: task/score/achievement等', + `business_id` varchar(100) COMMENT '业务ID', + `business_data` json COMMENT '业务数据JSON', + + `is_read` tinyint DEFAULT 0 COMMENT '是否已读', + `read_time` datetime COMMENT '阅读时间', + + `create_time` datetime DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + + INDEX `idx_receiver` (`receiver_user_id`, `is_read`), + INDEX `idx_ding_push` (`ding_enabled`, `ding_pushed`), + INDEX `idx_create_time` (`create_time`) +) COMMENT '系统通知表'; +``` + +**通知策略** +- 工作时间(9:00-18:00)实时推送 +- 非工作时间汇总推送(可配置) +- 紧急任务不限时间 +- 用户可自定义免打扰时段 +- 支持消息撤回和重推 +- 推送失败自动重试(最多3次) + +**钉钉消息类型选择建议** +- **OA消息**:适用于任务通知、审核通知(推荐) +- **ActionCard**:适用于需要操作按钮的场景 +- **Markdown**:适用于内容丰富的通知 +- **Text**:适用于简单文本通知 + +### 7.4 邮件通知 + +**周报/月报** +- 收件人:设计师本人、主管 +- 内容:任务统计、积分统计、排行榜、评价汇总 +- 发送时间:每周一早9:00 / 每月1号早9:00 + +**紧急通知** +- 任务严重延期 +- 系统异常告警 + +--- + +## 八、数据库设计要点 + +### 8.1 核心表结构 + +#### 用户表(sys_user) +- id, username, password, name, avatar, phone, email +- ding_user_id, ding_union_id, dept_id +- role, status, create_time, update_time + +#### 部门表(sys_dept) +- id, dept_name, parent_id, ding_dept_id +- dept_type(部门类型:DEMAND需求部门/RECEIVER接单部门) +- level, sort, leader_user_id, score_balance +- limit_enabled, limit_period(每天/每周/每月), period_limit +- alert_threshold, over_limit_action +- allowed_demand_types(接单部门可接的需求类型,JSON) +- description, status +- create_time, update_time + +#### 部门消耗限制记录表(dept_limit_record) +- id, dept_id, period_type, period_start, period_end +- period_limit, period_used, period_remaining +- task_count, is_over_limit +- create_time, update_time + +#### 任务表(task) +- id, task_no, title, description +- demand_type_id, demand_quantity, demand_score +- task_type, brand_id, category_id, spu_info, platform_ids +- publish_type, publish_user_id, publish_dept_id +- assign_user_id, assign_time +- expect_finish_time, actual_start_time, actual_finish_time +- status, progress, delivery_info +- review_score, review_comment +- create_time, update_time + +#### 需求类型表(demand_type) +- id, type_name, unit_score, estimate_time +- description, requirement, sort, status +- create_time, update_time + +#### 品牌表(brand) +- id, brand_name, logo_url, website +- description, style_guide, sort, status +- create_time, update_time + +#### 类目表(category) +- id, category_name, parent_id, level +- is_leaf, sort, status +- create_time, update_time + +#### 平台表(platform) +- id, platform_name, icon_url, website +- design_spec, sort, status +- create_time, update_time + +#### 任务附件表(task_attachment) +- id, task_id, file_name, file_url, file_size +- file_type, upload_user_id, upload_time + +#### 任务日志表(task_log) +- id, task_id, operation_type, operation_desc +- operator_id, create_time + +#### 任务调配记录表(task_transfer_log) +- id, task_id, transfer_type(转移/指定/撤回) +- from_user_id, to_user_id, operator_id +- reason, reason_detail, progress_keep +- score_allocation, old_deadline, new_deadline +- delay_hours, delay_penalty_score(延误时长、延误扣分) +- notify_settings, create_time + +#### 延误扣分记录表(delay_penalty_record) +- id, task_id, user_id, task_transfer_log_id +- original_deadline, actual_time(原定时间、实际时间) +- delay_hours, delay_minutes(延误小时、延误分钟) +- delay_level(延误等级:轻微/一般/严重/重大) +- penalty_score(扣分值) +- penalty_reason(原因:任务转移/正常延误) +- is_special_case(是否特殊情况:病假/事假等) +- admin_note(管理员备注) +- is_appealed, appeal_status(是否申诉、申诉状态) +- appeal_reason, appeal_result(申诉原因、申诉结果) +- create_time, update_time + +#### 延误扣分规则表(delay_penalty_rule) +- id, rule_name, level_name +- min_hours, max_hours(时长范围) +- penalty_type(扣分类型:固定/按时长/阶梯) +- penalty_rate(扣分比率,如每小时N分) +- base_penalty, daily_penalty(基础扣分、每天扣分,用于重大延误) +- max_penalty(最高扣分限制,可选) +- description, sort, status +- create_time, update_time + +#### 积分记录表(score_record) +- id, dept_id, user_id, score_change +- change_type, task_id, balance_after +- delay_penalty_record_id(关联延误扣分记录) +- remark, create_time + +#### 评价表(task_review) +- id, task_id, reviewer_id, reviewee_id +- quality_score, timeliness_score, communication_score +- avg_score, comment, review_time + +#### 成就勋章表(achievement) +- id, achievement_name, achievement_icon, achievement_desc +- achievement_type(数量类/质量类/效率类/特殊类) +- condition_type, condition_value(获得条件) +- reward_score(奖励积分) +- sort, status +- create_time, update_time + +#### 用户成就记录表(user_achievement) +- id, user_id, achievement_id +- achieve_time, is_shared +- create_time + +#### 用户目标表(user_goal) +- id, user_id, goal_type +- goal_name, goal_target, goal_period +- current_progress, is_completed +- start_time, complete_time +- create_time, update_time + +#### 用户签到记录表(user_check_in) +- id, user_id, check_in_date +- continuous_days, month_count +- create_time + +#### 激励系统配置表(incentive_config) +- id, config_key(配置键:ranking_enabled, achievement_enabled等) +- config_value(配置值) +- config_type(配置类型:boolean, string, number, json) +- module_name(模块名称:排名系统、成就系统等) +- description(配置说明) +- status(启用/禁用) +- create_time, update_time + +#### 自动奖励规则表(auto_reward_rule) +- id, rule_name, rule_type(每日/每周/每月/季度/年度) +- period_type(daily, weekly, monthly, quarterly, yearly) +- trigger_condition(触发条件:JSON格式) +- reward_score(奖励积分) +- extra_reward(额外奖励:JSON格式,如勋章、称号等) +- target_users(目标用户:all全体/部门/指定用户) +- schedule_time(定时发放时间) +- is_auto_issue(是否自动发放) +- need_approval(是否需要审核) +- description, status +- create_time, update_time + +#### 自动奖励发放记录表(auto_reward_log) +- id, rule_id, period_start, period_end +- issue_time(发放时间) +- user_count(获奖人数) +- total_score(总积分) +- status(pending待发放/issued已发放/cancelled已取消) +- operator_id(操作人) +- remark +- create_time, update_time + +#### 自动奖励明细表(auto_reward_detail) +- id, log_id, rule_id, user_id +- reward_score(奖励积分) +- extra_reward(额外奖励:JSON) +- trigger_data(触发数据:如完成任务数、好评数等) +- score_record_id(关联积分记录ID) +- status(issued已发放/failed失败) +- create_time + +#### 竞赛活动表(competition) +- id, competition_name, competition_type(月度/周度/专项) +- start_time, end_time +- participant_scope(参与范围:all/dept/users) +- participant_json(参与者JSON:如部门ID列表、用户ID列表) +- rule_dimensions(竞赛维度:JSON,包含权重) +- ranking_method(排名方式:综合得分/单项排名/混合排名) +- reward_config(奖励配置:JSON) +- participation_reward(参与奖配置:JSON) +- notify_settings(通知设置:JSON) +- description +- status(draft草稿/ongoing进行中/ended已结束/cancelled已取消) +- creator_id, create_time, update_time + +#### 竞赛排名表(competition_ranking) +- id, competition_id, user_id +- total_score(总得分) +- dimension_scores(各维度得分:JSON) +- final_rank(最终排名) +- reward_score(奖励积分) +- extra_reward(额外奖励:JSON) +- is_rewarded(是否已发放奖励) +- update_time + +#### 团队PK表(team_pk) +- id, pk_name, pk_type(部门对抗/分组对抗) +- start_time, end_time +- team_a_id, team_a_name, team_a_score +- team_b_id, team_b_name, team_b_score +- pk_dimensions(PK维度:JSON) +- reward_config(奖励配置:JSON) +- winner_team(获胜队伍:team_a/team_b/draw平局) +- status(ongoing进行中/ended已结束/cancelled已取消) +- creator_id, create_time, update_time + +#### 团队PK成员表(team_pk_member) +- id, pk_id, team_side(team_a/team_b) +- user_id, contribution_score(个人贡献分) +- task_count, good_review_count +- is_mvp(是否MVP) +- reward_score(获得积分) +- create_time + +#### 排名规则配置表(ranking_rule) +- id, rule_name, dimension_name(维度名称) +- dimension_weight(权重) +- score_formula(计分公式) +- dimension_order(排序) +- status +- create_time, update_time + +#### 激励积分池表(incentive_pool) +- id, period_month(周期月份:YYYY-MM) +- monthly_budget(月度预算) +- issued_score(已发放积分) +- remaining_score(剩余积分) +- alert_threshold(预警阈值) +- alert_status(预警状态:normal正常/warning预警/critical严重) +- daily_issued, weekly_issued, monthly_issued(各周期发放统计) +- achievement_issued, competition_issued, team_pk_issued(各模块发放统计) +- create_time, update_time + +### 8.2 索引设计 +- task表:task_no、publish_user_id、assign_user_id、status、publish_dept_id +- 复合索引:(status, publish_user_id)、(status, assign_user_id) +- 全文索引:task.description、task.spu_info(如需高级搜索) + +### 8.3 数据字典 + +**任务状态(task_status)** +- 0: 待接收 +- 1: 进行中 +- 2: 待审核 +- 3: 打回修改 +- 4: 已完成 +- 5: 已取消 + +**任务类型(task_type)** +- 1: 新品 +- 2: 优化 +- 3: 改图 +- 4: 其它 + +**发布类型(publish_type)** +- 1: 指定任务 +- 2: 招募任务 +- 3: 默认模式(主管分配) + +**积分变动类型(score_change_type)** +- 1: 充值 +- 2: 任务消耗 +- 3: 任务退回 +- 4: 任务奖励 +- 5: 调整 +- 6: 延误扣分 +- 7: 延误申诉返还 + +**部门类型(dept_type)** +- DEMAND: 需求部门(只能发起任务) +- RECEIVER: 接单部门(只能接收任务) + +**消耗限制周期(limit_period)** +- DAILY: 每天 +- WEEKLY: 每周 +- MONTHLY: 每月 + +**激励配置类型(incentive_config_type)** +- boolean: 布尔值(开关) +- string: 字符串 +- number: 数字 +- json: JSON对象 + +**奖励规则周期类型(reward_period_type)** +- daily: 每日 +- weekly: 每周 +- monthly: 每月 +- quarterly: 每季度 +- yearly: 每年 + +**自动奖励发放状态(reward_log_status)** +- pending: 待发放 +- issued: 已发放 +- cancelled: 已取消 +- failed: 发放失败 + +**竞赛状态(competition_status)** +- draft: 草稿 +- ongoing: 进行中 +- ended: 已结束 +- cancelled: 已取消 + +**团队PK状态(team_pk_status)** +- ongoing: 进行中 +- ended: 已结束 +- cancelled: 已取消 + +**积分池预警状态(pool_alert_status)** +- normal: 正常 +- warning: 预警 +- critical: 严重(紧急) + +--- + +## 九、接口设计要点 + +### 9.1 RESTful API 规范 + +**基础路径** +``` +/api/v1 +``` + +**认证方式** +``` +Authorization: Bearer {JWT_TOKEN} +``` + +### 9.2 核心接口列表 + +#### 认证模块 +``` +POST /auth/login # 用户名密码登录 +POST /auth/ding-login # 钉钉登录 +POST /auth/refresh-token # 刷新Token +POST /auth/logout # 登出 +GET /auth/user-info # 获取当前用户信息 +``` + +#### 任务模块 +``` +POST /tasks # 创建任务 +GET /tasks # 任务列表(分页、筛选) +GET /tasks/:id # 任务详情 +PUT /tasks/:id # 更新任务 +DELETE /tasks/:id # 删除任务 +POST /tasks/:id/grab # 抢单 +POST /tasks/:id/assign # 分配任务 +PUT /tasks/:id/progress # 更新进度 +POST /tasks/:id/submit # 提交交付 +POST /tasks/:id/review # 审核任务 +POST /tasks/:id/reject # 打回任务 +GET /tasks/my-published # 我发布的任务 +GET /tasks/my-received # 我接收的任务 +GET /tasks/hall # 任务大厅列表 +GET /tasks/calendar # 日历视图数据 + +# 任务调配(管理员) +POST /tasks/:id/transfer # 转移任务 +POST /tasks/batch-transfer # 批量转移任务 +POST /tasks/:id/designate # 指定接收人(从任务池) +POST /tasks/batch-designate # 批量指定接收人 +POST /tasks/:id/withdraw # 撤回任务 +POST /tasks/batch-withdraw # 批量撤回任务 +PUT /tasks/:id/priority # 调整任务优先级 +POST /tasks/:id/promote # 推广任务 +GET /tasks/admin-view # 管理员任务视图 +GET /tasks/pool # 任务池管理(管理员) +GET /tasks/transfer-logs # 调配日志 +GET /tasks/transfer-stats # 调配统计 +POST /tasks/:id/recommend # 获取推荐设计师列表 +``` + +#### 设计师模块 +``` +GET /designers # 设计师列表 +GET /designers/:id # 设计师详情 +GET /designers/:id/tasks # 设计师任务列表 +GET /designers/:id/stats # 设计师统计数据 +GET /designers/:id/delays # 设计师延误记录 +GET /designers/leaderboard # 排行榜 +``` + +#### 激励系统模块 +``` +# 成就勋章 +GET /achievements # 获取成就列表 +GET /achievements/:id # 获取成就详情 +GET /user/achievements # 我的成就勋章 +GET /user/achievements/progress # 成就进度 +POST /user/achievements/:id/share # 分享成就 +GET /achievements/leaderboard # 成就排行榜 + +# 排名和对比 +GET /user/ranking # 我的实时排名 +GET /user/ranking/trend # 排名趋势 +GET /user/compare/:userId # 与同事对比 +GET /user/milestones # 我的里程碑 +GET /user/sprint-status # 冲刺状态 + +# 目标管理 +GET /user/goals # 我的目标列表 +POST /user/goals # 设定新目标 +PUT /user/goals/:id # 更新目标 +DELETE /user/goals/:id # 删除目标 +GET /user/goals/:id/progress # 目标进度 + +# 签到和激励 +POST /user/check-in # 每日签到 +GET /user/check-in/records # 签到记录 +GET /user/daily-motivation # 每日激励卡片 +GET /user/motivation-board # 激励看板 + +# 竞赛活动 +GET /competitions # 竞赛活动列表 +GET /competitions/:id # 竞赛详情 +GET /competitions/:id/ranking # 竞赛排名 +POST /competitions/:id/join # 参加竞赛 + +# 团队PK +GET /team-pk # 团队PK列表 +GET /team-pk/:id # PK详情 +GET /team-pk/:id/realtime # 实时战况 +POST /team-pk/:id/cheer # 为团队加油 + +# 智能推荐 +GET /recommendations/tasks # 推荐任务 +GET /recommendations/rest # 休息提醒 +``` + +#### 激励配置管理模块(管理员) +``` +# 激励总开关配置 +GET /admin/incentive/config # 获取激励系统总配置 +PUT /admin/incentive/config # 更新激励系统总配置 +PUT /admin/incentive/config/modules/:module # 更新指定模块开关(ranking/achievement等) +POST /admin/incentive/config/batch-enable # 批量启用模块 +POST /admin/incentive/config/batch-disable # 批量禁用模块 + +# 成就勋章配置 +GET /admin/achievements # 获取成就配置列表 +POST /admin/achievements # 创建新成就 +PUT /admin/achievements/:id # 更新成就配置 +DELETE /admin/achievements/:id # 删除成就 +PUT /admin/achievements/:id/status # 启用/禁用成就 +POST /admin/achievements/batch-update # 批量更新成就 +GET /admin/achievements/:id/statistics # 成就获得统计 + +# 自动奖励规则配置 +GET /admin/reward-rules # 获取自动奖励规则列表 +POST /admin/reward-rules # 创建新规则 +PUT /admin/reward-rules/:id # 更新规则 +DELETE /admin/reward-rules/:id # 删除规则 +PUT /admin/reward-rules/:id/status # 启用/禁用规则 +POST /admin/reward-rules/:id/test # 测试规则(计算预估奖励) +POST /admin/reward-rules/:id/execute # 手动触发执行规则 + +# 自动奖励发放记录 +GET /admin/reward-logs # 获取发放记录列表 +GET /admin/reward-logs/:id # 获取发放记录详情 +GET /admin/reward-logs/:id/details # 获取发放明细 +POST /admin/reward-logs/:id/reissue # 重新发放 +POST /admin/reward-logs/:id/cancel # 取消发放 +GET /admin/reward-logs/statistics # 发放统计数据 +POST /admin/reward-logs/export # 导出发放记录 + +# 竞赛活动管理 +GET /admin/competitions # 获取竞赛列表 +POST /admin/competitions # 创建新竞赛 +PUT /admin/competitions/:id # 更新竞赛 +DELETE /admin/competitions/:id # 删除竞赛 +PUT /admin/competitions/:id/status # 更改竞赛状态(开始/暂停/结束) +POST /admin/competitions/:id/settle # 结算竞赛奖励 +GET /admin/competitions/:id/ranking # 查看竞赛排名 +POST /admin/competitions/:id/ranking/refresh # 刷新排名 +GET /admin/competitions/templates # 获取竞赛模板 +POST /admin/competitions/from-template # 从模板创建竞赛 + +# 团队PK管理 +GET /admin/team-pk # 获取PK列表 +POST /admin/team-pk # 创建新PK +PUT /admin/team-pk/:id # 更新PK +DELETE /admin/team-pk/:id # 删除PK +PUT /admin/team-pk/:id/status # 更改PK状态 +POST /admin/team-pk/:id/settle # 结算PK奖励 +GET /admin/team-pk/:id/realtime # 实时战况(详细数据) +POST /admin/team-pk/:id/adjust # 调整PK参数(如延长时间) + +# 排名规则配置 +GET /admin/ranking-rules # 获取排名规则配置 +PUT /admin/ranking-rules # 更新排名规则 +POST /admin/ranking-rules/preview # 预览规则效果 +POST /admin/ranking-rules/reset # 重置为默认规则 +POST /admin/ranking/refresh # 刷新全局排名 + +# 激励积分池管理 +GET /admin/incentive-pool # 获取积分池状态 +GET /admin/incentive-pool/:month # 获取指定月份积分池 +PUT /admin/incentive-pool/:month/budget # 调整月度预算 +GET /admin/incentive-pool/statistics # 积分池统计数据 +GET /admin/incentive-pool/forecast # 积分池预测(基于历史数据) +POST /admin/incentive-pool/export # 导出积分池报表 + +# 激励数据分析 +GET /admin/incentive/analytics/overview # 激励系统总览 +GET /admin/incentive/analytics/effectiveness # 激励效果分析 +GET /admin/incentive/analytics/participation # 参与度分析 +GET /admin/incentive/analytics/trends # 趋势分析 +POST /admin/incentive/analytics/export # 导出分析报告 +``` + +#### 延误扣分模块 +``` +GET /delay-penalties # 延误扣分记录列表 +GET /delay-penalties/:id # 延误扣分记录详情 +POST /delay-penalties/:id/appeal # 提交延误申诉 +PUT /delay-penalties/:id/appeal # 审核延误申诉 +GET /delay-penalties/my-records # 我的延误记录 +GET /delay-penalties/stats # 延误统计数据 +POST /delay-penalties/calculate # 计算延误扣分(用于预览) +``` + +#### 部门管理模块 +``` +GET /departments # 获取部门列表(树形结构) +GET /departments/:id # 获取部门详情 +POST /departments # 创建部门 +PUT /departments/:id # 更新部门 +DELETE /departments/:id # 删除部门 +POST /departments/sync-dingtalk # 同步钉钉组织架构 +PUT /departments/:id/leader # 设置部门负责人 +PUT /departments/:id/limit # 设置积分消耗限制 +GET /departments/:id/limit-usage # 获取部门消耗情况 +POST /departments/:id/limit-adjust # 临时调整消耗限制 +GET /departments/:id/members # 获取部门成员 +GET /departments/:id/tasks # 获取部门任务列表 +GET /departments/:id/stats # 获取部门统计数据 +``` + +#### 需求统计模块 +``` +GET /demand-stats/overview # 需求统计总览 +GET /demand-stats/departments # 部门维度统计 +GET /demand-stats/departments/:id # 指定部门详情统计 +GET /demand-stats/users # 用户维度统计 +GET /demand-stats/users/:id # 指定用户详情统计 +GET /demand-stats/trends # 需求发起趋势 +GET /demand-stats/types # 需求类型分析 +GET /demand-stats/export # 导出统计报表 +POST /demand-stats/subscribe # 订阅定时报表 +``` + +#### 配置模块 +``` +# 需求类型 +GET /config/demand-types +POST /config/demand-types +PUT /config/demand-types/:id +DELETE /config/demand-types/:id + +# 品牌 +GET /config/brands +POST /config/brands +PUT /config/brands/:id +DELETE /config/brands/:id + +# 类目 +GET /config/categories/tree +POST /config/categories +PUT /config/categories/:id +DELETE /config/categories/:id + +# 平台 +GET /config/platforms +POST /config/platforms +PUT /config/platforms/:id +DELETE /config/platforms/:id + +# 延误扣分规则 +GET /config/delay-penalty-rules # 获取延误扣分规则列表 +POST /config/delay-penalty-rules # 创建延误扣分规则 +PUT /config/delay-penalty-rules/:id # 更新延误扣分规则 +DELETE /config/delay-penalty-rules/:id # 删除延误扣分规则 +POST /config/delay-penalty-rules/test # 测试延误扣分计算 +``` + +#### 积分模块 +``` +GET /scores/dept/:id # 部门积分信息 +POST /scores/recharge # 积分充值 +GET /scores/records # 积分记录 +``` + +#### 数据分析模块 +``` +GET /stats/overview # 总览数据 +GET /stats/trends # 趋势数据 +GET /stats/distribution # 分布数据 +GET /stats/leaderboard # 排行榜数据 +``` + +#### 通知模块 +``` +GET /notifications # 消息列表 +PUT /notifications/:id/read # 标记已读 +PUT /notifications/read-all # 全部已读 +``` + +#### 文件上传 +``` +POST /upload/file # 文件上传 +POST /upload/image # 图片上传 +``` + +### 9.3 响应格式 + +**成功响应** +```json +{ + "code": 200, + "message": "success", + "data": { + // 业务数据 + }, + "timestamp": 1699344000000 +} +``` + +**失败响应** +```json +{ + "code": 400, + "message": "参数错误", + "data": null, + "timestamp": 1699344000000 +} +``` + +**分页响应** +```json +{ + "code": 200, + "message": "success", + "data": { + "list": [], + "total": 100, + "page": 1, + "page_size": 20 + } +} +``` + +--- + +## 十、UI/UX 设计规范 + +### ⚠️ 重要声明:严格遵循 OpenAI 官方 UI 设计规范 + +本项目页面外观开发**必须严格按照 OpenAI 官方 UI 设计规范**进行设计和实现,以确保界面的现代性、简洁性和用户体验的一致性。 + +### 10.1 OpenAI 风格设计规范 + +#### 10.1.1 核心设计原则 + +**1. 极简主义(Minimalism)** +- 去除一切不必要的视觉元素 +- 专注于内容和功能本身 +- 留白充足,让界面呼吸感更强 +- 避免过度装饰和复杂渐变 + +**2. 现代化与科技感(Modern & Tech-forward)** +- 使用柔和的深色模式和明亮的浅色模式 +- 流畅的动画过渡 +- 微妙的交互反馈 +- 前沿的视觉设计语言 + +**3. 清晰的信息层次(Clear Hierarchy)** +- 使用字重、大小、颜色来建立视觉层级 +- 重要信息突出显示 +- 次要信息适当弱化 +- 引导用户视线流动 + +**4. 一致性(Consistency)** +- 统一的间距系统 +- 统一的圆角规范 +- 统一的阴影样式 +- 统一的动效规则 + +#### 10.1.2 配色方案(参考 OpenAI) + +**深色模式(主推荐,符合OpenAI风格)** +``` +背景色系: +- 主背景:#0D0D0D / #171717(极深黑) +- 次背景:#202020 / #2A2A2A(深灰) +- 卡片背景:#2A2A2A / #353535(中深灰) +- 悬浮背景:#353535 / #404040(灰) + +文字色系: +- 主文字:#ECECEC / #FFFFFF(接近白色) +- 次文字:#AFAFAF / #B4B4B4(浅灰) +- 辅助文字:#737373 / #808080(中灰) +- 禁用文字:#4A4A4A(深灰) + +品牌色(参考OpenAI): +- 主品牌色:#10A37F(翠绿色,OpenAI经典色) +- 悬浮状态:#1A7F64(深翠绿) +- 按下状态:#0D8B6D(更深翠绿) + +功能色: +- 成功:#10A37F(翠绿,与品牌色统一) +- 警告:#FFA500(橙色) +- 错误:#EF4444(红色) +- 信息:#3B82F6(蓝色) + +边框色: +- 主边框:#3A3A3A / #404040(深灰边框) +- 次边框:#2A2A2A(更深边框) +- 分割线:#262626(极细分割线) +``` + +**浅色模式(可选)** +``` +背景色系: +- 主背景:#FFFFFF(纯白) +- 次背景:#F7F7F8(极浅灰) +- 卡片背景:#FFFFFF(纯白) +- 悬浮背景:#F0F0F0(浅灰) + +文字色系: +- 主文字:#0D0D0D(极深黑) +- 次文字:#666666(深灰) +- 辅助文字:#8E8E8E(中灰) + +品牌色与功能色:同深色模式 +``` + +#### 10.1.3 字体规范(参考 OpenAI) + +**字体家族** +```css +/* 主字体(参考OpenAI使用的字体) */ +font-family: + "Söhne", + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + "Helvetica Neue", + Arial, + "Noto Sans", + sans-serif, + "Apple Color Emoji", + "Segoe UI Emoji"; + +/* 中文字体 */ +font-family: + -apple-system, + BlinkMacSystemFont, + "PingFang SC", + "Microsoft YaHei", + "Helvetica Neue", + Helvetica, + Arial, + sans-serif; + +/* 等宽字体(用于代码、路径等) */ +font-family: + "SF Mono", + "Monaco", + "Cascadia Code", + "Roboto Mono", + "Consolas", + monospace; +``` + +**字体大小系统** +``` +超大标题:32px / 2rem(页面主标题) +大标题: 24px / 1.5rem(区块标题) +中标题: 20px / 1.25rem(卡片标题) +小标题: 16px / 1rem(列表项标题) +正文: 14px / 0.875rem(主要内容) +辅助文字:12px / 0.75rem(次要信息) +小字: 11px / 0.6875rem(标签、时间戳) +``` + +**字重规范** +``` +Bold(700): 重要标题、强调文字 +Semibold(600):次级标题 +Medium(500): 按钮文字、标签 +Regular(400):正文 +Light(300): 辅助说明 +``` + +#### 10.1.4 间距系统(8px 基准) + +``` +XXS:2px - 细微间距 +XS: 4px - 极小间距 +SM: 8px - 小间距(基准单位) +MD: 16px - 中间距 +LG: 24px - 大间距 +XL: 32px - 特大间距 +XXL:48px - 超大间距 +XXXL:64px - 巨大间距 + +内边距: +- 紧凑卡片:16px +- 标准卡片:24px +- 宽松卡片:32px + +外边距: +- 组件间隔:16px +- 区块间隔:24px +- 页面边距:32px +``` + +#### 10.1.5 圆角规范(参考 OpenAI) + +``` +微圆角: 4px - 按钮、输入框、标签 +小圆角: 8px - 小卡片、下拉菜单 +中圆角: 12px - 标准卡片 +大圆角: 16px - 大型容器 +特大圆角:24px - 模态框、抽屉 + +完全圆角:50% - 头像、图标按钮 +``` + +#### 10.1.6 阴影系统 + +```css +/* 轻微阴影(悬浮卡片) */ +box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12); + +/* 标准阴影(卡片) */ +box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + +/* 浮起阴影(悬浮状态) */ +box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); + +/* 强阴影(模态框) */ +box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + +/* 深色模式阴影(更柔和) */ +box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4); +``` + +#### 10.1.7 动画与过渡(参考 OpenAI 流畅体验) + +```css +/* 标准过渡时间 */ +--transition-fast: 150ms; +--transition-base: 250ms; +--transition-slow: 350ms; + +/* 缓动函数(OpenAI风格) */ +--ease-out: cubic-bezier(0.16, 1, 0.3, 1); +--ease-in-out: cubic-bezier(0.45, 0, 0.55, 1); + +/* 常用过渡 */ +transition: all 250ms cubic-bezier(0.16, 1, 0.3, 1); +``` + +**动效原则** +- 所有交互都应有微妙的动效反馈 +- 避免突兀的变化 +- 状态切换使用淡入淡出 +- 悬浮效果:轻微上浮 + 阴影增强 +- 按钮点击:微缩放 + 颜色变化 +- 加载状态:优雅的骨架屏或脉冲动画 + +#### 10.1.8 组件设计规范 + +**按钮(参考 OpenAI)** +```css +/* 主按钮(品牌色) */ +.btn-primary { + background: #10A37F; + color: #FFFFFF; + border: none; + border-radius: 8px; + padding: 10px 20px; + font-size: 14px; + font-weight: 500; + transition: all 250ms ease; +} +.btn-primary:hover { + background: #1A7F64; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(16, 163, 127, 0.3); +} + +/* 次按钮(轮廓) */ +.btn-secondary { + background: transparent; + color: #ECECEC; + border: 1px solid #3A3A3A; + border-radius: 8px; + padding: 10px 20px; +} +.btn-secondary:hover { + border-color: #10A37F; + color: #10A37F; +} + +/* 文字按钮 */ +.btn-text { + background: transparent; + color: #AFAFAF; + border: none; + padding: 8px 12px; +} +.btn-text:hover { + color: #10A37F; +} +``` + +**输入框(参考 OpenAI)** +```css +.input { + background: #2A2A2A; + border: 1px solid #3A3A3A; + border-radius: 8px; + color: #ECECEC; + padding: 10px 16px; + font-size: 14px; + transition: all 250ms ease; +} +.input:focus { + border-color: #10A37F; + outline: none; + box-shadow: 0 0 0 3px rgba(16, 163, 127, 0.1); +} +.input::placeholder { + color: #737373; +} +``` + +**卡片(参考 OpenAI)** +```css +.card { + background: #2A2A2A; + border: 1px solid #3A3A3A; + border-radius: 12px; + padding: 24px; + transition: all 250ms ease; +} +.card:hover { + border-color: #404040; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); + transform: translateY(-2px); +} +``` + +### 10.2 首页 UI 布局设计(重点) + +#### 10.2.1 首页整体布局 + +**布局结构(深色模式,OpenAI风格)** +``` +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ ┌─────────────────────────────────────────────────┐ ┃ +┃ │ 🎨 Logo 工作台 任务大厅 我的任务 数据分析 │👤│ ┃ ← 顶部导航栏 +┃ └─────────────────────────────────────────────────┘ ┃ (高度: 64px) +┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ +┃ ┃ +┃ ┌────────────────────────────────────────────┐ ┃ +┃ │ 👋 欢迎回来,张三 │ ┃ ← 欢迎区域 +┃ │ 今天是2025年11月7日 星期四 │ ┃ (Hero Section) +┃ └────────────────────────────────────────────┘ ┃ +┃ ┃ +┃ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┃ +┃ │ 📊 │ │ ✅ │ │ ⏰ │ │ 💎 │ ┃ ← 数据卡片区 +┃ │ 8个 │ │ 12个 │ │ 2个 │ │ 1,250│ ┃ (Data Cards) +┃ │进行中 │ │已完成 │ │待审核 │ │ 积分 │ ┃ +┃ └───────┘ └───────┘ └───────┘ └───────┘ ┃ +┃ ┃ +┃ ┌─────────────────────┐ ┌──────────────────┐ ┃ +┃ │ 📋 今日待办 │ │ 📈 本周数据 │ ┃ +┃ │ ───────────────── │ │ ────────────── │ ┃ ← 主要内容区 +┃ │ • 主图设计 [紧急] │ │ [图表] │ ┃ (Main Content) +┃ │ • 详情页优化 │ │ 任务完成趋势 │ ┃ +┃ │ • 视频剪辑 │ │ │ ┃ +┃ │ │ │ │ ┃ +┃ │ [查看全部任务] │ │ [查看详情] │ ┃ +┃ └─────────────────────┘ └──────────────────┘ ┃ +┃ ┃ +┃ ┌───────────────────────────────────────────┐ ┃ +┃ │ 🏆 设计师排行榜 │ ┃ ← 排行榜区域 +┃ │ 🥇 李四 28个任务 4.9⭐ │ ┃ (Leaderboard) +┃ │ 🥈 王五 22个任务 4.8⭐ │ ┃ +┃ │ 🥉 赵六 18个任务 4.7⭐ │ ┃ +┃ └───────────────────────────────────────────┘ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +``` + +#### 10.2.2 顶部导航栏详细设计 + +**导航栏规范** +``` +┌─────────────────────────────────────────────────────────┐ +│ │ +│ 🎨 DesignHub 工作台 任务大厅 我的任务 数据分析 │ +│ [Logo + 名称] [导航菜单] │ +│ │ +│ 🔔(3) ⚙️ 👤 张三 │ +│ [通知] [设置] [用户] │ +└─────────────────────────────────────────────────────────┘ +``` + +**详细样式说明** +```css +/* 导航栏容器 */ +.navbar { + height: 64px; + background: #171717; /* 深色背景 */ + border-bottom: 1px solid #262626; /* 极细分割线 */ + padding: 0 32px; + display: flex; + align-items: center; + justify-content: space-between; + position: sticky; + top: 0; + z-index: 1000; + backdrop-filter: blur(12px); /* 毛玻璃效果 */ +} + +/* Logo 区域 */ +.navbar-logo { + display: flex; + align-items: center; + gap: 12px; + font-size: 20px; + font-weight: 600; + color: #ECECEC; +} +.navbar-logo-icon { + width: 32px; + height: 32px; + border-radius: 8px; + background: linear-gradient(135deg, #10A37F 0%, #1A7F64 100%); +} + +/* 导航菜单 */ +.navbar-menu { + display: flex; + gap: 8px; +} +.nav-item { + padding: 8px 16px; + border-radius: 8px; + font-size: 14px; + font-weight: 400; + color: #AFAFAF; + cursor: pointer; + transition: all 250ms cubic-bezier(0.16, 1, 0.3, 1); +} +.nav-item:hover { + color: #ECECEC; + background: #2A2A2A; +} +.nav-item.active { + color: #10A37F; + background: rgba(16, 163, 127, 0.1); +} + +/* 右侧操作区 */ +.navbar-actions { + display: flex; + align-items: center; + gap: 16px; +} +.navbar-icon { + width: 40px; + height: 40px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: #AFAFAF; + cursor: pointer; + position: relative; + transition: all 250ms ease; +} +.navbar-icon:hover { + background: #2A2A2A; + color: #ECECEC; +} +.navbar-icon .badge { + position: absolute; + top: 6px; + right: 6px; + width: 18px; + height: 18px; + background: #EF4444; + border-radius: 50%; + font-size: 11px; + color: white; + display: flex; + align-items: center; + justify-content: center; +} +``` + +#### 10.2.3 欢迎区域(Hero Section) + +``` +┌───────────────────────────────────────────────────────┐ +│ │ +│ 👋 欢迎回来,张三 │ +│ 今天是2025年11月7日 星期四 ☀️ │ +│ │ +│ 您的部门积分余额:5,750 分 [充值] │ +│ 💡 本月还剩5天,快抓紧时间发起任务吧! │ +│ │ +│ [+ 发起新任务] [查看任务进度] │ +│ │ +└───────────────────────────────────────────────────────┘ +``` + +**样式实现** +```css +.hero-section { + margin: 32px auto; + max-width: 1200px; + padding: 48px; + background: linear-gradient(135deg, #2A2A2A 0%, #1F1F1F 100%); + border: 1px solid #3A3A3A; + border-radius: 16px; + position: relative; + overflow: hidden; +} + +/* 装饰性背景 */ +.hero-section::before { + content: ''; + position: absolute; + top: -50%; + right: -10%; + width: 500px; + height: 500px; + background: radial-gradient(circle, rgba(16, 163, 127, 0.05) 0%, transparent 70%); + border-radius: 50%; +} + +.hero-title { + font-size: 32px; + font-weight: 700; + color: #ECECEC; + margin-bottom: 8px; +} + +.hero-subtitle { + font-size: 16px; + color: #AFAFAF; + margin-bottom: 24px; +} + +.hero-info { + display: flex; + align-items: center; + gap: 16px; + padding: 16px; + background: rgba(16, 163, 127, 0.1); + border: 1px solid rgba(16, 163, 127, 0.2); + border-radius: 12px; + margin-bottom: 24px; +} + +.hero-actions { + display: flex; + gap: 16px; +} +``` + +#### 10.2.4 数据卡片区域(Data Cards) + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ 📊 │ │ ✅ │ │ ⏰ │ │ 💎 │ +│ │ │ │ │ │ │ │ +│ 8 个 │ │ 12 个 │ │ 2 个 │ │ 1,250 │ +│ 进行中 │ │ 已完成 │ │ 待审核 │ │ 积分 │ +│ │ │ │ │ │ │ │ +│ ↑ +2 今日 │ │ ↑ +3 今日 │ │ ⚠️ 紧急 │ │ ↑ +150 │ +└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ +``` + +**样式实现** +```css +.data-cards { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 24px; + margin: 32px auto; + max-width: 1200px; +} + +.data-card { + background: #2A2A2A; + border: 1px solid #3A3A3A; + border-radius: 12px; + padding: 24px; + transition: all 250ms cubic-bezier(0.16, 1, 0.3, 1); + cursor: pointer; +} + +.data-card:hover { + border-color: #10A37F; + transform: translateY(-4px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); +} + +.data-card-icon { + font-size: 32px; + margin-bottom: 16px; +} + +.data-card-value { + font-size: 36px; + font-weight: 700; + color: #ECECEC; + margin-bottom: 4px; +} + +.data-card-label { + font-size: 14px; + color: #AFAFAF; + margin-bottom: 12px; +} + +.data-card-trend { + font-size: 12px; + color: #10A37F; + display: flex; + align-items: center; + gap: 4px; +} + +.data-card-trend.down { + color: #EF4444; +} +``` + +#### 10.2.5 主要内容区域(双列布局) + +``` +┌──────────────────────────┐ ┌─────────────────────────┐ +│ 📋 今日待办 [全部3个] │ │ 📈 本周数据 │ +│ ─────────────────────── │ │ ────────────────────── │ +│ │ │ │ +│ ┌────────────────────┐ │ │ [折线图] │ +│ │ ⏰ 主图设计 │ │ │ 任务完成趋势 │ +│ │ 截止:今天 18:00 │ │ │ │ +│ │ 🔥 紧急 │ │ │ 本周完成:15个 │ +│ │ [查看详情] │ │ │ 上周:12个 ↑25% │ +│ └────────────────────┘ │ │ │ +│ │ │ │ +│ ┌────────────────────┐ │ │ ┌───────────────────┐ │ +│ │ 📝 详情页优化 │ │ │ │ 🎯 本周目标 │ │ +│ │ 截止:明天 12:00 │ │ │ │ 完成20个任务 │ │ +│ │ [查看详情] │ │ │ │ ████░░░░ 75% │ │ +│ └────────────────────┘ │ │ └───────────────────┘ │ +│ │ │ │ +│ [查看全部任务 →] │ │ [查看详细报表 →] │ +└──────────────────────────┘ └─────────────────────────┘ +``` + +**样式实现** +```css +.main-content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 24px; + margin: 32px auto; + max-width: 1200px; +} + +.content-card { + background: #2A2A2A; + border: 1px solid #3A3A3A; + border-radius: 12px; + padding: 24px; + min-height: 400px; +} + +.content-card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 24px; + padding-bottom: 16px; + border-bottom: 1px solid #3A3A3A; +} + +.content-card-title { + font-size: 18px; + font-weight: 600; + color: #ECECEC; + display: flex; + align-items: center; + gap: 8px; +} + +.task-item { + background: #1F1F1F; + border: 1px solid #3A3A3A; + border-radius: 8px; + padding: 16px; + margin-bottom: 12px; + transition: all 250ms ease; +} + +.task-item:hover { + border-color: #10A37F; + background: #252525; +} + +.task-item-title { + font-size: 16px; + font-weight: 500; + color: #ECECEC; + margin-bottom: 8px; +} + +.task-item-meta { + display: flex; + gap: 16px; + font-size: 12px; + color: #AFAFAF; +} + +.task-urgent { + display: inline-flex; + align-items: center; + padding: 4px 8px; + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.3); + border-radius: 4px; + color: #EF4444; + font-size: 11px; + font-weight: 500; +} +``` + +#### 10.2.6 排行榜区域 + +``` +┌─────────────────────────────────────────────────────┐ +│ 🏆 本月设计师排行榜 [查看全部] │ +│ ──────────────────────────────────────────────────│ +│ │ +│ 🥇 李四 28个任务 4.9⭐ +1,400分 │ +│ UI设计师 本月第一 │ +│ ────────────────────────────────────────────────│ +│ 🥈 王五 22个任务 4.8⭐ +1,100分 │ +│ 视觉设计师 本月第二 │ +│ ────────────────────────────────────────────────│ +│ 🥉 赵六 18个任务 4.7⭐ +900分 │ +│ 平面设计师 本月第三 │ +│ ────────────────────────────────────────────────│ +│ │ +│ 你当前排名第8位,再完成2个任务即可进入前5! │ +│ │ +└─────────────────────────────────────────────────────┘ +``` + +**样式实现** +```css +.leaderboard { + background: #2A2A2A; + border: 1px solid #3A3A3A; + border-radius: 12px; + padding: 24px; + margin: 32px auto; + max-width: 1200px; +} + +.leaderboard-item { + display: flex; + align-items: center; + gap: 16px; + padding: 16px; + background: #1F1F1F; + border: 1px solid #3A3A3A; + border-radius: 8px; + margin-bottom: 12px; + transition: all 250ms ease; +} + +.leaderboard-item:hover { + background: #252525; + border-color: #10A37F; + transform: translateX(4px); +} + +.leaderboard-rank { + font-size: 32px; + min-width: 48px; + text-align: center; +} + +.leaderboard-info { + flex: 1; +} + +.leaderboard-name { + font-size: 18px; + font-weight: 600; + color: #ECECEC; + margin-bottom: 4px; +} + +.leaderboard-role { + font-size: 12px; + color: #737373; +} + +.leaderboard-stats { + display: flex; + gap: 24px; + font-size: 14px; + color: #AFAFAF; +} + +.leaderboard-highlight { + padding: 16px; + background: rgba(16, 163, 127, 0.1); + border: 1px solid rgba(16, 163, 127, 0.2); + border-radius: 8px; + text-align: center; + color: #10A37F; + font-size: 14px; + margin-top: 16px; +} +``` + +#### 10.2.7 响应式适配 + +**桌面端(≥1200px)** +- 数据卡片:4列 +- 主要内容:2列 +- 最大宽度:1200px + +**平板端(768px - 1199px)** +- 数据卡片:2列 +- 主要内容:1列 +- 侧边距:24px + +**移动端(<768px)** +- 数据卡片:1列 +- 主要内容:1列 +- 侧边距:16px +- 导航栏折叠为汉堡菜单 + +```css +@media (max-width: 1199px) { + .data-cards { + grid-template-columns: repeat(2, 1fr); + } + .main-content { + grid-template-columns: 1fr; + } +} + +@media (max-width: 767px) { + .data-cards { + grid-template-columns: 1fr; + } + .navbar { + padding: 0 16px; + } + .hero-section { + padding: 24px; + } +} +``` + +### 10.3 响应式设计 +- 支持桌面端(主要) +- 支持平板端 +- 支持移动端(查看为主) + +### 10.4 交互动效 +- 页面切换:淡入淡出 +- 卡片悬浮:轻微上浮 + 阴影加深 +- 按钮点击:涟漪效果 +- 加载状态:骨架屏/加载动画 +- 数据更新:数字滚动动效 + +### 10.4 关键页面布局 + +#### 任务大厅 +``` +┌──────────────────────────────────────┐ +│ Logo 任务大厅 我的任务 设计师大厅 │ 顶部导航栏 +│ 系统配置 [通知] [头像]│ +├──────────────────────────────────────┤ +│ ┌──────────────┐ │ +│ │ 筛选条件 │ 任务列表(卡片流) │ +│ │ │ ┌────┐ ┌────┐ │ +│ │ 任务类型 │ │卡片│ │卡片│ │ +│ │ ☑ 新品 │ └────┘ └────┘ │ +│ │ ☑ 优化 │ ┌────┐ ┌────┐ │ +│ │ │ │卡片│ │卡片│ │ +│ │ 品牌 │ └────┘ └────┘ │ +│ │ [搜索框] │ │ +│ │ │ [加载更多] │ +│ │ 积分范围 │ │ +│ │ [滑块] │ │ +│ └──────────────┘ │ +└──────────────────────────────────────┘ +``` + +#### 我的任务(日历视图) +``` +┌──────────────────────────────────────┐ +│ Logo 任务大厅 我的任务 设计师大厅 │ +│ 系统配置 [通知] [头像]│ +├──────────────────────────────────────┤ +│ ┌列表视图┐ ┌日历视图┐ │ +│ └────────┘ └────────┘ │ +│ │ +│ 2025年11月 < > │ +│ │ +│ 日 一 二 三 四 五 六 │ +│ 1 2 3 4 │ +│ 5 6 7 8 9 10 11 │ +│ 12 13 14 15 16 17 18 │ +│ │ +│ 当日任务: │ +│ ┌────────────────────┐ │ +│ │ 主图设计 [进行中] │ │ +│ │ 进度:75% │ │ +│ └────────────────────┘ │ +└──────────────────────────────────────┘ +``` + +### 10.5 无障碍设计 +- 支持键盘快捷键 +- 色盲友好的颜色组合 +- 合理的对比度(符合WCAG AA标准) +- 图片ALT文本 + +--- + +## 十一、安全性设计 + +### 11.1 认证与授权 +- JWT Token机制 +- Token过期和刷新策略 +- 权限控制(RBAC) +- 接口权限校验 + +### 11.2 数据安全 +- 密码加密存储(BCrypt) +- 敏感信息脱敏 +- SQL注入防护(参数化查询) +- XSS攻击防护 +- CSRF防护 + +### 11.3 文件安全 +- 文件类型校验 +- 文件大小限制 +- 恶意文件扫描 +- 文件访问权限控制 + +### 11.4 日志与审计 +- 操作日志记录 +- 异常日志记录 +- 登录日志 +- 敏感操作审计 + +--- + +## 十二、性能优化 + +### 12.1 前端优化 +- 代码分割(Code Splitting) +- 懒加载 +- 图片懒加载和压缩 +- CDN加速 +- 浏览器缓存 +- Gzip压缩 + +### 12.2 后端优化 +- 数据库索引优化 +- Redis缓存热点数据 +- 接口响应时间监控 +- SQL查询优化 +- 连接池配置 + +### 12.3 数据库优化 +- 读写分离 +- 分库分表(数据量大时) +- 慢查询监控 +- 定期数据归档 + +--- + +## 十三、测试策略 + +### 13.1 测试类型 +- 单元测试 +- 集成测试 +- 接口测试 +- 前端E2E测试 +- 性能测试 +- 安全测试 + +### 13.2 测试覆盖率 +- 核心业务逻辑:>80% +- 工具类方法:>90% + +### 13.3 测试环境 +- 开发环境(Dev) +- 测试环境(Test) +- 预发布环境(Staging) +- 生产环境(Production) + +--- + +## 十四、部署与运维 + +### 14.1 部署方案 +``` +前端:Nginx静态资源服务 +后端:Docker容器 + Kubernetes(可选) +数据库:主从复制 + 定期备份 +缓存:Redis集群 +``` + +### 14.2 监控告警 +- 应用性能监控(APM) +- 服务器资源监控 +- 数据库监控 +- 日志监控 +- 告警通知(钉钉群/邮件/短信) + +### 14.3 备份策略 +- 数据库:每日全量备份 + 实时binlog +- 文件:定期备份到对象存储 +- 配置:版本控制 + +--- + +## 十五、项目计划 + +### 15.1 开发阶段 + +#### 第一阶段:基础框架搭建(2周) +- 技术选型确认 +- 项目初始化 +- 数据库设计 +- 认证系统集成(开源方案) +- 钉钉登录集成 +- 组织架构同步 + +#### 第二阶段:核心功能开发(4周) +- 任务发起模块 +- 任务大厅模块 +- 任务分配和接收 +- 进度跟踪 +- 审核评价 + +#### 第三阶段:配置与管理(2周) +- 系统配置模块 +- 积分管理 +- 权限管理 +- 通知模块 + +#### 第四阶段:数据分析与展示(2周) +- 设计师进度大厅 +- 数据统计 +- 图表展示 +- 排行榜 + +#### 第五阶段:优化与测试(2周) +- UI/UX优化 +- 性能优化 +- 全面测试 +- Bug修复 + +#### 第六阶段:部署上线(1周) +- 部署环境准备 +- 数据迁移 +- 上线部署 +- 培训和文档 + +### 15.2 时间线 +总计:**13周**(约3个月) + +--- + +## 十六、风险与挑战 + +### 16.1 技术风险 +| 风险 | 影响 | 应对措施 | +|------|------|----------| +| 钉钉API变更 | 登录和同步失败 | 关注官方文档,及时适配 | +| 并发抢单冲突 | 超卖问题 | 使用锁机制,充分测试 | +| 大文件上传 | 性能问题 | 分片上传,限制大小 | +| 数据库性能 | 响应慢 | 索引优化,缓存策略 | + +### 16.2 业务风险 +| 风险 | 影响 | 应对措施 | +|------|------|----------| +| 积分滥用 | 激励失效 | 审批机制,日志审计 | +| 恶意抢单 | 资源浪费 | 限制规则,信用体系 | +| 任务延期 | 影响业务 | 预警机制,责任追溯 | + +### 16.3 团队风险 +| 风险 | 影响 | 应对措施 | +|------|------|----------| +| 人员变动 | 进度延误 | 代码规范,文档完善 | +| 需求变更 | 开发返工 | 敏捷开发,小步迭代 | + +--- + +## 十七、后续优化方向 + +### 17.1 功能增强 +- **AI智能分配**:基于历史数据,AI推荐最合适的设计师 +- **任务拆分**:大任务自动拆分为子任务 +- **协同设计**:多人协作完成同一任务 +- **版本管理**:设计稿版本控制和对比 +- **移动端App**:独立的移动应用 +- **消息即时通讯**:任务内置聊天功能 +- **积分商城**:积分兑换奖品 +- **成长体系**:设计师等级和勋章系统 + +### 17.2 技术优化 +- **微服务架构**:拆分为多个独立服务 +- **GraphQL**:更灵活的API查询 +- **WebSocket**:实时消息推送 +- **大数据分析**:更深入的数据挖掘 +- **机器学习**:任务时长预测、质量评估 + +### 17.3 集成扩展 +- **集成更多设计工具**:Figma、Sketch等 +- **集成项目管理工具**:Jira、Trello等 +- **对接财务系统**:积分与奖金挂钩 +- **对接HR系统**:绩效考核数据同步 + +--- + +## 十八、参考资料 + +### 18.1 开源认证系统推荐 +1. **Casdoor** + - 官网:https://casdoor.org/ + - 特点:UI友好,易于集成,支持多种身份提供商 + +2. **Keycloak** + - 官网:https://www.keycloak.org/ + - 特点:功能强大,企业级,支持复杂场景 + +3. **Auth0**(部分开源) + - 官网:https://auth0.com/ + - 特点:云服务,集成简单 + +### 18.2 技术文档 +- Vue 3官方文档:https://vuejs.org/ +- React官方文档:https://react.dev/ +- Spring Boot官方文档:https://spring.io/projects/spring-boot +- 钉钉开放平台:https://open.dingtalk.com/ +- Ant Design:https://ant.design/ + +### 18.3 最佳实践 +- RESTful API设计规范 +- 前端工程化实践 +- 数据库设计范式 +- 安全编码规范 + +--- + +## 十九、附录 + +### 19.1 术语表 +| 术语 | 说明 | +|------|------| +| SPU | Standard Product Unit,标准产品单位 | +| 叶子类目 | 类目树中最底层、不可再分的类目 | +| 抢单 | 设计师主动接受任务大厅中的招募任务 | +| 积分池 | 每个团队拥有的可用积分总额 | +| 甘特图 | 项目进度的条形图表示 | +| 任务调配 | 管理员对进行中任务和任务池任务进行转移、指定、撤回等管理操作 | +| 任务转移 | 将进行中的任务从一个设计师转移给另一个设计师 | +| 指定接收人 | 管理员为任务池中的招募任务指定具体接收设计师 | +| 任务撤回 | 将任务从任务大厅撤回到待分配状态 | +| 任务池 | 任务大厅中所有招募任务的集合 | +| 智能推荐 | 基于设计师负荷、技能、评分等维度推荐最合适的设计师 | +| 延误扣分 | 当任务转移时如果存在延误,根据延误时长对原设计师进行积分扣除 | +| 时效重新计算 | 任务转移给新设计师后,根据剩余工作量和新设计师情况重新计算交付时间 | +| 延误等级 | 将延误按时长分为轻微、一般、严重、重大四个等级 | +| 延误申诉 | 设计师对延误扣分有异议时,可提交申诉由管理员审核 | +| 熟练度系数 | 设计师对特定类型任务的熟练程度系数,影响时效计算(0.8-1.2) | +| 负荷系数 | 设计师当前工作负荷对新任务完成时间的影响系数(1.0-1.3) | +| 需求部门 | 只能发起任务、不能接单的部门,拥有积分池消耗积分 | +| 接单部门 | 只能接单、不能发起任务的部门,完成任务获得积分奖励 | +| 部门负责人 | 需求部门的管理者,可设置积分消耗限制和管理部门成员 | +| 积分消耗限制 | 对需求部门按天/周/月设置的积分使用上限,用于控制资源合理使用 | +| 需求统计展板 | 展示需求发起数据的可视化看板,包含部门和用户维度的多角度分析 | +| 成就勋章 | 设计师达成特定条件后获得的虚拟勋章,附带积分奖励 | +| 里程碑 | 设计师在工作过程中达成的重要节点(如完成100个任务) | +| 实时排名 | 根据积分、任务数等维度实时更新的设计师排名 | +| 冲刺提示 | 在周末或月末提醒设计师完成目标的激励信息 | +| 同事对比 | 将个人数据与同部门同事进行对比,激发竞争意识 | +| 竞赛活动 | 系统组织的短期比赛活动,如"设计马拉松" | +| 团队PK | 不同部门或小组之间的团队对抗赛 | +| 连续记录 | 设计师连续达成某个目标的天数(如连续21天按时交付) | +| 激励看板 | 综合展示设计师成就、排名、目标等激励信息的可视化界面 | +| 激励游戏开关 | 管理员控制各种激励功能(排名、成就等)的启用/禁用开关 | +| 自动奖励规则 | 系统按周期(每日/周/月)自动计算并发放奖励的规则配置 | +| 奖励积分配置 | 为各种激励行为(签到、完成任务、好评等)设置的积分奖励值 | +| 激励积分池 | 专门用于激励系统奖励发放的独立积分池,不占用团队积分 | +| 竞赛模板 | 预设的竞赛活动配置模板,可快速创建新竞赛 | +| 排名规则 | 综合排名的计算规则,包含各维度的权重和计分公式 | +| 自动发放 | 系统根据设定规则自动计算并发放奖励,无需人工干预 | +| 奖励审核 | 管理员对系统自动计算的奖励进行审核后再发放 | + +### 19.2 联系方式 +- 项目负责人:[姓名] +- 技术负责人:[姓名] +- 产品负责人:[姓名] + +--- + +## 📌 总结 + +本系统是一个**设计师任务抢单管理平台**,核心目标是: +1. ✅ 提高任务分配效率 +2. ✅ 实现进度透明化管理 +3. ✅ 建立公平的积分激励机制 +4. ✅ 提供多维度的数据分析 + +**核心亮点**: +- 🎯 灵活的任务发布方式(指定/招募/分配) +- 🔄 **强大的任务调配功能(管理员可灵活调配进行中任务和任务池)** +- 🏢 **清晰的部门职责划分(需求部门 vs 接单部门)** +- 💰 **智能积分消耗控制(按天/周/月限制,防止资源滥用)** +- 📊 **多维度需求统计展板(部门/用户视角,数据可视化)** +- 🎮 **游戏化激励系统(成就勋章、排名竞赛、团队PK)** +- ⚙️ **完善的激励配置后台(自动奖励、积分配置、游戏开关)** +- 💎 积分制度激励设计师 +- 📈 可视化的进度管理(日历/甘特图) +- 🏆 多维度排行榜和数据分析 +- 🤖 智能推荐算法(任务转移时推荐最合适设计师) +- ⚠️ 延误扣分与申诉机制(公平透明) +- 🔔 完善的通知提醒机制 +- 🔐 企业级的认证授权体系 +- 📝 完整的调配日志和审计功能 + +**技术特点**: +- 前后端分离架构 +- 开源认证系统集成 +- 钉钉生态深度集成 +- 现代化UI/UX设计 +- 高性能和高可用性 + +--- + +## 📝 文档版本 + +- **版本**:v1.7 +- **日期**:2025-11-07 +- **状态**:待评审 + +### 更新记录 + +**v1.7 (2025-11-07)** +- 🎨 新增:OpenAI 官方 UI 设计规范(10.1章节) + - 明确要求严格遵循 OpenAI 官方 UI 设计规范 + - 核心设计原则(极简主义、现代化、清晰层次、一致性) + - 完整配色方案(深色模式为主,参考OpenAI经典翠绿色#10A37F) + - 字体规范(Söhne字体家族、字体大小系统、字重规范) + - 间距系统(8px基准、统一内外边距) + - 圆角规范(4px-24px分级圆角) + - 阴影系统(4级阴影深度) + - 动画与过渡(OpenAI流畅体验、标准缓动函数) + - 组件设计规范(按钮、输入框、卡片等完整CSS实现) +- 🏠 新增:首页 UI 布局详细设计(10.2章节) + - 首页整体布局(导航栏+欢迎区+数据卡片+内容区+排行榜) + - 顶部导航栏详细设计(含完整CSS实现、毛玻璃效果) + - 欢迎区域(Hero Section)设计(含积分余额提醒) + - 数据卡片区域(4列网格、悬浮动效) + - 主要内容区域(双列布局、今日待办+本周数据) + - 排行榜区域(本月设计师Top3展示) + - 响应式适配方案(桌面/平板/移动端) +- 📐 完善:所有UI组件提供完整的CSS代码示例 +- 🎯 特色:深色模式优先、OpenAI经典翠绿色品牌色、流畅动效体验 + +**v1.6 (2025-11-07)** +- 💰 新增:部门积分余额消耗提醒看板(5.7章节) + - 部门积分消耗月报展示(月底倒计时、消耗率排行) + - 智能建议功能(根据剩余积分推荐可发起任务数) + - 积分消耗提醒规则(分级提醒:绿/黄/橙/红) + - 月底倒计时提醒(30天/15天/7天/3天/1天分级提醒) + - 部门排行榜增加积分余额和消耗率展示 + - 目的:激发需求部门在月底前充分利用积分资源 +- 📎 优化:文件上传和交付方式说明(2.2.8、2.2.10章节) + - 明确发布需求时仅需上传需求表格(Excel/Word) + - 明确设计师交付时无需上传文件,仅填写素材路径 + - 添加交付路径输入界面设计 + - 支持共享盘路径、云盘链接、服务器路径等多种方式 + - 添加路径验证规则和格式检查 +- 🔔 完善:钉钉通知集成技术方案(7.3章节) + - 详细的通知数据结构设计(包含钉钉推送所需字段) + - 钉钉消息推送接口封装(DingTalkNotificationService类) + - 支持多种钉钉消息类型(OA/ActionCard/Markdown/Text) + - 通知队列机制(异步处理,避免影响主业务性能) + - 通知表数据库设计(sys_notification表,含钉钉推送字段) + - 推送策略和失败重试机制 + - 确保所有站内通知都预留钉钉推送接口 + +**v1.5 (2025-11-07)** +- ⚙️ 新增:激励系统后台配置管理功能(6.1.8章节) + - 激励游戏总开关(11个独立模块开关控制) + - 成就勋章配置(创建、编辑、多级成就、奖励设置) + - 自动奖励规则配置(每日/周/月/季度/年度,多种奖励类型) + - 竞赛活动配置(创建竞赛、设置规则、奖励结算、模板管理) + - 团队PK配置(对抗赛创建、实时战况、结算管理) + - 排名规则配置(维度权重、更新频率、保护机制) + - 激励积分池管理(预算设置、实时监控、趋势预测) +- 📝 更新:数据库设计(增加10张配置管理表) + - 激励系统配置表(incentive_config) + - 自动奖励规则表(auto_reward_rule) + - 自动奖励发放记录表(auto_reward_log) + - 自动奖励明细表(auto_reward_detail) + - 竞赛活动表(competition) + - 竞赛排名表(competition_ranking) + - 团队PK表(team_pk) + - 团队PK成员表(team_pk_member) + - 排名规则配置表(ranking_rule) + - 激励积分池表(incentive_pool) +- 📝 更新:接口设计 + - 新增:激励配置管理模块API(62个管理接口) + - 涵盖:总开关管理、成就配置、自动奖励、竞赛管理、团队PK、排名规则、积分池、数据分析 +- 📝 更新:通知场景(增加5个自动奖励通知) +- 📝 更新:数据字典(增加7个配置相关枚举类型) +- 📝 更新:系统模块划分(增加激励配置管理模块) +- 📝 更新:核心亮点(增加完善的激励配置后台) +- 📝 更新:术语表(增加8个配置相关术语) +- 💡 特色:管理员可灵活配置激励规则,系统自动按周期发放奖励,激励游戏可独立开关控制 + +**v1.4 (2025-11-07)** +- ✨ 新增:设计师激励提示系统(5.3章节) + - 实时排名提示(排名变化、超越提醒) + - 成就勋章系统(12种成就,积分奖励) + - 里程碑提醒(已达成/即将达成/挑战目标) + - 积分冲刺提示(月末冲刺、周冲刺) + - 同事对比功能(友好竞争) + - 每日激励卡片(签到、待办、加油语录) + - 即时激励弹窗(完成任务、获得好评) + - 竞赛活动提示(月度竞赛) + - 团队PK提示(部门对抗赛) + - 智能提醒建议(任务推荐、休息提醒) + - 可视化激励看板(趋势曲线、目标进度) +- 📝 更新:数据库设计 + - 新增:成就勋章表(achievement) + - 新增:用户成就记录表(user_achievement) + - 新增:用户目标表(user_goal) + - 新增:用户签到记录表(user_check_in) +- 📝 更新:接口设计 + - 新增:激励系统模块API(28个接口) + - 涵盖:成就、排名、目标、签到、竞赛、团队PK、智能推荐 +- 📝 更新:通知场景(增加9个激励相关通知) +- 💡 特色:通过游戏化设计激发设计师积极性和竞争意识 + +**v1.3 (2025-11-07)** +- ✨ 新增:组织架构与部门管理模块(一之二章节) + - 部门类型管理(需求部门/接单部门) + - 部门权限控制(发起/接单分离) + - 需求部门负责人管理 + - 积分消耗限制功能(按天/周/月) + - 部门数据权限隔离 +- ✨ 新增:需求统计展板模块(五之二章节) + - 部门维度统计和排行榜 + - 用户维度统计和排行榜 + - 需求发起趋势分析 + - 需求类型分析 + - 数据导出和报表订阅 +- 📝 更新:权限角色设计 + - 新增:需求部门负责人角色 + - 区分:接单部门主管 vs 需求部门负责人 + - 细化:设计师和需求方的部门归属 +- 📝 更新:数据库设计 + - 更新:部门表(增加部门类型、消耗限制等字段) + - 新增:部门消耗限制记录表(dept_limit_record) +- 📝 更新:接口设计 + - 新增:部门管理模块API(12个接口) + - 新增:需求统计模块API(9个接口) +- 📝 更新:数据字典 + - 新增:部门类型枚举(DEMAND/RECEIVER) + - 新增:消耗限制周期枚举(DAILY/WEEKLY/MONTHLY) +- 📝 更新:通知场景(增加4个部门管理相关通知) +- 📝 更新:术语表(增加5个术语) +- 📝 更新:核心亮点(突出部门管理和统计功能) + +**v1.2 (2025-11-07)** +- ✨ 新增:延误扣分机制(4.9.3章节) + - 延误判定标准(4级延误分类) + - 可配置的扣分规则(轻微/一般/严重/重大) + - 延误扣分计算公式和算法 + - 设计师延误档案和数据留存 + - 延误扣分通知系统 + - 延误申诉机制 +- ✨ 新增:时效重新计算算法 + - 剩余工作量计算 + - 设计师熟练度系数 + - 设计师负荷系数 + - 智能计算新交付时间 +- ✨ 新增:延误扣分规则配置(6.1.6章节) + - 可视化规则管理界面 + - 延误等级配置(可增删改) + - 扣分规则测试工具 + - 申诉审核配置 +- 📝 更新:任务转移流程(增加延误检测和扣分步骤) +- 📝 更新:数据库设计 + - 新增:延误扣分记录表(delay_penalty_record) + - 新增:延误扣分规则表(delay_penalty_rule) + - 更新:任务调配记录表(增加延误字段) + - 更新:积分记录表(增加延误扣分关联) +- 📝 更新:接口设计 + - 新增:延误扣分模块API(7个接口) + - 新增:延误扣分规则配置API(5个接口) + - 更新:设计师模块API(增加延误记录查询) +- 📝 更新:积分变动类型(增加延误扣分和申诉返还) +- 📝 更新:通知场景(增加3个延误相关通知) +- 📝 更新:系统参数配置(增加时效计算相关参数) + +**v1.1 (2025-11-07)** +- ✨ 新增:管理员任务调配模块(四之二章节) +- ✨ 新增:进行中任务转移功能(单个/批量) +- ✨ 新增:任务池管理功能(指定接收人、撤回、推广) +- ✨ 新增:智能推荐算法(推荐最合适的设计师) +- ✨ 新增:任务优先级调整功能 +- ✨ 新增:调配日志和统计功能 +- ✨ 新增:调配权限审计功能 +- 📝 更新:权限角色设计(增加管理员角色) +- 📝 更新:系统模块划分(增加任务调配模块) +- 📝 更新:接口设计(增加13个调配相关API) +- 📝 更新:数据库设计(增加任务调配记录表) +- 📝 更新:通知场景(增加6个调配相关通知) +- 📝 更新:术语表(增加调配相关术语) + +**v1.0 (2025-11-07)** +- 🎉 初始版本发布 +- 完整的需求分析和功能设计 +- 技术架构和实施方案 + +--- + +**祝项目开发顺利!** 🎉 + diff --git a/README.md b/README.md new file mode 100644 index 0000000..b837e06 --- /dev/null +++ b/README.md @@ -0,0 +1,362 @@ +# 🎨 DesignHub - 设计师任务管理平台 + +
+ +![Version](https://img.shields.io/badge/version-6.0-blue.svg) +![Status](https://img.shields.io/badge/status-completed-success.svg) +![License](https://img.shields.io/badge/license-MIT-green.svg) + +**现代化的设计师任务抢单与管理系统** + +[功能特性](#-核心功能) • [技术栈](#-技术栈) • [快速开始](#-快速开始) • [文档](#-项目文档) • [截图](#-系统截图) + +
+ +--- + +## 📖 项目简介 + +DesignHub是一个功能完善的设计师任务管理平台,旨在帮助企业高效管理设计任务的发布、接收、执行和评审全流程。系统采用积分制管理,配备完整的激励机制和数据分析功能,大幅提升设计团队的工作效率和积极性。 + +### 🎯 核心价值 + +- **任务流转高效化**:从发布到完成,全程可视化管理 +- **积分控制精准化**:多维度积分管理,合理分配资源 +- **激励机制完善化**:签到、成就、排行榜,激发团队活力 +- **数据分析智能化**:多维度数据统计,辅助决策 +- **消息通知及时化**:实时推送,确保信息触达 + +--- + +## ✨ 核心功能 + +### 🔐 用户认证与权限 +- JWT双令牌认证机制 +- RBAC权限模型(7种角色) +- 钉钉登录预留接口 +- 细粒度权限控制 + +### 📋 任务管理 +- 任务发起(配置化类型、品牌、平台) +- 任务抢单(自由抢单/指定分配/招募) +- 任务执行(进度跟踪、时效计算) +- 任务评审(五星评分、质量评价) +- 管理员调配(分配、转移、优先级调整) + +### 💰 积分管理 +- 团队积分池 +- 积分充值与审批 +- 周期限额管理(日/周/月) +- 积分消耗与奖励 +- 延误扣分机制 + +### 🏆 激励系统 +- **签到系统**:每日签到、连续追踪、奖励递增 +- **成就系统**:10个默认成就、自动解锁、奖励发放 +- **排行榜**:好评排行、积分排行、效率排行 + +### 🔔 通知系统 +- 站内消息(4种类型) +- 通知分类与筛选 +- 已读/未读管理 +- 钉钉推送预留接口 + +### 📊 数据分析 +- 需求统计(总览/趋势/类型分布) +- 用户排行(需求量/完成率/满意度) +- 部门统计(积分使用/预警机制) +- 设计师统计(任务量/积分/好评率) +- ECharts可视化图表 + +### 🏢 部门管理 +- 部门CRUD操作 +- 部门类型(需求/接单) +- 部门层级结构 +- 积分限额配置 + +### ⚙️ 配置管理 +- 需求类型配置 +- 品牌/平台/类目配置 +- 系统参数配置 +- 延误扣分规则 + +--- + +## 🛠️ 技术栈 + +### 前端技术 +| 技术 | 版本 | 说明 | +|------|------|------| +| Vue | 3.x | 渐进式JavaScript框架 | +| TypeScript | 5.x | 类型安全 | +| Ant Design Vue | 4.x | UI组件库 | +| Pinia | 2.x | 状态管理 | +| Vue Router | 4.x | 路由管理 | +| Axios | 1.x | HTTP客户端 | +| ECharts | 5.x | 数据可视化 | +| SCSS | - | CSS预处理器 | + +### 后端技术 +| 技术 | 版本 | 说明 | +|------|------|------| +| Spring Boot | 3.x | Java应用框架 | +| MySQL | 8.0 | 关系型数据库 | +| Redis | 7.x | 缓存数据库 | +| MyBatis Plus | 3.x | ORM框架 | +| JWT | - | Token认证 | +| Swagger | 3.x | API文档 | +| Quartz | 2.x | 任务调度 | +| Apache POI | 5.x | Excel操作 | + +### 开发工具 +- IntelliJ IDEA - 后端开发 +- VS Code - 前端开发 +- Maven - 后端构建 +- pnpm - 前端包管理 + +--- + +## 🚀 快速开始 + +### 环境要求 + +- JDK 17+ +- Node.js 18+ +- MySQL 8.0+ +- Redis 6.0+ +- Maven 3.6+ +- pnpm 8.0+ + +### 1. 克隆项目 + +```bash +git clone +cd qiangdanxitong +``` + +### 2. 配置数据库 + +```sql +-- 创建数据库 +CREATE DATABASE design_hub CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- 创建用户 +CREATE USER 'designhub'@'localhost' IDENTIFIED BY 'your_password'; +GRANT ALL PRIVILEGES ON design_hub.* TO 'designhub'@'localhost'; +FLUSH PRIVILEGES; +``` + +### 3. 启动后端 + +```bash +cd backend + +# 修改配置文件 +vi src/main/resources/application.yml + +# 构建并运行 +mvn clean install -DskipTests +mvn spring-boot:run +``` + +后端启动成功后,访问:http://localhost:8080/swagger-ui.html + +### 4. 启动前端 + +```bash +cd frontend + +# 安装依赖 +pnpm install + +# 启动开发服务器 +pnpm dev +``` + +前端启动成功后,访问:http://localhost:5173 + +### 5. 登录系统 + +**测试账号**: +- 用户名:`admin` +- 密码:`123456` + +--- + +## 📚 项目文档 + +| 文档名称 | 说明 | 链接 | +|---------|------|------| +| 需求文档 | 详细的功能需求和设计规范 | [PROJECT_REQUIREMENTS.md](./PROJECT_REQUIREMENTS.md) | +| 项目总结 | 项目概述、功能、技术亮点 | [PROJECT_SUMMARY.md](./PROJECT_SUMMARY.md) | +| 部署指南 | 本地和生产环境部署指南 | [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md) | +| 当前状态 | 开发进度和模块完成情况 | [CURRENT_STATUS.md](./CURRENT_STATUS.md) | +| 阶段报告 | 各开发阶段的完成报告 | PHASE1-6_COMPLETED.md | + +--- + +## 📸 系统截图 + +### 登录页面 +渐变背景、玻璃拟态设计、流畅动画效果 + +### 首页工作台 +数据卡片、快速操作、待办任务、本周统计 + +### 任务大厅 +任务列表、筛选排序、抢单操作 + +### 我的任务 +双视图(发布/接收)、进度追踪、甘特图 + +### 设计师大厅 +实时状态、进度展示、好评排行榜 + +### 数据分析 +ECharts图表、多维度统计、趋势分析 + +### 通知中心 +消息列表、分类筛选、已读管理 + +### 签到打卡 +签到状态、奖励规则、历史记录 + +### 成就中心 +徽章展示、等级标识、奖励领取 + +--- + +## 📊 项目数据 + +### 代码统计 +- 后端Java文件:67个 +- 前端Vue/TS文件:36个 +- API接口:118+个 +- 数据库表:29张 +- 完整页面:20+个 +- 总代码行数:13000+行 + +### 功能模块 +- 核心业务模块:16个 +- 前端页面组件:20+个 +- 公共组件:10+个 +- 工具类:5个 + +--- + +## 🎨 UI设计规范 + +项目严格遵循**OpenAI官方UI设计规范**: + +- ✅ 极简主义设计 +- ✅ 现代科技感 +- ✅ 清晰的层级结构 +- ✅ 一致的交互体验 +- ✅ 深色模式优先 + +### 配色方案 +- Primary: `#1890ff` (蓝色) +- Success: `#52c41a` (绿色) +- Warning: `#faad14` (橙色) +- Error: `#f5222d` (红色) + +--- + +## 📈 项目进度 + +**当前版本**:v6.0 +**完成度**:100% ✅ + +### 开发阶段 +- ✅ 阶段1:基础配置与核心功能(完成) +- ✅ 阶段2:部门管理与数据分析(完成) +- ✅ 阶段3:数据导出与系统配置(完成) +- ✅ 阶段4:JWT Token与参数配置(完成) +- ✅ 阶段5:通知、激励、管理员调配(完成) +- ✅ 阶段6:最终收尾与文档完善(完成) + +--- + +## 🔮 未来规划 + +### 短期(1周内) +- 系统测试(功能/性能/兼容性) +- Bug修复与优化 +- 用户操作手册 + +### 中期(1-3月) +- 钉钉深度集成 +- 移动端适配 +- AI推荐功能 +- 性能优化 + +### 长期(3-6月) +- 微服务化架构 +- 容器化部署 +- 数据大屏展示 +- 第三方API开放 + +--- + +## 🤝 贡献指南 + +欢迎贡献代码、报告问题或提出建议! + +1. Fork本项目 +2. 创建特性分支 (`git checkout -b feature/AmazingFeature`) +3. 提交更改 (`git commit -m 'Add some AmazingFeature'`) +4. 推送到分支 (`git push origin feature/AmazingFeature`) +5. 提交Pull Request + +--- + +## ❓ 常见问题 + +### 如何重置密码? +管理员可在用户管理模块重置用户密码。 + +### 如何配置钉钉登录? +参考 `DEPLOYMENT_GUIDE.md` 中的钉钉集成部分。 + +### 如何备份数据? +参考 `DEPLOYMENT_GUIDE.md` 中的数据库备份部分。 + +### 更多问题 +请查看 [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md) 的"常见问题"章节。 + +--- + +## 📄 开源协议 + +本项目采用 [MIT](LICENSE) 协议开源。 + +--- + +## 🙏 致谢 + +感谢以下开源项目: + +- [Vue.js](https://vuejs.org/) +- [Spring Boot](https://spring.io/projects/spring-boot) +- [Ant Design](https://ant.design/) +- [ECharts](https://echarts.apache.org/) +- [MyBatis Plus](https://baomidou.com/) + +--- + +## 📞 联系方式 + +- **项目名称**:DesignHub +- **当前版本**:v6.0 +- **完成度**:100% +- **开发时间**:2025年11月7日 + +--- + +
+ +**⭐ 如果这个项目对您有帮助,请给个Star支持一下!⭐** + +Made with ❤️ by DesignHub Team + +
diff --git a/S3文件存储系统实现说明.md b/S3文件存储系统实现说明.md new file mode 100644 index 0000000..28e2675 --- /dev/null +++ b/S3文件存储系统实现说明.md @@ -0,0 +1,331 @@ +# S3文件存储系统实现说明 + +## 📋 概述 + +本系统实现了基于S3兼容存储(MinIO)的文件上传、下载和管理功能,包括: + +1. **S3配置管理**:支持多配置管理和动态切换 +2. **文件上传**:支持多文件上传到S3存储 +3. **文件下载**:带登录验证的文件下载 +4. **文件记录**:完整的文件上传记录和关联管理 + +--- + +## 🗄️ 数据库设计 + +### 1. S3配置表 (`sys_s3_config`) + +```sql +CREATE TABLE `sys_s3_config` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `endpoint` varchar(255) NOT NULL COMMENT 'S3服务端点', + `bucket` varchar(100) NOT NULL COMMENT '存储桶名称', + `access_key_id` varchar(255) NOT NULL, + `secret_access_key` varchar(255) NOT NULL, + `region` varchar(50) DEFAULT 'us-east-1', + `public_domain` varchar(255), + `enable_path_style` tinyint(1) DEFAULT 1, + `file_path` varchar(255) DEFAULT '/', + `is_active` tinyint(1) DEFAULT 1, + `create_time` datetime DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `deleted` tinyint(1) DEFAULT 0, + PRIMARY KEY (`id`) +); +``` + +### 2. 文件上传记录表 (`sys_file_upload`) + +```sql +CREATE TABLE `sys_file_upload` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `original_name` varchar(255) NOT NULL, + `stored_name` varchar(255) NOT NULL, + `file_path` varchar(500) NOT NULL, + `file_url` varchar(500) NOT NULL, + `file_size` bigint NOT NULL, + `file_type` varchar(100), + `uploader_id` bigint NOT NULL, + `uploader_name` varchar(100), + `related_type` varchar(50), + `related_id` bigint, + `create_time` datetime DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `deleted` tinyint(1) DEFAULT 0, + PRIMARY KEY (`id`), + KEY `idx_uploader` (`uploader_id`), + KEY `idx_related` (`related_type`, `related_id`) +); +``` + +--- + +## 🔧 后端实现 + +### 1. Maven依赖 + +在 `pom.xml` 中添加: + +```xml + + + software.amazon.awssdk + s3 + 2.20.26 + + + + + commons-io + commons-io + 2.11.0 + +``` + +### 2. 核心服务 + +#### S3Service +- `uploadFile()`: 上传文件到S3 +- `getFileUploadById()`: 获取文件记录 +- `getFilesByRelated()`: 获取关联文件列表 + +#### S3ConfigService +- `getActiveConfig()`: 获取激活的配置 +- `createConfig()`: 创建新配置 +- `updateConfig()`: 更新配置 +- `activateConfig()`: 激活指定配置 + +### 3. API接口 + +#### S3配置管理 (`/api/s3/config`) + +- `GET /active` - 获取激活的配置 +- `GET /list` - 获取所有配置 +- `GET /{id}` - 获取指定配置 +- `POST /` - 创建配置 +- `PUT /{id}` - 更新配置 +- `DELETE /{id}` - 删除配置 +- `PUT /{id}/activate` - 激活配置 + +#### 文件管理 (`/api/file`) + +- `POST /upload` - 上传文件(需要登录) + - 参数:`files` (MultipartFile[]), `relatedType`, `relatedId` + - 返回:文件URL列表 + +- `GET /download/{id}` - 下载文件(需要登录,验证token) + - 自动重定向到S3文件URL + +- `GET /{id}` - 获取文件信息 +- `GET /list` - 获取关联文件列表 + +--- + +## 🎨 前端实现 + +### 1. S3配置管理页面 + +在 `Settings.vue` 中添加S3配置管理标签页: + +```vue + +``` + +### 2. 文件上传组件 + +在 `CreateTask.vue` 中修改文件上传逻辑: + +```typescript +// 使用新的S3上传接口 +const handleUpload = async (file: File) => { + const formData = new FormData(); + formData.append('files', file); + formData.append('relatedType', 'task'); + + const response = await axios.post('/api/file/upload', formData, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'multipart/form-data' + } + }); + + return response.data.data; // 返回文件URL数组 +}; +``` + +### 3. 文件下载 + +```typescript +const downloadFile = (fileId: number) => { + const token = localStorage.getItem('token'); + window.open(`/api/file/download/${fileId}`, '_blank'); + // 浏览器会自动携带token(如果在请求头中) +}; +``` + +--- + +## 🔐 安全特性 + +### 1. 登录验证 +- 所有文件上传需要有效的JWT token +- 文件下载需要验证登录状态 +- token验证失败返回401错误 + +### 2. 敏感信息保护 +- API返回时自动隐藏`secretAccessKey` +- 前端更新时支持保留原密钥 + +### 3. 权限控制 +- 只有管理员可以配置S3 +- 普通用户只能上传和下载自己有权限的文件 + +--- + +## 📝 使用流程 + +### 1. 配置S3存储 + +1. 登录系统,进入**系统设置** > **S3存储配置** +2. 填写S3配置信息: + - 服务端点:`https://s3.1080p.hk` + - 存储桶:`jsxiangche` + - AccessKey和SecretKey + - 公共域名:`https://s3.1080p.hk/jsxiangche` + - 文件路径前缀:`由壹点AI/` +3. 点击保存并激活 + +### 2. 上传文件 + +1. 在创建任务页面选择文件 +2. 文件自动上传到S3 +3. 系统生成文件URL并保存到数据库 +4. 任务中显示附件链接 + +### 3. 下载文件 + +1. 在任务卡片或详情中点击附件链接 +2. 系统验证登录状态 +3. 自动跳转到S3文件下载 + +--- + +## 🚀 部署步骤 + +### 1. 执行数据库脚本 + +```bash +mysql -uroot -p designhub < database/create_s3_config.sql +``` + +### 2. 更新后端依赖 + +```bash +cd backend +mvn clean install +``` + +### 3. 重启后端服务 + +```bash +systemctl restart designhub-backend +``` + +### 4. 更新前端 + +```bash +cd frontend +pnpm build +# 部署到服务器 +``` + +--- + +## 🔍 测试验证 + +### 1. 测试S3配置 + +```bash +curl -X GET http://localhost:8080/api/s3/config/active \ + -H "Authorization: Bearer YOUR_TOKEN" +``` + +### 2. 测试文件上传 + +```bash +curl -X POST http://localhost:8080/api/file/upload \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -F "files=@test.pdf" \ + -F "relatedType=task" \ + -F "relatedId=1" +``` + +### 3. 测试文件下载 + +```bash +curl -X GET http://localhost:8080/api/file/download/1 \ + -H "Authorization: Bearer YOUR_TOKEN" +``` + +--- + +## 📌 注意事项 + +1. **S3配置安全**: + - AccessKey和SecretKey存储在数据库中,需要加密存储(可选) + - 管理界面只对管理员开放 + +2. **文件大小限制**: + - 需要在Nginx和Spring Boot中配置文件大小限制 + - 建议单文件不超过100MB + +3. **文件命名**: + - 使用时间戳+UUID避免文件名冲突 + - 保留原始文件名用于显示 + +4. **错误处理**: + - S3服务不可用时给出友好提示 + - 上传失败后清理临时文件 + +5. **性能优化**: + - 使用S3的预签名URL实现直传(可选) + - 大文件使用分片上传(可选) + +--- + +## 🎯 后续优化建议 + +1. **直传优化**:前端直接上传到S3,后端只生成签名 +2. **CDN加速**:配置CloudFlare等CDN加速文件访问 +3. **图片处理**:集成图片压缩和格式转换 +4. **文件预览**:支持PDF、图片等文件在线预览 +5. **批量操作**:支持批量删除和批量下载 +6. **存储统计**:显示存储空间使用情况 + +--- + +## ✅ 完成清单 + +- [x] 数据库表设计 +- [x] 后端实体类和Mapper +- [x] S3Service核心服务 +- [x] S3ConfigService配置管理 +- [x] FileUploadController上传下载 +- [x] S3ConfigController配置管理 +- [ ] 前端S3配置管理页面 +- [ ] 前端文件上传组件改造 +- [ ] 系统测试和部署 diff --git a/S3部署说明.md b/S3部署说明.md new file mode 100644 index 0000000..1b22ec6 --- /dev/null +++ b/S3部署说明.md @@ -0,0 +1,195 @@ +# S3文件存储系统 - 部署说明 + +## 📋 部署步骤 + +### 第一步:上传文件到服务器 + +在**本地Mac**上执行以下命令,将所有新文件上传到服务器: + +```bash +# 设置变量 +SERVER="root@192.168.150.22" +BACKEND_DIR="/root/designhub-backend" # 请根据实际情况修改 + +# 1. 上传实体类 +scp backend/src/main/java/com/designhub/entity/S3Config.java $SERVER:$BACKEND_DIR/src/main/java/com/designhub/entity/ +scp backend/src/main/java/com/designhub/entity/FileUpload.java $SERVER:$BACKEND_DIR/src/main/java/com/designhub/entity/ + +# 2. 上传Mapper +scp backend/src/main/java/com/designhub/mapper/S3ConfigMapper.java $SERVER:$BACKEND_DIR/src/main/java/com/designhub/mapper/ +scp backend/src/main/java/com/designhub/mapper/FileUploadMapper.java $SERVER:$BACKEND_DIR/src/main/java/com/designhub/mapper/ + +# 3. 上传Service +scp backend/src/main/java/com/designhub/service/S3Service.java $SERVER:$BACKEND_DIR/src/main/java/com/designhub/service/ +scp backend/src/main/java/com/designhub/service/S3ConfigService.java $SERVER:$BACKEND_DIR/src/main/java/com/designhub/service/ + +# 4. 上传Controller +scp backend/src/main/java/com/designhub/controller/S3ConfigController.java $SERVER:$BACKEND_DIR/src/main/java/com/designhub/controller/ +scp backend/src/main/java/com/designhub/controller/FileUploadController.java $SERVER:$BACKEND_DIR/src/main/java/com/designhub/controller/ + +# 5. 上传pom.xml(已包含AWS SDK依赖) +scp backend/pom.xml $SERVER:$BACKEND_DIR/ +``` + +### 第二步:在服务器上执行数据库脚本 + +```bash +# SSH登录到服务器 +ssh root@192.168.150.22 + +# 执行数据库脚本 +mysql -uroot -p'Abc123456' designhub < /path/to/create_s3_config.sql + +# 或者直接执行SQL(如果文件已上传) +mysql -uroot -p'Abc123456' designhub << EOF +-- 执行 create_s3_config.sql 中的内容 +EOF +``` + +### 第三步:编译后端 + +```bash +# 在服务器上 +cd /root/designhub-backend # 根据实际路径修改 + +# 清理并编译 +mvn clean package -DskipTests +``` + +### 第四步:部署和重启 + +```bash +# 1. 备份当前JAR +cp /path/to/designhub-backend.jar /path/to/designhub-backend.jar.backup + +# 2. 复制新JAR(根据实际路径) +cp target/designhub-backend-*.jar /path/to/designhub-backend.jar + +# 3. 重启服务 +systemctl restart designhub-backend + +# 4. 检查状态 +systemctl status designhub-backend + +# 5. 查看日志 +journalctl -u designhub-backend -f +``` + +--- + +## 🔍 验证部署 + +### 1. 检查数据库表 + +```sql +-- 检查S3配置表 +SELECT * FROM sys_s3_config; + +-- 检查文件上传表 +SHOW CREATE TABLE sys_file_upload; +``` + +### 2. 测试API + +```bash +# 获取激活的S3配置(需要token) +curl -X GET http://localhost:8080/api/s3/config/active \ + -H "Authorization: Bearer YOUR_TOKEN" + +# 上传文件测试(需要token) +curl -X POST http://localhost:8080/api/file/upload \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -F "files=@test.pdf" \ + -F "relatedType=task" +``` + +--- + +## 📝 文件清单 + +需要上传的文件: + +1. **实体类** (Entity) + - `S3Config.java` + - `FileUpload.java` + +2. **Mapper接口** + - `S3ConfigMapper.java` + - `FileUploadMapper.java` + +3. **服务类** (Service) + - `S3Service.java` + - `S3ConfigService.java` + +4. **控制器** (Controller) + - `S3ConfigController.java` + - `FileUploadController.java` + +5. **配置文件** + - `pom.xml` (已添加AWS SDK依赖) + +6. **数据库脚本** + - `database/create_s3_config.sql` + +--- + +## ⚠️ 注意事项 + +1. **路径确认**:请确认服务器上的后端代码路径,可能需要修改 `BACKEND_DIR` 变量 + +2. **数据库密码**:如果数据库密码不是 `Abc123456`,请修改脚本中的密码 + +3. **Maven依赖**:确保 `pom.xml` 已包含以下依赖: + ```xml + + software.amazon.awssdk + s3 + 2.20.26 + + + commons-io + commons-io + 2.11.0 + + ``` + +4. **JwtUtil注入**:`FileUploadController` 需要注入 `JwtUtil`,确保 `JwtUtil` 是 `@Component` + +5. **S3配置**:部署后需要在数据库中激活S3配置,默认配置已插入但需要确认 + +--- + +## 🐛 常见问题 + +### 问题1:编译失败 - 找不到AWS SDK类 + +**解决**:检查 `pom.xml` 是否包含AWS SDK依赖,然后执行 `mvn clean install` + +### 问题2:运行时错误 - S3配置未激活 + +**解决**:检查数据库中的 `sys_s3_config` 表,确保有一条 `is_active=1` 的记录 + +### 问题3:文件上传失败 - 连接S3超时 + +**解决**: +- 检查S3服务端点是否可访问 +- 检查AccessKey和SecretKey是否正确 +- 检查网络连接和防火墙设置 + +--- + +## ✅ 部署完成检查清单 + +- [ ] 所有Java文件已上传到服务器 +- [ ] `pom.xml` 已更新并包含AWS SDK依赖 +- [ ] 数据库脚本已执行,表已创建 +- [ ] 后端编译成功,无错误 +- [ ] JAR文件已部署到正确位置 +- [ ] 服务已重启 +- [ ] 服务状态正常(`systemctl status`) +- [ ] 日志无错误信息 +- [ ] API测试通过 + +--- + +部署完成后,请告诉我,我将继续完成前端的S3配置管理界面! diff --git a/backend/backend-src.tar.gz b/backend/backend-src.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..cd5e91674caef1baf25f15b82b152aaaf74c6156 GIT binary patch literal 125095 zcmZ6yWmH{Vuq}vda0u=mG`PD1b26bVCOXX?j7Cz-tS#| zjoKx1R;{W{8ifEchAjIX0{R^I>VYNQLYK{Jv*EUM0P?+>CeC4L+#ke9v|SN^ia;^n zMXc?k;l11NNlUYBjtOG)qQbwPFlS1FF%9>k+QA6N62rv&Hf21r7Z4oy#^S-a0;1o_ zyI!m@STtDFSlsX?gDSHOU)*?DZ+@x)dR(mH5HjOP=Xtf!lf*rhsD6H53kK|s+P_8h zbXG}x!MHj-J(Vrm)f7ljcytvs8zOb?vCjOGGA}uoqR%J%^&lzEoRIMDIaa(ryC+Wm zg#~=qBXq|yEzrmy_~Qb<2{dVnxG8wIHPS=%{DgapmfM4*WwA9gBlL(R=PL$!_fSgR zYQF*xTbod%=4MZ<)Q_6(#OEj-Nncp`Ke0^vdNRJe=x$v#DB_v(XUuKhCoG-2{${QL zc>0pXgRq>?-aUq~BCM(?(aMEYy~j>^80;038?hc!B^;F4aIQA$$VueyZO;{dV;K($+U{0T zUH~ZYl(Yn0JLxU#3?P8x@K0{)ZTM%DdpWBHQM3G{J!R3Gw0U>UWiHi(u!(R)%iB`o zLMk}-bbqFY0fs9FEu;yIc)vBjwy~B9)=iLjHBu_*?Bvf4f3QDSv|mP#^Js?cr4{16 zzf$(Qqwb5p&1(YB`6KU%?Vv>mIjJ=L;y&sXK&2%c+Ce94O5 zXlDSCBNYG1u7Haldr*LcsvCkIBBM&E@6e0FRFuZVk3E~4k01y6L;-NUiCCGULm}#O zE%$5-xS6K;pr@2?^!^GdaB?5Ic^n|PVPN-aB%de>T5J2dxshZ$4p8z#lgr*R~Q66)BYgEC#k_7`&Qq=?5$o}|v%T4uUD+MxZ2)=(D z5DiMYxs>qucqEy4AUx)2wlP|hTVt+O(a8iBypb&igq>#5ApT4PXFmw1oZJ7iu>YMB zRiAS{qFqQbIB`^hZE=2H@vx_re{mCRr}*Fbw}k*vOxtj@4*~rbTH)P`_pMZ_QE+V~ zcdmkcc=2vIPe$D1^HwR@^Q>_GOUXz8`GK(0`;}bN6F@KV?Vv||5*^&12R^=(byEXR zZ`OFOftE7=^BeXzkWDli%$#s8*zF__A!K9p!=;f_x!?;~nk2!Sn0ob(7+y)YjRGi% z-WGzzCo%pV-3)$wbS+)IHIn}@j!%Ue&vWfp zA@n)W(Hwwnc5!X-Ur=;K&Hn^H z6rUCIXXM|sCfvCIL{RyL)9yt`{9menm;~lU>E5;V9ju#J^uA>-u(?PaH{`QN$YrbW z*n$$EMPS34(Bwc^Bp}xlHwEmy_Yqyxk#5rJ?Uq_j|0p2lCa~^NI1Pj)D0uOgg-JH|j#L74 z7VG9w+|k*wD`*FKJ*wn?AE&moGL3r~~JnT7b3; z?7%&L{!8*w$6F(_|ILw)_<01|$lm@17jw~I)#qNCPyeimAzZ`l6?`6z0~u5}D8PN# z8S$R<>cs99+)M?V2ey7ADEkO(hl)<_z{jo4|2M6%L2uIU|DYr16~OyW`|){EiN0Wm z1{*Ua3bVc{L zf0Z2vFv)^$x7=S|o68e~#5z}0ejvv#`U=wpN-KGI;JuQrgXhE#dn!wrz|D03rF&=+ zm|zb&@SbG@40oS*`jJf4vYW44T)&g*FE`CSDoPnPH^F|lAK}Ms^?+6ct_d*%d;9_0 z^MDr(!KD6~3UMR~aciH^%h>O-ma50UDSVE!s8lA3Q&fnHFJ~E&OB%BE7dZ5W9duWL zpY5A<^>5UMV2{O_$9mimyD_#cQl=Y*JCTP>3W_Q!9fK7J7Sd?afqmZ>UzU?Agg>89 zpPD}*e!iEPB600b=&J#Mp56Z3r3PR9x!qMgxNJ{xS<>n@3Sy~5M$^Qt(fn+cmX`X3 zwpLTgUx(Mt-(u%q*l*DI-H?gK0`wIx=eFT%m^3?Lu?SHoZR6~!(S};QLNL7o6 z+Wv-uLnmeww*oDJ>*t1URN+S_B}FX`O^3>T)yIjf&vLW}EL($4R9AAtaZ1L%mQ`d^ z?nO#uPYckqfUcfhz)x>0!#iMHPCxL>>lbqhYQG<^;F|1iW@9a3awGn{dbo?-{PQw9 z6iiFa7WQ-|_0Y+)O+&G;tSXcnlpD(hE>9joA$#2Fcj&At=!4t+l7};w&{@|@k$m+{ zn3yUhCCeo%q{4dWg9v>Q3kR|bJF#7xCYuK;Kf5$i&fTAHU@N&TL`ZfdfX~L-+3N1v zUS`lEn1+Vn?mZ0K85}nT7&VW~1uHa916pXBFNBrckUGIj2&4$@;18ZDC~frvHB!sg zgQ?W&Jy}#nxou_s3Hje{fuSoLR-Tod-f4N)UO&+LBRLTl$Kd`U;10;agJlAs^?@%1 z{|CDE;DKX45O5RgKSq^%YsFp%FJdX#$x#1CyKl#NDt-e{#xl=J`d}=<{~#C}9sD#> zCb(k#H*WPDKu-X!EiEMmw{rduIGMj~{roGU_$Vv~9<^@+w|^kkThTJO!^aFb8dF|V zBjKR+AFMjO8GU;|kqh8l0Xx1cQLo4ZE%4(1 zpA14bs!ns;VrdZ6;sIu^#@Pq(N5Siig271iAbHdn&zxYEz5q z?nntYZE$HDs2vN59k;^xP%+UwK0VEa zW!aKED(%ytarGAKPPztO+XTPRY`c-@wCSNYXU99&DnLc@V?7KoQz-Sj*zZ2Wc9h<04>B>2RXSFv(*E%v@I|L3q%GmZZlhT=+))wDoWE-qY=0PFf+%%{{^X)@_3s$~l_&h&fJb4%jRyI!d+LxQM>`8C3}l!kY|xdh1`)WEZ3uO#cqqnU{6jeT->Pi z+wAuXe6Mj5COvp`RAS9%k$T5Ab6yxGBI&7Eoms0w#?lmOGI)~$R!do#CGuS3pXyEH zYC~O;hr{klG(EtnXZn;wViP63sxp0EaQ6yRk3Uf3{YQ!S0fD$cq|lTW>>8c%KDXY! zrrDl5KH!1Zm%U;kSh^;3;12|e;)Y(;n>uqT6tx8H%P18Z1{eB8Kxro5T??F0<(7Yw z_b$AkFNBI$>G2hTG1T+>ufT`r{=NlsbeJZ^aWf-9zf$Kzu>;U>3RbBmGn2+;o)NDH zkk7};__g-QGA->J_-2yarp$2$j*G)u&6=5IvsblelYd9OxiOx!wFF_92%?D!xWc8u zh)L3r*B!~`jMYKzUT{0fzc_tViR)hj^2}{&ynUa$-IeYCgg-WAh<@WaHw*AbGiZ6HtaBZwnmkQCEIL7a6G#Lqy@ZH`IG!Y72@$jl}YB+_Ij-= zW_+8KaRc#(uzZRKm90OUJfuBU-f2-lX$!DZnUq->2L{hSINb*bde2Pinhu<*%QBmn zI{r||xrMHnCmtzsg)#hMpbThVz8laGj^=fDbu)2t+evGVLWZel7zU}da|bh&g%2sO z)$@-(3IBY+spNaNvv_17|k9wE^k!r>$GsdA`$M5C|#EVC34wCVGkkh+Gxvwp3nx! z{7M;O6jmqWoUkL(LG=SU(+PPygDgdg-+19PsIieXmss)0J%O>ldt(i#OfF9AP`OmA zd71P4uxVj#xrkQNcMOXsV^P$dK<*QO9$c0#VZ!`y;H;7?X?B7cpvY}Oyc=Y^!b@>a zC%E3IwAOb(-JfG6<{Ix76rStuEw+XjN{p^y>#fxx;7xXlzfq#->-mxrjbx+>-$nLh zH5Kk0RjTO##!JveUsWm7GWU}@l{_pd)T>qAk*{nbNRvAmyOhss5=o-Rg0Cu6utqtV zJ1JwBhGv>u`T{GL626b}OuxvcFucR6pNIipfF`-~TB2A*PIo-2#;mN$iZ1x$j(}B_ zhau$K7MI*^W@yrb1Rfi#D8gLH_V@HMzs@3BD_fp`_?(0gYBLsIky~oxmh~l>-^N;Da_^u-LfwK~u^VX}u|9Lv(;ZWdrU$>+r^kBY4xwdWUF>C+Ow z8&)LbexDs7gguNROEP|6+!z@*DFH@tx{0CPEwEu38(hhV^U0*`vj~TnYb?bmo(i_k zpCmTdI9D-7Ax_l-(n26f1gfqTfuLif?N5>TSQfcTmDK<$8-gwle1@QxMN<>8D$_>Q zK(dKfnUeT)qOgp`$W6mifHOcg=wJnn!AeOhcB7Kn@)7o2h2KDb`14CtYY1IK%)yTQ&p6mFw~935GJgLXlTZ z*QbTSa0B0F?trm*ISsr3001ZV^qdKza2#AJy-;MWq%DmkuCaYO@*0c?Z9!Re4&MarPo3d zA#@qA*qc!icyrT}AEYM#Q6rOPgXIQQ4G6K&Bib)OH~XcI_gf>Uq%z=g^|UmQ+DBm@ zS1s!eMq6_$ntA0nwys+i>f}gaAoQgu5-?7+o*@sUuUXik%$hEnB7qDwoLp_6h&5HY zj%M|uijIFVvn{OW5rWOS??gV}{#D~R$(IryI4roY<^l_oXyIX2#6tO82;*rUuCi9_ z)YpAjWBVNndzARJsq+%YIzG}a_;=q>{{D-udaOaMudrJC(>DvJ`r8K|2CKNj;n1_M z`DY#Hl=)U)*HOe+X-uHUqtX~?YfY09{f==Qa8!n(hgyC}8o^1-r5SFHaRD^u({80S zYelPDAX^JC>TP;9I~AIL5PHwlPY`mDFe zQd8r{jf=zu$wLCA>KynBi7Dn`BeQo_N6)cNWPP8LD@5;-`6RV~fU@k5zG6?n=qi%} z%F^0JJx6(Zpt_nR_E&*ZQ}6R?F)lQBPPQZrr^gVTG!gK_Q>C!Mp3C%4T-Fojtmua) zJJQY8J*8KVPpA>;WJ_jZfhomk49IB%7(YiO2hc;)jhKlo4N0?P&B=U2l15%`-c=9o z&{A=gG!Fd@`Q0l}gZ{8Rp|e*r;g;A_sx8EpF<;_+GApldc1hE8*o=mO@C`5?_2e5K zK8&V?SS>Ln7qR{FqiP;uwRu{o7UW4Xyxv=Z^=WA}wZN=3Esc5mvp(HKHLNFdQ&3+p zZWB)cWL-$uV65Nk<9U*!e9b?O$XP5r+NK7gl-%yn#w`I6OfGsV&~ZQ%Wtrzaf=)Bw zaDwGOAN}a+<5nr(;tz0j7+Q$Hzq(b)!QrF&T#GCk1j|vI-KbyvG}kKls2jnfN;}6r z35&w*&jdOwXD`Qa+ynkSMF~XUvd~84%lmp<>BklTgf81*t=Yu z-aE8@#k$qf6&Dnq=-WH~NPIC4yz&UMm0i$2jZMDrEiu2y8iTIIQo@lHvN-=HA%K_y z4K7VIm?&{Me`$M%UXzWA$jQP@a z98+vXj_MTCsRT8Ijyk7#GMahc!&m5^nd$JsI*P`67wI$LQ!uR^I3Dn?YMT{9HkPoF&Lq?iWeQ3{&qa z##HCVnVnBi9?`#J@Lz)2?jaFB>J5vf+Emtz7sE-C21pku8|18d2PG18JJLRzo>M3n zyH*$Xl_?n`!IgLlGYPu7D2t!i9qV;k>639d1sEBcm5o*(C~4c!kii7->6{dftJj#Kzr;M) zMFPx$FRdv+bBdWkk18&+x&pMa{<|pn>fu0}-1y|S{I};u)&k@WF*&mr5&ccQz|wVr zQPb^^pNGo(ba3p!Nb}of%@mPA``hN`5`&dJGmF7M2I6sb({&=wK5JmZJ+suNR~%Xv zo=cA)+@PXd*?<}k;`R`^-f8Y7PlayXAZ(iQL=^e>0u>Qq(TFJx4Nri_;Xrdnp$&VQ zR*k-%PKP=#yB6@8-Pwt(98K7XZGO=`Qx*vVE-!LG74v)M1c?El4p}#gK#;qJ>#Ftl zZBnqXhy;=sQANicW;lah^}yc+X;!Ei)g*_?a6AG2zGpi1Ui}>7XUz8KreilxHno=J z?-9NUBrm^FMXCMH*Bb|+kLj&8yG=|npvHxbDu|)pn?-*K!@#_pg^2*rVP4$OmAh82 zC2gZgI;kq*&9=eW89p`l(-~?h{K8};An&VV7wWSZ(X;ls+NZ-*+!dro@6*b~t8Z0P z6B<^+)j9I$B3olH-4#i=af3fpG>OjY+aHJy#4JD=!{dW2*QA@e!ZyBN0 zBg(#?;q~suC{!6jfV?-q*gUG7AB8m?48`FT{aO7oj^60-+VjN+niE*j zsWnALF||mb?7A&w*?-K+z-1hpoJr1QmzMQj^6|!-hEk+F0iRz4pIz(nCj#P7IvbxJ z5)C;pGlCL|+UVTl$*R=y2;j`CtkY1h@$&@mpnzW*J?OkYu8VXG+I0C7y;faczW=Vr z#FZlZGc6kD*|4HKB6ToJze|@?BT(44ih5?@;1AU^a~#8JGF}gX5B&wpqBQ>i?@K&} zR};hy;W(MNm(vPGp|iZL8NP``4s!@+vGZ@$PlIAd#)6=Oxz$$}#;y@uVJviYC8NsR zPN44-bG{volb<^Zxuj-pN@&K|I;`vTz0S4`mQ%oVw+^BH5XEXJ$!vkjS9t%_Xa?Xi zB5|a}oki8}*(6zw{=>36gE?OmFXfzv@vrK1r7oGDAfvd63)2?yZb;8f9R3VWkRujT z$t_#Ot(u>6EF`wAR)kO9bXxN5!k$sk8n%6|DA+DM3x;OxVB`5Ife{MR>sg@;!|UX4 z)cYb=uliWqg1g-ID}I^-O@ZBfQA#v9{w@^v9#Z=kPYkkTQNbIJNxj%U{=wQGCe1PI zLdhXFgCvJncd?M}ZiclBR~l(`kDbaE?xK;#7hQwB64hd^i5PaSK&fEq&)6X)BpR~Y zGA4`E^)e8&b`l0ild{d;SVvCpkl9VbFtitF`$g{$PIDkGT0a4v@8E%*qW9{9qIV@$ z+6xB~TIzH8jrn+7A(KBF>+jB&{^0do15~%-`PTf@Q#5AkOMoYAaJ6Rkv4o1Gsqd!T z;NBnaS~AE}RsAhaCCc{~sTakV`z*qQ&%6ZyCjF`@Mh(ljv!9W#>JoW`Ib-#H3cj3P zby&pQ55OlT?$oIi+iF7xHckGD78+J^V#w8>U(lHO_O7*~OzdV|hi6OqbMVqu=Hi!1 zO5nUH4a~pt@E31QSUI0*GKiN@Pn%svzK+*ZOoK8b<4-U|tY~qe}_J z^@%*^aO4P*q{z|K>JlFqk}c*)WhhTlOg0O?zQz;r5p~BDDqEzW8_^AM6Q1Vp&Q)Hp zgsw$~nob{)IfY0l?Lt%-RTnf)#Lt6Q&y8yiV9;P`LPG7VgjP4*Iqa9+?k6FOFv<*5 zCh?)%U@3HqcnxMWE$VZSkHM$UE*dJUGLC&KcYexhHY?SCghA_-4+V%sbn?cC{S*)%>2)?RQs@4hVm9yBA(4u9Qz~?k07=U~` zoeu|2+u>7eaKF&@q#e>5Lqjd}lT9ZNo9@m~7PmZG4-!kR7K$5TXU7`DR8CEcyqQI+ zI|lX&*LW+BqbX4-&F5wwksi{2r5;S;`&&ZDO1VPIUcM895^F_4C=@Z#>5Vi%`G-#k+cU-@k zWfJ#U#9Y`=y7Nr^2s`PWF+d3nd)@wq5kDc-Bd*oCiU?Dmyo+pxRSXf*cDW(y4YAlJfwmxHmFHpeQ#)HSS)dot`Vk z(m<6dpDyDoZzH38@j5D_e9_y5Wi#<=qi)v864!jG!_U$0JY?5Kw8 zGWS2ZTyF+RrJRJX)2Rpt3;Vo5Rs}1NuYZYm|XizAO8jhO?ijb44qjV4bA+7EDk3 z(H!2jmFZ}?LKCTKxehAN4x}(x3l0(7btFWC>e^2Ex0!zO_B!3rHFO-YF>GyCO^^)J zfOzj>mmCsk&E?l+F}}8_K^0m;#Iod?l!rOV=k>=Q_aB@Nt&PjerzB|sfFAf$cG+FA z{sAd*hi$2pm7G(fHOJbI;W?1aWNJ8iPm?YA`~`-oj5f`#@bmfyHO5SO8?B zlJoNfa4j#MU8&-3s?({zOkqLjw`>sP%p--dHZ$LLKDrXpwTKa(WhrrNob}gr=h-Q4lf8H@fu-V6Es)bt8NlJJ9#lNsnAOmDs_Z5vBd{`YS6* zsmN#ev4hd&yXrVC3`hAPYi9Y9^i9XF=1K}dc`-zLyE3*-Jc2sRgYL!B+&03=G#4s! zJF^7_{u{X@R7%cGQC$YH7hA0^(j>cvID%UZq07gRZ4UKBl$EwZ;;svJfAv@Oead2v zHo^&PNOJ7^BO9*7v?H}LgUmim5&AH&T#F9sU1z1QduoAYVP5e@v z^mJ@J0xEqOwp_Vrv~sA)hK_;*^U-cv8D#)NRx0k;7m-^-Y-!EeNxuF3Wa5p&^iEBG zFF`0j$e1Ve_r%H>872K@KQetag=92vmN`GSA4?FDL-I)t!|;rnh!R0JR>~dqoB*!@ zv*6Q7;Ac~cn6sk-s-(fEeLLYwGRW_2ma5!6M%nqM^dDmcJ1-$@o}D=>ZGA61jXOVZl{Ozk5*p79^h0XF=fW zywFtxh;;zAv4z%TT6x%TPmbKO9s)dQF4)}k_W$dD<6uMpu7YiW%m>kAYS;S>_ z+vyR_cI})j#-F~wTpAPQfdaV<)wIp3=s<<=(_ezXgZ8fbz$$$FwTs(!Wx_4ijfe=M84;rCuYX$=Q8Z3&NwO7uCp25{~_ZHVPHx) zVcBOdF5^$dt*+q-+_03Zx0=%YmMyo#wRg&LrFB$`w|cLmlZ^SL^V10$1)m~9M=AuV zd1w}6hXpKqJ|w0MiSo$)x(cN{m)p;uiQIPwz zUD5BzdG?m%v4#ADA|?BOozM#=+x<@+OZukL&Qc?Tn{flyK1g?3tKsy`qh{0fNIJ;E z^qQIFA|tgatiEbMyGZo~GNbe0By$EIYnla@WhEjx?L0pU13t zByAEKhDL4l*-QNxR@9O#aKfvAW9nX^?FGV_Zj5>qGb@|oomRNl6xZanHuUY9&J**6 z5t4}Z>nqp2;=SF(WQ3jSd1#&NH8^CPPRCDh#b8@#7_ zFF&KW4%@79qFyXlwb3-7pxsF@TSGf<{1B~Mp}{^vo24w5v1W+Xa=u4pn32;U{Y!T` zeeWeAryQh=#k`3vc&2iI&~)2Qx#%c}UtykhN~e@Hm(^O8DvCJ)A;>b$!t5d@=?2Y; z9YdBPFfAA8P)t=AQCpd|Abw)kXgT?2B+5SS0ykG;<;5n^6%GV2O zP-!>n+B3yB+1dD+4qNi~n3+FGaiCUvYZJ&1-|~Qha=s$?=_R*RK>Y$q*YG$f48|Ah zrH{=ara{#0mYIX%x8i3xLta!u5X!hq%_~w3%`RWW83J1VSRE8lhbI!R7@CJbzf;}x zR2;=L!X44+RXIt&|Fded8f1{HOlU%%2gv?l?%|-*qq5a1<;+Zd$K1%IXnUP^jsj!w z<#h}8v$)T7iJ(-nY5Wizo&Rw)SA|H zqT7D+%lR=`%r#dEyYiRqZsKh^8WhFEu{q{RadvJx45U?udJC=TL=294oWTl_$yx_i z>Dw=JccO#FE(0os@9_dsIbZmJ5SUF`4t~ta&$YN!BUd>U$Es(KINhT2_ndFnDZ5RH ziC9VVM!NmBk%RjPwB&=-x!iS3sVv&+xBE?Tz5N8wOMrJRGh;Wbx2ynC$YUrC7 zc8H|+c4XGQz&Fd6)xE$5X*udC`4`yKdY2bupXMbR*!gwRcw~2Ce&-aG^N1TpOx5^>@cIG2D9A3* z$kl~HNp4>{;_{ZjOZcy@5!)i=E#GaTv1SVqY)6tlU4b^Y`*Vu&*BwTJ&i3@N(aP^+&dW^eF()sAik$qCO2fv zYSDGmhO*}?iXXWl0_t>2H(A1gs?PD|vC#l9v|@9!*_DmBBMz<$kd@#`{z~cT>bJI# zSQW<*HxQqI*vv8fLgmrV3g@sbUAL-YL(=YtFwFYDFJhS!ZqDz?pBBU8#x@u?BWboF z<6x67=HYN9=tuRIe_@Uv1d6*-{QhfB$WoF(sg@)ZsG6VtZbMK#m^>`7iL_zX1=r@C z9>J|4B&5;qGgSroRS40Dzs4Ojq++|$Ntl2mJ~tGx2)HBRTI&xj6C=rlqZ6)MFwU)U z(IhfQh>0q4VXGc1=9ujD!smG2*vWBPt*x1dI+aWX?)mk6<-{yy#;&nU+fwnd$NIwD za#k6jlXva-B*My4`-Dnn3ob?t!e%8r*0$6bg?W$_`{azk&;**Ks;OUS4nN#O%kd3Q zWuA8zL@GqLYi4cVDc$a;AI=r3%;@6fk280%g`@$LG@Khv;76t<>JVG#tGc5+61gSoT;^gxyx z=}tJu91u;uMhPa5{zPe(X55MJ#&dcd=~03qCU{7k*a3&?RG!G^HO^U z5#Rb8aOg4P<5{I@lKLJmkii@d@FXBrnbm7c44cZXiTku%t(e2M?7YQ0jIk28*JgJ$ zyj=RZ?+Xon^>Js+%)_^g_W!gc!~r&ZIB)5Zmt#Whq{x5T<7vHxT*Ty9FKrct9Q0~j zh1m6#sp8`8=JpxCs;!8bT-YOsz4h<_8Tel*>pz8QzB|ddI)!a~#-PEFEHjO5 zt{@9ZaI9yIOGajkMf_3&@(=CO-^Cc^-6ojeb4 zF64w{KG4Gi3K|#yM>iLKYhV5Loc{Uu_oiF9q0Ny9V5{4%7Nx76(w)kH8P|;O$Hv@K zX<&CSVABZCmwajK>9EgjgRoju)%qy!9SM&V;-z_<$NR?;_r>>G zaJ5Vv4~QY^tRt>W1F~L+__dIRxiXRP6gkOZq&;R`c+3AN5>32YJTzT~+TpyMGqaMJ z^%OycQ@*~Q!#w-5S*7Yw%OPIPk8IT$fpz6#YsqpVpk})dydCP|vzo+#$&Q>&q*{du z{)=`ovw5hGI+qPwhZ^-ddF}Y|6i@?uMr86>lHPq-iI?!0mOca~HY=rm0w-%fA0Zy_DuNYfIK9WWBAymCLmAEQ}vrS?P| z5>3GUw)xXjwxBm_U%6_w-p?B0xmD{tjn-Y_-T8ypYAAGkH7BDwzl}dGeT_ecQysP~ z_*s7;A?hsHU#z_v-7DTQrk@h!6tVNup}#oxvFqrjFn)1b{_9#utmg&EYGl_RYI4fu zI|w^6Wr}l)igb*^E%=cvYpQ|MXx1nV+LOTZ+tOBl6Ehli`-iAXo2^Nm;*&X6h-F=% zT7)B*`&tN6iwphVRl zLd`ZLGvmA|8(tzQ`oas=gBY>)x&uyeBUia*GMyQ9)}$@aiUOsDK1el36UJNoPsRFz zM7KF{L)1z=8o1NjJ4qd+ zM3ZPBDCp!37H^>4Oa<#DkVL#W)O_z}E;Tucm}S@sM((?Sf5sdc+;jr8K&vpw)hKJm|a&^T_f(QNi%T z8gWHi>BY1JILJMF`PGlZ*)E(B=+)rv)WNekF}6bb*9W#C%%wVt6ozr-61AwAXlWFhgse$6zH5X zxwK5HnBOaO!wehrUFjL32gq+5wm^XrWS!FrOQE+zJfL!8_ygX~cE%Io(D<`mW2Pzd zt@y)BCTs|YWuoI?wtP0H88c+^Z-K*CTQ5}is8;n=H}ji15%)eA1FntppyBK;nY#dU z9aphwtoCSwALG5TPm6c7T`amW_Z+gJ4%zNQAulINj!Q}#rjT&oN=+d<`J3k1#^En< zUl9@xdj~ruArrTN1@`qN8_cKCg6GtQ-?qob((<&v`PchCdAeC5rlGXqBNH>6l#WFJ zjJg4cgfq1in9WEez?V;Y#C7EyrR^JnS@8KW-i&8wKC}8;mP}7K%01evcjW`#ec!i8 zv%BoCTzzK6x*)YmNPixhT#w+%22uv{lHe~^NR}r@s4qy2*{jsieE%hR^PTloYboDh zEVOvQ2*4FNuu|H)Ieifz~uOK(9&P*YIDjN8dCY`}LR8XQ%;Fe6=# zz!FHQ5;f`HFDkmF+Yge$8#rb^6hUdg?}HDzNVbI)IKXa3Dplyu62uF~P5b;~Pzgko zeKt>HL~xTO;fZmFn${V^OCv+|GUL3S;Ro?K@SR|%gTeAsCHYE^O^rnj(r~+hfIh!A z41}lx9)VFtsHYaJy6>0VUOa=gRLQ-aNAK24vo7Li)8^qpFGTv0^gUI0e*J3KBz$rlR z3n_9$5zQjF=9~wkiNi;_pNNpBMcB`L!Fv;8S>mA`k;;5UP5aiFIF*4`c|;Q4|DDYF z8jCH1aWI!SS)z9DSM7&&#nAQUxifhlB*`i`EWL95boDBmv55*zE*X1Ie-Dbww>mzTdgn7C zYc%PjO78B#@)e2U@o8YhmptB}aAvwDV{d&$fF@t?-?6z?26^tf zRyp?=4L&${{vvT=Ir>PubyH|<13Fb|t*qHl@S=D+`sfX@hK8N4Eo?sgB8rx!R|wqu?iy)- z5%Tb0Z5gmj`(FA-bUnpq8`X?ScjcIprR*8gtcX@i;UbV_h`k``^XS%cKZp0Lai4UW zvxq_0s0w-j8(3-My-Q)&T=>}I+==4fV%xk*&E0l14?xdj8A`^cO4+)+UY&d{>Op7W zis1w9@v5vOo*0Y0PD;KSUf}32pUWR54QpRZg2jcq4pes5S-x=oI_Sl1RiGr#m+*HW=QV8~9 zrNaZ>*ml1U#3@wWoK2j!W1Y$)Pz6%g*zHay zj;Y?~Cq1g315kL79;+iXDkq7p@7CiaQqj~=u9V_Tm9~!7@^QPUVY>PoFEeSwKr}@! z3$;PD+;&q2Sl}yFr1Pn80V-vH4TAt#S8z~Z=&QC6XK)y1)2<8dof_(UMSD-mOob4v}|8g zr752^qJ=b|An-#ai|+VZHyx7MKNg-X3Bla@9nE))!{_Fl=flNy^L zp@$&D)`zghUM~)0k;K%vZjTiywQNvp;r)KvkuC#CQ!IH#E>CR{8_qBB+!Rw}2&sG8 zTrN9Mc~_`onTIi~$M=n?u(NkRDbUfda{A>L6md%VRyqIVGEk)r&FWXTV26^?<(cgM zW!X|ub%E@_jT_7vE{j)M!}>WUnoe13fA3pVzfKl>+9Yb;W4h!1e8IO8%{6sN>K@H@ znf>`HO-!w|MFg2{6n4>(T9X`z+V9#pQEvpy0gQV~RZ@So2sNlEpfTF22Q~zzf~Wm) zm~3~Q%)c9SdKUhPk;krb4o%#T{+yX7D}*d#x5hRE0Zu*O*H39FT8cTpsL8;+uS$9C@V10NX^6dASdsp$PX{uWdqKAE$qaS z&wiR~7k(fI)%ZJ>AMHMo>-6R%#fuuuno*7!biTxNm?KrOyHe)&YNO22z~5)HWEdW0 zg#;2S>Qg_Cb0j24QFQomDIAdTURzrkgu(Z%t&$D|ai!8TEuWpE4u+D(qqamjJJ_YB z%Gr@o6Ru2`ce_GERc+b05+;>Wj!RWJFjg6mPyHKd_R-}Q!pQt(c0Vl+pN!!;6d_TO z7CSD|U0M)e7m?oxvW6H@7eeE@b>KkIF33?HciIrvj<4=(R-R8apn+_JsPYbiwJ<|& zh}0x)JzybA8BS|*zFMA~|2M3Y7a5ruK%T@v{|h6m|7`q_rZg(n)I}!?w_SE~=@$}g z$o64v)b%q$L$iPx-Cckw&vw{nFnh*v>=TWeY&baM*e(z3p!j1L>|3EGcq43c@X-?H zh5H=$lyc9`$AVWP7jQiG0FP8$_RHy~v3ASxGLQH7gFdT}B6)wYg+NxoN&xyhZRf|x z@8dt>r zSFd>^2ebr&g-~qswHm*=voHHYOj<8@XhZ!A=B35H#8i##@N%8a1e2BLEmahH?=DUqJ-(9pP zIY?yP?kdzMfWz;$-fLvFdry!Rn1Z*v!RzDjv0DMl|3X_eO|PKQd1 zy?@cpk>liIGb1kqT1aG{;IGPJD((icbvQgV+S1G~>`6$!;f;kfm786#Fp^_ZQqoTDCk%Bza9b_cb z#bRrSB+BV=uUs(CmwaAlxGo2h$(vj&C~@oLO7Da~(`^er>}>>@J(pUrIm)Ba_FAz( z6@(l~g~LhW5d>xq%q)jy%*jzR-MY3WY}AzMfCP6Cmb$a_fL_T}3Fx4g^~r;wjMItM z%RhR7n+9Bx@1`-T(3in2V)oZ#ko(1Lvxhs*%Qpm+$C8eg2ol*$4tbT7xFjNm%(9-k zM}8N1V}~QEHmHWb8&cP+oS5QLD&6+SS#dfu_FiVKEz@#lRWILCj^{^Qj%C9w3wVVM zreozp`W;W$QHGnClo3ig4VlvgJEOlPGa0H}nCRj~;uqA1|JC62|8zY*>VJNG+P{0t zg1qP=3?1ap5hk#)zzh3~X$j+m7>R1t=)kqV){XDgnXB;Qll!CqLg#N^xz?9;6Yt%l z=BMfKzlSMJ1XAr7@vKkthO@=LuotTn3VlCOB%iqR|HZ!?!P3#9HyhBS*x|P@yPy2U zY^hT?MpVRy&t61!sKAd(ceFDWFJMif|L_A{%uPFNu4+D-0QqOBG98ZY_7i_@gwH8j z@c0g@z@9VTQSfTK=v5NyOPMELKhuPFc^%`H;WLNjaefB!s1Uk8Q5oASzW%;|2PAc% zWdXFC_~>Dyh5JhF)PzhxG;)tfgVkjCus>dkY@4?c)KC(b>7zIYq(QOM*AQoOmj zHjs)(^kQ(!aygwlIBess)6g0XIYOHiFQA4ID3@hZ>=15XXV=%?o~<6eSv`GyZ3a%# zMcC>P7N$}M!HGIoOYh!USYEnq=$+@A+%FNXEJB&E6BOL-Y#z=FWe?dgp|`LM!(Zp4 z+f%U(Z%^?X-f&+B1l_J=2=u0h*^!lD2NzRHpTINx;GYM(U9k)|>ZMXTPg{aR$L^y=W~jF0G!KCoC=mW~2{|q>Hf$J3kdWP?)eq z2;(;daSW;QUS$eVY%TxbW;ZH?;9cHZq4 zT}~RR0cO;gZ@W9gQDL@?`QPBPA9e5`(00ht!<0{N)P_`pHJcwBY2W_X>=f?&2ZY$G z^YX`M!k_<0Cb|;6-EjYVS1R82|7=ZapZ|EOQ*Gxz>f-}p&wse|pGqd%=RaDKr1KwL zy~(a*Z)`(vV&lfH_{OgG`Hv?)g`xk(R{sF{Ps9`5ab^8a_H?z^|5l{m*!|B!O}HD( zs=|H!&~?>&Ep(<_i0v$poZE-?_+;5w52^nyXg9~cLe6+k zb-kx!px!W-Mf3K<*|s-1G#?9}k!JlfQ;6huE~eVVVz;frb;$j}?SG5BH91 zdvvM@Q#V?io+L;!LjT74x51yi=~9X0%IwIGl7T;hqeeazPH{WV4QwYs0 z5{z2FOD0xEp&PZ(5CMyFop)LDQdb!I!^;%i-`dWR*&j;h4)W|7`B_(lk*M_+Csj3z z7Wu77&odg~w0Wa^=aSd!0uh{+-KRapA@D&#uN(r{vK$u@EQ9#g=J^BVRF0?vN< zA1mJ+B_!*6n_Wg&CNYERy>oo+>VHc{Uo46{wMV#KRAI2o1t(L6p+n zQxh1NTe;ctpgj$!#;$b2*>dgRB?=HFJS;W@b*bTCX;Yeru+9&SEgb9ni|{IB2qu_h zfosPOq)S6&$%?X3DV~aA6J(*t@E}>>D9?VXhWK3eAYPS4P4Msow-k6IYKcNoXV91H z;TLu^TF>4Cg?z)6>;M-GLnVIH1-!)IC@F#3{)^kzc7|gLXOL@0)T_C69=3{MuC8{e ztQZ&AsYDwL*h|3f%IJ*=+$l8?aQbCnJBtlWDO^X}^p-Y;d8TVUotxYU;V2!``&iQ4 z2JnLC-r}j%U(QuN{k(GX_{tYYrO)&x5xCWcEmW#AwFXxHU|4oR{Rcysha( z%M?1Z$r%L($s}?UyK@GqRue3ff!b*#CIAI*!ID1hqg84?AbZf?66%}_aDUa!_%_*h}4Uiv2!h;wuukebk@VF^_Kb+mBXH{4lT`&I6zM4t<4jUX~YK51i`k zg|+#a>i6$dFZ}Er6=pp*02{`U+6i)xPlleIH4U?R@r&wt2;7LhUYj{l{ovojj+Dtv zasbbI#LQqMYL(n+n$F5cbIe8c`=!d~A5`XkUORTm$;|IjA@cxM0Ww(3bkOsql1aKh*-sq!B3+p7m zpG~-wO;|W1-aFU$lNOUi{51(*L4sEZaZ4qQrpO%VtVK!6p)>7~-o}=0UkE<@HCr7{ z^~49&`MZ@fchupSlv6$iPHgI(qk^ZIBDHEf%plb$yF!xWSEfdK`)1Mv`S^@SnFB%X z0!{~N1h*gH*WOuJS@^zosB@0^n}9mgyS;sTcu;3hs}a1 zHFZ@9^oTcPE2=9y_Z~uTH8dMZB)hs(J&_J0@v{T=D88OOwesyPMYk5$M-wwLiMm>| zuD4Rp+|{%2nxh7IFBldEC0a>!gYsF6h4unh+%TpCdbYZmYngnO-@Kfemn!q^z4G!@ zj{<@t4Rf{?U9RbY{N=mHon`IUdvnM=Yv-H7iVF&))ahr{Ok5vu2JETBJ8lY-68XmS z)F%S)ktj9!9OrI8B0$Ld%>kk)nFHh%zhy2V#P>3>4i?zVh
=Czs2tG~Qoz43MR z^tH;FuMiVM3bQ&6pq`dTiu67FOvCHb`TOVpsQr8lx#lMcmGb`fBl75RkT zB>PYqgXioy9PGXGy_jKW&-Oi=c$~2NAH!AimzFB`&tnS%+r_-lIyEfBP+BaabWv9EM)3sDr7fjI3F6GI}7L zhwCH_lTyfK__tans+PT=hst=EwtVqhjS}&!cLpaQtc_iYN`nreR``eDIDG}%CcUlR z_-f_q!fz767Vq-C{^M)MP9bk=E~b(9+3`HEFm{FL6k}pEJRZE+%65E8Vdx)Aw;Z8x z1t61Joj)N$$!a<8X8_Rhe|8nA^^EHTsOV5Dwj9eWe^pUuM>}*R7 zY!8S9jcCtimD`~O>QKT+I;i4|s>JxMFv1$+K{pHiJkWAFXb4$3k)D*R#_@Y{l?!wn z=3~Azti^hCeR>!=dATq?o})rGvn)7kq}g3bWLkcEKHf#qW5ZCqNg8AFOuRmJohXH( zXBSaZfb&tmFGKWa2lsfmS@^Au?yfBTHKAcpG=MZG_zyCqb)H&glc1F^fg)qQ}gqK=-JQX=pNz?v7%!VB%RkjSvo-uq_FDVVA~o?9}~cCvMTq#uY7!J_3B*p{4MMzG@!~Nv{^rU=&jFI zZ=OdF8W#49f``l@yO08-z6pC;iVVUIAkYPoD(M(CL|iyQJ`sb^?iqQD$R1m_($f@f zMoCJe+)Mr}u`&6RSu+YazaVmaG1k8b4Oz%{Tiwb+cK`yE%+6?`Wh|j)gRt-BkQ|#6 zp>i{4mrla?b&7%WgGT}OP8WxH87}!SPaGs3S%(z>>LBo8dr2M6d!QbOOf+G8H&C!i zC*wedyZ{JG1%Xh9v&y}`p2lIZ^Hd~kYKZTNI)i0Bx3wq`V)&AXSPrW3S#4cn z5=#j6c;Nrr!z{z>Xz>aKrB-5=W_ePUF`cnxu6pAd5{;-&Rc3GDYNlPcX;`(=bbb_s z!&i%i34Y?yb#mymw5x$lCOccbZ#?zK%y2QZmfh>BZTFbIWb$F zITV7-T1p|f`U};_PkU7xo;X)6k*0G4c|J%FnEYjpbbfjlEPW_#_;g#6MFn|Y5X5x^ zqm=}n6?$wE1Kd4wzJ!-*9h@^r*dU?f^BhdbNPIb<63xY6Zxyn67P&@u z@}|}z7%3GCg%V^T1uRcu5{#s1??ZWO_Lg8Rm3KcvBcU@C2N7!-$J@%=8boiz5Do<0 zLTK3TGua|FB08a33MmPK|f}nfFVzCH5#Y$5nC0qmIpnsGP#3o2X zi8%Y5vx%^A0BBHW!cuQ)MFl|g16W=7W$og^^3vBUC+4KAI%TI^IB4g$6S0)77eWXa zzSYz>I%=0nLy$2_0im@@rQKYjMa-$6Nx*I{uXPKsQL3aqCuTR=RBZde?mrCm?R zZ~xG1!#nov+p`aDf55vp>5Pe>s(k(Z>QC3q4$sm&Fvurm$Rp0fKEd3v%Gv*z%99Qa zWW3t~x@n-qX8X?r$lM}?^z*0>xQR~*T^ay-j0fUdd=;=O1v&%WwqS*@=g3hBx{>CV@1AB> z{!f2ez4^_`(s7QACv-FA(S^)7m+7D0k8Sel@N_lEf?{pa7@R}fMc5Xy6OByXIM!lK zI5iKFr<)oOY-c$f^7B;%ogRcRx2TxI{A6Zt;q;pp@5xv=Ie%Ul6!9sz% zd3Q@^25<$yl9z70lhd%uDkkTug9g`8X;u0Iq#=q56-j zC^i0i@3IK%<@?8(hn!JsgbJwXh@;5|L_5K-BvJs5kLD`JNPQn0s?j5z+_gFE+VbL0 zaMj>X-&D@r*@Q4bwuImbVbpN6Vs(37$?I;uWAu%Nv|>-#B&xQmoLX-)eXB4s%q|0S zZE#xDdD-%@mP*xZvLf4uMC6xsgAHMT>ErGxW|2aDZ~*w^#lzXADRqp)0fz=4_GgT! zWtqYR{EPYKGZQYtb$ECaZKzRv+{@UnRg4Jj=ninY;r|xziFX1duD_OseMf*g7N z4O{KaVykPmS79PM-_42Ih*yp!Jr74Z2Il0=iALezyd2L%WMw>z@F$7^m*0W=ZRvc2 zeDvU$D0#uP$Mlp)fE*4<%MpD8Mu@OiB{vk+`~x<1QUmxPWnuuX0Me3e)H+vmkC?W_ zw1{X0Lh4wH)FT3sfVFz^)5`mIS5Ev~T{u~}_fBPTF~m~fBXdI%C{IxSwaO>(HG|}LmJig_U-H2#f@pceTnSev19x2;EtU;whaw$-?4XS6BT*|i~_Zf zzT{I9>{Mi8&wx#LegIEb77gJt?m)2M6nYbgfsA+<44`hIl2Dr&JZW8!(ECXCH1awR z`71R3>U%0?t(?0;7yz{t>$LDPv(&R-?qmRY8*chWJAV+h>+Qcqd#q@e4h$Kp-Y0yn zwFT*c`HZoRn;6_sNJGc#Re3tnDE+S?huH6^_+ZG7voD9fG3PV|axwLV={_32D}4!6 z2~>|JL;Y;EbE;?0ru-DRI<*p#^YU$L6!@X$f-*P_(IT#LO$yP%w=zcy6O*7y zaef0>H%N$PvevVHhW<-3g(I(rI_RhFS2~}3Eql#BelMWG{=pslhIj878s7151A{}_ zgMH!voNYgk0$H2VsKAJyYin>Yu)x+Z7}#49LW?os^`kjWoB=@wS2|ytuIi(o8rp4& zHmcepsJKAIbPqC0Wdi*m3I2;y|l0rC+cFXv(K?)b?%B#()oLo4dhUVR6JUuq3WNY9Kzb zb4DRG%$851D-zC(_@s2f7-C2qgd15XRJ5TQy1ut>aPZAN`?jMvRa@72Pkb*Ysuhge z2L}84ckb9;lLpmRpD?x6C%OJc)hA}Rpe&4Q{NY%b;ABW7(71+$v5D0=GK!=RH2`(U zDvZ#t(R3+OC=|nWYa3?9?pQA}t0>AAYv(Sl-n@yn(aPD=D@!*j3!kr?z9rRBkGUx# zwOToO1>z~*x?G+Au5xdtdhS6)9a&wnwA?5JQ#*IFI(KyC@_Q^&Bo$oMP#~t8e<)d~ zE`z$ol=~c!^;B>G?4L$56WNgWbh-9e4Cbs?KiUoY@t%DH|F6VF4L-%sa?W)qoy%q% zrr5L|keQ%0;%zM8pmOI22*5gjCE&6Q)d9feu?}kiJDu2Josh3i2tj;aE!)@7?6PY$ zT~*f5lzaqwZop^}eK%QI&BlU$2hTUAjd@#vzvG7!3=wXW{yQFUV&K%}rLTzeCx%C} zm^UKg##ZOL81N*653$0S$i7tv>_h1yEmUH6%?!HkgES5qPOLdnHYBmpqB(dOPjrI- z^1&Vf{N;@?u5Mb$#h7L_O)~4%c+~qwxynJ!H%Z++92s7BnFY-kUS&l3RgVSfb4nu;XtFU?l#)Tz z#Wght#;H?Oh9I>TQdlO3Qp_8B95!e2O`yH?<8chxBu?kPlX;u`o|}1eWal-4xTQ@h zd}a1d^};Pdtg3HvB^a#(h${{yguvBqGp5bhgs^=PmTP+X5DRx0_S8vn6s{pDOzXb@&*T9%( zq?T&|bO1AlZB+BJkc{%U+$aAKjo=q>XQ^`X3zXwL1C*~d_TD8ReF<};04j6VCi8sr zq#GxZ+;Wu2+3_1)9-ZG5FBU7wV2Vmm3M%JzSRd9m03VoBF|X6OGiDCh7JMs#Ke>m$ zq(B^O31Y1CQK?j{i45-pgcA3C)>p5e660u*iP1rl(TanwWvFg@v@ddub`ekO8>*pt z#3HwD(@@pGRb;@+OV`Mlt8?#H7A`xpy}YzUmnwC{&K*GMyxW0&)Tzf-=hp3%$~pIF|aNjy2Pt= z9dRT5s$TfD|3e|2t3~G%lfVAg74MCwQ2$GI z$3Yh)A4!3>{@051i1k16G(lt**R1ESCjur0=Kx$a<#MzF#kT0LZ}NI~`He=%+frQ= zPBu;D;3$xwTMpt|JM)P3*m8Elj_rg<4cpmuH=EV(WgFh4bFm@zqc8K;6!fjMIY=pH zUWcD9RKIwitGo59z#d+wS9@h-p!Qo4H}nOB%;t={3+3!sc9cSucOkF7Q&ngd7=l8< zM@beXEj|islwL#xKcmjOaq=I}y``%QS63D;RzAF5oxf7Oczf;QPtI2|xua7=SFrcm z#cUx1lN_ZrXmmJu4@$~545;`99A1--bn^^s$1VbWKO{xU=Q;M-httK(wp^ODm(kXt zJ2LM4kmrpnJ*IvO#~uV&YYtID1+Gs6?TNFG0y}m5%^wPm?R&FVR2!!(45cvDTH?+F z#+R#nGzT|id?rncF5b0no{Up2(JaBj!{G8DzKfA}+HKR);Vj+l=Oe||RIzn5U9zo6 zCY{YqM>Z+?DW5|uO|XGX4rvh_6?iYA9kFzoe%Uj&Jw2^GU?*3zBcIV$LP3%2>ae|-8dxZlu+Mqo&!fbxN60C5Zg>(kEef-0jA8CGP`2xzSlsQhJ$ z^wchD;R4s|uR=%yfDV={<96+Z=Fu7 z&s$yA3s#~N{vU_yRf1rNlP9(o3jAV$7)YITkfmr>2gJslDh=8ofWmp5T3|a(M$=q` zL-@1^-%O1)f^MUvk-~%qBrG<{!sX?kPvFAy>%BRQ?X`UGi^?%DX?a8fp1{x{uLB6t z4$;;jX8;g%1f9{Z%R|>Qh{`X=)~+t0X-d12>Oz&M;TbgW%-CaT(uL5-6&fuB^iPC2 zV#?0$)g>3=c{VB+I@gODr3L4MeT0bT;0>|GD`5HEy6u)~$SDN;L0;WM7AP{=IOu>2 z85N09cl)U@2H}jKNl+A=tpWz`KV7T8d|J8tO?{KLw^$f25+LEkK~D#SyJ08USGrDU zClN~cP9Rq4nM5r0h$m3eE98+Hc9f{qn%S%AF-&PtQWe(LEe^99N*Bq0`C1EGOc8#t z+C6o~Gdu2wUbuL*9k&V(PjJS^Gf+;i5y<_{_JGOy%!g|;N7>Qsnae9zu1PsG+9qi= zpAj`vHHM<1R1tX!V`zaGZh`{^!ix zbESXIoPxj`wEyILM5|C~+J{`4c6njwwe-;++d{L25DH!8%g@)|KT|j9Q_U`$Ct5u0 zP{@?eUCfw+8uS8e{GC*Kbjo-YeZl`4uc;;dS-m>9ym$jH5x;qjZQ}@wie;HXXabP7 zT8A}PzDil8z`cj%yI!FR-)p{EQu6=787?1qa2bBLpksDWO5|+0gs_x%>J((PhH-5T zWQo_8*RU<;>T3iq46kJ3*VL{5uyV(y^Mmeq%bb4`)~H+pg_k-YCv+L1#Bm{3kAqk; zC-N*!BIR{D^@fmXq+9JN5(aFb3V=c+f(Jc?sMn^q9);QIDl!TSZZpg^>8)S10vMf+P zYGF~@{0uw#yvX5{t5s621xawty!lP_-D5h$aCsXp33Qoe8cluDucE#@3duKwWU=<; zaR)w?YZodfzYi%>I9?cFTSqgytV$Tj>2X#d=R1M7mpNr(WH&uYX|s_8q%B zX|%xVXYW=&KDB&*zIx+p>|>a&)uUb1WhG%d8}r|BA$tx3cOo_+9);*I@0`3#Lk&BL zrt}kgLX-jQSj}Ccm#^y$QF4ZtG*Kq%>ajE51fX~odnjmvk}AYt!jclr)fyMez0Qx0 zk(H#V#K!D~g!X>f#P3yyL_e1JN2$&i^w9L!Rsv%uNl+kce&Log;5X zyLqPa^}ko%pQ~QFUOn;c>eabW%;bPQdT_^)NpNjJRi`zYztI96pxX4Dv#A=PJG zc6#S)V20!sT{ZSzNFG~r<-zRQ(TmmN4?xn@n43v;5I<-hwhY%Uovj={hPD55=AD(L zZy{>Y$?qyBKdr$SP5WvvNQ?geG#l%<`;)#oNTT1+a5-H%=;MaKjo8r$WE>tXOy$dd48TFW zNOk@nl@G7u%q8Mvws5iXD-71XVip(J2*r3>1;=<=3z@amg{#4JiTo`Q>>Lv1epEg= zMMWZ|t1`&H=%E6;IzNkG>aH8uPz48ecfG*w4g+lNa1|2T$#`AhER2C!3pdGQ9;NGqzGJ=IjcQ7e4Iat@;tPkpwZ`R|BG1ZV$*h0hcq z5R%stA-HKvI%q?uw8EVKLE!D1a)X!Eb^^%%{7-ix-V=Av|8yte{7-kh9si*v>5-iO zG1oL}O+8XQrNOymd?7(G~m_zEy_eA`V*x38D3Rvk2&rTCxizCPcu`sPMK( z{v$;UAkXxk*L->;=h;c&-*iW-ywG<7NkIK>Ht$g?aY+0?6^p2tcMej~b{%&i{$x5p zRiwNc1tc#VaWAbgIHF;N!qG9<`v6qvMY>i^oyh?u;GO@ba_Er?^qt~T~PJ;YpC^td3BXqxI zA1mAGLZW78K~I%?iPByqqOj42OjpRy$Iw8ZFbh#rJ8fnpX3s+ox z`%UG{hq#kc3(G9zLF{z4@uKiV#m`RH(?{#DkL|B~x>z~(BLvh^=_{r|xb)r?f^S6+IrioyE7ie>M(?VHIEe#zym1mo+{Q{z zm^Y@beRg$u={6pot1ew#z5S!S3@}~S@h4V$?isg)oDRy0qfzhJAi!o9baXO(daLEw zB+0ahYBj{5&fw;P0w1na4i&TGgeKfR;`tOyGE%*yX>}{CXK(u*z~k}o@lHDyJHNy2 zEneXbpS{}|tYl#l0*TWQr>4?8i?;^w`YgMfD^Npp_teA)tS6o-=#}D;jt-)xa}O1t zK|#fY1x=-MyyCb|ZoWNU)@+PJV%u8o{;RqH0|{cfFI|CPD0oJMuDiGfR?pwk7Lof% zD+go)EGtQY&I#4~I@>_fY#6z{XFS8$bL=N!jiMv3xvKyS>PSngmArG6Pn*2J57ptZ z>>+y?wnDL*v&@>CnNUnMQU$rBaowE^0=I|qr0b7SdT$u_Y!Nb59(VCK_B z8_`}EM5jZ0ogx{Dk~F{O#$XNiy|;Sx+pnshpB1^P@%C!{=!`0Ms7Zi#Gi!co!&z#|T8(`{~VRkQo#eo3wUCBNEVQnUNy z-lEpNMPEBd%^D|4WM>{m=9x6=zb~_|x(_o#@dl(UD2@^n#%K*Xx#r|IoCL1t^F(}ur2@QVwbQefN`8lVeICz1Pq z15;JGdydRgO-I_OJ|s%OX2Da@f|y(=p9&IeZ2Ama)l);wJ9q=h_wbFjPaSFnfd_CP zNe_;@Z_~m@NV1pmHG=+G?5I=?p(E&>n%s*QS5|cK#R{$GGM0D>3mtgnouy6#95uZ} zcwm3JvRMuh-8;~9wOPb^3KoUk3-l;?9r*Ry&ZxI;z(p8qpjXh#rnLF%t2eK)5Q)c+iN;+tOvoZFH%Rjt z&>N*4ICSzsJX8}k75#8>rARUwcL9b>!i;-o+6SEsrz9IZA;~UDqH38OSXI(XBIvYf z7oC6*9F?%&YCH%B`|(^!WpNfSSXK#u>bv7OdT#M2))!boo2=3q>t5LU`(lMirU+57H0Zq!5BrjF}y-P>95Z5!mElYK6 z^V_yGk6ld$X`xfU@VECt+xwuNdfV9As2vVs7c)o-sUl$JMXjxbZ=_rR66ukuj@D?!=h1ViP}S9uDbbpL$2TXfhdz6pFq0nrnlQ6 znxTTI*Va)Bw2#VVi;F?XK_ z50>x#|Lnc#a}-CmDDJrtFJ8R+KEL<5+z~U;402n$Kpwb1f$+2L0OATUbLY)W5RJMJ zJ+!)+?iRN09Y+X*Kr#Yh54&X+8w|r332!VC%m4C5Rcd|W{t3T4xmB*Uw1mJ|6<*X; zxtu(C^5n^rXPHacDo8%`G!~QsAUc6Beet<>g9c%2HpDBCn&c-c`I15_rwMwg|NH|M zzW$Kv{DUBZ{2|rw2QF6OZ<{yacUVqBFWNq|!-7#}3HD$p$>L7fN)bguDqAR68#1}C zNd=fMf)aDV=ElbBniMkpK88$Mb7Ks$B4@-%x(bR(gateAx$(u*Cl(%^L_2TFT0^5O z1CR=7HJM^z$gKv%r3Y@2ORgAsH8*}30#6u$nTndiZOG-!G$xlZ5t&?qQ`yEiL>APw zP?;!a*-497AV!i_P%I!Vy@QmshD2KAj#{h^0i!5A>EtB06p)i{a4nY%Cnr)iA*WJ6 z38N%aQASDDVrD{;%NU()r4pQwHpFte<#kPjB>Lc5_k0>fZDm4@tUa&b6DR%)oEccw zhOM9tOBe!_Ov1^J=n--H6nX>S$U&-ULuX?n`6Lx4SJXJJ z1Be`t?#l*>I1EWt{1Fj=*gNt1=w?tp^Uvj8Supn zw)2xwX|c`Pv}u!LF!|F)L+Hvu#ZxA^iBr2%E#4V?x z=-Fv)DoGe;IF??$S)2WWT-uA7yvRs{KHz$rXk+t5hEi2`zi5dyE4(~RGhES;F@bg(Uxf&w|Ft<*5iidCu- z;>$7hsGy*P0>hzS_P z$vLf(k!|sSHt_c*8~dVE(_6GyVT2Hm)}x7<*u$twtAuFnyQhvP>?B7m-mt}}sbdS#Fj#r35%94`K?3_p zdS!9bg^M6dfllcI44{ZV*n!n)8NZEb5X&A|h3{v}RO6*hJvWWz2qfmQ2@tEp8DkZi z*>eH2Wo))#`fkD&jIYADWm;mEp@k89iNS4z3jO(N!42x*Um;Zl#Kpv=QW4XQNPPAx zoV^pSVK3N2ahXd?{7VnMrPKpcZ-Gmh7?-^=o5n5fe$YI!$~?Db1m0t$*5h1qqBnn3B*PB;^GHA-oB7Tb#edIi0b5z1tQ%;aeBQj?uM{u$rg@B2r%l7|n?|Ots>bR-6)v zVDyjCGvJH(2_Y1T00lvfvcbfC_#fRy^W&er&rdY0Kgh4~_`Emu;Q8arKd(H0JV8XV z+Ovx~+0TJ8n*pXg^~odS-aFANka{)JkVffIQ({&4_$JpJL!FV zn}m-O73;7goV~p;ciR8qWNr4GKYo63=KA89RhN&69f>Q(5T??sYjOk;lfSJ5n}w6ZDj*g{FV9W` zF~WWrI$Ks8kVrihycVj1xW?hN$!M|q;Afx)0Rbi(JX3tw5YR)SzJ}`=jgCtpM052F zBr_w~bytXJKpkNu93f6mKt zy|KBY3G6=_8{5|GKUU@WZ~yzhzwu9Rys;xY;Pmcx{+&@OynEw+pnsduzkf#m!tXER zHQCkQ-$(z18W+*O|5GXQv*7*z0;3FY`*t&ET^x3ckdFC{pZ(JtpNyq{z0~!y{0an3n6Nu7B(4I{3RirQPcb zTe^OG@Sj^ZI=fM&?Y|4FB&_i@yV95R#99B#+x?@~e{*9;v%3CU+d7)p_W#v*UJd(C zfx@EG=S3H$hMhvexC!s!S?tMdYb}v8Ko6ca& z_LbV~I9YwZ^lHY5#Rpi!bnDe)TUlCk3)t?CAk3bv2rCl=aIgxYt56VL$zGH^5V10X zq~>{IpB5`Up@?Lxr^jsLIefa%(O9d72&xx8Yj%3Jm@+hG(}QnoPoE+LE?zzs3ysQr zb8U=-OG|`aArg*897$2D!eK$LQVip2PbD;xubeB%)BydRlbyru@dSw?{L%f|tuL3( z9wBI!5G)(xrpu*5VISH%-o+6(_)S3uFX(mQl#SK9UB%pJb5l7e*Rj4C&;&e)AhzL2y?N4c#+u556lHn}%4SxmREAq+LK10BMIKR2 zqK34I7?L)XOK8|&Xo)GeNr($+3SzfGoMg8avZ()@^=jU{qUpW0@=;v~ybS}R&t!8F zHf?po!kV+kW`neQ1c~rN@vM;O_GbN_`md{}Cma&RoRaeSdxV-p>~6sN2l%Hz`)ECa zt%5>G0&1uk?RvYZMv6Od6ZdU%lTyU+5D*XK2M?r-pKWuZML`#aC&fP(AR@*EOo)gY zy@42ICM6meXkwy)+-xG&(ig|!G(ZH{sKf;cjz!Y#qdI&cCxpre2UR|}&bVpABwR!L>pirgqV-Iu zMk*1>U{wP+Lj_zk=H_6YP$geP2yaFs>hJCD?R2QXG4+Xm_Nkb6K`Lz%C+%W1hC!Y$ zc>pj`S@R@?F|yd@3{i;axT(BEM67#9AzLaQH*sqvm@wb~9>HA=7ZVwW#QB7D4;G#m zmI35Ld9Em5tCokQU{NaH6QW5)#*kXcv!oha%BmgVDb~=@O%VhKuu+M)-1nn>dmH#* zRnn>cuHC=h+ti4ViQ{6Nn5#`95%~=Mjm^ZGf{-~Wmar5ZkQy7DHuP_5U%6EL4=G+q zjQ-kie@lORT6;1NYPI86gZ|o#{Xt*NjnH8wUpVZR6LyFsacFGu(o;m9{^TbM4@boj zT84tTWijc$;PWo<>`d<1w7~QFT zw;B6xYYgTg>D&k77CJ|h;XNET4cs#FMuq@R2a?j{``b$=rjpLczwPPT-qZg(w(>~m zp|3vjpUl8-1`-g75L^g8L2&VWb%ZX#rn=C=Iw#8zk>ujj(+dyh7amOnowtQ|-lnY$ z5hqKdSG~Ef5>x4}ZQHsjMwTwzSv>p0!qaQ;!=F0iO`H;=yf!uZU*nZiIpn%I3o>}O z!PX*jXwJ6Ppg$UaNAq%Jnx_(nFhRskQfKs1qK44XQeEFbDd%#zAGsjVRI&ie zQmkP?dB}7OG+tw}CVAH6U~A=&7SQktvI(r9x3Wnw2A$Ol(FShC7zRAsrCPg6$u6B? zU8-GMl9lC81)wyBCvFXVa4YKGdrA#4G?_p>vAeEn<{T)UmG^Iw|2Cw+5f)wPb~Z2W$pe^>%XnBrKv;R|C>9S*7pC^ zcwPb+2U3WkHc@DAa~%?@R@mI|PmDwQjO;qSS7 z{;3(zmtQ%v0*1c|XIW-g)jd3ap^(bZRIr=pr=-Pm$_rDaeuNXOZrN2T6&O7FJ7ZM@ zcAtAV@4hdLLwsSX)K@CF3@_#2Z=0R*()6hR%||3B6PcywkH={qRGj0p_Ux?p*%i>+ zo|#;n{~k2bmyfZM%+25V{}3XwL+mI_ZqpqSF>~?Ky!YfBF%IbIRw}@4a9_5FSP9Ag zNOG^Lg>VW?hb5~7Bebd6rO}D!k0(hqf*=UN)tigcCl@Ckc{7(uaUDi--RUrZVzx$# zTLK&C?8LztJmt5Ydpw<>Sn*GyaZ_Le1DT=9;3(S}I=_DEX!n5oCky&1a zB)Tr)6xSu1;yNG?eBlePw~SCTGzm^Yvw#y83LmNMhJLqhYk>d3)NlDpS8ga@qySYY zA5nDFnQU=n2k1`V-q&^B+U%sdx_9*K3^K)*3Vak;IlKU#Kw-abo%5H!IH}!xc5Un1 z8(B6SKuE?4hnd7Mc5mC!wX=I~_qJXAd*9pE)g1+H__h;diJ*tB#+%kk)2u4Ry-QWbd;CP%3Yf3pecbrf@$Q&n) zPuO=5AM>U@LbT`|{|-X4;6W=hK|+yW8Zj6|aX$Y1@nyu~7v^s+jb5%jJ3*S&w+e`6 z!(w@`4Q>+b%f~N~!qko*f6`2a=q42svre1|DU9_l*hMM_*aBh{YKf~z4IAZ6ijIJ; zT)y5?mbQef+jKiz7Tm_w6bUo67o;$YjKSEJH3p)!18?j$!VBp@y&@Bo;{h%NMh_Pv z21X;lmN<J#tbQ9ZVNeCo3a8}f)nKuG`CJY&v8 zgSHk0vo<00eKj=Ty*LJQtT4>oyzzU6kn&YIki2@8qsyL|eZ%2ChhSQ?2#I0R0XlvlA0khg__AS}shOa4MN>1@p!fZ8fE&@c<*V;eA8-Do&Oh z*d)XUU-}g2)W~dL^@}_gR^W%ShlHFXP?1>F*vCl{+E+R|i=`su9Wj(FySd>3Hyyy0 z`M=xxdK&~lJ3F(vT*x@GhX>gxBm&#)H0npIe-VbA$>k5{b5h^f*r68^$^;FqYjBzy z98uqxd9Q2xyL09u%}B>k0sgq3n4!~A1m`~GTbh%XI*)-a5er$U z5mlL}vLHq1l3K%j8GEcnGI5J^08eIE@<23y$7Xa4o*;RGp)`yOm&OAOj_UA}4Th&= zNSX5Xh_fq`rfJ#B#F18C80yFjEiM1F!9(omf~{b5s85PtkvQF+y6EQn{K=XB(V->wtyt!_jrc*;$vN zNzMC9>8$?MB)Q_2!v!*WJgQe90}9gwvOtopTOjKuD>h6Z6E{3e?aFaYI4~|xk|WK< zlIB!&ya;Iy`iPR|sH!BkGzaBlN^{BBs!=z>lsS=-opiH99IEhNn@smRVVSIKIytcw zG%NV@E55^opy-UG#x=)(T^8lIO)iTHRbLrdG$~B4lq?zt_*Y35E<}Z? zV!Z2cHeZ0e*{EfM6wKS?8=30d`7e&Wj5&V~&T=C4E$o;I=B>`5@aPECC40G}2B$Ty z9M4H&Xt+?#A1aKP4hcxg4$FT>CYtkS^}FIb3J)hFEAc3kSa!ks2#tmEIg<{kyzz1l}hf<7QC z8?b##CviwP^aLggULhwicrs)sPn$M$sSH0`$%iRRjRa>YT)vL;6t0)>OclTueXhb~ z<)vXFGy>7l-BLWq>UW1wHVVtpJu5;*TKR3M z6>!mX@TaA$u8V-j7$)b(pqZ%MKk0q^Otb}vsgvMWV^kdxT$QQn2;DeLRZx)cW)%ey zB=FHPUp(mM-pf`FK%LFjeBapkg7>v}_NqUAhGfK1%`V_{jgC!t>qMl@k8=W`|KfCj zRu5CO`Fl$vDzH%y{k04xVOi2;1qDEqhZ7V)^_J804*Inj5@9>}x8Blq5MmM@tW&_< zWrolpztWAGNaqzY6_A=aG+f04VF~m!bJ7~BSY!NR+N|f2&>E<@Obbg@bOGcciY`HY1sJueuf$54mLrG9 z#`3!JhN5*ftP0hN1Ql0TQ)5$m;7tm4C3#do3atmHD4Vp;sW0rR1t%tx^`FKo6h8lH z;qS2P4^mkEX}V5eGmz$gCA~o4QJ?5=BWx>hG+c-r^YCc04O6iNVf&_PA`+#|57{iC zt)%*V0x8t2TbYj=quE&b1+3n-ETV87E@LTGBS(mj&7ANn*=0ZsE(zVmEf1h3!C`5Wh&#A%madnf&9}u8^GtW{=s(VSz}tnhRr}vcWo%#BIjvS_QNqAg%o-s3gmqrowQT zz^3zfouZ0@NKSs^Bm7A3emGGhyap@+q<&+Y_yi(1`hLE80JnmV$2cI3&R&C?B{PFn zYM^%2yQoClNr+(1BrxbhHEYzQE(t8aXT4Cp83k6pvc=sG3yjZNGT`vUym;t>`L}{6 zfn~k!j0;*q;W+|JmV3^)z`VHo%?P*$z*oPp94z7n3W>4EUmJI2dgcg&BHoT1Z#S-I zCzjwwOFk~#E5%Y?q48tRQpfODfOow@SRfqig`&aEBwscj_|>rY=sWN86C}zcxaFOD z7KmX)%+DSk+{&%!RFAwa^&@o?uy@5RLk<0icAiQv;wQZiyVHg!n%O)!V&36pL~$f7 z{!q#Z@;UYLqo+6#>a@&! zFGqF{phgaFP)vNS+FJcR}jnP4qrTexj2#SYiz$ryNB z>z^LziHcA3VU3Q^34WVkGL%j!>w-k)BoRewN1xzqR%JYr+V$H!^fg*5)U^{|eeFOe zFwkeISL3GX4quBs^90c^iowkVNHf zT7ug03t4>Tnc#oo1&3vf_e#U%3J!TIHEca+_LqQ{5j^PY-4JI2@EV{Z*l$Gx5KDnh z{ZOczyJ?Xry!|7=lryHuRx_qRQ}4+8{mi zH7+3iZ4K#yzAk*hD1v_U3Xx>5wGC{gH!zM-y4N^_g;wm_n4S_7p4H#QOemtjuLx!+ zEn>1K+ak6VCLM&{G4+=qq_uHpHC+xX>rMbs?ECGYZ$&v?&n~Jg##P%(>j;1SlYe5y z|AxRkKyxoL{&!PjM@M60Ya5RL-O{`k|8rHIwfNsZ=_g_Q@4EOv%=q8J`fqP)Z(NK2 zy&8`g|GQ;ld-KMoj!e^r4UH`uI+{1G#s7ZopIGs~m$myxt^bzx)|Mu9|HtwF*Y^Jx z|Geb*-{{v+shH_=E5ikx2`oHLcn}mIyc`f~cnOGiVk^NDkZu{SbJqCG5@oJECHIpqCOI4!Pk#eyrLCj+X8?Usv)L0oa}=z z;yk`1@sX(AJGwM>Y6aqnE1YVX@x*uP+1^bV;BBGES(V}7@9<#ctU^wGv#jvo9M_O( zbnKKEnQ^)Kc(MBmdWqHn{7K?sSE+`DMhJnAV)k*EA}t2?(w)iL?2#bq=jTV4E}fB7 zyl>L*uz=V!YedUrhA8lTtS-`)ZaUyGnRk2pw)Av&Z`%p!KBw;Z(-Q%XaClh}Q)0Z) zz;{IlzBco;Hgny3IL<%efOHH2G(wpWr5ire;3W)4N_MxPt~nq=%;E0p>g(FEcUxay zZy)3{WpHxUOX3mIU_RIs81y>N#+I(n`xj^Z(>J|S=ag{m%2?t=S`CZ+A|Nm*CZRDT zWU3kw1_E`znJT*jZvHS>({cAU!515+fm~;HeX5JxYDb?C`dXa7Bw}pw@-dF#kRZ4E z1O-lHhOng=HDpOD)J1A6i?VQP=bgR%d$;|2&+dM+?KNJ!++LAuxTzbPbUB2lqCA|( zbE*~XL*vO-T+4hUfp~X+SO1>fd_{5ZX<^RPC1 zlLq9)t!ooucSHfiyjqJ#0*ow_u0o;oJ~QHh5me*>yct$7T%%4bP=5bMCrhg0N;Jlf zwM8VH=Bw#cgSw93VaWh!d#EWPiK6&mCmA7ma8J(Zl%TN1rLa$GrdlEm3Z|R*6U$8* z9_oStjD#NpynKLVH+wLC1Pl}OZ_>VO;MBqSAonR_Z~nw6+IMJni-)KDv$Ou>5&!ND zarKy{?n5h66xU;I=AQTDoOk*vT5JB<`#4bdILTU#wt8`6!V*cToF4>?jt!{Q9KAWVF)}bdKS1x{ZFMvGqQ$igbx0~(V)n2> zce~kK0a37Nv>7KgO+0KmiHjE;CD;K# zS&~zqER7z4!0=aZBH>Dx$PI@#gq9+{0~QZ^@)fkuu{Le$yVQSO{p(2wEe0}3GT@X_ zCOG{*obx}rO|dd}oL77P_%hBPPA9rQUoE)8eXu%g#@b|-H%Sn&Fn^xH^5EOr7k?*9 zm2@dBPXikiH*o!PAS`nRrxJF3_=ZRCuEDs3P483x@A9?h52qX|kqHA%849Bb;IbY; z5uYBZs%js={xtYeW9^gh&2$*AJ^6a^*8GYe#<7xTKM7TAIF6$oyW4Ui=Rr!4^iU}k zU+n|Bh`qQ1I^Lm-XLJE8A;)wHcgk5xYGDNr>mtmF&+B4k@yKq2{KcIvESJkk#c4e;ft zfAuES);##uo4;S1os|m7Lk;GLi0i_Lxdd-uZCW)!yfO*=Xg}Q#MxtsxBeMfB@bp#A zq4+S67)X!9_@|OeQbKgjEo4WS;j4<3bC%!~dp#_<|hX2|3AU+Dyf*2NQKn#IT zAFDP)6BHezp1~%bSX2$sTXG_`K%fQi9*=srPD1v{3qevt{kTE~wD|FTfAk1idei_F z$o0wB-e*@FVM~&jM)D0Pp3=l3NUbS0UN6OlM2OS3y&EU|?`|Wpf~;=3*5q}~G{E#^ zh0d+O@oBUt8f!20`=3|-Xg=Jbv;4yRY0d(Eo+BT<@q5BM$WM9ts`uG7RQl%?m8+Ss z7ATU!k>Zsfgz;C%Y2vKv(2PMe}UZjnT1bhol_>jgJu{A!;E~A;HjN z=!N`=M@uKBR^CDb_L_a*!O_L7G-$dU;=4pJR7qg10WDUB2L{mo$$U};UkrR_Cg`0U zwi30=GTo&L9>JI-T<@z^6hT_QUfIrKII+M3OvNpiOJ#Iq(&mG>u}F*smz9;&q0TEN zJ%NxqRPPcyfOZmB9o!=wrAzUYmOFV5Xs zL6v1T_ov|s_RHE;&Z8Aw9g!86bf-dWkA1fj=PD~(=vl_1nN#=LQX$7iAZ)aJ(1xv+ ze-j38D7M1aI;OqVL<90^tw8>dAl0eUyt4-AXpm>ua=Rm(8$@jiFk<22$0(YMdLEmw zN@wX11*6~n0C<})L-58j6Y$i3*cRfUkj5b0k# zQJcAkwppDd4aY}#>fUc*)3`srI*gFSZ+qQ2AP-@>qTCF(8jF`n6Z&K4Wx=pJg&!-! z#{@*JaWL^o{lCvA1rCPs6v@GGsoLyKo$?Z~ED=XM-SLY>lGM%0V+pMGrati=9#wRN zq{fTn?er|g&Tszt6aMtq@=<`giP4p<9$=MYuHj6;!ZezSz(c|s4wnbPO9MwRTKxW& zKYCNix=4y0$PXSMg_iz)(O9AgEU!Sy_?KqTS~C_al-?(07M`3|3eX^l@Rh+VpqF71 z{;Oh>T>XP>!}tSY0D%6>mjK#3ySjJu?DWUa@7}X(Ti;&%%NBu+`4xzEaCjF4gECo7 zrRzQqXUo`&5AFm7Z$7-<;q?|s{RYHL8-j0eQ; zQlT8{5v+2+=;tqgF;o+<5mwns@ri;8iE~uD^=WP5Tc(O7xKLHGC zy!bzYuW3@}|2JaC|7mP#X=`h1?{FHMTUtBT;{UA9vljp7C;cRg|5G0yh#CJ!SpRL! z&1><0R^<`n|1`HXwQXo=%Cv24-q5xY{aB0t^V&ag*8lQ$|ETrf)Y8<_q3-|9?JaBj z|7tuhIsOlg#sd@y7UNZSDvgjZiLtHBJo%aedK)UQTQv zLsTDfIwQwtIhaqPC_Z9j8)E>U09E+NYY*UKnh9J*pB}xkJSq5CDb7*oWi2_T;(KoE zH7zuTuW7pq1$@Rg-OmQ&OL{+xo44H0!Z+!DwkiBhHYks#zZc%exRfPn$a}g8VhBo1 z$biNjsyM)UUaU09=i*=km(S-SU?92!8Irh@BWqtQ`Wzd0A*vh0tiVwXIl1BaD+t6zv><{&H@yX26{KeYL4R7?Z1OS7TxgyvusBpR9cByHd zs(?}(+%lXmRC|iiEwX}*kxfE!s@(%6aGs26G$@)vlfB=98-XP9MMUUT+?8%|SjHQ( zP+459*kNWRR{+z=l3OJEBET>4g%$p5lf;i0&J_O`K&Mcpj>||g(x}TwFnv2dhNvzm zjzbD9Je%>)KBAQS=_D9)jsM*Yokm1Ww#nw}3=ifVW0~#lVYd)gfOVx5(Jwj#q7m-- z$mDEhX2m8>d2Q*^jTb>DITy?-Fr657Ax=69f#lWe>GI2=2!fYr7NX65C`=(;+2Ip? zD#3jp@4yRBp1~h?rKxl1-U7QLGbop3t;Bl_liY0k4ExUa8n`aV=d3$R09xYRW%edR z+{xOriyRZrA5Z!huKC}ci^8Kw>LlobH}+la$XSW+s?uzRka6t&IH(`CGY6%@@D{Sf z2~=#wge~xehjUCy1M^TZ+Qdq&bmC|bjHyBve3_0)aFIB~%P3w9Hg-VTSbdg&Nd=s{ zrze#7950_{QMj~tLGFk8~#63k_#zDk6n@Iu{q%R*MbJ)`zal#^ti_(ZV zK4~RtNy-#bR7T6L&$aQ_ad=*7f>$uA03MRV+R%-NFD(r!$?{|sp3cmoJ?QPlua2Pq zy$BhqS{fWIU>S-YaHu5J;Pe!$?x0(SSP|&`k4ds3*oq-p0b?S=gP+NSg^->t;9|4| zXB&YfiHs;H0IWpCs{EBs8J!^`(dq(w(1f(B|8%SPOW<;wFAkak)2kxpn%$Q#<}$te{)B{>pH_+q;hIT% zjW8jkl=%AVg@+UV`A6PoS8Df;dN+<~w@~$@l&~^COKu4ZbwJutiC7F(voX_Bh-BcD zB%08KJ&Xm>o19>0WhB1CrF_mgkS*p4*aeQc!?BU`*x4K>#D2TMLE!$1d~BejLC5Uu zNTHOK1pdIl-!_g;t#NABue`NHfB_ZN#8YqfkvDf%@vC$qd{cUh@s7^1+$8%o3~2)} zog^fAG|VMN3~Vrc2ZlU;PZ@O`n1rW(XB*RekSj1UwIm`>Fd5py;!BE(y?O~z2xf&r z$nD40|J)@((d$bkmC>g|&>_}i5a95X+<`I`QJT3MG3Vl_#-m-1#A=I`OsN52!Sdiw+o#gL~sDQYuWaHBNuof zM6f@bFX#(Ntg#J1W&S3i&L=BDIrYtZxc*7tdd$TJ+}EsQVeT?vQYVe@D4_p^$ev`H z2#<-DKk0`GwSHQbII0iOC`vWZ-po=J`?@BluF0t{PfjH?xR>yhHDUEfE37_$JWdTC znNCDxQvUSU-t;~4Moflelx%CyE-jsyBuN+4HZLC|1mhjK46gBbk+Oik*Svej)V3CH z%q=`U`TX%^T5W2!cJv9v{zYQWzM*^-1XYNq#1Rg^0vQ&5@Hh<4;@t_UeT#U*p%s~R zr-NCB#&WfGUKi=H;SZ@HvVY_EWCY-`jtBI@rAvK_Od?~-JEyim!4ocnM$PmxWKq+| z;z*=xlIF@tnn*CJ&3>V8#HOfd+O~_W57>`Bcc4_xRctb&!P;BFPtx#dIyg4zODmaJ z7URuHG3m_hiW|1U%KyW)JKfjmiRb@sNQAwT{=dZkzqzTorK6**4g3E#wXNO%uFA9K z|NoPIlKKDF#|L8i{|oECy`!~d&HsNj9?}25y{Wamv3X;rV?%pO`-YZ|_BH?i*ZzsK z{+GA=N3H*6{6k*0S zCg@MBWggf+o9fECZwcu^8^TqbKoU%!SmqH@iB1vn6%p;BIy5Pks`iy8IR3 zl>(C9ota&jJHGVk1p3E6eR~O%>yx7T+L zsRFAgu}o4{Q?QYwG%LRD@bxWDOtf2Fbb@zKFp(90X^UsGA&3ZHMB7Hyq8HX5mP|(I z5bnN&2-O8hJV)LUKP2)-c0~$&$^sA_k?=HsGAk>gw}KK16ve8#v;?XRdW@(;Xc;3f zqU2VB(lkYFMCqLhCetcvBgeuKo)(ZFuNFoCo1X9*1`cZ~7RDvj^07EoNTw$P=fW{8 z2&)okiDq@Ox^xi-JmOhQbFA?cE91wPuK!?3s{&Hhufz$&8gcNB7GG9WLJu6dkc8&$ z93GNi>_)&gI9picg<=`uy|-A9!U!9BhN^Te;IEt0ymi*9fL4P>U2g8r!YA6Sq0VM; zsfkrU@h&z17~va>D_W zgQuoJ>M0}y56gS-;M>~l{K{+}%-0Yi%Xg8Ljfqr*nr9&prbG4BCxruN%6y{^~Sdn8B16cKE&3>u&l(xq%l8;Cb^n2iyBXqu~$yW*)8e0E%I!Y0?1RL zJp`wzxP?tQmg1crI83 z&a-C!`;&f>+5gtZ2jbiRvi0AA-mTgHuEwL-|88twa zKaO)wXM3Hqc@vr4aDrl_G*)|d!u#a5KYbOFmyF+!B&%I+IbX_c&kyCRYozt+krr4+ zfMF?)PQNsI8BMPkMMj|>*^a_SY7DmkES~sn#q7RTIC{_%x1!kWN~YIDsB!<9vk*9{t^vZxGpe#?qo6c^;R+Vv}Va-vbW2=OATv`AOL$k zS%c26Hi_L@`N941+UNzc(xlbDJnsP{bykLlT)gnt7{yCs6ro{W6c(b|(i`*5@U^t? zu42D-p9({*xL6VL+YO)|bT zu9)NJX^hjG=e>`=Q7>7OyQS2`U$IyALh4iB-Tc1CPvbRCZKAEiXD=SUY zuTccO9Ln$~zFfL=gHr}t2@7+lz4`mK*;(`ny=W8?FXapokws#8Q1vB@#GUIlfT=A(!P(+8tN6{voOm#Ms;$KffZ zaS&+rY?lLBB&VmkyLz_&F4bu|*HcD@b__r~+B!zP8-GK*%y{}0(fB3;9kL6u;RJ3R zRt(C;HCWjr4qjIn5EQF#_Lbf2!FLQp|LwMIzfLgrPj1H--P!+M{Nei3<8g-zkvL1m>H!=rnQ#pIJgbHO@)z^aNxQ#uv*j2q zl436qjfyzHTa0m{Q2of|BEdk=h?SD@`DQQ%lcyQz91`4I3}9bheD)9Lm?rqBi@O4w zpdhv~dJ=IO9cV(}t$X{I2O?-UBq4wrsOG3{rv<)2RU2I$p>A>j4){j~SYi0Nl z%;lJPV53cWhYu~q1j5il_$*{AA#Jv{LR|$kWBmuvwtk>g$fY-guc{aek0@c{F3uP+ zkb_mDE=-~kqdGV|EmCr7_EOy=1r?i(%|y+2Jsbp{*z1)q)1@(89+PiuNWU@IO31aS z3VT>)cm=@4%g4xJ*8u7AP89?wOiySr?9UIHPZkA%%sHzm1Oi%vaqp5Jg9bn7AS*4A zNakfw@nSY5b1)HVo8H*Ckj08d8sn{#;m2y6in2mAYt!N-M?}*iLlCN2o0btB)w;sD zlv%QZpY*6vALj}>UCtqG5iPaTH)}Ib(ZUjS=7COX{B!TtNpJEz?C)nL{ku0;R^=pY zX~wm;k9=H#HKFdHv}NIx%-(cL(N~s(-rCId#hD9BUmUC5{Yp$JVdk^wxB(<)Gdfwm zAS0fH<6prlrUZgO{bRW7Y|3;it?b1&6@&n8I>h<--+k{(T(4^<$9vO#`ph#{NJbq#D8sR zTC@LLooCJe<0t(j^Z%%i55)EV2-bglV{7Z0|Ho=PlK)3bN884Y8#3+98#c6cZ0u-T z^Z$75pE&D(dAonq`fq7#r}5u~{lBTHb#4D&jps+^|B~-U@%oe7F<)oI^0HRRK$r1`7A0ujUW#}fbu z4*nb5L#r{gQ9+^!sMsw{W9P055NLBt2}*FE6cQOE#lcC%s=1q{Xl@c5StxNTUBgOL zYpX#X5vRiRoDS$3uBc($>qLWYP>!g0%j7HDh7Li*eTyyxki}P;99dYj5P*+B)0-VO zmoOy(X(ycw+?Hh;;F}GNyj;E*lw;0A>fj|eB2^>7#}P6y)dTrTXXn1*d?6>%eWLjh zP^qm=v`7{n-bu1Hm1XJR;fqTW>)K#qWL+B|3%@o^8`k^LIIVebWDGnxrCz+`wmN{mCQ#*0@DC-%;tt6OKZh!9ZW14{K*sE%;kl-;|TDD`6pn2H-3qXpf-DxyhIaE1}Y&R zLm=JH;Wzd*jbR}=R0+;4BiPP?G^k$(>SlvyzTz-?W493>SD;fm)At1vL5QfKS((3? z_pY2obM4Jd@vqEq2|5u&wVBO4ob(V2 zRy-WpvyU5^5o0}3m8yU1bQ;aqXW5Wu^lzvctrhj27XxRrv2pkB&ky9?;=qU*Q~tT} z#nUGcrob%<8*ZbYCZ8xH;<~XKI0oLfl1$i0%SOpjFX41cKAMsRw_H6CxQSZ@r-sdME2qrVuH9T}< zibKK@in|UEB9cN|gN|JBQwRaGCu?s``(!3x_E^xW8<7bE7x8VdKTEPOa`{4j5b+ob zH}+26@J>z;9^;SB6L%3bak1G#`t#x`wdZ8A}=;OjqrnF2xp{0@9;N^JWGoX@52&& zV*@Q53Z7WiY6NuUm^VMC=pjskK0WrUlDMLI_c=F{o2n8^M8p;F&rO0_Cvn~gU@+Dg8t9vrCxV{g z#B(qEliLb#_y9HuSahhaQd?*P))kf)RJ94azXoa9sDu71Bxrwq^Ttj40lOjdpzL;#F__0@`uka%f(^ zxwCNYyf{m=Z{CA%DLp?qUVC;C!Vo+j^=_R+M^e%Z99U*|mj- zb6}`{;VW@%pqj)+$h-H=!sE-{)J1g4a0EO`>>b02U?zD77tb88-TD$WOQC%7S8w7J z`GmUQz>zVcfh|0|LmUJa=1)_%)El?F(_>6kK$9B!mp?-CB4Wa*HS7|s zH`?(0;9GKHvQYK_IfA&k9|vJ`aKp)39u*pqKFn~U-YS((%hheD#vY^=Jm=g(cH|Yu za!;k3Kq+g3b@YmZ!P;(lY~iTw7D}hZsy5z?&G`7SUlOE*4Pvu?7E@=FowF6{z*bQ3fUh-ublzgnC>P6Rt{5le7K4jA62ljQU)@>EJhgO?o1HRNI#d!2QJ5)QS1GYSUjTPQ(k>u@{v{mK z=$p;jn4@o1q!D6gPdWg*hMbzTW|@Ko4VBd~j!~XUim1*`#*7Vxvxr(D*`(o9sk{v> zab*EZ^BE|9ID|@3>4&HcL(93%zj#$ut&p5|vYIZ|plwsPX_M1tC_X^+AeM2ddO{@7 zqT{y$R%~V%U?~KZYM0SW)0@7%IQNW5tiT*+M!m`N-i>2O6y-875r9bmvERD;c9#YY zx>fKbcy_e*;5&bM&KtkKIRCx>@eQMJ*;_2+i!R|&a;6nlVtcky?HZ^eo`+o~Qr`Gw z^iRq}@o|(6^fnD&LGi1m_&NydU7GQF$#!uD`}3_Y{VQh(1Wx*Q=y_|$j)06v&ENmg zfs1h9KjfmTa;T86rc-Z;!^bWFP%2Y!i-Y8Ttl6TymmogL1*%mt zUUeC01(fw8Zni8a=@CQ~7gz)a?e~p;6d{I8oG_l9!$bBeO@9oP)q=_U;Q{!sd^))} z|5yL>s|fg|3*Re<1ez#UyNSs@d6n4@sWlKXf1Z2m=Z(#|w;G$A#>P(kPpSbAr-4H$ zKtdaIi#cdco`zsho17+X4rmo41dUCG{+;zsQxp#fYcz6LY6oU?YX&UhiXkbgG28GS7snbK$=}8sl-jlzA$OZWm@9VN_IsD6pCwI^Nwviu>3lGo2)FNAwbeYpwa~)7i)4M%F*fL-qg3u zVrfF2tJgzw|MDg|Lc1ShsuO9eF-`mgbk^nxr3DjCsZ=Z0q=ss@ldN9EDR~%0U4ut` z+8@CQ;I-7zy_8wxf4olmKChf63kSdQ=R;bxK z{^A$Zp0Ra`-?~0CZ|<9j4L|%aDBX8 z9WS9l){=_{1#Vff_|-fr6RiO^&WNG`SAyQC%8eM>Z?0M3=OS)CBC7KYUL-`G64c#+ z3|8gAvZ=8O*QmxnhJHr3FwWYbK zjphGqY;0?CASGbiTKtz)d0vYDx4oqyx0=mmtJzG?E(BF{CHI4>TdY6~G4yjVj{rE7 zMgJpN@U$j^0is_+s6?Mz87?3pDrYN4y~^ndh*fA8l9lrV`9s+#XZ8?R^}2HyQW)ZR ztn1(|!_R$x7Cg~E7%Cv{FmSK}G2LJ~{}ug(*!%z*0NPs3W3W`kKJ9P=)93DY%itIh zQ47dsU%r^j^zQo;+92vn$w-Rw4}{QH&K51;wpqwW5-?DY>AO6vSC?0}bpfRrCAv0~qrR{G$HOR8^!+rz8=oDsj<>$r$I7SS1Hl6eTS>ej2rw{z~(= zHx{l7K^-EYbG@G{&qlqREA4Gyd5}wOi=idPxF0mBlBn6bt;!29e>% z22c&5bEhzqeQ2Ag1gFv!Sw?XHkH3u6>5(T-WSo2Z!%ArUe2IxmTqu~jE!gN!z1 z9#2SfWqA119f*{J=ZGjcI297l6EdL@ASceI61s0~arc9^A2Kp%!4UNQg0A8yO`)M? zg~@&KR#&1W1X?2&b=jNxXyM`M+UQs?{9<0*%PLVVO?|R7dSq$z^5WH-{5=+AMwoC0 z2#mj^t*e)T%qTzt+?41?8%f9(BiJW_+-8a(Lilo_U?O~>o81GYLnbi>a5Z*&;mKdQ z{!997GjVd>C%>qPi=o(N)y$XHkSw97)Y0Im2D*5(R!c0}lsRQVDln+4A1B1Th*K!F5b-6GrMGl(Fp`1zE}?4jur+6Pggx0o=kgBw`l$wlHk=69*2;S61)t< z6h%TrO&gDdh+WtU6OVO%w!joXcND~zH%A1ovFAwVn`pyy0&72~lMGsgPd|2x2X4BE z^g>!noZu6=+Qb!t{m@|2XQ&o#18AubZ4GFsNF(curhd`A#j_AJR3w&c^yW*?a3dFf z7l|&gck8EFL*Z@Na$zL6NRCx!S2>%n7_6lXMR%6)9e;Orq!LwBu*rxhxplaV7HYLG zTRa$5w9kD%Th47QWRV;gi8xX?5(FhJR`4O(osgtC#uCuN+?;4f5~h&A>y8H9NPY@4 zo~MxLN5wIGbP~ltlSF2a3&UhGeaqAtK+=6y5RUZ5r~wM)8JglmXa@6k6Ewm+0%OV1q;ZoAx;+Ds9P3r}R>a2MDS2kI~7W zSN<`23M}Vv#pjQYc+dXse}0pYuXpAadXqA#qzrJ-LWa(jnUcP1>&bRAO-b2$361ZQ z*sSA=X!6)7kJe0YyCd4amu`S`&+orLUmKh)~%`w{G$SW?K4r;Ss z`A5f0bijC4KmeFuz8<=sc&Bj@Q9L-P30GD|sYC#!R?nREN2kL#XW=acKW3| zTg__NwiwR5GCQ!rmL{Y=*~z#S&O7RIv>!5T+jVcz4co1m6D(`UqJj^+NAWyL-x$4@ zku++fVi*^eR)v|<#)xUbF6cu0;XIgScoa<#%#golg^fbN4(;Ai zM9}fI=|c8IQH`WVRBqs@&Gys7O7O;&Pb8`Xw7AId0AP8M(CpfsN3b4}>XO4YXF7?$ zO|*FAO17=mpm-M9=4OX{r#~x}Z3%NTxx*+?m2Y&NN*C%6a$o?+b!oY9=aTuafphVjsj>rIyT`gjCRj~rC zlc|`T(h7pA613&6yl1>)K4 zl;LjOt|ksUj?!!S27`h)vR|X#Smpv~oE1tZjS<`e4+88gtJ!?9@@sb_ovMnfJ*A!K z;32KAm-v;5QT&o12|j78tfELR&w(|G&{1vM!nV<8g-y2hotdxe&2A_CHD)Vh8cl@ zAQ=}K(Q-@!jp}~{CB>Ug$&%t<2R=mauW!)!@FMPz!~_;LX3|`pTQh~lxH((b6p#S|!?2W3i(EIi4O-wxUup+hvJUnkY6rhq+eUS$t zIZ`GK(MuQy2@i>;=}$cMW*>QTXNe{StzTueNO&>KA|)JjPNfuNJ2v1!;UaxxXiU$) zj=m;a8i23fit{F5Q^l1x2~CA%)d)IeX8?1rWXH60Ye2YoGbywm(t!1Y&Gbus>bsD# zjUn`!N_;5a12xah^8`f0t *H`|LyWaWf=Z}y0AKm6!SazF*whS7c^y{yGo633=8F8Hr}6&y@8^g-fsr71cxB}vYeJy|2n?u$)JqXX z!)4x+$ey%#j)4qiU`u&a1u3R0@v=ao(xA%(u@gDUK%ivzA4)i#2oQds2bT)XUV#3J zBB2`uZL!&ebDIq~$N~20Uic9hK&;1Kv=gs3SZ~x+=1m|CO1&%=pPAh5jHrKc$hlD^ z0g&-%sh|#u;o%-pqVb57sG{Nk(&3}vO%~v_lTMD$fgLh&LFgkdBS>#bh{K6Au&u!> z>SW@{7Ic4!UU#+HZAWurj0_Sk0LKQAQ)=LpHGrs9p%)-6ABJmablT&y{y46F9XgZt zF3n49cv+8465j(6Cl(wiZ~hT)h`5`kavDwgvvMTCD17t@79ac9g+43yKW;}Q6S|Cz zueUxAApYPH-+&%1{BI>gPc11eM=he5pT%y}A9LrNpXZ*R53HZZVJxnCAlVz6lbyAH z!LGKSaF4c3?=Lf_y02fxounP_A0MZ=@&|re}kviR+Cm%7TdG)tLx7*);QD8IW#)nT00kSQ_YiLYZkv1 zip#41AFCJjT~qdtF%QE5AFbg_tmco|k*nEY?n^-jpnrSs%4hOARrxfNntvdg)xa-!nd~+HQ6DlS)ZE2_5h`c!z=k|%& zn|I$%NkIqX8s%negP+^HoKkSr=}e!Zd(hB!qEEe?;vTsu1aWtcRp`soB6qw{NOT+iS1W0x`#f5}DrdIp7_>l5nNxd6bW3VL>L2+F&WD zD~||qE#s@cEh(gsmJt*)1}*ImDIzGN8v(wkp8&9YV3a63ykw6dUNlPis{483t2W2ADwN$4w?h3W z^lHS4G{js(Mf7#LLgb-VUb9&`${u; z@{zv0!OV;z?(^S8@nY8?H{^;Y0PYa6_HfCn?LmLi(N2+NqAN;Yjd~l3kp{HZn0iVO z);RrNR3G?{gj>Yiz#&mnV59+d=Di5#Kk5d)VzrD!1qKccX6)cXhI|}=ZZ(AbkiP{m zC{*QlY|kVCLmK``8rt0GQ{qTh6K=(3F=!ANd%D>5tBGlGHv|IqcMa)-Ubg4mt}oE# z9e|s$I4c-08`mZ;3$E@D0J6MuWIFqud=rSKJ77&Cf7XSJoI=!A<4nh6@=0tG}_1|LXaO zu6i^#G&z%=cGlwOW(k!1cYS4yL?gjwQ@cB*9}u9uUz zIIU5%tz>f;n$A5dfD+(hL8z+7Qh!Edrx5TB=bU*q-6wt6S? zsKc=*x=(BU>qb7Gk)96__lM6rVqpG0CNcpOe)vO4qqyosfgo)ZG5O z0MKTu<$^x9{46A3bnpcJ^2Y9AEgeK!JaHIy4H>yTDX1hG=#`9D}~3%e0!T?leoiK?m;*DU*dH%XarCHIQa17QzK2dX-se zf0Wf^xOlW|yGGnk zuwXbB?lP!st6xf!vtIHZf#pYvOz5Ea01TM^>FAB@htAdh+Koj6Qpmy)V>%)%2#}%w zmN1WP-H;W#y#hz-;-zB)3Wu-QDy^-UiQU@Mk!$8%9aGsA4lq$+aG2 zfs!bHxUDW_h9P>3f%Re5@Lv^dSNg^GAQ9m5IIV4~3jsK5wUs7dU}vzV_pIx?W+=IM z&4DIdnR_uB#`$&+yVSAs-NDEAX*@hT`nI$AbnUy{nDU~&LXD;iL0Y>aixI>?Rw)T! zEw#R+=$T(Zzhrg41sg4`aW+_Q1fOF(m5w&6*Se21tL*RdMj&c2#~_;k-(cgXk(H&T zh5s{jdAPahRj%UU*W314lpA!Hd%h1;cjf!~Zfj|4rPJ`E5iQpIV{U$FW-)DLWqWRB zxxU(r`se(~*I>w3bNL$2{AU4>@9=47etW&M`KM<1!D992*P4K`KcYoLY}*O}XZ`Hl zys1G@kv-HQ_e(MUCcEM_g4dMAGEfqH2-1kUUS)Zpk|~&el`Gc>%s4_0k5v)hvMq=C z;o|g)s-iGLU{fKP%_QIk59i8KZI@wbmH|iU@Hd*~)sb(pzX_fPx@iy7Zb0jtKg~jk z`TUe};5#&gT!A~!LVU>W%u-V^Tq}ScLRguJN8u6(t9C)p6oBeiYvI8hnE~;ot}mrx zmNUEr8r)_)Z>Z@6$olQ$->P8X9B=jTNt(F13nSOf$EnbX2UB`awpN?NtX_$%MiRPkD7EP3T{#sx8%15B&0$*e0X+va^V7S z1SrmH{krS*Q}0G*-0*a-inIV#1;=aWKEQZo3o?wLwP>kM@Rh}TliuRbUqaz;K=ths zGZYkp?%ocF2vL8BRdk_=Kw=dm4rC<-e0#vsa>5{q)y}aq`89w*t1nWrr`WmJ&Y9nf zvlxE(!K2F0gdBqSkMq-`@OaT!Gf5qBy?@ep0s+&}jO-hljUuJFUUngzNUs=y@n$d( z_SOffm0Q?{iyuevHztmE)J-O46AxQo8MCTHXnB|1*UD1)K@5A;`aA-;Hac@D&CwUhrHIBnU=6x2E7xNt2Zmpf$*2 zeVgjiFymt5FdLIvLrFt@pUp)a-CZ(-6ud?#t7`!5tZyGU?*rKFN>39=g;BVpVoN@y zIH2Y0+~5`lrKm6!J1d}hRX0${a>Qu6*(<>?p#0`q2#QLu*l;cVUs1YI94Up`IXG~x zlZEF@OpXs&GeQ97gGh${zu-{>g9d~<>(R%ENGS3(D?lKJK-zc>a#v&Fvq40ML&o^X zo^`+`Q93us_&*afd?x7`^I}27I8qU?2p15@O{QpHwrA#$SF2ERF&}8QK%U(n$P%km zKpEZ;g9d-UPrZ=M4=9XFbiB^57IEqUTL34b(K>_gS+p)uxhnRLt}3*+0&3|7^RR?R z=SIRN#HD@?EX|~n?}VK3Udb&vNyN#LK?ma1SZJ3(8c?IBH8#b40Du2L?_p=PrHC>H zg5NTs;P^xbm-nM{6oK{qqn=_im;=4m5$a}SBfnToXX7E-tt?PFglmvo{)K0U^syY| za2CZNx-202n#ow1(HMlv5D-D({jrHmNLKt3Git1Tu3Y_%2LnmR>neZ4+5F6HZf>qF zZ%5qtYOegcsL$M;ulQmbUzPCqzDIUc_2MjJuOv)<6n>#b^B;oBpXTFVz-h@{_#^f? zCAta+OOpv}ePdm7gVky?ZSvc&)^}w#SM#Sczlo>&3ry?4l5s@JW54p}=;|BcqNlv7 zu24wlhi1yU>VNTwf$jM?t*PVsC94usv;fL>M0HbSD!Zc?AJ}7rKcV%z=#jR2P5*oS zikn?iq*bFa_WSGznfucR*k7ew7@@i=Vn#!nb+_dl z0J)Z8$FenI$kP%rEekvVsRZ{^fd_ppDm=ic)9n=>^6>WEn}vUCZ{*6nlq;!agIL+$ zuZKdZ)M@^xxN6xTd&yDDR-SjPT&i<`Tra)yN#{n+BE%qB$$qEb|IKVpq}3+JG#8JJlZ>2 zuB`0J5JKj7=;b5p#oqgDzsIZ@Utbt1!_kXgd6*#L4Q02L>weCPenA1(bL_XVV{^gt z@GX%ODxJ1`Zf%3Po=@VoWBKI&o-Fdc z_C$Gm2Q~`~00XDqMOr;k*2i=be2DH~BaR*iM_v8%B zfk8Q;Sj<*WWe9V_HlF`EZRTT$7zSIDgs9TZLXqI}*xa+MV}1A~Q=!k7Jl&l1ZDN?$ z7piCgqF2fqVNL$5bJqTrJusQo7O8g&q=cx~Eptex;h)5CDt+jEisS6?#RLvuT$|>1Xti)R?a>X6dgASnh8_ z;buVl_`fcz`A4*fq=M(cmf+>ttKg)_oY{dxyXKp`Xp?|pG)JY0+ku(v(}%|NnE#~~ z3!&hC7c2{@u>>yl^gsNT5OOQuUTun3aM=yxH&{Prxy)(w+u~7gh@8yUbY^mW3c6|L zbaXMsg$CV{LRbW6O1g6dS9M|Gik=&kr?uh2Q`q5J&9qEG$85f3^a-%AUFidQ`I{X( zz5L7h#go)&2h@VY$YbyU`k3eS1&~3cwXdhr4TgkxwwHBm52JWpAddke^oXsJ1lYEp zxcqf!VS2W_rXUQvk_e6!UGhh6)8^^?a%XUvZ>oSmxe;@nU)y$SAV7{4Cy{`swZVooK zpL#qE4VUze++68infM0Pw@@e%%+lh)TdJLBmh(HW3sb!Z|FNxp@8&Nxuv#?0i4Swy z$FIT-_vsd(OT`QeLy|=XVCuw1FJ1ehRm;d1+r-gn5HxdATS4FjN^{miv;Nskpy^#I z?3w^=A}ZX)M5^N9a=meX*~+Qn*>HKj+3IR)vBm1I4oe6oOHJv1#lFJ}O7LKY+a_!I zI;;5pPmn*jSjx2X?n5H3Zi41Ss_jazqf-*Rk9{@rs^rDy|NoF zraE@ao_PviNqp&DBx0MHld+p1BO0U}(=D6WP;+NhWM`;%enD^Umxx*F;`pzMuHah8 z*P6+-Ik@<;psXX9`@0@S`(AHxj&Tv*G(Fe(a0!q#ndZ7X>gKCrHI@9bw1LLL>}SKX zzasFSquA}on(VSET(Z$yQ9P_Q2{~gq41z9;6@$8JcL|D92lQ%juR3*py{R^=0x76G zJ(0pbd_`)tl?X@R{9)4Wphh2MEqa1{&1a*5a}%cauN#|>krH^4AXZ+9MZO@n6j8cD z=gVx4E4f*Nc}&6y%$}FuN~QZ0btnj_Fm{U-vgls;9ZtlHq%ya%-Iyg*V7Rio-_wCX zRZARwBVS~iT&WN*?X)hUitK;9;ilCSEcLJ3LsR*baNSR2a=%l)0?N70{fC(*`+M>e(5$J z=f|6F=3`Cz{I||)tN|xWKn@OCTt!S^47o<@qBDUk_PFquR;vW`@ekOZYTuN9Cb(GF7jf^?{QyMkVxQt7>|qi)>^c z1YXO*#31`XsHTe+c-$1@vD%6>2KX0BP-HwN!y?gvLl+KO0+Vs9X z!7S>0pt6`_W-j6E-P_VB^La4K4R{>(-Niu_kn>`9Ll`3>h!G%Q$L#dONq>A`)AwP7 zLR)Xm%vaSmTS5-~YkXC)YSPi*z5PcCc6pvxPzFKyy0I%z76_#(Z{McpuhcRKWsD_Z zMI4ovM5ndp@%}B0YE(r|E7q2j-=OsL8g^&7@N~Hf+dsYpIpX5r!0XYEVu#xfMM||6 zad(eCDiWeL_INJ3n*|9*pibh=Bay8{%MOg(1H#B43bm{v2i8z0b<2gjG)sY8XEh8q%(TJ1Z)w0=yWShMEI zB8QOPasv%x2~5HnMD$f*tlrr-;48m6$U&nbPH4}tx6&QgZ9%5uDlLeHae`)nj%h71 zV(n$Twtvc=LsKc^sHuG08o6A%$8e#~!CY8;c(sC)V#OvbJu0jakoDjt+@s3jt_qPCP$xxqBJ{wr=^q;5*sEX`NL>da$E!=W3vS>PKzn_?NwJP zQ&WqE(od#^$Q@sT-%5;Ot8}0>Nj^lC`J^FB%~*D3jZr)&phG>XY|v&?^s;ejOGzSi z=Eg?E@HEyzaarHrG-77!#ydy(>9c zcn|!ui4@vw#6C8-1{|0~qdqsMTylSiYbPAI0AUG{_0JMb)!PIA!k@kTr7sPHRI_+hQcaJpq;QRoV7wr1uTPr>*) zSebxz!u#3Bzs>St$hFBzh%-d}*JaZFX3%ieO>nZlozVbJtk~4aVy}@x!B!};IRRd< zI5)W4U^qkPA$_rfWaq>)J4SqYw$ueb1o6j)&ck}b&~Vu?U|VrYgHY`9_ZY~nne1}& z$RK5Cb+|vb#In)sWPk;~AIc=DnXX`Wm}10)-Tuo6U%uoolwy#}>>5SSiuO=x&dHBx z+z*vw0i&otj=_x_C(eyuSOE}p!66@iy|o7A>d4daF|dyzV9HAUn(KX&+tZSea?c(LGxqFH zb41aP3CL{T>c}^$MjxWEKv+%EXm8Ekgf=)%|DC)uLEaSf2G1A=&PD;6wMevEh5(lR^BE^7Oga`X2$3)>odF(CE|Rrw314_9WWx}u%D|_0 z3w3mw#5s4^-qBOxJ7Y6c9a^+;mlG4efvYr(<9s%Vf|2-326owMslD7hKf8W;Y{**P z*p>uk5{DG!^bxK@x0p~>|Iz6a>7ha0bXK=nF|h?b?f;c~Q4%8i?^ToRe_=8Ji`nkK zCJ)*nX(&9II{3!X3#Fo+10~2mjsN7;vy%IftBOR+%M;i|&wi5US;Q65#~6Z?H^OEI z9W@r-byA$CG5Eg6@BuK?k@Lt#dF*Htlm)dGHNaVjP1E|Y&5#XDgWXCBWAhuS*6EO+ zyJXW>qqFayL#kqEio*81g5(~e`~{8w%W1;9PLqYx|Je`ezRKGH89e`CLt92n$V1PU zfCZzQGS5KmMU?w&KbKto|mS*EG0@t0*jvh&llT|d{Hq6Yw1 zT!U{!#MP%ao82YTC9uYouUDODW!6L4B}SB!MU`q}?gvI7l{zew#dhA@MORcd{GQTe z23Dx_Y&c5rd-<>R?PYk@f^%kpx4^pkd7#T|*@j_>e$-XT6!qAqvUOAoW1B6hCS=_G zGc3sw3co$-a$FTo7}6K8l}`2*>eKEtgms#3v@DQ);jsK$$`;X1*!yKfJAHw;gy&3VKv2@GkCxl; zqyj(t;2dIYlQ1eO6~4iNB-ByRD^^*+76;9C*|}Lqsl7o>i%1L zkjt0^Plrp$s9;eN0iV9X{}xXs?A*#(sPS7FMh8YM9jZQ4#lAq07NN(vj9dq=4r03@ zU8gjyI`=^yLmV>%p!GWR*7L4b!X>jQG0{k&{{V7J!HdA~mgH4h-ycQU%1CGm8P)26 zo=p0-4|0zVSbF zXkvk*Z3fup{e)29jF^ov#Y9m!{0IKXGTb=${dA@8&}cCelpx0~oP_yB9u;W;>knL! zS)yeqBekj^R#KBBXm;4M+FZQ)&CF#}p2lCcA!m9*hRm_uVELtfA;DOoj^7KFSqLo$ zYTGh;s4Av%AP5}6w(1si9&%|9b6 zag-Q=Q^U7@<6;1Af+SmfLZI*}XnHXb{M+9n7$Al~1c>31-iRLxTJagwkLN@@k3Hcl|{jCW9_nsC@&^k{3o9 zNq;9}yW0Ze+|m7vnQ=jwgZ!jnm*~jOIiXv@n`!!dV_t8)wUYBTg45vHU<)64o+CfZfm_-KhYDc60twCOywzF zQYG)eoS`ddsHRqj-wi_|e;Z%ay~Ld%`_@C64@oc5-$mPL-vFl7&3^vtu4lJ$d;Pw7 zH0MnuQ!s=b&EJ1_Ly|vp95$Ur65h9}p4L8%MHrr`K%O$KmnuwnB_R zI|r2l45&a81teUROC=Y!vI&c?H$^Zby8!rY=--^pYOb8UY&|@O%LUm?^>5T7M!3 z{-S=x6Apo|aa_qXR&3zd|2vi)pieKAG0un>y{i3V00_E=EF5Y6z@IS(9v%596&yuv zXiY0Y)rtU#2>M!-z;{Kez4+NEgbIs9KiUP{2HWEViXEyPHnpUPh;kM)LD>FpwDMMW#TEBSk^_r34+2PpT*bM*<9lu z{XuzwoB-}F8_(CgnmkIgya|QBCN0G(1YA6iNv+&UJoIm^QAT% zVDRP_{R&>OCE&y5ywL`k^wikcaCL0BW!Tiqqe#~eR}~GGj$u9@!P^x_+}6ElxXBj$j|U*4JD$4C4Zk|xhTY{kb1FB z5$#eNgD(3nqJbHlCy5sG6%ZChl->%q4y?)T|A#bRtx9RZsu+p%0gOGEPjz4q7+i>u zadtv(>_KtrG$_UF%I<%dhHU+G(7Z2a0D^LQ=LRQ+M7#mIDCoA_`cl7v=b!Hf? z(74;|VV#@foK|eIFUc*fS=d4#Y`6rZ2#6;8kmqzT_~UFYWu+iWtR<+klO9E2&cqPh zbgfnhClQA#ZSJ2#&ar9-AK;-RGA>P+1x-oMuFlQV5M>FbKYXcYZ{fAmOnv`?@XPa9 zPS=S@U%^;;4k~6XT2c;L_|Uy56Tu>Zf@2aVb4rXN*=T;2KW)x<uHnN5o&xyf_P(S|0z<~ff^lNK~ zFcWV#7!0RYLL+_{hFQmR!*W{0dwMwL>A04!X_@`{Y+6*a)dF$d)?5sj)W9D|!Fp!w zlitrl^+oYb{bCN&jLjd&zXA>J7}2 ziVYJ=m!B0Krxblsl?*niAsPF(Tdd_-&xN3y>E*K*kTP`~99!3Jw6tyGY2w9SQ#Cck zT9+iB(L}swD29C_!;Vr>NI&9RBmz;wnVs>0j?tOy9`benRwkAMiJcTg$|#*WKUPfc zH77i1OVBjr{RvA^6+gh6pkKc#YhpT1C<+my&TE!qq1U|xOERto9BZDP83r!dK_C<7 zRSoYJpQ{p0&ml{UW4KWLRh;(+R_ISmWEecR4vD|QEnU5+$uqVWR#1Mox^Qlw%;5RFc~3aGNkvl&$0z5i zUG3&cgor8@Jw>B&giL+{)}vrPzWNd9>57Zqr68>G-(so}R0kPdRr0!4r^7-|OeV!m zx~k;t>`4)%06bpEs%u^;Ooduyg&Qd*KcVi8t1EK3)J6!;(a^HRI=mBQ?CNNH8_$py z3Kjjg$wL)C}%%&SI2=l648gUz*n1I3Ke`?4}&*U?-I4M zO{)YUMw3!P;?4!)cL=<5F_GQxOt}baAPAkB8p@%j^@n^ec`AJ;zV=YPanAl)6=;eJ^<*(+>PT0EC zn4JdPX+rYawS)P!r;CPYx>(kn+{&6wP?f|8cdS2nQIUS{R0_4ANB2M%%P0CSOIv$G zO8RTc_iMYJh1wLa)8r%o%?m%(I$HHHz5@tro97J|tGXfSw1!XYbCr?+{i{UyU=GLg zMjM$|og0!QfcdO7Of7;>O^xMF$zg@{TZ)-ssz-)|J+;Cs?pxj|G%5G}yZbC~zItftp%uCszQh=wN#Egl7z*Ig+8u0 z!1T^n>U-+$@;UE#N_^<_r|fTS`YiU_KP~{p>#a1zV}NgRgn}sHGCCIr3_sJ%M;Cgy z)v8@Si@D_R`?7DbE-YUI9>@+X`zSO0Z3>yMsffX&tws+Z#@yoJamUwP6OJ_0AXx{= z?O?Tz*egO?XLfFGLYN3UT25g~zUH{GdKnnA0^xF+t#vUFx>ex~AKy%bgRtWHXn2x` zPQJQf1+29c)}ymU_yoQ~uiG+HXQaW~Q%@dojzQv8nDPP(=XvJ)fJYVm`nLA5X6Dmt zI5hPik-l0SOIzw#Mupxkp)i}s@c!3pQsciPy1%+O_}xVlfD{1&GIkA4=tATWZYz3K z>GLx8P8ORH5KOb~z`(X&skOlnV^6tVuKrNRhp46o77A4Tv`Z{*g5WPFWW<-ItHTSH zLSL0Gm8obIB8S(Hk(G6rMbkZ5l_kiW+3JFBK!N2AEtgZQe?mWbRaDk#_Dlh-f8d!V zhK4XhXD?ipsH;(Wvt*lNMa}KEO72B2wWUpHb0XNg?{@UZ_gzXoHhiu_g*`hB!}!iJ z$D!``$#91CE}yo=ou9B{)*rl6d*DV~;b($lo?}_NaST|A14Ka|CtU!J1KTc~9vH1? z%T=jSp55h~m`M$uinRx3W9N%&B`v&kcdcUcWGqY~az>Xnbn!=hw^* zP#JZ;LVu)uL}UyHidO!IDQCaM4@S^X84{jpL6504!fp=q55o$nDK9fHr+m5IL$}G# zO1WpdSpR|$J4Zw7ZMEB3Zx1iN(!Kc3Xzu+x?Gkry;2{k$;BtXxw0tslCVJ_v>U1qz z3U&jQ31U$CwQUui6%gn16&iRLffON1HOermpHIahZg9dN#t4%EE;jSfjiCRy|HX`3 zeN{OW@NQ~~$i^BzQ7MqHqAx?44njvb+1`O;Bf*VQN3_ndLp>_t<+YtzI#D-C$G9dxJc6eMv!<-gtB20f$%o?x-BbzR-T$t#g=9Mb3;lN4UKukU zFz(|>(o<#u)CCqOlyfmGiB-g3_SV}8BBs7h!@iYX-9H;hLWr<8rlmW=E|q$(XZJ3q z(Ed(_HL<5Jnss3C&Dv7Yg{0Y*Nx|DVKfqnS6%s`=6;zd+-{oE&i#N5HN5(kb*~w46 zHT$^A+Krr`k0tGZe_AV;0~7eHDZW?3(TWZmb21iOV6-E&#spY zwHKRFvxZl<22n10_@<0l_mh49%6ShYTzk?krk-LzeELUmR?Vr~AgQH(?Ra#tYs=8xpy_e<&hC*IG@&s)RKb3^Xcc#PO{ zl<0Tz9H!4p!w=LKUHJD)&)m=LO-~vN2R&UX%Mw!qKFjbgm7!}iZk*r`F7Ey@E! z0UgzzFfrdp>F=*VjTHK-#y}$SVVB@--#D0ty_E6a1xqW2kzSu-Z6-aXZoT`Lt9q_&2nV+nMjhKD0HGORwv?A@%Y8htzB7B_%6%`l(YkHHZ!QKpT2c{g&EPb(P#u zAQdga&dNb^bC1bHLlYzNPk~wvm%ImQz#JJe*~=!+p{yM!A1wRD9@42pz0HAKHM^g) zF);bk=v%xkVA`w6i$u42O8UsK&R1M{L@tX|GfZC`01~vpufoD_4prvwoUhj|C`un$=a@kT!U8)?rzpTFqhdSs z4hn2)d^zq~R4){B@_DeWY^{j?vKk z@@QDqLTF-Gr9rzqiFPk(7-cb3P4EJjeY!B4Cgk(r?dNQC>N7U+ozx@J@yDOo6^9Ri z^GW*}0DbHd55xOR&>^jBq&9znDeZT+;$LN5*@`lEmS$+7;qKj*_U6sy1_>3w1khQs zJyW5E#j657o&cjD0sSKkp_+x+sjYJRq>^Ep1;1JGYO4v>L)f76Kx{FfnZwFkT_xH_XU3J~3!iiGGV+U+d zoL#EatE69hN!5%r9ak!oji(Tyu8u}%Bxh4O>DbNKZ{3ll)FdqB=nf#dT3g$hwQ{>A z%E2T?dBY_41T}U&+6lNK*rB#I!41aMwxP7!hXDUHiv*;fL#4UjH(e7(ikTYjeKeUu z=>g@j!-F4~up!!@S!$b$J><@w+b<1^)uRxvdIXcBWwuOgYy<&kFkUcYfQqT%N=+xX z_$jzTr2=*Q1V%>STl!PQ`Jz=BT)$joe8~>BkQTJhi-&zEjhjBblya}_5st*NqNY@W zku4wN67kBM!tXG}({q^=^=}EZC)YV;yR3;^u>mHLm%Y5=;jkV5}2)lO+JPmk3V0{>ltJG@1w<8yecVs*RVa% zNUt+OOqp#nqPVDQ1#IMZyedpfQ`=)kx1`Elhxh|6e>se#hf^Vo?3u$CEYZX71vARX6n42Oy`vo(noeD`})4xXY3VDjc zbM>C__Fnz+zrb3)nYzN=M!pMi;B0^5*E7zdOWzIBySIgRQeIPxM!pYc7OQRbw~~K@cWi1gA|4F}a`;L4=c8E;rO*6ysmlVGh58 zO8CTCYKLE{1*}!FB)5-l8A_H?sAYFZQ>fj5bBc>0m2okO zIP9$7lyMoJT^$p|p-7Fae9Jg4uC5na5{A(?*bqvT_0&z~ilg7aJ&9g!77Jl6qk&w> zj}%>{Kp1TPaODBjz1SSo^OX5fcu*M?T(cetp>Yv(pZZ#fwHn%!l_lx z@a^kEiy~t4SmhE(5_9D&A}dgKfeSN=kIY|1oe$;F%uPew)(mi;oR+^kwr_B>;jhn3 zhtI#K;xS)OV<-hSs7N~X@%oK#N4{t8DWa53+F~b+%w;od+C|pUFs6^pC68Idy$30k zr!=(b^hsYJB5^*suHS^sP+^6(-?zr%rf?6IyD)VX*w2b=yGp@3MFK&( zkQL;rU3Xu z%0t7v$usCurH)o|?Akc&T@ZH7_WLLwSU(i&v*p9UGy(%H>rRu!C_$Fu0h7O_=I#%y z84!Rq?b!sRgxgQA!)YmcyM&)K=W;}c=j1tas2C{3=G8Bjp`WgS`ok+bRX|! zL(i8^b435|-yB~u6v;d0U%M;w2wOxm(KY;X7>pKNT_k9-%C?!x4m=H0TA|nE_kql3 zwd_jV%$)wKJvQyk%llR?!tMn{tm6#3`=#3(wzoyRF*QizCQk3Fim4iF#R%ouXeD*1F!&Kt%C~c2j1wJ%amlcxS3o!! z`q8hAVm(lSFw;VA!Oh#lHNPM-IA{;nz!rh|t=?8%Y2Z?ma2cZt!#CGpfQhHMbJVo< zdz`!HYHD2l)Q!o1Da0P(vO0gRPtXi#HY5cE8&i&+tv$xij-MR_1j>;J+2j3IyI}?6 zuvI`+s4?62t5ErZeZ0#2HV_U0R*9gAJ38?iT`7lOpKXW%L9VxqO_@+(c|xs}m9Xne z7mvb;Z2P8)gv5}U&S%v#9IxUM_o6(Y@xj|)#wnV z_dC%ps}CDy-VKk~X_+JOB1)UAPr8Yn(}hu_AiCuVXn8jg@&jr$k!vd^^(4O|piXgXcZ+}-ns zt}F35==n%VZrDKFU=42ih;nvQm+H3I66t$%&%-YK@c#r)9+ROe8b;x;hgOn6FU{;YZPfUQWkp-Kyp-Fd)S zGub*734p`VE(>~A(KF0 z0HY9-s+IrO`2pQ7{^EsgX6So&;JsLEF(E39xJqL`jxSW*R8SEZO+m3~{}Mc5m@0J= zXENg>&#g@_!73`#t(0Z!WTzX~X=PtjP|13`yQAHU_FAwfDIG{hf5*}WUO8%@1n=l~ z#E7v{p@*u#rHGTCl2^k$VyDFE<2EAH;z+vb524VEk4gTsZeCd!})_odCt#`D+F z7N;A`I&&K}Ue#NWbCHgAc?Mgj!z1dPf|s}B{gTrTE*X9st%_r8T-j*~_u1MeNd{^k z{Mn-#21JmWWgHBGV5x>$H19^-``TgfY!ps56^(k7{Bv6)s5<6 zTeQppM7#$%TP>s7c?DcBdgl&i*Ro#6(J`4M)-Ai&Ew5Mx1`0{eE8cHSO87To_w#t$a2mss&&I zddodO+)wmLHK0`)25@>bC!5n9sEWHbqVz5|Jvi$5|KaML!XxdXZtd81$LP3&4m!4N z+v(U&Dz?+zv2EM7ZQEw0cHZy*_CEN}>Z+^Go>kA9bKYajf#gok|C0@@Eu6-t9oGNx zc|V{LW8bTsFVuEbSk5B8h!JiWB7>viD;}I%sLEesP7NWDKv1#RA*1>pSylhQKS^zAp74HF&!JGclpzgaZx|Ms!{?G z0H9QNt#a>I7>qIv*#Qg{*~gBnd7QutT_O_EU$FwT6*odHFxNwuGx6VRxBH(Pqhbi6RcE_ttQnU zE#OgR`ppK55+Tzm()-PPZZM}b(n@F*$GYx*GKV``p}j*ih8nx=ERaHEoGXb`8EsET zu{SI~DfaUpeSLVo+H;fv^v8*b&z7wma>rk18OoM~5V(^hx|OnKs14IY$8(xBa2crb zayEY&Gjy_pRYJNyD?>jDlSo_JEvh+++KeybKR7gDqzZ>&W>iSKYYN%gWap;mQ7a{- zShkQ&uVUDIb99^wh_a6LJ%)N4>u)avYLb!+9PK0dw@R~@qACtoJ2e|j#d0bP+5r$!1(K>KY`Sv?nl zgkANpm}-6|u_3ra)|_31>Ndg~Wn7sGd=Y*^|)Ow$8fE*jPl z9PvIfoKK8?wG=u)GJ*U;n9B=i3y4x`+?k^;dw|A$^!si~ zl~Prf(9aXOBFs`%>d$2vP)Y^p$PKXlVLvKcrlAcgl`X>r)iX>-C4erMx4_Fb_><5i zUOt=QzDNGu(nTw9kN=ZNS%Y=oxFTBl3)>qLXo01FF{x2J6Mj#CJtz$~q)Ia!`drf6 zKEg5KXCiE8Kk4q`kTS7kTrog!U=h=At+Kk#$XAlBSA&zSO`V($2Tgec?~7ORlShgx zx%z`$PAK_A&PwQ*8Jo0bfs`!QCnScQ`qNL2&u4Ks-UA+w-^qQil28-mw5Gy zC81rjMrfZkm89itp{OUY4l;}IlINlo^kwL=s)KBzj|A@cQO$;`f^4z!LhNyaB5OFW z+Z*&=a(T4v40NZFM)4i$C%89bz^mA6c!KS{&yXCk8UON1%rFEFhDK{#3i(Yt@Zz?q zV0Fud?>?S8Y_PS0%;>jkka^AE6?=<8)kc|W0|qwtL?gt~h#DH*JRv8eZNL#f)Zklt z5PB-96Th8iaJqR03Gv05Ibhj7OSjt<`pb63q$xvUe6H}2?{_5++%tKDlvbW6KYl(P zeq7Dh+Y{4UeycPoS0GXJW-CrsAYNvg6r=u?5Suln?Jgi(y(Hi%`j+-y<_G$BpMG>1 zxQ(*q3g@p2x%663hk}B0H%Qn@p)spCvg*OVau%}sv7-|iyYpPH=Q0w`rz6zT~jwezuhCB z-@VK4_26vG1ltnI>1Q07IGFn_uLVHkfCJsgI`Z$BL|*!d8`k5YOS{{llYJK#S43)t zYH)rP+WSK#}E7Wf=1wzcm4IUPECJlkD zbga1f8^iIVec>OeKZxZmsSrij%-rf45fB{@hZMwc>^9oKo88QE+$e~Vu$V&&5juJv z{|QxEZvY8a<6<89`)YS2i{3LIRDd<{Y zJ}kO40n9J3L1_}N#G=`8D#3&X;~C__=^|i;_ubJ@*sve(;oc%nx4!hYIWh|8^Jk1R z2~rNX5-P2x15)c~@P8^8D{Y}8>G@P?V;{3v!j0klu(30m>qbwl+VtJ|2yOVYVXs51 zo$h_1ikn_JJ!P*yY*Czu*=aBONh_|LL_IIK@o%qa za)>2SkFx+wI14+gnD4PA?DPx7m_L-?i|dcIr}>-YGaIKHT^1`1TFE;4Qm=8QNRM$pIzqyf6pc*&jnVO*Z=!zem5vA)%~jGbn!*tcy}yylR$?gyYU}Wpt|wp(d^r? zhDLH%Sh@+#*j3nl;pu3&h{Qw2oim*&Me8=VZ_o$+X0yrg%!wu2x43;)yj$6@X+1VW z^m0>%jk0Mzt@m~E;IHL>UGI#q4ZemBpsid`tG%Vmk2M+X-#_?E1wI6$qrh!rkS>T; z8UdEqTYTQXwe(7T+hLz#I$JHSZ$S9?qJtP7hRfY$+3Dqgr(INq>71iwe!bc(J>$de zm3sI0o1L8*J5|mQL;w&vmOxmUMnwN`XfJc9dH8JnSnKbm_s~;!t;M$8hF>+`L1X!F z4jwKq#Mm{yFeLs)TsiW^M(eRHOHG4bt>0_=8%MH@0zln*IBV1Xf^aL~whlWE(GIBK z)A_Wh^sW8z+~T%#toWJ6Z62%LznpCXh2A8SGgj>z#GH88Qx|)OiKT$>mJVb;4{Xyl zq41)zTjSdz>RC4LkpCo8m0kz+b9b8yHg;Yn^;Ti$wMZpKPS~d_o}4|oJQXBcDMGB| zZa}7Y7z_;PL{*ZzOv5D&mo6G9I<~9R;+m8aP8APzS(`UNQYQ~?<6-B9Ce#WI_u^2; zcH?b*Em?vqXXxp}$dUuUZ{$XwCf35L;zPbGSVllVtJ_cH2Y1JNWk6~96aW(@KIFli2!%vxBi*dx zaS=x$QW5n%AV8x+IygM66L5S}Wwg;7o0}`d3o8X7wtwsB^9-xROgznQE?|WI*s|OC zvja2Xc4UeV@|RhGKRlY=;Mv^iVlhYM{|Go&2*EUVVN=P2f)SV{%^VZC^TUTZU6tL4 z&Y$$odwG#GSOrT9eBzzxXcvoCzJ>GHYWpuRFEHc#v)TR*~|2fOe&9fZv+wG=|>wgiGLDzN-e7 z$PcqurIl%avX3wYL72*Khv&4w+|3Jz_ptxUrM8Ii!VDJp6VGA_A1gd%5Cb)h9 zG#sNp3fy*8Mr>*3F(q7VCSIMNp8)jVhBZN$UeC{Zpw5VfIuLcDZziytNP!eMZP@hB zSMJFgZ&Cwc)p%FHkrPUSy`jm;#P#<-y6_qk9u($qfgKRgk4|S)UfhqTDW;9MCl!uO zq6Krd8Vn9CnQ@2bH#6m4`{ARMcD^shd#kin$KPsIN5ZyUAs1!IFUr2vpJGppZA)Fy z;_m=9yDTO#`Zgw2jrX)9>bE<}JAbujXM1iKfjg#S6F38z+K(1zH^%qcQ+4{X?f zv&3nxE{cLTW=hribri22Ptd>(J902E)9=YwFG~No%??5G{+Q>T#||a^FWLheB7B@y zo?mdpZ0fwCaR0$6nw6`U4`GZlZX^mp(`?{+UWQ#}8GkJilBo4r>`C_(#Em1kv4%Cv z4N2CNmW%;}-stSI+>WA!HDnH%9-i<#=PP!~P>(SpWRidwOxUd51SXDRG1T|c|HQsf zWHfp6j;Ubp>9jv@MeI<3U}5cP)HJHU#zbOBZavSZE&7Cw%G&07p5~H z3!q3i-EmcFK@SAADv870FH135+|n($_qYE*GVFx)M42BZ@7oi&QHMlN>7>H}q;K6_ zvV_Bc@aIRj15gt$5petKMO+1tfW-?$eW?O@WH)@WrmA-2gRF;>Ht0@3P2b-WYr9@hU&t2MXCu$Ny3R}{y6E%*%XI% zA}Y1^sQt#zy;&@8B*H)V2JJ;=q`mc}P*S{3V>+lLsyked`-)3GJHFC(j;mC-4miWw zrPI!nXQ=@DwRh6zXWGO{?D|*liHRS>u}Hyumx>r9s6z= zwH`mJnACz^aPZw3Z5@Z>8d&t`q#}GXj1%eU`hMkoj?ie$BwQXkF(gKI61>VCRG#{L z$L=)}KVrIJIE{dAwikw3n*9nMOL{XfKj*lzl+5TTHWids<}e`oCVNmnxE9PrrdT)Z z5;xwS7+BBi@RP~dAA@D?ImV^+Pw(3_D+{%+{POP~-9Lqd=;u zleM`u)xV87T!lhbqUUanou9fOxr@)ww`@^v+H25hEKtKcYv~NopcHKK>vm;VZ8QJ% za~%C=WVPmj0~btbnS%#j28u(++ftU>?`))f$a&*ecSrvO%|?%!XN6j&kL{kZ0t0$< zuVeWI0fFgL6H-YgJ6+{Zhe38~`K$A_M#}RS4oHfA@ik%>{qxbf)nI%W#{l*T5fUOz zsy~wIxrB`vg()N1c!wSt144%otu?P;VgkIG<=Y=9z}c4f-VlN6!DzL;ec0S z3R3F}xC(VHn4Z1KlkWYp=%e9L-l+mRR``cWW=b7&b3V>7BT)n7QL z7O23znl4Xy0|eOTQG7k<)32-Kl)C1V8j9pb4>SRatNdcqv*}kSfVJE3Pe3Zesj9(` zpdxtS_L8Xt_`{q)=y`X<=C(Vqcs`Zi6Qn@J^9i!`{G|*MN#eO}sOJ#SuR8E~5c3H^ zqsvcLLOw`57)2R?b_`Vj4yN%Kh1?`5Gecv0`1n}3p-KG!il^U**W8iq)W62iO zHc3ufJs?mZ7m7M@pZ%7%g=!=F2LT(n4e5uEh$?x&6eA8-Omymr3UcRs?ouZZ6*(L) zHzAmXx6S@Wp_boG^2MFD#?%o8=COIXMsWewRLFEYym9&>25RjDYef-!h-!bilp2wL z_CY8*vMC#!vB)P+HCh^wJSv{j1X zxr$P}eCsd7ljRhBh9Q$WEvA)4|Lijf+chhgxc~D^XR6Np%Rv|p*M+&Ailz@UF~w9k z2ydwi??!jVcuiYl?(J(9rOkL6MNn@0DrNcimos-Tz6!@8{`WP>yi6CwmYc@Iyn4NC zf$lb+^bmJ8G7M&x4u?VQmps2S9?QXC`J2@KI7Nq4a0oFOOH=Pw-3bes3bX1u{K^(l zBaaFu(h#K;Z85VABu=QKhuO;=Wwns|l)5CCPkEnBl88E-8EWa^^Lh<}X$803xQju9 z?%FEft;W&h$i=1#Y6JSoUH67TT%~Vz6`)PMDr^*-A$VWgluwQ%X{sbqQ2N!%|94gl z{yQriP$bODpt;BFNa}qe&~y`T-lw|^Q(DwleUd3_D`!9I^3cunm2vf%z5)b3c1LWr zbbzQ;Ql~z*W2sX8+NVT%Z$VoNWssNw)2$Hvhy zO3JmcSrq*E$4t6PDtP^Vvh|ZqfM!aKFNcW2mA{|6dk!)h$g7>+83e5Z|8fqbt$&_= z3)4?8Pi>Qw;>hg6@iQcYxe@jgBLuU(8hFFqwnBbkO9Pi}={}Tz8k6v8INY|fdEEJ! z#)4)eYe^T%un9>Pv~b5!Ltx1NxH^@ZFB@%9b#J)I2CsNd=`(Iq6-wO7Qg#%t9aeD^ ze(UuMI%6AbSlMGX?mtHQcfRrb5<{O#6XQ>Qw%l#?qiRMrnmdO6p~Ex7^yk7^!MLVU zZ~!@_R9k<^amIZK1v-t?KJm&P{*m^r2-NRIS$jZT?G2dvSGCGA^-|MfTs~dod8tMA z?1xGmjf43|xk*g1{~Ag4InRk3+ZkN0_NO0;;#t06D`~_zS~D@2)Pn&9Tjm=IIy`B) zpH-6|@DKbL+@om}|CuL*VC_h}d1R(=>5&w#aXo<$&(g-^qk=soIo?N*X9lTi0lt0U zXaYX3geB&#CHEF5MBNcn#WXRrZm#d}lD4xBYH`KVj*MyTq7N-~Ui6;1Hx~zmO&Y`r zZ(^`8TFo@HlC*_zlAmN?k%6R`#qlgZ_ys_V{7gL?^mTXwx~WxYeLiJeE&yu}Ux%Xl z0kiLugPDYR0pxVv%{0G$3$h#UKL@Dz-ZGqaiT*%Wu$Qy&QYnr7d@ck;Ujjs~KmE=H z&=t&B=H4V!S6V@~@Lz}Fr|_evoKBo zVP}GZwXV{PYdG68BUIm@F;NT-$59Fi+-Z!XdMJiM3-c zQ1Jy%$+gXeLzCu6fYUp}$FaE8l<$wC-+hWsY|jfN&WCLHu2hnr!1x1wXfI@o9X}Cl|WcvcI;-zkznj8-j8k|gxwE?=w#WE;c+4;x_ zZrC9M&|T2bCYMi)!>2vlJ43|M2@YD0tVU|GZ+#ni^Q;HhU2@G$8N+VXgJjS`J#Joq(y6DPRqd!H=;YaTd=kSY8YE{y12`lVk?sqCWRnZ z4w~9jzA}1R9<2iCHK%z^!_F1tGzwFXc!^LPavFVp2KCqzLHi6zPt`vdiqyd zdfGzd`BB>tqKcj*5^s;KzLj@K=+?)aDo~mIaT2=3TSfzwaL)LG1X0F#hLIAkBgQ=` zc5@xytVnz`yC1?0Qu*O3jbB}z|79RFhWnQ5j@U*Cf}?iRqQ~qm=XyylRtjT>eSm#? z(iH1{Z_P0#A#M7+F*ms#e+wIabP5RHp%u;2en5alB&S{J-ax-pFm211iuQuv!=Z@{ zRum<&%?CoYfFJ=~|IR@fFVmx<+!2U7FLAzKr@5Lz;rR3Vub=P+2>J*z^cnC~XUR~0 z%~iVHct3y!RE?DA*K)&6RG44?gVC>v^Yl0fZyOY{1q6Pny38={KC{PMJ_Oe88pu9T zQJD9T?WdK;`0&AW8NsA{+07E1h;OTt_L#Je0{x`Xl=Op-Y?p#hk?dRcD ztN{wonrwH=)hL&-(8%6uIN~o9%F6PMmd=+)qDYA^*&BKjHrw+=!kkdd z$z9518sRWNQEw0j}td3oxBYC~pJXl%v%&T|p)&&&xK#)B|-80^z) z=t5;E8W=yDl+H!21_h9sb3ej}T0dJe80`E%lEW!VQSSiXRXR^yLPr3dL_h_dtY0I5 zXg42ILG=r2g4^1VIl`CfFX4O1Ht#9rm=HV%SFAl{Ma&!-8^uvx`WV$iUOL-y@FTo) zwK_aTHM-pgxI;Yw;1{XecpT_opZ{$$XN3n;{xLMt1UA(J0SvUkj1X6o>&qsM6f5rj zOc()@agO6TUKgWlI-6*milxc;ve#C5YufI}Jg2agC6$Ur7=^UHz-cQIiw0pVgh5}R zXE2+E_!8xmYyc4KbbAHXr+YMMFKct`q4 z`?HUUAwsvK1BzgNv`dMcmptlF+jvy)Bv;O(>dAUAtB66E8#y^s0tV#dOxDE`61}B* zXJj8K3;BrIq2;CGu z-~U5DHX!W(*KNP=oyvb~H}Q^E`h0AQ7BQtpQodn~zWnpBAO1K$1RyN~Fx$Jkff{tB z7oZ4_yn0acKD;+**zVGwd5zFAc@VQFNHEaLCQm&*vNPh76@e*Ump_E=Xx%j^EL5Pw z;$-vo150qQ>Xvl7x$$LzyY<2~W;j`;`u3%|m+9utG%(u!<{+f1yBpJ-FA?{5&$)-R z{|$TVjHU*mxP{6ZddjaAi=UGn`)FYYsP`w$j9$jwRtE!8p_-W^JhT^IK8Cvlt6&O* zs~q|q`k{VP)byAj`?Zr7R8iq8U%s2D>H4de!LY-B`^(#~N-7b0R$#yS_HcF_9mX?` zN*FirD!T$820Vzp4(?{DJD3R|ie=|Ax{w_FO;gZfC8V?cO(WpP3dqAMEa}i94 z{DgN75Vt66N%92nf+dqqp)T60|C43Jv6-J&AkzMST5Ta2N+1pzCAC9!<{0Q|ekzc5>hOkL2rz8L~ z7iMMUMbz&jvt$Rw)aSDwzbs}J#B*zqcrcsvg(h{PjiI~mLp&F~RI!MNN)6~0-M!$#(@dI`~w{r!@XSXQ5qi(#Nn_eRH?1X6vI=(>FWEyoA#YX1S zD?3BYCEX)N7XPa4wUa9rKezp^Naxgp<+`I<36$hVq3N%4%Bi!ar{)!QYi)A@r=l4# zzsh=x2-#H@*V&mhu6N_hN);)AC!Qp_-F#yHEDvX@uYu~JNwMd@Fo6+*w;^mHO(gh> z62iOIlI6%D&YSI~n9XCK*)?kxEJl9s5&W4Cy+Oo>s#XWfs}zoyLRurm1qM^re-_ty zx{eq?M($5f22DQ_J(X>s*LB&%nBhq|SP{Zn@|))^b(l?~7`4TW#RwBw3@#W3C7--_ zxhQ4iFp87*9jR(t7*>Vo);-DXMl_8CHOt;jV_-9fJ1Fht*cg~;269p%S&@%<>TYRwTg~dD^~vG44TL; zZnHx5+Kjf$Qcg3F-?WeIG?(q1OyT-Hz$4=NITU=xJoHxR{fYfM=T15mx`bIfT1|Bn@hRQM4%Q!Ohr8dgN{nHfJPHCgvMUcgJL34&j2tyw|IjyHvJ(8@ z6B0E{D0pk6{;-2#eDP}N(s`2hZs?U zWzbX7xPrM%t0A8j35gz$>rh7=B=$mx=RI6-qc!5uWSj-M^A8Pk2(X(?j-?OCd*Y(V z)wNzHv$+mf>A%m@{XQwq;&r6OsL`df{Cs=))7q#I`S?R)!A4JKP1DreIe{ZHt#TOLh%A7X>^!K1V6g+`nqTM)fH(3$NIN`|F2z z|6A~q0_N21bOvZNt`=MT$LN!9t`J=4-cFXyv^dhQSrJZ^qYNL^WJ>J5QG!oJey?Z# z#{~z_X5I((VG;osMyyzO_4IcHVlyXuoZz$rOY|Q(<2daD?5uh4LS zTe1tXU(Ls_EYm{JIK{tg>M6sVIqyPSo?6U$MrsR_nN5?E%}CvQuoVN1`Y;{TP#$|l zWz1OM>d(qLp;IqbhnGt0*-F}l_7IXs+Ugn_t;OA)SWT4vI-&387Y_@m7x24(+cn{h z!;Ujbdx%^hh%AIYR=wn8zs1~AxPhalINnmHC1jEH6SAo&%F-bDW_prI66F8B+9qV48Pl{^RWr!QlajD}NG&-+Lix5kO9{xOH?CV-t zuaV}Fy-GS1LeZ|@U=_t=stg|5#9V5;X2%U^>-(q!4@De2)0nEP%(&6!a`+=s4e=wZ zvqD~_{N#`uj_z3Cnd=hO8R3Bs1Qf-IljgB2r*WVo_wnQ`dM zTXDdf$9+HM_f?&GE|aRef%$^rmh<0P7CDBxr8mKwo-db}*i4gC3ep}QRSOGV;okRT z4zh5Gl9R)Ypg{zZV`ANd!kr0TWqlVX%nSk3EF^m~yNNMwAH1#L{T}}x4v_Gp0bFzN z`>96G`m5{5*pgI+cp!MEp{q>BBN5Sny9m%fsRGhZ6Y^B)XG({UVwa+7Oi(GqG$Jl2 z0>;V-Zi-qrNrhQ-rYKuo)rjlzPo=fv9wocHpIK%X>}Ugldz_YYws;4ILg^s;P?+>L zxEieQ@Yn^HTe?E^ro=Q-!_E)8iCFmIs;Xs*< zM|0-GfYnkdsl1P}#=UM;rcau@r{wjCi{4S}Au>j%@_cB#%M}r`c(5!z_O;lAYHMVQ zOA}0{SM}}5r__cb(KT*{5F9yVgutUq_SPI}S%U%hnP9pE`_3`$ip*?i-oqg5L>~sa zt7o(4&od-3 z>MsK`)Q2b9i8gEyuXbb0Ph1aBp#XO`R-EH!Sipv9?)M=^b~P}(V}PXT+uNL!&*!zw z0MGFkm(Bb@{Tf(uf<)l>4KqN!`XJxVQdw${2oLd3Q2)QKi6svtIWhYVrq$%L1Axcr z|3ca3Ps~vyFWO7csYlU&jpFLa2jGYc*XGX4$n_;?Rr^U|;XG(;v&|s{f1K~d0r;ZD zG})!v%Rn92wApAavvg}8c=BBD@d1IF``$f8N8kLzhKjEPoi;*EWcDt_+ z6X2#UxZOo##G%%l|40}MisN%cdM=g@Mn8fIj>L5#;<@0w5PQN9lBpA(e-y0jD)n&N zXc68H)pu8K>itY}2~JafH0;{@d08QO5&;WRU5hMtKO5%-d138^B?Otu2r~z?Z)OUx zSgJ_7B&o*7E_A->8&hGpPVDO7mS?pHztrP81m5g0sGiGalE7U2?MwAX3~tz@eX5&N z&J8XM`u$*l%+Q)ImG=HqSX(W`vW5J!z)YUgv`2j7v^VTgCeD&Ls92+7-`RTH{n7nj zv`j7#Rn9<;_3DdIK`GaHN8 zxQD;ciibE6aqfNqolC$z&jDIAAt#|78qvdpD9rFv1)uKjG5UKxPbyU5eqa!X8xB=Z zvd8TUdttO8Sk+IoV!oszRU@7|Vo1r0P}bEVWI{$OFikgSsSZ-gIT<8)`H$dGFal3E!#?Q|>pivjzg!pHWN1X|1`Cl>eGpPHi z^#gY8uItsp4EE9oEaY zKCY@f#(i2rbar<~L_?p^&wZzIsl?kfNGeGDfyfsJ0Z`ngKX?NHx1GCZc~Bi40?WdI zHbWtj0XkMoGZApQzOFNo2<`@LM*&dPYR7V_GetbtT#T4lhJ}b#w9eF83>)*i=Bp@k zD8v=K4=`EvU6PJl#>>Vjh9CkDVzaRph&&`+HdmS?BKCemuoiix_sux5y3Tr;BZC5t1}Pf!&3NtOr{nlHY^`x#RqQU5M_2GE?y(xKbLETF+96S z?xP&-GrUtEg9kc-@FTeH!9ohem<|%UII(QAZ}XJ3ZUQn=<-&Pq1e5wuMkS8|mM&k~ z8Fu3hj6w+1TWMQ|VAgc=z|nwa-%wQP{DWaNtVmmKARN4U_xu0AGvndhX~+y>6%Ptx zM8YMSDxJuOVqwJNjG!vvkX4h`>vO_Gtp&qCKpmsyrlRl)=Iu?n-CEfi&_g0qfwVnsm4$sVu@`^)~V8NA=2mW07la|R!w8w~)rhT{IY2M!|k zA|}T{cgt=*c~*q~)f^A_VKQ<%#-91E136y3hdk|LnLS5~IkFLzc@ zryDN2Lh^!w1XFzxkeze>jr3-1DR|!>YQuXRcz`t9LX<`Y$4IWfFXjZ6-~lv>S*gL z4n7yB*Op^0nUZgXgU*M(h3IJZVOUo)z0O?4nAvz`P+PSvVvG{gDAN+67h)Pi20UdQBAim^Tx6*YeOjo%7%D`H zejI3gK=l2R@DtxCD5%~4#dkyx()XC;AdWel)fFG zhs0<*1Ciozi;0*f_4Z&Pp9N8=Ps>BBk#&+EudD;Ro0aqMa_ z(JlA=|9(`Yr z4#`^`X;ERX9Hh&w8WbARv<{7!uyk}r?mrv*eg0%1G;PTDsvWGU$g(ijTETyjU8(qG zeTEl&-EjDggv%k)2VR1jlk~M|4P(JAD2F1OwHXTRrP7(Y;m9l!&U6SmBGVlBm5b9i7p!&04{}`cGL%H7N=+T05!v zu92b`Sg~Jw)G;AU>e&D#fwq!mJF=$Y?&>2IrfV+$`g;U8l0ocANyT{#_+ypA=3jr> zKivobCL#y}MMc)Lj2(ZmrRNV%&kWs)B46?&w~2(&#e3h5til&@^>pz24QQi3ZE2$< zZ0B8lU#GZEhYflyu0RHuPf<&wfG<~dN8BOkQm$A94OEN?>i%-Gq=Aw#GL*;$1gt`C z`&63_)Uy!T{pmYuv|!d!m3Hj2Il4O40S(5PUB|R~PKRv7pbZV?54gkUIO;)OXaBxv zT|H3iXl%617XyRKLdEOhz!J#?tOp|cd6_~KQLgkDB+B++r74d357I_QgcpzmZ8%2O zTl|4v$r7_d6zAb!k;6QmANdglt8szOK>}rJ{TbN!kW1fN=h6zbb{5QW(GX946VRK4 z&)!g>tMl6Oxr%I1b4S^E%PQlL!5S)t&#uuZ#^ppdSIs0!tmFqyLxYCHSnF0U2uUCb z-k|s`reBzH$?mtZi9el#vPo0Xjti84Uz$|RZGg#IkFS0j2D}Yd;ZIxk6W(AcOPhKQ z3rRWr*z;YateF&Pt>e#)mpxhF_<5C~kp*gl90`pDy?z&Q|?zbQ}FW%n!_L0A9W8Muz^w3e77F4 z%(f^#eNuxyO196dmm11ySt;bo#7XlOq6-`;Ef*7dJ#H<6b>=M@3+Ly$Xf4)7*eCC$ zW7C(Jb6->~qw{jg?EFClHz7D(&P9@5)-Kd^kAJDI<$HnV^+f(ta6j3I2}Jrxqn!;!S>^ln#I#R>ZjPOy4P*-IBUG$Co$$!E>a&yN;y!rOegsZXq1Y2m!3D5a&V$uq= zVR0}ZhHucT6I-?rCtL`lr3iRmaCG$Ar)xp-~AFv2KZy2!%3&fc)kfMUPCA}}w8Ym`T>k4;sFxS{) z7avkxpDE5K%))8++Crr;tdc#@95iACQ2Pdxq|xR1)I(RUxmB~km*0zbh;J$*-R7{5Bj;7EL-Gl zqSH*3SjrIb_3iWjrJU6u36>B)$#($v>@2A1a8(j$|Ft9G@&^gspRZQO18X&1Kvgi& zVXzZR?Yl`5LNms7>~X{$Ym;xAHVs*=M|kJ064$=C$V*VT7>bJEwo?!oHYp5QVUStX z1Wb7Y=#MY8PN|tmSpA?d%EFxeu0YUP$M4#YcFRQRTB}>b>ulrvmu`Yf5(b)~S*?m% zj|RW1D)#4({@;JNk9oLAXvoVcA^GfapHL0MIvdBc671p8{z{8_%2IcbNdOJOa=HV%O}p_} zpVx-)9yL-FpfDn*dgxHT$r%25ggP{MN9KaYuNjN5Z~H%nlN^D&t#``_Mi$kQySNLMhSJO}lXs2D?d5dR1MfMsH8J>@U)opF>Fb|B({9ih2);Dd zuWmMUIf`nwG!Jr*Ux$#9fMux>5Cur9jQ6(tQiU&4STn6a@U&J;UA$fV@Iu9W?Vo3T zEZjfO-l7t5)RQ{=-H8%MtC>48q>uxag7-5GOtHI86LfI&1wmaKY!eFbQcOWjUjg75 zz@apQ1A3v9FU`2qVW8`0h3k_t@Qr1S-*NMGaPcqC(xbQceXFpm>nZ<$>P>11Bc8tR zRfp@Fcyj8m#Z`^PQ*GZT!hp%-++`C>>Nc;6+8ZBW614WR87sHuXuC)GYDGLwq6KqG zX$`9agy@h>914Vbh?_6_B*>7YH2W>RvsH7M-=6nHu^%+GS!&=@@o$(`Lh|p(5uEHK zadd$s3r;n_+vPG#X3gM1fkDTo*n(H*2eIHW{Pn5fp!>`mahAM|^w4+hZl4(zh=))_>GvJJU$T8HWBGCghPX;U@J`)dk&=T}% zVjxeFd>wGDu!+j^(>2k6Bh`M`=-mS~d%5bq?f&5f*3UCDJD;<;nIb$xPpBv{(U0!7 zb0M3sb)U_k9xWY7D6H1eQsZ2l%d_i5<{%P1Qf$K7Uiw%BZ%I>FjQWB<)5qfXVyXlZ zy%Lx~v7DaM_&XIdi^wLQ%>w|Be?!5eg{fwXCsvU*QQs-L^BE{X6muk}a{+W1!d=R= z8hH_wx1t=ziDOX4PvLFuF|%?`0+n;2>dzKxSbwI`_+*IRWNr^ ze^Nguq}p+lwvTB36|HAtS_XYW>#b$;hW0JU%gGWmm}^{QOJBOjKIN z+m>|=Oo4H5;bk4+Svh`IfdD{s{bQ!*Jh;4;SzekhV#dd zL254dB!(_rnuRxYH5=pxhBP|B$m}XEtA5k(pYQy3-OlM%;wkkdX%g1KH?$2#b=BnBrm zA7R2#Csd9$hly=HAbDc=CPHv(V%gJP3&D(#{j2ur=9BO;5phhJo1dmMW;TBg08E}! zs!$|K%P5-^Z5$?73gk>-z{=ONR!FXd#jQ9VP8hJC1Sobtc_Zo_b#MY>J!!|JM`rFW)($YacFT{TcV;|uf(IV`6>*@qdJ8dSr%H99j zsjUL1Wg2@^A3c}QJ^Wv9F&>+4{WyC@hh&z1(^0l%+4(#~|AtM~!SMKB-Ns`+&%}q& z;^fZQkjl8Ru`TxHXR7=~NOt2M0Iww#gupW#y!N`>Y*()qg-0eI_xYX;nV0&7qV z5HR0D+620K0X3bTt^m5{6b#?5(R@e+KTzBLN0ISa+{CN=5%=(~OSXauQ-N|{r>Thh zY5V_5nthiAw9h-gM0r;)fX3tBA|I%-JpA~^U%(*d*@%F78|b{hu#85zOweFhvUUzMZvWw{hhcaw_BBb2>L~+*| z?;%uZFN+DLNJoaov_yB$1w#z20xef3-}6^=0`Q{Dq2DAl-W~%4W1_;LSfM_SD3q_! z@Vdd#BmB?^)u7r$kH3i{dv%z_1(8vbNE(!KbQ7qN!C8vb8=-stA{bP767u=IZ42$a zw2qLLIlMAwFS|_c8&X71<{aqB6{el;K&m8{P@_s-{$aG^7qV;&&M7PNJ2#%#be|3T zNiMp2G9xR)zC(Xe-51}(7!_%WSvd=4kqRu>d%OJcG=wD%h$)7!JP3+~MW{9h=VY2# znjA**#{PL0=j(5GHLv5x4bIK|?Ras@QVD4}NG#BQIh&y%U(u6S)3o79Q;dl6=?@{F zEjxBuumi6=3N>ty5aE2P5;^AZyfQffHAokm*d=%w(jX1rw;cqbFmP6S3`=D6uNj=b zBPTwM?b4QCMfAZiMfHBC*~1^up$A2g%6YU}_RzV(s5))F`qI9>|3vji?TnK~;uFnl z8f}kW?Z^>_Yc|5D_g^eSrNr#Xa#fi=hFS;SnSVuRq)G67-v9eWxnW`pS__f&?=pEV zd1mnfWTlY)mL-2(6jxXo81|Xg@+`8{265#A!d2dM!Pqn&?K!EoNFM-p12p8=`isTfo(L<&&ahkXwA@}FU6fJfh4?1m9rLki~l;}N#69K?MxM~RpJCA=6fDeAdM zJO#i`xIGo1>64P}#7*;X`0mlvJhDRXtE+{?x1kaB)IWZ+3tc!s732(w3I7nEkX=9U zi~lZayf@z7q>#ZDF`)}rCfF||c|qoGOi*-h{J)4tR1&MiI#F<`%$q~ zU>A8n^x;G^&vmW&3X^qY*T=}P%=I&L2@+bim2zntb(-s+Yo+kc+q?la)t*541=RoJ zA2pL;-%0<>q(uR|FE1+8UFJaT_1-rblCcZa=O=$>ZET2(y4OZM@(S^-Pp3-fTB()x zq+e7NXZ#jjjH8r&l5f(RjYcyF5VVutUzdl7_%|{qBn%MB+xLWl+lkv3KH!Dn&N@`5A@LC?Os=JGNsVS%^HLv+SFh2A_7H`Ler`qaF6R1kry7fU-O7X z)FLC_=K$vGWGbH!e3ABqOQzDDOKjnOLE&D7zn7q6Lxu^4I(1<56YI;p5?o=z z!!_w6NaIY_f60aXTrCw3Q&TAhA8EY_ssF|BQ1GDH0?7Df2mH@b%|xPoxl#Gv-Mn=P zq-HyVyNimKoJe-rbZGN@_ENw<9{RK z3Q}tNi9}^ztk}OuV*?ak%lS;Ze|C2aWHOY7=r}f{aqs?2T_LzPjy8puwZgGrIUN976^-CC>Uh_7)=UX4l#f+{Ggh3np64e+@X!P{_ zSlVK4b{yiLKFg$9Tm7zBj(>g?$beQ)wxDFYpEP#$8#Dk1J(bi(FJr6#%W%-Yyg-gH zcpTgGuqqo|7Qur7gsU3u_CJdw4BT?F9%=6hMYN+{z(HkInQPo-o_J4i$6=N!gnl`$ z@_!prO`p2f*4AP=fWhg*f5xTK##``dT?;Oy>miaD$p=f!`h_XSh~^e%icIs9>p*)| zrj%#3Z~2>|VK)lMPg28C&s%ir<|dlHke)sgB_YCI4!KUW@1$qE@q9GA9wNxx=<U_8lekIC6s`5SxA&glXVyhjcK6xS1xp&s6;O%grA)U0u2wo*a`Pe{EsK;+~2a zRh&|6k>41fJBXRr+jv?-uTt-_NK^?w3J>BDf(d=rjDhn2G=~YWBMxH?NI_A%@up~I zE8!?;gHttB(J*<@M?@DF$UIkP3rIF>>f3hrSjb@h^ftCoCYSqTbpOBDx>qJIUTmRH z&<8Z@oeltg&EjdC@yS3wsMH;^-4f8xaDV>V$#%$jUuY7C^f-}d&sJu?gH*-FY=2DB zg%JOY{&BvpV6~_h5l(eRPfN8uBh z@aNN}_m!BkT=S?awr>2jN9@p%mLz4JBM*gqs>P4Q;e!&rst(c{!^Om^Bc3xyd7@x{ zedA%8ixOLl(KEN#^{_fw$T6baOz|)g^vLW z6+_`h7SBKT_Y@KFRpXb;j#f;bPB1Jd#Iv}huUA~>(RDw_OY9Mu00Z+ESnv2f{&^YJ z?jRwn-kK|0ualh#+RTcH&FiB!(mMy@j!koVUVDiexxNoK+}IW&W+i2?CLJ|(kytx* zU9XQN%&X$zu7P>!|7yRZ%JzDyzA-Zqmy-Z8UNX_u#qtkHg^}J|8V5%5Z47L0Bl;ql zyGG@`ofJvcp1xemk?6&I23uK@xVRpb!tEtHta3n{&k6&vxqr)Mly2D-2T+H3#9u25 z=k|zdR)}k}gB_g3Cht*4fAo5Vt~1pV$@#JfkLwInZ#(fgK98v3=G%bBy(3lFUD=iF z>$D!QmXZ%@$qu=6-iXS;+2z}T(%DhkJhm7^*)q_oqW<@a>PJk6Gtkd z6!fRZd3L;UnYq25r4ZvCG5=|m8ddiniJ(+ zg4?NkEL#ab7MI=S%nqYvro5{8tEJ-Bs9Xly;2Gar zRW{qNx9TaifmUjz+|5#7FUW533OX1pj#9`Mj*u$czu@-}T`obZs#knllLn8l-Zw}X zKOZmJRl!uJObszpR_L!77_RG^D{c<4Zy>ELG11oVaJO|gKQkX!Pl>?e|9ZQ(M7?{c zMc_1nS%TZ2J-Wvd)oq1R3qyfkw&mtnb{@?VS$I}*qp&S`+%vNlD^(ocVZOlGXUCsO$WMRDI}Rk);3bX;HS7)4 z=p70DMgA&`py{6L*tZBue|UtAob z_DYgEb-$fnpo)}~nmoGa9((;Ex4OW7!Qsk#M*z8W^${Rh+0}`cJbxI=(@<0+gJGYC>#~GTDWAV zyivH@N$-Jb6LW%pdL}{;Oj}=?e(yac2&lXfe+`L@jRLaX^}@gciK;gxudu%WSw|~C z<2JT8;KN{;%e6ByT{oPGs-#f$j;yqTK}B`XLEk;%Ii2+1R&arROMIjok+r7gDKomai<#+D{t%6+ z>`Rc2DJ&CHxZ2~Y*CBrY9kfiO$jB}gS*1=-!LjX#t0nB=ciKX(_ic}+x>|i2ubZgU zHYQwvL-lI#!9WIgnu637D^E4&SLJs?$-Tb>-Vvo^FHrfilnf%`n5;Zs5%bh3_jX$& z+WzK2?dC}DJBypoga5PL5!DF2(&&>GC&Mo!|Mquk7lA5KLk8-8=}w}uvJ(+-6*yNg zgcUE5SPf`WAhTm7Q4+8hI7f#Rww}GzK$fXalFZ>|+wvBHU~X~j7hDd9u5TG~sr#c` zijzyb;0W!fj_a3Er*l}6*&l>YOwZ<5tRiQ`tUUij@@t&(DYl>Yn4XT1Tq^DIl|Z&N z%8-3NF1ZxBjTHR@>W_QLv|qaZ-WYh?VG&^(c0>V5&du8A`H8;WjOQP<=)C7vXT*_3 z1SNk4x8?F9#0O2To%obL{d|g2c~_>Ug(5v;;7m_bjAd|%cFujglb^}VRi2t_L_dR2 z(zxFw59}??^Iw*-b9v(irS#SRudseCq4D;k5QT<;Sb< z5~qpfGMM(?H%e78K?;Ma0q;+Wqkvmco26Z5N;TeLY0@x~ql}m8<}tmD&Vo{+eqL)a zOd}pwn3}7~>#Y2{{h8Mk^Uw5J;YwC++P>0gjhpy^D6Uc%jL%><(-IhFG3M0KW~ut~ zuO1v<^i^I`CXW-}hU8xoB|qPj>Ph6IHF{NwBN7ru!+pSb_+QiWjUs0(8$Cybc1$a- zh%&~+X-E?4cApg{Z;mUG-4ZsxA&dI}1<}GE$s|x+ZOt6c_TvV>!7KBx*P8A26{X1z zuP2I^YHJYL*{DzLHaEpKib=;>^g@a|3Jg!LYuk6Pu5oU<>asb@wv9GRLy(m+y1JD| z$IcN39AKw2y~X1A^y_Z^S zWl#n_IF|`)g>t2c39x7w?J`KgJjs}AhAe3SOS8bj1W@*1(+6oUsfbI^R+(xCZ*I?H zo~pp~B+#&b*!M4Cy89CJ&!=-wKlV7txbP@;2|`37Jb;^TOQsAh%GTC8xib;&9#Q}i zF#SJ=*!iRSjrA4;POt8RND|z&lZErfd3WGie5?bTduR5h1|Di-T5dxy2^qJ__vP_* z-+8FWb{edM44;<&ZZv`QG1vEe1PwY1y1r}fze<`_t*gn!p2E@ALoFfO8!)q>*jtP0 z$xNk!`YJ2oe7xs3P7aQ=>zqY85++Gm@`reBKb^a+HB}5Ey)cNS?({^&BvhMRbD*JA zHb^1MPpMROCYl@#4J9u>0gC}o%}rH~n)i2Cx(5R1uVt-kEr(4fYQ&ZEOI5FW&;@>7 zWe3nz*P;L5Pg87476^KhD8|PwR#wV74`*dm!8~2plv{dKfX!mq*9^Esjk(Q3!;O&q{W*YX*h*w@ zpdIx|eHqmWI=|-wp1-e2^z9n~>O?6{fX;5=dteULIW%uurkZH1bR)-&X5Q1VBb4!> zH6zR{QDmB6#%RVMK8?x7#?%q7+qw1KEwkloE$`w-!}D8aZtS$GO(lF}2ZAAc1D~M? z=H5}}Ul{}MS;lj#xZx!$0q>22@(tb|sL2mKVa*wcgk-ETtkOoAiQ*m9do&0x%0KD% zUF0lYB~Be*qJhwrIAsA^0U@h9#f1ayl zJu~~uHQ#CAo$IaCpbv?aU-kT+g=I(DtnaT6B#r1lNB28A7y5SB(Hc-F?C@;)Ybl#~ zQ^zX3>>0#c|H{!Y&bVW9eeZqj1@=~;3%5wVEPcQQrHDw*h_O*K%zWQz1sBK0&0>GU zMMzfkvq)Oqbi`7bcvV|orkL7kK*=3-K*HkP^778}T1_qEQ<`_P!Az7xHKVOFc2@ne(NT{NjrNEE}9)v*U!IcDJl1J}X|S^ZR9rFrpcxTgC!ES3IU7}!X-0jAgI zu1v=r7jHB9RU-UtKyF}Q$L!Dhg$aV5qL`N3RQJ=Dbi^N>4T9Y%O8J`TbNn$ZmvXvt z-CAyh@I1c$#j#G<;8r0DwlxYmGDi$xh`6b7P_@JZg!X2I7@cGQYa)-5H;#jglEp*F z)cY6CX@ZvehBCPtUaz*I;-VI%)$Z9RV->glSZ<-+^M=w_D&NM1MG1@F0Mr}bl?#ym zO5~n~%)r+teI@l|&vOR7ark1U`o5sbav_;USAaR;-Ut{ig4y z(?ex%wxXT~(`8g{K&0!beGnX5i;W8mq5|}J-I&0+*l7#R<}7Y!CZ^x=;RI||x6v?t zm2f9!7mHvuqIr#Q2lD7T{p686hYTq*>a`Mh7&!dywWk2PJ{mQ42@TfJ2FrOEO;2p_ zA>;m%#=etj1!AEPrS%#1nPUy&CSjW0zvrBH>eB=`37i7& zBD7}NU7n}OUNF-~3>d{nZuXRZvwq|HW>#>ji>mW2*y7D*B}k;Q@ULfM0Trs)qlz7tgs9WgN0mFHdqbM z)EQy?k1a-tkB|fS;V?XFCjnp^96*JQQ(&ER1yHd+bU6b~7PJ=@KtGgTeV{NDHcwc) z1P*AC%TO60aZJMB0-qq~?*Qc9U|qOPE4$4mU@P!^0UU(<6W09Zid>tO^1EA6Sv_ww zk5=zQi zM|*E~A8~i*9d>W7`23z=9I@KG4crvV$AOE{{0Ji zY!DWRz@Q-?{OrjQAUAq3%5ooHfE}T`X&Dx=chNdkeAwHDgKIF=PeYC8sR{0>fa^BX zIvT!g;sh<8IPWMbrG8Yf#|X+$VS>@qG>O{GG_KLH=dNFClJ?8&lUg z6t@BeZVhVsa|4DSj2fuxa;^pdTTfi*uO+F66gsJZWz7iwok9qWFlYj*c1z}i}S*ukH?ijE{&wk(4zq+0S zSY~?x@dKni4|%*&bHK%@_I~g{xEtSxjrbq*Vyl>w2g9Bp$gjq|*&wty_M^2Ti6_w#9v9HN}vx`135o-(h|FZ6MNh!mkoHjSDdE1Ye}~ zBB|78I?{nGJ#?c75cxc!>cVXH2bov;Aq0Sh7;k`INeZ>%%yj020a>B^Ih3h4wCTts zWXa^H9H)<7qi=2RgRssN|x>D~-bRf_`7 zpyRBKFc3NVPboAQ^+gwbEj0vGQfi$EFKwS)KVw%H*}eKB*x_7gQg|JY+d!r}jhYHu zPY@{0{1CDR&kL#U(tyYJqr%qdu}%QQPKVgoj4%<;nbdFr{$v^U1nNYZw?w2U_DF;? zORgsRG7%Gw0;hz}pzB*_>XyKE!305JR8YeCX$va3gp^V1*j8&r~R zCyyl=_m`1Vq3eHaw6#&qX2-}&G5q-U*V0h!A3y{RfE-Ofpo{@MCq#n)xftpYaPl!y zBedoUEHK83@~}?G_Xmh_Zpy+}EL0XwM4BcU0-Oj1;aydy@M`F|+hqn~zG2>iz-A&) zF!jN3k$}_(%8#VQT;hE;51x~f4q+V+m2GY1qd~mmxkCQbQ?^XrMsnBE&g@U2X;wYH zsnq?Jye`fo`xH+BZaf|)kVwHaI z>?OxPVmxJ7l)GBUh1j727q6l1peu9_$J%C>gxB{zLPif*E3Y%wItyg&r5v7h<;*C$v=@WDc8Z~m{K9NEEmPkR`z)Y$5L zzuax3pcrP{ZF#FuPX(x?V7xbUa%}~jHt3X(|oa>>TwJu769daqq%QE zN6l}C#aBH61Bn1HH9iFNh|<0Z#2=LDqi*D2HfG&nyeVnBvMe?#bOHHyDN2I*H5P%5 z&R=2Bnk7I~nA|404s8#Rg5P1HJgp-}@V1L}9{KH4UMOH0CFcr1$E7?4W5=nnB(0iP zmx@2%CmHTWZh;{djEmEq&o-Fno7pxwd_Rc~u3ihAOPq8-&o5|ARX=L=zi$B}3HHF4 zS$c{#iZ=FJkQ%}QaiC#g(!%++GvY|JT`$uV%6|`P3X4VSlkm)K_F9Ogo}%+c_SE>S zj;1Zh{jgWXi>>=P29ayT?s!4Pl4wjY&k8b1;{6Nl6yogq7Aj=Yq0<_K(e)VQ5a(Fq zPsr8`%MW{~rz0f!WIe1}B*5~sYn>n0j2*Ui{?aUFgykD;O1sdO&` zR)snWO5o5cT%R9xed(XxY@pIt0DfPG+-uSi`IL*YzD>T_%cK(w3_J0e@E>_bZAy3c z~gsni7Og~s1y8okVd zZDVqvScOKCQU1MXIiWZ|r5K=k{yk2(SN+pwqjZevHx24P?M#Z=iro$wQ=q_Va(DR> zHCE(2zg*zL@7E7b9j7F0y}wsLVk%}_T%yz!Yt zhDwjxxp#bGGQ=f12S--0NTNnBB(9Jh|(-BwoK_U1AwDfYkO{D|c65!w69s~7aqfE8ci3Qu3;_-A( z#U7><(_qgQmkSq6f>+8HN9W7aUzGWEvu&%%r0{)g75rtM;RbHwM);l2pyg>V>mdnj z^t@bo*7x_v%g2ZB16vm-KmSaL-Jkq|5v8ZU6$s==CUeKXu^kUR`KczP_C>yQ%=3sX zXE2_?2d*qRCC8Mw$<^Mus^6q@#aPlbdG26c+{a{As*RGtWobxFJoAS*4W<1s*rkh= zp`x@xV_)<8B}!7k<~sfNpNsE$sq>kUm8Zj_5WWLHE(}V-y~|`CdoBgH!r*K}utK~t z8M7eEkvbw?Qy?s&5FSpaVyrMxJCiqI$?ph7!jD^ZNufnF&OAv<73I`9#7i(}iuf^; z+z?BWc-d6kDF$trJH{?+(8eFuMe@(hS@mK$#2?gc^q0n=0IaV|8FF+pVG6l`f(h_e z*S%S{S6*u7P5jbfA{LFH#y%l!1LEb>(`~=zv-bcgHDzdGNfSITrKzMbmZbnw2y zXxp-{n2CnG9+v|vuLSyVVIfB(alJT3lfQ*_bU z3(l?%rg7uS&Q5LP4h<72Mx0D*w>`iedO0CRU!Tmp2W4$Stk}i)>JDY)(^L!fiV}Rp*Jy zJVo>)o9k;}33$6%HZq!}mY36qk9{q1}@3<{jw7-DS#RMm#bk(#XFL zr3d$16Y?v@vmgDQ2jGM}W2ofrvs@FqSR?SHU(Cet3gO+be zP7FKlaW?eArZd>61h|wWsNdsN$~2YM;wVtD+Ol*y-dQiP-LO5&GuNsQ{+cHiSWSxg zIOR(B=fH)gbFQRw)AIE5DV45ahE8sv{*!&Hw`J?&waLxl-EJ&?P-wD^%X^A+@cOrY z^qu-w_HVlu|3E}UkR5cv0i7Ofby(gCJoSLUhY>7!O@aCHH;&4M0jTd(Z&-i8dNO9{ zDL>BziN%h!%voPh9jh(K%;IOV@L1tCONh{h7Wgn@_>F2T&=;KWxtoQnZ&YAp#%N`f zzp8QTA6lq-7AJ})t3ly{M|esmwWDUf2_RxM9>zFxPWhXUgDHHmZP{|?b?iJEm!_LG z{i|dtQ}sKhB17JG*=L^P#&Vf5cs%hO8YkG+?tK<$X&5-TVDbMKl=iFS(N(@fPsf{BgCW1lI3Da9C;edQTtP$_23=3@X*rV_J5UAJ3O~ncA z-;=83tqwgN3EQuSWrd_GiR|;I0E`*gT~-(5sbjEPEu`qFi&$6>fQ3o{%hMamfP*`2 z{yZQEj~_ILpCjJ$v;6{$5B#pvQy0knQYZP3|48)$2=w>tvP<+BPWY^c0iMoVi~E?T zp#E0@Dd4g~qD!GBJ99X_IK%G6tpyX3*h5}A>t`AB$PbI52i#$!Jb=~1 zL>{_Yy}A)l1_J(szXd#@l0SfQRR?}!$3?T~^Uw_;aJ3+Ged+9+4#vBTyp`hr8rWLWcJ1VKM!}sR>5T2Y;@y#% zL|8HJm=88HsfivIX`8#Dms}Fj^I=Wt<8pz@$Tas)oYvm;#Jj>~zx)zmWRJYC9aPwG zIUQnmfiU%{dx}WSQNCOkm0NJBffu{F+7mIJ_9==ToClqY;aTk-{Si}gZVrZcysfJga}Fe?-J1rf&Y7i~^incG-)rJDo>t`l<@QnWw-(W>6E z9fGc4@@}9CUjWybC@{nyvwy#?R*6b?4Ne6}6ZJNLLtMQZaJ4vj+?^bgnQ#jO+~*1P zBlH}>^uJzp*-o_q^@(P{9lAF&fQWfe%SGCXj+vc(wflGf8mA*Q=9I~BTOeSUE8#6X z(!dW_M~ZhJe2Ik%120%50E6^cJAfp9?hb$ej))I308(Y1Az&LE=0XIW1@gVqMzvvEtsuL0d1R*Bu`o9V5;!b;!4^%+&zSJg4 z0e7gCcfdmKnJW`=F<*5%+w+6}_p{Frq3X*3^W}g4k~F+#T02<=6c=LT_w(gC?3>AP za}vMuKux-xx>FZ1`z{dpR_5}T&kDdx)dL4lZou;kPn=eN1~{^`0Q7#5C8bqsKgf&UZuo+Az7$2hkV?= z;jvskLcWT8k$aBE<9=$4Sj!u$U8slHp`c-rdi;rFF!sSCG7MCNkm(Ky(H{S?fk%XP zfb^YhpxCTV`;Y!>!$8}PGO+*=;Z_%dyX*jE zliDiId61Dpcd&8mrBZNE*xcr{>X3JEu0dJz+OKJ{AJ;%ZnM`7y1k2t)xU~)v;eOY! zt-#kS0DZp;E|m|Q+g%g`BCQ0zqQHj-5V0D@ephFRJc0ukIkrS@7p~Uj%DDpdFoZT@ zxB|1=0a_3wR31UtDHhNl)|J%%asXYP6();u~U)e{x4*l^V(Aolx`#%41TIuKS6 z@^S-HOG{G;_aViGG#?l6Al8`$TG=(A%oFhR4HUoy!QKDK4&LIp>{a7RyZ`A1LszD0 z^SmEQZEPJ0yhmjX{HXpUO@(h|XSTc?`R-B?O_4%RZVOeMqJ$}R#+iD(m|XjD88(~8 z7hZd&r-c@lO8c_=!P@^Rg20Qv|NUkkR>p>ew`?e3ES$A?Eas}eS1Fm2Oj}DbtJEYf zZB!d4a5WM^OqXfb3%^-q#cH0i6^NJDl7g+uWL+$9VY$BY3-+ztus_AI}T zRX3DT|FYkqPP-PY;Yy=HM5$(*N2j{`P6O{FMNw+cK?6#J-dOuDa`X+5#uFfT_=MdP z2ID2)-*_-h!9N2(poPL*)Wd9myqsDfYYW7t2`yD}1(qpnV}NYDJGm`K5-9HwFawu5 z1g3wVshb1QEL_m>7ezfVLQ}JJDA>UL@pA8A`($+*&ITQm8U}!uB|p__kpBq<7*F&K zKp21l2O(zHC8)SGZ7yUahin)AnhG~NGw^f{SD#kK%8mhSErtkVUL(pb7KsL)Yikb#17K6A;h# z>ECpC@jySNtpWkX0c)fEd15b@4^e96o&w-z`AGnw+QlI$>~9IUG##+yTJUE20iaf5JJa@+~3{EqX(fU z8;y`f{v%4Kh={~eyH!H6E&21AN$U+%ig?qH=53FUSqNbia#iT|&K8>YD+YzXCD+e3 zzxUMwZTvOit4c8{dmu5-1E+0u3UBC4VZA5@$Wc?n4>1-u%tqAZ5E+)!z}CSA<_NgX z_utcEU3W?1(`|!A8VNLx!D0YgtCHXZ07a^Yryp3iTW+FJC@UmQp4sLDjglJufV175 z&pek?msgUf*3Vr1uJpaUK4dmR~z7wSeubXA>Tze%Up%?;qE0xNXsKJ z_Dl^2Xk*xx{N-y$r0$uhi;naPq~XM1>QGid9L(|J<2Qo` zevMo}7FhR9l7Cod9_|L_DHdX(=VFpVSx>N)c*w)Yhe@voC~1d`fM8s=ju>l6;QXHx zVjHui`#kjf+F_*pYAC%5)fTw$34Cdbs0gBabHYm%)Va}-KJa8~=XOHUE>~rSn~0?v zEu15JOZBsK z(@u;L5)-r^J4j5Dq|Bk2xJ|onH$DSCBLFXX)fMO-SF$3 zoqAl`HNX(kN~@6rH0{9DgW!MaE|mm-*RjJ1sqcYD)SicP#Idm7g%BB2_Pa3P@u85O zhZ=e_2|&%&NIl!KS2->A?plrsaYQ24*b~^Hz3b`0B?U2xG+zO-6F{7>-5j_JzH>1g z54@a}e)KTe#|>NhC~eWS|HPXT&AH&w*hP_dQBFbeA6aO0-~Fq?*ZS%j6J4FNd0YwU z@JqtWp?DW|&aMyXow{cx1N66d@J_D+_r1)(ha_Uthnqro|Jh4HJsfI?QZEi=)S($1 z>Jo!2cF}VY2~!MEe2kC7yTSK)YtA_ReXx0!N@oy_x+<5=@b~PQ71}_~w zYx~ua!pKz6eJPzJyasxsNp*U=a4}2cn4vi?F}SCO`0pRr`SQLw_Tb2s0Xh#11KNUcLJk?!+UTwe5^hi5Z z3ARw5dVuA17@(Vi5SFPIz|c_ROTeYLR0JX*+T;EHe?;s*j3S0MpA|iCZ^}LI%tg(t z*=kH(Ysm(g4IP;3J4PLvv2rv4^LC1IkJoHP=s4%~IGvsgW#$d-6~>s*cF4S?GjFb3N3=w zagj@=+e+Q$eE6iZW*K6Nuc!tld&4x(OMA^`#6M#c!DG$&jtGy#G{Jk^smari@ed#3 zfz{%hbuU;vYjPU^j!|KH0&`K}&w)W99q{eUQa#|w1kgb^Gd+$Um9=$GK&8YM>8kaX8>YWr@w?gbO>@qKd_o~)IkcCtiVEuR3v53<^f zw(FAmG8t(?)G;hROIoW`6idhGrjorEn=E+eNPj0cw)q(`JBMXhIrFE$+uuVao-jsQIAAWz5kpw7|!f*GJKtOBsbJQ^WTy|WLz53L|LCW9q{=FK`aqAom zygWtXw#W%hA61v^wtW4%0z=h|mk9Bos?#4m!J)(R@QxdjZNI}-aQs$D^Tih_gO2>A zuzHP0X`xW$nDFlH^=6)0MvYG>umVRM!5lvR5$NvHqDuv54F|6-(pq-Lw(Rar^t~HJ zQF1%^-S1w$DZp=|d`UOFfHEjfRA@PoH)7 zd$(E=SZ}o@V^~JVPH6o+!uaMXw?NUA=GZoHNQ{9>nw~#JhX_!Fa^xx;!*ni4o9W*=Sw`vZDK$F#wlm0QYm;e@ebf zG*q$ebdw#6$83WJEmU?M_BN}R(oI+fvG*-m&A7m3A^PT!PKL8 zx!k3iv}AB)IxS`@woj;7O4qC)iBf3Wi9}xV%fr62lgZNn+Heb?W*@v%0F57js`G0w z{C10yBLaHpv7q2vK|~4B63!NnC};xoseomjlE4L`n}!Mkda<=B*T6PM8{JP53Lsi( zb|2Jn|D8P`3PeQ>p{Cpe_bB4kfXnt}JpVrF94xfcq7+`COFE&3Ii*ijY>SygKO)6_ z0KM|bEe(WscKGWA2WrFJ{qk@Xp!nwWt766`UHB1gR|)L9Pp{&9r+#2CG=MeRiLVkaPt~L}cYSNHJ+uLCV<_9#o(6=)EJkIt{iQ0k}7LAS&mQLnTED!}B z*=~Peeju?%3F6G_(uEzpC@EiWwg$vJPi$8U|E8D*aRV1Ge%xFUbdUbdNKn9Ay?m$1 zacNAe?`DJq8qiuqxkqn!{U$y1DC_Y6xAaW~!j|B8@FI$hri5Op)UP|OcMJOsrh(34 z8_Jiyv8C!stVfKXt(e-!$Lya{=btpf*RO-pC|I(R7jHrM1ZFkJsW4MD%b7~#kR>=` zVr*MDB1kx*TJ^8qc>7Ouuz%_r1Neoh7Jwk+c4&Oqmcek#8DLRQB4P|&C=KNW zBiaY<1bwIe6^kVf01jZV6*1}mP}Cj)iF7$&Er36$-Tgcf;;8fb*W2w!8?m9L9}f!t&V=WE#uuAVkuZe)0_YFKzp*%OADvf8YukJuW+X!dj+<8Zn{*z!6x4a_j+aWS6>XcwZ zU6C_EgAz@K)BKzBScrOAjtomk-vCG0QmK&vM)Aw>YI8y%^-VI!_uq0gM>Ptz>@r_2 zk9&9_A^wwp7j<9V<7bqTxcgzhtWV#)y1VYCfb_D>B9LB9>j?fLS{t8mh<}G*F>1cc zjvJ@TYKo0DC$&nxxc=q)A5e%Eg`Q&Z@O$Cn6$50)nSNEQZPJ1zb&>XrB($6vnbcC1 zkp@ebZgtJyS!t;|sL94=KXF6wo#BDxsz#duTg_$E~( zn6K6CPX%weI{EO}ibgpIZ5zqE{c4Q z->9^t483w2jbCM8*tmJBxuXtUMTuXSRm>?cQ9z;cGTLOXwCpP>%T2N?xVj=nU}$+S z8T&70@l00QG6pohPKbWmV&3}Tu>6TthgT946|)3W@I(r0T9(3tFMPi;8aY;yEXfqg z0m{S}F?*Ty*#Or$Oxw%Xh`K@?(v0jAO-gybEzHBG_mRuRQ8Xz28kBe+M3l+b5)JVV zNxzR5_XL>7tw#Vt~?sYA{uSaCTw}5?#8KER(Aq))_H!tZN_5+s$0M*S*<nBw;)%ty=BD6^)7_r3Kbf5eLGt z_d3oR_vPfscUFXH0^7wZJyKi0@e8Hib6j}y)QBpuq;6MIu}r<|mHCQGUiAT)@@Bd< zQtES$MU}A#_2dz4X&+vnXwsMPYl@?Yt&HL^HKD>{hhk#R)=bfZmtL=C^~%Oh1-FVF zO0lcdWb2Lvy=2{pbkifKT(yK}zRX!~X*%j1MHGhow{z!Ub<14d;OKOoO_mRL2UN9CzHkoPaAkv+TV#;Oh^?CODeiJeb z^&_vw?JbAK12BK3aT*Tf?E&L5#(Y$nb0cK2WNEmf>Bi|7phC=3Llkav zA6UnDU@0l?@Pr1<2`XtFK{D2!9dL*q=b%=(Z~euAi_yz^#$2_IM`QJK8E-&iu?&HjWNt4vhcEPhe0zzJ>(;^B)QCX-5p@AL{g- zdC+Xh`LKF5a#HO#OI7Y3wiiWZTwnKXonv1%PD&f~y>|ZYe|zEBBoiVTRn-0m+@6@jW2LI9gix!NvM&`QU4I?#AQ! zXHrqfgEZgr(ap3@IC~BhiCD9EwwNN#v1JJ9v&-JVL#+}$GAyi|sbAXP3TOGR#<Po{T|K2brRF&gFvBpDc2-L%O^{{EV7BRB|t@{#o+NGTH z4SwdfW_c!~G+PAK|NP`~OG%BnL~N1b;4sop=C}RU55|O-++K^gU+&A|Qd1(-My3Eu znDALdYHiB1w;|c@aL|9wlIX@sDrGvkAjLkFwJnFdUe;TJlOI%IfRor(c(mq^_bVV3 zuf*qIfnPoEv66XQMp4B)8Ejo8m;0F&V-SoVUUA88=SM0hE7GPsH^nT0 z9r~BujLhT$JVi0(z))9h3t#jen(T8R(_v!s@6T@*5Rm^!%1Yd?^xA$Nh|PmZE19Tj z%z2b!ljm!o;+TAU*EP8njJ>5wwA1%;`S`F@K3tI@=ViKv+PdjMSu;9oI4%6}N9Lq=pW>3A-eM#=>VD%u(`>Y)ARfV^JoMkW` z*VCIgpy3@)6>tyl`(mc#U;nH$S`J@(n!2=DfIDbgPCOilHZxr_ z&B(yqFof;y2^=CPKF?A)Cs-n(m~|r`r=4Zt0zP_sl4|a?-oq(4#Ax-`t8t9<*~)N319)Wmv4bdsL31Q z;QP{#&GiIS$tRtly1L%=;Kh;ALIC9#P@v9~*sS|l(hJJ?Kz#dda`#z|W(Pp_-v z;&tI~2)IUq9eu#Y!W4{&>lk9*R?U=#slUo%A)dSf>_QP{fGw+R;0exl7!nQtV+^eQ zrClu}(>?+y3*w<1oJXzOcvs|`zqSxEQM45m%WAJb(CGey5cxd$a*FWbQd)E*AVap` z+jDS3daj?qZ~85geyJbRZ6%Wm&9zg&9vzKQgxW`s8yV(Don4XlK-SpM-(f$7KpxkUALAXODzjBB&@cHCt$=g^RdPEZ zHHDq=FNK57K7RnsEkG+S9O;%C)UgSW?=>xQISoX(%Em+g4b!*(oe57N?QGCljv(X^ zZs>CDQ{N5o*W)WGH5*E+(4g6G;HIPX_)X?_n6M(I*?BP`piR8)gCpuPX_w1#Kk?fX zLhk-jdUzd?p$@arwe`NvQ9KzR|NYJs{m$VV*90!~Xw0G6Hv(!D*x(Dk{ zJYHpG#A<#K&=!G7m5Qs9%3c9!x<8}U?p8|mM?opqS7~OtA6a^58myZFJF;;)PT?~k zI{Tpy3ur-N5z7Kf&XamSU zz-+itJ&UJW3`0nyK2Y!M8kqYVQ;3Gmg_`8t91~DWzg`MRJ`*WgZ7F0T@p4$3MSx*( zL15+|h5upA1y6$7lY7@sk$}goqdqc&o-YYjF5Ed?I%BWUN1?JRqg{J~Lt3sbd~-nxIs&sCT9qlH*ZnaDI_Af7{(v*?FW z8ReUc&2TxAJg5{Z@{wRaiZ}$qC}Z_cSe?1^;?i-eFEcm3h4pvY!livRxd;lhqj z+BlAi`;$Fg7PU1Mh7L*NV?J{pze*#M0<5aR%nq+>8`ZO`n+43sqeVrp;M^_W1G$47ij zBa<)fS+rZ47jIk2I6L_hYHzSPbn<5OX?sp~qqp+%nM_7)vFX&|p|R)=w@HAS5Y<{L zuf*2M;_WXzlzAWjapz)9C)x8M(ylwti&rNNjHO%e|6JOQ{&(cLN$YkOL$3T7y!}_5 zC~8hnyb6>=k)aa^Z6Y{`GhDR?hmn1 z;+A_H8k&_V0YLmc-Nypy2uN^0maxM#vhdu#Z_7>YgGM*yCu%2oItHv|w@H_&VLmj* z6H>3JAxLzt5Z1Z)3#%gLrG?6n)!7SZ6hgh1jSQvV@T=D$l^~pjzv9-fzfgwqGT;nR zdKV9Ut?PGK0ZLBrh-@pAk^LTYp{F!}(i1!)P~Y%3^A#=)RcS#)n16wi&v*cJFpcJJ zTe8{sR4Nir%s1J~6f0SVcl8?6;`CqZWy!LMP7x|w{Ju0C)r{Hn#3&B%yA&JunjO>k zmI}=6`VyH2%UGjyT*PX{S;eYRjcdE>rVh>&<`Tex2eAhqa&Dv$lphS!C?*}(=50jt&->I5rpJo!rbNHq%I;jie{)t# z2(XunZxuHfM(go$d+lmjZ(8-(+WvUCH#AicoUhRP>D7n1H0Nn7q3l?uoP*s5&cxot z!yzdos~68#rD9Ewmd?&tVt#SrOab8e0$WY}EMo?Us+e9G=w-h0GNW0s#(-K4%NrX> zxy^Cy;QPui2{Cn=3`9e=>n;D7#RzOk^~ILl6}@Z=<(ve@MPm)3#75$MzgE%DMN%Jj zl>$0GMAMXr{`hEsE%#IHHp|!QAKosE4@lPo44c*OL~tc8^yxzXFSDw2n+26tb)J+m z0AC0(`Z37cXA*)R$z-IPFFeB7yj3lyBoYG5LH@(}P9fB~bT-hy zc%?6lYNe^N^y{}54{H5+liE{7hzDETa5b<_$MhCyeC}{Kk5Ee2vJQslu{NbzqLNS$ z;r+tx=Gbc}*wIIpyQY>HL*%$kww|_y*NNaCTfdLK^y~A_3%=j924XHeRv4+ahrE2& zT21A%tOQn-n#kYWVP?chfg5pFdIIclVF$opp(joNP+}Wn(jovd!jT@^p*!Vc;79T% zlrEzD0|9&aze2M@`*b3wVby?Y#Be|fy6nXCHjef@FKS9n*f0pKBl>#X`rU?x5dGmY z$YICmVF#|oNrfzfAdL1aRDs{3?Tfm^3p`&dh=)5#Jrb07W$m~OKRuKBa2RK7#IQV3 z<$YWyysstq6`&-jd4}OsKtq%}$h?F32DF=tP=zz+yfDXMT(&)Fde*-i? z));Vs5CK3pw60WT(CpAfrkzr5bMT3x%%<;4{r|4HHy{q&nsYF3EXpR--LMUY1W#+d zTJu8Oy36_O!m9#f05=@lqS&axIPr1*#F%2v!F$QlQJ{ud0#OS7`1-3CZ)iz2XOnW3 zrWO6f5`cDY7>^{b`J}&M4uo&rk6Tqio?vbJAEV1Z4+T07O=~9bcgMb9tCW|+D(4+g z*28QvwO12~i&W^$F;Pr4{0I83ry8F!iFDK*WMhQr*~joR1cq*%Tz>2NtLkQ%|R1#E_ZngQmi~5zAX%vb?&VkfSht0Sf9rYs;<;whfh+ z3*3?m4EpIb1=SLNektlpMr*~hp!oXnO4I$&8%3QrInmr?JewgB0uSLh6 z|8x2)aut&<3n?4Qan=v2Vc9AQTLAL!Hs$$n123t`qY*B%IB^!Bj+t8oFgGJ4egvrR z0a|paD1V>i0|+N^C}X3!K-@8s<5Wfn5>j>@a>>0d`w8Uq4xFdk)dlGCgaEtPEtI!e zOI^Imh1IA&huhSz?jtAv@Cw<`!{bolC5Lz5wBCK_Ag)a0&+1mo|2DKG_=NUeFee1x zW?5G=5YfsneU>Vn)wXnM{7^WQNI5ifP3w|>^>RM_llRpYovSNuy^)+2OBPIXXze}Tad;j|Fo!-0HdUAz7kA{5}~FH zm~y-$5ZsMc=3<=Y7Z(1c+G9(sc>;sVjgYt&y^*&AHT)&v!&RBV_4cArp~wJdd$H#C zvSz2!&S8ne2CrM`L!N+#{mg475*oE8FI`vb!HYsk@efIr>83p*H0~Q&o<%Lrs%>@| zv2G(7v^$6BP)uEhP4WDbT&BCmpB89FG-s-4^k=Z8wr zesna>Pfsu}w{Nj2>BDB2)oVr+{Sk#tHP`st!Fh3{JgNnCdrHj%j^nxK-ghT5cxN>2 z6iby<>~;F~&tlkI>J$+mvIUYjj0v6cN62IacWX&<-pck$q}4O?cro}=psDt6tivE) zR9trb4+&st^5bu|k;7=g*q}S4i(ukX#8S#M&)8ghP<8U+Z^3mAmBp-&zG^K<9Id3Xn zZD1VS8Cyugp7qKjDBcpL5B9yH#;6DFxV~oQsL-#_3@}-Vnys_U`$$5m*>K0io%x6* zM2h{92}z3kGH!Ds@6)$VM9%|!jfZvbu=B=cVYwFt>QrPdC0Vc*zidf4A~Wg#xrb$A zi^U-iZ79Db8^x)zRv7-EuX=}7LRZ%+W%0RXqx(T8=(yTrgd^U5;MEpuMrj|1qE}$? zr?%$3>Pj!cG>mHHh##ryjDE-_yNb$b$Qo{$WFKXa4J5xe8obvyhk7R+a( z1su0y0XNAFfgg6uHG&CpL->*jnh|c7b(o9Q|DAWuIhyOrb6pm#&5gfjVevT;2gkd6 zN@+h}a-}<*sW=l>gv`!s!ZyN1R{zA-j zTaPkv=>Cdqt8WSCK8?a#+~kh%BYRN@iG_+UvX?-KSK!tfN#p_SI|!GHu)vErG$d^?t3p_x=?6ZxAy@|r;7vh{>LM@MFB02kbm06 z7L=RbLQc5?7zH^713MXa-SXU+N6&n|~QG?6`AhKabU@DFMbCl4P*Cz9Xo%o1M)*-WS_$9NuA3m&2 z`qDkR{-A4SSKg@=AHz5sJiCsgUK7mq4&6RvT&o5N8_m-NLyX1ND#hM6s^sO%y~;l@ zp|R-^0Fm|KQ90IiJG4CCg{!2 z)Sp%Svg=72GZ`1dejAUcNlPDgvF>F?=I7-T^L2;jAm4FbG2HV|NKBZ{SrlaxzvDAD z!6iV8izC;$92ho%)CT0CZq z^WgKjt98_25`$cK*<_Q?)7tx@oAx+j<82R1{ilEMM#awLvH8KK4Ji6Xe${|Jw(ZA8 ziWkqQ=gJV%#q+9|y=en0$(@_mzk7-Ai&fvuu+n`uv&rgDmj$^0j5&VH1b(%6558)J zM=PsnDS_$>M>TpuR~)loLi9_$kEj!ok0_>cFMStS2tBm4s&Bx^b zcnp%g@yW;xsUP~*{lZzU>oE9dc6yYBZqP%{!i6I5rL<;$id|f=Sd{-j_^5JB)|*f zinFP9Q@2%ML8$HCw%;Z92s=NCjtB9?nL^*J$^W(g>I<5)7fmCrKO>E3r`h{~*@wi# z{H4H8rj9VL%%w@#-g=>}GeN^D`Ak56d@KSKDaAeo{(0v5qIdKO z&yzM0y(qHmFlTY}cB-}l7!$=03hGERkhjGc>y@ARMx_{9J5Q| z)@f&uW`djMV8YsjA?Q05fuXjfD-wMSDEb%oI8$v{k6n9 zx-Teet|vw@awz#flM}N}{q>FP;r5lVOQ+wQc4b9w23Sh%ulCSH^fOVB%LIJhl@9BX z!|w8qdh*aUUS{Rx@O+U`|CqRlSJuvk-dnofr1|mJ zAkNL9VnVa35~3%ut!YN#jY+$kZ$y~~3bq?>L|+8&wVw_Op4f$(mo>ZxS4J%neMhJ| z(ooeP?Z_KmKPhVf@@KGdsK71oH-d%e3;ZR7A%6+sYe+j8AnSh*i1{u8{QqZTFnM_R zA;kPo<-y-*7Cbc5JnFDA$1SEK9#3x(Vu1 zIA>#8R}3d5U)dL3ydcqpxwefJ6mwf>3u9xEkilBR>2@}pk>CaRYS|Hfbz;FKLclbl zwCBw3derYy%P;{U@??bH6 zdGwy$^Qwyc$V)u-Yz3RzE{6{{;V))))Mw8i@2GHXuCBc{2@sFuH@sR7wI`PvhcnH> zjk;#v)e`yO$E9jPX~w#_1uaLbmxbl!gz1IVb)q91_^*&c*tb0o_PiMe*YzquxC6IF zB#;^+yCnx0LsK3~`s+{@>f#3kRra_)9J=FB@OF=UrBqAQ1V*S-rPsDkPhcqVeRkFL z=M;g8NNOn0caKbW+@~SoN zv5kvezs;oiZu$EomFvRzg}{I#v3)7!asl@r3GWCt9wXgk&VynBABwUSy$K`mLJ9vI z8dd}$rQ+dwdAIGi=|aot`qy_<2a+frLH(q_i58(2Tp%}XWKt)s0}^5YRbDQY4ZRNe z__vWjIZ5X&x@80o7WKe47xD3G2!@`^FzMT%Y~m=?t*35!>b(GNvD*XM>+i_VKrJ+r z$LqV(ZqA1L7bNhzf+i;jp7lZh#8@$48rr-;g}4Xy_IY^boT3bM3O3el5njUFWfK4_ zzh|!lM6O_0Kwm=`C_Mvo*+`+g4VXoUzge3NfZJoYp@#Oy6)P(us8@|vgw^!kiq?~D zd=Crby8TE;c_1>>kRb5Th&}Ax`-}C zFAd%wWmuQR>GQT+|)A2n&(n-t+X~q|I)oA%NxlipB)-wgYIJ|EzXKq&G7SqYd!Vln=slFtm4GF*BPCY zKEfWSEu!)PkaeCndIp(!1ocOHus3>sl$^m_)k!DK*eyT7CN8rJe<5&ttr z4CKE;p{__sqvvC6%Z0A9wY@CfC;o7Is5@~-96*PCF>+#cR+r?!+);OcunP%}o-|g(0M9}23mSHQ2lh;n_kCKxG#ELSUKQD}n{?qr-z1yK zF9QG#_VjPr$I%!?M^eM&}fA*dOm%!GXIgMf!_hT zRjRWPUgztF0x0KPee?MF>A~dkw$g6(cFo1e50m=gX#xJ8d#~TG?y5!*z5y?u zu4XF>d(3EJcym2#fO;9V#heYsrT@iM&&|HuNdT=*o&@2{Q zPBl!L^6^gpr=M21?JQ0@Z6))ITJl~xnj9=O$&rNKoj#{OnN!Bv#@7$ggoe(aP*!36 z5j3o1SFp3xvcwMe;nv6}jx2AYEU$%ai z`8w8_uUWZvyQ|TP#Z(Sh@U*>k^6PXg_|Z!Cg*=feulPfzpq`#~znEG!`sGfzfWGjl ziu1{9f(x5Ehnp|%#>Ea6>fYysE>p2)naTQJIuG7PRxtnY*JW~~Nh4IcNHvb*;@DO@ zD;ApXD#LR(uFd64&4wBRn8x)P;G?c8}(7{>|naTAU~{Z9UfY|{spK1 zZU76moDymB{a5r1sNZId^Qt~;j>tQ<@>%Oo1H8v)@7AL9FO0lZXEiNaMRyP4sJv=; zkWka41ubG7v-21BL@ix3a(UU-yTFV`gWJ!qQ=JWng;tzC4P$6x9jW~Zy+DG9xA{*) z^k1QAXTJb9Y8L|cmU~wqnY=-_fWPHXX#d_XiNMhkc=~@r6#mcA-oCv6^N$ebE-QX= z`Kk*WJ}#JAQE0|+xVfDisL^FJT7@nQIKVtL%@JU?t1ziA#vUS=4Nu&Q-@-jrd86Q~ zl+2JLKnO|6auAF_B(tRzN%GrERh9YPv1VYtY>M?2^TH2=Uih=v6IX^@5#9@)w~swmpqcMGp zMAva+8PL-~y~qEO!z1YW^YUzCUT>s&Qt83z0$wh=%S7TWS_uR&q5+1N%r0evUOj_Jbr5nLLVpM8^b`B#JQi z7l+s!yhEG9KNLpA5Xl_q&S9NP!p?V}q36&S@kwkLy~O$*XBZ`WmqB@gfeQ|eHzn>b z?Hrhay>X2;##}^^i%{kCecoxR6y}VO``tf^6o493Eo;;w8 z$IQKIVOY6=9Y0AiouH_*wL~xr>Rlrd;qE7KhNJ+n=G6(KI$oY!oh1RtF(oDlc1I=_ z_5ywavzwAw$JyeT5Jn;O?RYGFsmGs8tDR6(CP{_wLqdv=~L!r7`4Qt%ggoPU4L zKs;%G3;C(-)Vbi6J{wf@l*EXrz_uMmnX2=9b$9VAggI}ny3&xzUd(!zGFngip&bJ@(fF0{fcF zy|Q(~23S;GIXFp|;Jt&9X~}fncLVsnby{X@&y#-i+1Y9H`xypYbjP9J+IWVMVRSJ8 zjw6F=8Uy>e)GsORto|^eeK&TQtasogmx*rwO=&ZP7Ey^94UMp{se}d)+AMr_2Q|)@*)vkp(hm* zbsQEq^O5lk<6@c>l{vW3zO}mN%eM)5Ul8bB`!E)VqRFs zJZGPB_l=khY4P213S8*-yZKDbF{{1r zOLsE!kmU_6+;$jb5CT3R+<{+w*Po$PE5TPl!948a)n8tT8`{8fhd8naSkc9rbgS?Y zLF@9&V}PG8or%EbKQDUYK;DJh2(+&e4giCFcAtUi8Psg`{A-dTF5Gwb^ygW$&jvA^ z+tQYgi3A-YunT$wU4fD$uS{4B!#mwOJ{{Ib8jdKRg0V5Z?n7BJNCoIO`E}L!-DHEP{%-Ntqn8IpBtP?O?-6~*D5*tOl0Sr#c8}Uci^1oc z^kSa%zq8;`$FO7+dj|@idmqc+X_#Zs;1qSQ@?|w{+&YC4?c)~&q6+7aA66!ikeHmQ zAI=xnacKr`FyU_6a^$oqrbQnMVFIEkP`~nw$Wphy5qyZ+aGEnufDXwX&C+`^cXJbO zb5QxyP|`LZ_JU|`Q!Jm|XZQndoGk3y*FJ>wVNwy*imcbRV({M>YwybR)ud|1UOsp` zV2Y;Kc}CxZ_`T#c>0Ik(jHIxKzU@`=2jddW8nugPp(jizuGMv3X{7ARq2+gL4~zAm86Ie&TxmfxT81Ja?fntcp%( zdLJ>4N1y(b_us>&GgY?mYPEdaYPgRVdE>7=WpBU>heaF!?uyr%8t9-+4(aCL0-I`m zcnZc{Lzc~}aYEVxcp z#J_4(FQ^GPQaw`3W#v&6Dr-c~qbIOVch7iztVH%vwRntgNjhSFsFo2o{(S`sB}2ib zro zpKH~rrEpPC(c!XKin^Dc!D?3}Uf4v`fd(OjwjCZ!R zN84S*`nGjSdC3NEY-ht2ID+uS6f}6BBaJFCDTP2VD016S8XY)$OH2qd(qH#VA zJLQCqbr?uX*-#YuxN1y{l3n5b{-qH|h7n7{)Ec)PQSu zvAI4BUm6~c)TbVRQ^2br%%JYXr%jRJE(M`Wl^l>W%s%pefqOP&SzbKPzpy-7#O^W$ z^I?z@+8+KP{mt?Hp}W9Z@lru$e4`CJxIW9}&^T(dmX(;AMB1!1PAOl_I|aa~Lb@il zNJ4=MbfuN!gIB~mzuzo`zgH(OtWPgeNo*RKN_teLc&RO|7%t9Dt^uP;`k}+4AqpEJ zi5?&nbuG;2EBin)oSh;Sk?$>oYdq9o6ka)`;mE*_ex73)9@yZ_Zb2uTiepAi*wAk; z)BVn1>&04G34Ep}3iG#40ZdK_GpW;Wq#qvlhK_57;zxs~Ew7v`wn`&klTF+~m@lm% zd!W`ajn%}nEFd6nde|St_@!6j2fbHFRG8GyYlK((TskFdIDKMjLsy^3RDGte4;>4f z<3c(!gMK)5D9=ZS&u^zz8a(2gwMi7<#kb{@3hObc2dy{5-;g`pk*m*Rl@@ZIhN^fl zWb+H`e*2PYLwInSrV%-~e!J}RY;NIDJK3J%)1vx1v^w~W5vpDJq3K8MMTz5Q8I`mz zQy=s{?yCRb&=ynv9yHsiSkoFmAR^C_uyj-yA7b}P8R*h2YT%lARB)Hy}~(x1JL4O_vc#u z5+3^n?M}%%=?nzw4etsE06dEYfc|d*6?)7INHNSg8U5`$P_3tS_bqi#J+z#j=-e7a zv3hFTZ+8zLa<&G^HR2NHoC%GJIS%loR-3(>=tZKHY8qE+u{Fm+%3dX-@ockG`qKEC zQ_sGW=A!smg)V3*`E_C9ANdwIqy%&Sd)-S)8}{NS_rfEu9K!<}lisqc#4Q{$9Y{yenT^y2j)};1V7=DRt2x zw#M=iTFhcypk;i}FwgLVppXnBqhP{@Ld)h@deJH4JmQSuUVV&Dyc$n9m~OXSjS6@L zN=F(e;bYMMf(XW$Eamr!{hEgmQ&*zVbO^q`y_aQ?wu^6uDTgG7Fvsb_?Si9!S;YXW z|CQ>@JnEw#L9c241$_FD{x*Ei06x#7P#>Bi`mSfujR9IjeuB;M+>Qwi>pTN2{o40g zQWqfq7w}$Z9%OHDlG4HtnSu3f;H(5cJMPzkI3-XVf9C5oC8)Tq&R5CeN{sPHM|+Vf zlZK%F_p$%iGtt=O1{+-AI+MeI+w>c^wrDu{6{!3bI`Pg`_@zj~NcD)~y8=Wn62z^c zJ)Zunk*9A(en<*%%^tS8#cge+x*qR*@r6XA3ecV}S~7TYKH9?7-jb2e*kOnMq@dl@ zb9cnQ9>;Z=-6k% zPl)D{$#w|d4?N^n`#jz-xS?vm`nMM(g-Dh*PAe-V6WS&tM&F3n3D6RBealC2l)9;Z z#k5alZC(0BrWb2y|77-0`$l1zEl0yK&|2KpQmkaV;glHRl1C8WEoxg8Sw$P`Y7@!^jR5(bnkK4U{W&!3*6KE449o0e9 zEy9ET61DCI`oc-|`w~PR>Cg%F+^i}gnvK6%+gpOp?miWHy>iB_Z9N=1T8d1( z-0dD(L1fNj4+DRg@KA=)x*LzgeJ?MB_?(H;{`p9uA({F^;xFmtB+M zK34IRmO<3CALqt85f?!!3wmu6O6HZ#mI*{t8P7W$O|phcjD@x*D+wbNl2N~x1(8Ts zr`ItnA7+=XSI#~YZ>O@m?*uv+7xVC2@-rjp|Bib!J5m%B^DBkSmo6_yXKXW?Tb#F0 zDj$Nmh?Paif{(y#Lr6#MAe?>Zc#EQ&cA`1h_b=le(DWP|9&`+V&mh2zx*Pl(b zKI1qFS`c4((s}Y+d9#`&354?>LyP{P{RO@n&tMcWkrM2FY<-PnQ_^@ObFnk}n8d?3 z6LyLIG=uY;SPLy845r8Wl0T1E`6d<>a_XSvWL{#qw|oqsW#uEHSWgy0GdHhglFFrgsBE*hgH_D9CYPjL#!zq<03 zkB2+2G}3VW5Ru&Eao(6118aZkUHI)8Fu`|~Df8|%DOY3r-rQddjH_xcj5;jS zOdK<>D88~qp7_Sf#LAE`Gw;P9_&5FtNP}h8O*%5ImhXa>soQ85k$n>f+Tm>RO8szW zE<;92ua%;xXmsPGuIZ|&>8$Rm*=-bw2J?9s5tb7b@v9Tw(&ex1j|dY&h-y-{P2OzR zBnT-}UqVLu5VwC~eTmy3M?a zi;$rP#{!_;HNaNs@}&Jv=(ho2t%c>b2E5q?v!$M&0riZFf50KV_{(tBCOk8(?4*&8 zw`;;#KbF9iWiM%KfLy?)%L{ebJ+zS|PUkn^BwSf{pIhhlK`*lxS(LKkcKKK|EYeA} z(tBD-_V6ouDXrh`XBC{tuQiSImo1z-0R=bV=ZabpmTZ+Srn9JTWPXc*r7~LKaJpJ6 ziQgP%7|&kyuiiFeJnJU~#7)3B-wfjIOOPyI_9haIhYQ_y4oxpj#nl&>A%te}Dai>L z`n!G{Tb<<)yYcu$fW!Z7)zhBgd^QS3IM)->{5T!DG{s#%b|hv00E4WEafP-{Pt5B& z4}&GrVs8zRsfo>q*%{$-&tJf0{bqTyg6dQ2af6tx{Mi9>BzwBJo|I(GN4MTy%wD(o zvi5C@0~Ut?*}ATqGJAXeCR>?L1W02%K=8TT5Ii6#KY~ghKwc1)K@?T8H*TS%@ol`# z){b-FJm_3{+h3q;E#-SnCM@FyK>y1< z_(VSQ1o$F<$o`RFLnh}F%TL0_2{tkVfB){MZ6V#6F^42gCiv!_P%&C|Q}<$v{g9i$ zyrs&ux9v`kDtf(?)U*5}r_EJLu=7O$vx&^#9;leQ7yV)Ha5rLA&<_;1!^ER->tj&< zOx&Wn2fjnkK;tmCR8lG}{1S(qCP+)cj?##6+@29(^n76FMnoBn6!fF%nBoik){ilC zuk~m@0S!MQ8gYwZ`xXnGX3(kUb28@%lE^3?p@A5=BRoXekANeFehuVl=g{#nW z2WhR~MJKST$qy-Xuy*NAiX2$Pg`?Biw6@zj0tkSS6jqV|y@;pYYfS;4v^F#{L{Hsr zI}cvCarik{r=_FdvvLv+c4j-v+xRhB@{6VE#U+)Y_U#B?kcgpw|1~{!IC?|PydNBY zI*m=0G|QX5hgwQ9ymp5V!hFaDZ#`*hJyp`$eP4J7ZhTiMvBvvo?9Y(|Qj^$i(ye;n zZ%CcHBXoRJ3>{qccN+BB( zjtgb!s!Y}$Reu5(cdJ57ooa*0>|Z2-nza$RqR4@Q4VBsbO>b4Z3zb+`OimYjxFEXWTA$CMrMGX7nL5_E|8T~*#sS|^jO-yYh8o`$JQ=fj>G zaMCx?W8V6ait*=+TL&lGuQ)v&#bfPCc8ZFq1jS{jeK2$j3nZ_o;qfn^cadIh8o)t0 zV&45Rj6mkk+N#%qiX{VA+^!O>k%ns_*uO%8U;Eh#6lNDC$!pAmuTF- zB-2BtyvH$XhUp32C$MsP#8WDZepmh6)W8kHH>nK=x>l&B+@VMhd^55fnNUAF}OZ7bngNXq#cD*q0 zD>H^2{`-We6z|EgEGb&)B=Qa>hR1hTL&`MRa}b1DF;_p z4kE^a3@g%YSR?2=Ta%K;ZH{aPIVJntT}w={Z>Ek%lb9n>+i=@8#zO3gk?EAGpFHbTc(&v^9REf(rlT{0ufJCQKvNjAZoId~Ak0D<}m=0Ino|p|7 zQX@-68)1)il5bV`d5(Srd&VDK)!2;NH0uqQ-Kmd>PbrEPm9g_4#xymjwT0Ge7pFc4 z_h)_&=PG23Rr+aq2`cLcu==5;Qy8m2$kRppa{L!P$alQM$Kv}p%(cv%RWxVh&aLd9 z`W>;5Ik+U3rE!@rYzVAqW=50Q(Q6O$a*a$LZcTz>+yKI+RPkP)iOP)Y|Uy8`;4)mj@b)N8=q zBfb{|G(#E51ZO~+86n-J0)i|zXZ;ujH0L2y$L$SzK=2kS0PQJ_Lw*`Rpa46j*%9wg zNZk&sD=2{cS9PNomMd?-CTh}ThNhE9x0=)0bB9wGhoiP>nnA;FD_OCZ_!)`Qb8Yk5 zxYb!r`J zmXy}u=^$Dej7PH-BE7-ns}-i?8fNmTJ=OP~(W}d;I)Z>b4R*AB-6`_5IUr59@s5Es z@B*|I8^Z}K$9q1VwJMSe0+S^a&_V;;D-bMXZy9_dm;aI+;?mz~B!crNDZ={b&+an# zOybfTVw|_se()R}Y>c}69L*RHd5Esd9~A=(o3l13+vkCmo(@i%< zt2eTm(~BM3$qjv*OC`1MLIJFLyUk;Bwj@66j99)B{x28DLxFi4Kjo{DvgH97=Vlt7 zL#mlQTJ3s4{){6&iK0-eIcpQ2COw?k7aK*VrlazQv$-cCgVqU}zK#*8?)9L#h(2Z( z`XEml)L#KXZv=^yGg7!MxE)~_ZSLiSVrX(hI&udC;dIl9j5SvGnadVXkr~&-XIq`= zq8Pd*V1(kgeF{Pu-X4?#2_t^1FnB+r6mk;94q(<$vcKmhkhJ5Ic;E%qOQ&N zrqpZVF&9t5(yEj&|6!LY5l63+TN%VADkV=N`O<(w3pXKQp*=M)f=Y0eCh-q$}e@Yj9)!z`2F^t=9-pAYx{ z{XqZ7X#cQ%gV6EJ?9#LKqbIe~4{-%f2W9cwy>0>0yqwE5>c9|Abr|7`>gxDzvAXyx z@i1`1El(K0M{+n>d-AllP*&%jdBu~Tlyk!CKVaL+{vQep(3?33MCNAR|DyN*VoB)# z2Y>qVzb(n%|9h{~M)v<&l>;s9|DpMxOvYmV{$D#%-Tq%B8Hk3W+Y-s{?y$fA_s%Ep z`+pnN{>|;bXx;peCzJm9pSGlS-~Tp_g}Q7E)-xG-b-T{8(Jq{=tS#V^lg%v~KJBu9 z7pC^_YLCCyE-I!XdXku6idO-^e#dyX1J<6UQ44I7bwu z9}`haRf_3%sT3Ed+iKN{e!^Ova9AxJM*Toms`rd@rFwCy6U{pDPf(|Y!uK$4IJIK` z?Jfwf^ZX}{{}D|<`5%t^_FsFFZ~wj5X(RSut8&1j{YT9IL^9;te{D&m{fFXzg=2w4 zGMWg-yZ!fn-h<@b{@bYbZ#w^@;h25?$C9y-um9VT-jMyb8KGsGy$OUL-<`d#u9UsT z4x@Oyq2)$cm_8C-f-@HL#+ zg_s<6XUV5#zpY+;z=Sy*U(T^T^a(lWj&N$vUG?lGo#|%2tt~EBuN~7LfRV4IbBc~MO2W^Y@IG=XSQCPXn^U;#ypu5nlSN{zvCeZld1K)HqJvCw)O?56` zkTZeltn>6Wj)jx(E#Ocj97+22e|wVe|9`KO!~C~g{+lERyq^EE=6^I4_TT?# zOQOzyMMBZgwpbt$jwK@D?qtIE|G)FeVg7Hj_HR1>W65yBuKxjmum9VTK45kR}FM z#?n36>Ef4CPcSISXiNBPJ!hxKamNUW*rBD+7l%C`Zd|D9-{W*{c z>fBLM#=HMi6bgrh0(^t;D>(;bR8i!EBa?h?Vv-mC1aORu4DJ}`rK0dB*HA2~mx+IW37=RrV=tG52dGwzTnA*#qwR3m1 zUvJ{>g&s?y>dNZ8dgiiG#iR)Ac@cLy!(_#rm@Q>FF(ZMx!XYD@KZ3IJvMg~$@hk8x zZhQmQe@;M-NDitFxuo09sBG#|8Km)Yff$Lp(ApWW*w4GDrdb z#AG5SU`&3%T`w0P_Gofjd6fXiaT$swV^)TvsJu_}}fp+;sW#wsQ`BY`~rr8mYj{zmP<}5JUMYvyO z5F%hFdkTpmjd?4=pVx&_!Ug(zkN1pbpF991y9@my>|1D|0qb!>0+Bj<5fC7g3xp0D`TdNDTo+k{UtTDKN!SXlOna3e%|( zr6A`fQoG^A6jGy;Vlg$4L*+||#{fD$4fIlxQ{+QJjuVdL#RAe!Jxnr!r7a{if?pCv zj57}X?+)b?p;)&L@d1-m?>vO<))vmHr*5fNALxz8Vgt4xvsibiJ7gaPFa;Fhh{EN0 zWwM9C{&YbXKE}X6sZ@l$aA5RfuDii#q1PD999n9q=T28wA9gV4kzCNKB5X&8Go~!y zUk<+OTm+eby5OXem00xG5#x`%1e87gGhHm=KcGt&@1?PX?sv^Df-!;JcIB@6@`~PP zk~}e?H(i47nPUZx^00AzF+>~65RJ_leHU@}E6Ov9NDhZ!tUfq2LGQBBO24>Mm+gT(FK%Vjdm zC!h3WvpvOPU}R+E8|J@0R3;0+Li%s!LoSY*%X|nsKq!124}HUM%!k?H#D}>psF%=$ zRv*o)-x>U6w561nWIofQOK`FmYR{I`7sqt5h8T=)E`}`HS{RSSBHi7Xx6K)^@A`pR zpa?n0Jb*U>N2DSP|8s;5WaKpZ5BM{m8D|4uiFru&-sxTN&0S_lDwz^FJ3R*TxID<~ zK^&PI7bi;k+`{rwp5Vl69w>5Kiz>@!wUZarvyYjPzYpqk=ebf2SefX3bud@CG#FV4 z{I{sj1&BjkJ*u8xsw|&YXMe1gU)I+Cz5Z}j{r#~ve^Y%7duT$E$9MpRXu(8e#;Bi& z6toSyesrbw;sZu#G@7MGKFeq-C%LwohY9KcS3Cya_PN$D(y9_WB^(`kW zw(lX>ha*!w3 zuWA5z^#6qt!2FDa!v6ZdoyqtAz1PXJ|L+y#fJgryG5;fpaK!ijwIfmIf5Y1%+Y;eG zC=rcsOT^+)-~ac{rzZZtP0;?${eRJreg1>;{{7FkBv1Z7xC)>9;qCDMrCx^v(B|WV zcB{>)6pln=@dR?UEj=@R9@=*gs@INb3;$LZ9s`>R*fx}ZX;XZ8O?`H8cb8q{Kg4AR z83c7A<}~%*ciPVj+HbeCg_DN2^i1{A)#}6YW;^b>soVyBJEEZJwlllo?Q+}Id*Awm z!*=JJVO<;Z+VtJMN6xm*@M87#?>g{TduL)FR&D8Cb>%p&0fEYei2xRg;{_lN0Pi0P zObG@O*CJp%O9fGxw#rc+D|i@Belt!ha?)a_6?`b@*7NH1Mewh>cB!&_+Sqo$qZjy3 za9+Vd;PZTXQV56$ETD5npr~Ykpb3m9nf)bY02D^O@ZNj2C_#d54|w-%nR8R0-d4c? z^k2LCyb$1uc**XVf#7P4e~Yka~lmmlSum81BvmjgPboP4}5WM}PSu=)20xTiUlv z9b#@=*2j+=H`A-AH`9#5(8HK9;qise>f){H`Qz&I-_Fe2z#WhXgnWtrRwQTp zKPygP&)BAT0$R8Kld)*rxBuIbnvMUb-Ki9x$)tev5^_*@rWELYD+Y>RdR`ec*8kCX zB5sa<0y0V{5)Z|qzW#4V;y9-I%g?p{xeipWdg?*#)`O1S!~MOZ{mf|ZuEBn0;A3Xr z{!ym?9|I$!Bg_|QE2gM?!E_;k{)L$Ng4s3j*MWVbsMx*(gM&=(fzkb`fqekY-hPn3 zd;eba|1EG8adB}Aiy@&h`GWZr+7`XTUExq@tA)V?!KC3RO9mjJalT+i`~NX&FOEY8 z8O3#qQ}A50RbPaxuCM>&-UEXGJ`2iDO4D2H7;%W5KUS~l1vqVCRxRISrv%{;_@(~v z0RAfGg*4FQTxI#f`mN_i{gAyt?ILV#AL!T7fxZ0>Erm;)`=_-lPuH(JF+jnZs5u~v zcJR{;8W8R?ifJ*==af`2EjMpCFG)nB)w%B~%jarmzE`h5K{0A1PMvvBS-Zey%t%=r zjw!>!O><5=b3$FbW(|;0C?#f!Uod|g*}u<1j>{VFwy3jL0LvMW5Scp+ext_@Qz!vx zWCy^*R=a;poxO@zMrLtgiZ9gHpx(W#p8Az8YW5;zwG;-dgF{^fplMJX%BfkeXA1nd zLNc2XHwt~=YG+SYAG~CX=}9328kynL7c-bmSiFwqYA>(XZk?$tKVku^zyi;H!|cGO zp@A()I8U9stUaA|VvaP7+WZAWO<#k&kiC_aa#_1%%_|rxO@_l|N&u@o3c3c$5A<0~ z0e6u=+0lW0pEs*d4?~5vv#9z6`+P8fcOMuY2B0aJIWpS2cgRi*JwZz7{bBFOz>psp zg5?@jq+NfaU4BeeG&DT0w|Dq+W>5d;OcxZETXFutzP|o{Kufqv*6ub}R!6q9l&eVlYBM4paP!P2Kih~Jm zE05pXB7~yiI9CiwNELKYjy3>VC&KtKDR@IjpJYuZ^x_$9_B^)Ih&eqD*LRS=yb6X& z`Jy&|L%q9JTRKMI5%_{MZO{XSr$=dMpF(vo`TA%R7a6<$iAy?qZjj;^RLuBfxeO%^Q`VC=+H zfzKWCFivY1K!?Eagcc`ZMU+ZQJeb?93Djr5t=~Qlv;o$3HiSfroG3vcI3k=AC3abA z*Q}^sS_r>W68*rwfxjQ&PFVYps;|_n2=4ZC3NZ~I4Q#@sQRL&;Q< zt9O1?A6?NF{|6o{X{1?DPfWo0vt#;&Xme5K0k!# z918^oha~|EZS&j&2ZSy$8&TEXtSVG@1FOo?YLr?-V6=4wbj5_Aq|o6Yn>e#qcXZV* z1D&8;g&D)AP3txstCwTHEP)k?0Mr*djkFk>E)^9yOIM0l zWRB7fA$Kz7bV#r$bBA-X<-C~^*dTDSCF)ECbD4ll1Eg!&anKbYLtFTlp3%U5FyY-r zK33pVGd4xZY?8Xmitu!vgGa#Z&YYYhI}@x+){p;KTYP~|yIq4{SIUSXS^E zNdz#ywbR(z@uadmm_+1k>LTaXChIg>b?FLCy?%Rr{>IRrW=4)8@Yz)UP`jLELwh_s z%7*sX#5kmsPuYzWCw=#ucKNy8X3@bLSuG@>&31vnV;<_RJiqEz_gKTaNkF>pH{qPi z03dvr+3rjl+EZP50+bx>OVT^nqrrjunYKCfF7k*0_m%?&K4q;^JQLz5Q=j zAL-ld=$sOBIcR%XLV=J8wR6vE-!FhZ095pHxP#eU9PSqu&^5)ujq6{J8rZUj^C`PM zU}D?*>5$zL@cpB0^N->=g{)gzkGT{bGmBbkXLHvL#0o)QGt^G}OFi`fPa(W7(QNgg zFMQ$tM!QxJn6Q=&Lkvc*C?MI5_S|g1rXh@$)tt68r(L;g?5}}ez&>Kl%{TCIVU~r; zLpPQUgBVL&Q!o}pc##BpR5Bu_9|<%k8t$ocKWIPQs;oS<*&SZC4UuBqVi%NVlPEKO zjL%F6K)|h4f4Rwy$;u>GS-w*{IaVg%H3@#AYJ?*pxa zO>+nVD5$-7p{~vuXe7R<05n3nR4*)vd3XxCp%E^dO8{6c@Lrz>b-g;T&dgTJ_kHcM zx!Q#gQ(c72a@ttaS+2|EzRgt`jtW9o9@@*J)_ni zkhNy$7>`ZD`q|h6f^Boc23yz~F0-9x3(*RoRr2tH!HGN?pEm+YY;^=0J2@>{yl1QMdBM~f^z>2QbQJ#5PS-I>)AcfB3 zAQQ;y$vZ3*#wB?w1z*Qego)|qt1F>ETE?}fGup+a>Z8lmOLyVYQIYeU`t5@D=r)Ur za@ZBk@+C!vXEK3WE&}ry>yFz$tEbGx9V(tujF-shwPZKZAo8U%^O>{PxBDQ;Mcr7R z`F;J$Bep0gie#GfB=h)7oK0Ec5K6d<(8%=#p}I`zI-@)CZpNP* z+?S)+s*B>XP2P5Mnd7<$SpV@P{1-W*o6UT9!3nIv(RrM4apE-_M5abuUzx8h-qC&K zgsw#yw2{P5;FHpByI1&5R__FQan}UO=TnD-=_WAw{2pPN1i_J^NSWXWg+oPI3(Z2l z0ezL#Z^3LJrh+^sE2$}AY*Ln8XN0y;R{)h_@jr;v^y zH4dg{sZf8Y9GAx7b?99X95O3~LSVVIxEGZfg+;2X7t}}dmW>FqQYdhiyAuh))n1-} zw&=Tfh;CEGhfO{hPL#PH)tS5M+67?Dn*yd|VBbjp@F-Lz`)!UBJkeRwi@`FsWf5yL z@!5J)n`K6*tAwq$4_I@tzZG!s;DP>;j;`JN_wDW-?fSeQ zaPz+XpLK2B$^yoD4u+9IuQXekpigZpwk3VjBZSs|LN zU%KJM0!xN9?T@YGKw}9^`qSvKrngtj!6vsmSaKVKb=pv1>9rPX^WU?EkDax|6HEAg zt)AY3cRa9*qxRwjkZxOGw=T`8cOK#m+IVI8U-vixP&`-VSAsE^d&;nWUk&RS6-afZ=Rq? zvh-xOt|hunWa=6qOP%~-iNids)8#qVxMjv#XCo|e$vR!BB4&0t0=N)wis5;qo%unV zod+%1f(55qzK1{K_JMA$luxR6%H$^u3095j+_OJczomJ#1L^A2$NPu(0%`3iw7Ghb z&4t29Mt}G@_hFX%FvEmGJ?P&paOFSPJ2IN;>;2p)-=Pmq2O4TH=(~+$pq)KmccMWA zbCm6z62yr~rL!j*`i4ckFakPySU4;SQ$#_)jAqG59F#=f{N3h-5u#|EC<$@mn;;}E zqC`0U%{Pd~|N6QU9+(C>tW^%o%1*R73OEsWu6q2Ydi;9lH>^=SVigb77q67B(Z!=? z@oop?kN!!QhOO3%N6F%q)%oiEqvpP8=Qsb2+MsJSmVtBOb>06BfaOj$aAY&WytpQh z@!ukma6Fz!M43=H8cO={U)q!W_;2rZvVy(6J~`kq{u?p>V~L3W{zqF9_5N?PI}#7a z1KsgNG8BtN{P=J0etP}dzuEkUlRaje|B*z(kN?`1wEZt&^o_t=J37OGP^TFfY)2;w z4A%LVogLf3v^^-K6$X^f6?b$_DoVa57|iluE*qiJ<-$a8X#d_|ED!=%I*gSB2I)Cc z6iu+Hsj0wJ6ahp40|fuEcW?w?$nu<+1N{tS0|{Ofdy44mpp2u)d8pn3heeA{Wsejy zIFmyV0r0zXCxY9al{13$DejY<=zgx3LLIudypt*!Rq6-Ga3j765E5uPpd#dka%cC4Drfj zP;Xt(AiIO2nR?g2Mkq;SUEIEU+Dy8_xbG;swnGbjrx`_#*=1ls`M3ySyzjIHmD5h$ zR?l9-q2;Q-uT)oVFdUQXO)~RDY7h?AHb?35Q%LM z+OiFfC5F%7kUYE!l1tM=`hPf%89}p%*#%}1^O-Qla2!#u4DXQ7mXmdN%X{^_wX#8WS=H)f2NvtmXrd1{X( zDXGC|&@MxY{ok*h7LCX847A!3C|o8)1+0<-M?_wp&Wc_}TVGdk zU_5eQ5=Ozu9|E6w&Orxw;s)D7tyPKh30jOX>a)A*%ro`OOsk!c3*hCPho<9#94~pB z!}Kz-)piZa*)jRh2AES{aZ3o^L#vE}t}T+0mBLFq;bT_YgXYhc7bT-YQF*g#61ctE zRLA2&El3=EmWLVX+p~cg2$rnVB`*`tI3?(T_ae~LPPWK!e}^}QzGC#_9gn|P%lF>w zV4*8aU$rh8dy)Ty*)3^0|rV23n%KVbU0lhc@UXISE$y%4F}BB?-TVx{-DK zhKWyG)y6zhqo3mNVXL>u+E2@-T~6R6m87i|wV7(!5q19y63*%nlQz57TtfZ5j* z*LvYa;YeC4WrRk&q8E8f*=2jMA${iOS08F4r)bDHU7nfVTp;J(^q!EP{N$(iBmI8@ KogqsAP!9lePKN*h literal 0 HcmV?d00001 diff --git a/backend/backend_src.tar.gz b/backend/backend_src.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..181dd24d5c2ebbea728caaebe8fcd33e3fd11a51 GIT binary patch literal 125281 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
=Czs2tG~Qoz43MR z^tH;FuMiVM3bQ&6pq`dTiu67FOvCHb`TOVpsQr8lx#lMcmGb`fBl75RkT zB>PYqgXioy9PGXGy_jKW&-Oi=c$~2NAH!AimzFB`&tnS%+r_-lIyEfBP+BaabWv9EM)3sDr7fjI3F6GI}7L zhwCH_lTyfK__tans+PT=hst=EwtVqhjS}&!cLpaQtc_iYN`nreR``eDIDG}%CcUlR z_-f_q!fz767Vq-C{^M)MP9bk=E~b(9+3`HEFm{FL6k}pEJRZE+%65E8Vdx)Aw;Z8x z1t61Joj)N$$!a<8X8_Rhe|8nA^^EHTsOV5Dwj9eWe^pUuM>}*R7 zY!8S9jcCtimD`~O>QKT+I;i4|s>JxMFv1$+K{pHiJkWAFXb4$3k)D*R#_@Y{l?!wn z=3~Azti^hCeR>!=dATq?o})rGvn)7kq}g3bWLkcEKHf#qW5ZCqNg8AFOuRmJohXH( zXBSaZfb&tmFGKWa2lsfmS@^Au?yfBTHKAcpG=MZG_zyCqb)H&glc1F^fg)qQ}gqK=-JQX=pNz?v7%!VB%RkjSvo-uq_FDVVA~o?9}~cCvMTq#uY7!J_3B*p{4MMzG@!~Nv{^rU=&jFI zZ=OdF8W#49f``l@yO08-z6pC;iVVUIAkYPoD(M(CL|iyQJ`sb^?iqQD$R1m_($f@f zMoCJe+)Mr}u`&6RSu+YazaVmaG1k8b4Oz%{Tiwb+cK`yE%+6?`Wh|j)gRt-BkQ|#6 zp>i{4mrla?b&7%WgGT}OP8WxH87}!SPaGs3S%(z>>LBo8dr2M6d!QbOOf+G8H&C!i zC*wedyZ{JG1%Xh9v&y}`p2lIZ^Hd~kYKZTNI)i0Bx3wq`V)&AXSPrW3S#4cn z5=#j6c;Nrr!z{z>Xz>aKrB-5=W_ePUF`cnxu6pAd5{;-&Rc3GDYNlPcX;`(=bbb_s z!&i%i34Y?yb#mymw5x$lCOccbZ#?zK%y2QZmfh>BZTFbIWb$F zITV7-T1p|f`U};_PkU7xo;X)6k*0G4c|J%FnEYjpbbfjlEPW_#_;g#6MFn|Y5X5x^ zqm=}n6?$wE1Kd4wzJ!-*9h@^r*dU?f^BhdbNPIb<63xY6Zxyn67P&@u z@}|}z7%3GCg%V^T1uRcu5{#s1??ZWO_Lg8Rm3KcvBcU@C2N7!-$J@%=8boiz5Do<0 zLTK3TGua|FB08a33MmPK|f}nfFVzCH5#Y$5nC0qmIpnsGP#3o2X zi8%Y5vx%^A0BBHW!cuQ)MFl|g16W=7W$og^^3vBUC+4KAI%TI^IB4g$6S0)77eWXa zzSYz>I%=0nLy$2_0im@@rQKYjMa-$6Nx*I{uXPKsQL3aqCuTR=RBZde?mrCm?R zZ~xG1!#nov+p`aDf55vp>5Pe>s(k(Z>QC3q4$sm&Fvurm$Rp0fKEd3v%Gv*z%99Qa zWW3t~x@n-qX8X?r$lM}?^z*0>xQR~*T^ay-j0fUdd=;=O1v&%WwqS*@=g3hBx{>CV@1AB> z{!f2ez4^_`(s7QACv-FA(S^)7m+7D0k8Sel@N_lEf?{pa7@R}fMc5Xy6OByXIM!lK zI5iKFr<)oOY-c$f^7B;%ogRcRx2TxI{A6Zt;q;pp@5xv=Ie%Ul6!9sz% zd3Q@^25<$yl9z70lhd%uDkkTug9g`8X;u0Iq#=q56-j zC^i0i@3IK%<@?8(hn!JsgbJwXh@;5|L_5K-BvJs5kLD`JNPQn0s?j5z+_gFE+VbL0 zaMj>X-&D@r*@Q4bwuImbVbpN6Vs(37$?I;uWAu%Nv|>-#B&xQmoLX-)eXB4s%q|0S zZE#xDdD-%@mP*xZvLf4uMC6xsgAHMT>ErGxW|2aDZ~*w^#lzXADRqp)0fz=4_GgT! zWtqYR{EPYKGZQYtb$ECaZKzRv+{@UnRg4Jj=ninY;r|xziFX1duD_OseMf*g7N z4O{KaVykPmS79PM-_42Ih*yp!Jr74Z2Il0=iALezyd2L%WMw>z@F$7^m*0W=ZRvc2 zeDvU$D0#uP$Mlp)fE*4<%MpD8Mu@OiB{vk+`~x<1QUmxPWnuuX0Me3e)H+vmkC?W_ zw1{X0Lh4wH)FT3sfVFz^)5`mIS5Ev~T{u~}_fBPTF~m~fBXdI%C{IxSwaO>(HG|}LmJig_U-H2#f@pceTnSev19x2;EtU;whaw$-?4XS6BT*|i~_Zf zzT{I9>{Mi8&wx#LegIEb77gJt?m)2M6nYbgfsA+<44`hIl2Dr&JZW8!(ECXCH1awR z`71R3>U%0?t(?0;7yz{t>$LDPv(&R-?qmRY8*chWJAV+h>+Qcqd#q@e4h$Kp-Y0yn zwFT*c`HZoRn;6_sNJGc#Re3tnDE+S?huH6^_+ZG7voD9fG3PV|axwLV={_32D}4!6 z2~>|JL;Y;EbE;?0ru-DRI<*p#^YU$L6!@X$f-*P_(IT#LO$yP%w=zcy6O*7y zaef0>H%N$PvevVHhW<-3g(I(rI_RhFS2~}3Eql#BelMWG{=pslhIj878s7151A{}_ zgMH!voNYgk0$H2VsKAJyYin>Yu)x+Z7}#49LW?os^`kjWoB=@wS2|ytuIi(o8rp4& zHmcepsJKAIbPqC0Wdi*m3I2;y|l0rC+cFXv(K?)b?%B#()oLo4dhUVR6JUuq3WNY9Kzb zb4DRG%$851D-zC(_@s2f7-C2qgd15XRJ5TQy1ut>aPZAN`?jMvRa@72Pkb*Ysuhge z2L}84ckb9;lLpmRpD?x6C%OJc)hA}Rpe&4Q{NY%b;ABW7(71+$v5D0=GK!=RH2`(U zDvZ#t(R3+OC=|nWYa3?9?pQA}t0>AAYv(Sl-n@yn(aPD=D@!*j3!kr?z9rRBkGUx# zwOToO1>z~*x?G+Au5xdtdhS6)9a&wnwA?5JQ#*IFI(KyC@_Q^&Bo$oMP#~t8e<)d~ zE`z$ol=~c!^;B>G?4L$56WNgWbh-9e4Cbs?KiUoY@t%DH|F6VF4L-%sa?W)qoy%q% zrr5L|keQ%0;%zM8pmOI22*5gjCE&6Q)d9feu?}kiJDu2Josh3i2tj;aE!)@7?6PY$ zT~*f5lzaqwZop^}eK%QI&BlU$2hTUAjd@#vzvG7!3=wXW{yQFUV&K%}rLTzeCx%C} zm^UKg##ZOL81N*653$0S$i7tv>_h1yEmUH6%?!HkgES5qPOLdnHYBmpqB(dOPjrI- z^1&Vf{N;@?u5Mb$#h7L_O)~4%c+~qwxynJ!H%Z++92s7BnFY-kUS&l3RgVSfb4nu;XtFU?l#)Tz z#Wght#;H?Oh9I>TQdlO3Qp_8B95!e2O`yH?<8chxBu?kPlX;u`o|}1eWal-4xTQ@h zd}a1d^};Pdtg3HvB^a#(h${{yguvBqGp5bhgs^=PmTP+X5DRx0_S8vn6s{pDOzXb@&*T9%( zq?T&|bO1AlZB+BJkc{%U+$aAKjo=q>XQ^`X3zXwL1C*~d_TD8ReF<};04j6VCi8sr zq#GxZ+;Wu2+3_1)9-ZG5FBU7wV2Vmm3M%JzSRd9m03VoBF|X6OGiDCh7JMs#Ke>m$ zq(B^O31Y1CQK?j{i45-pgcA3C)>p5e660u*iP1rl(TanwWvFg@v@ddub`ekO8>*pt z#3HwD(@@pGRb;@+OV`Mlt8?#H7A`xpy}YzUmnwC{&K*GMyxW0&)Tzf-=hp3%$~pIF|aNjy2Pt= z9dRT5s$TfD|3e|2t3~G%lfVAg74MCwQ2$GI z$3Yh)A4!3>{@051i1k16G(lt**R1ESCjur0=Kx$a<#MzF#kT0LZ}NI~`He=%+frQ= zPBu;D;3$xwTMpt|JM)P3*m8Elj_rg<4cpmuH=EV(WgFh4bFm@zqc8K;6!fjMIY=pH zUWcD9RKIwitGo59z#d+wS9@h-p!Qo4H}nOB%;t={3+3!sc9cSucOkF7Q&ngd7=l8< zM@beXEj|islwL#xKcmjOaq=I}y``%QS63D;RzAF5oxf7Oczf;QPtI2|xua7=SFrcm z#cUx1lN_ZrXmmJu4@$~545;`99A1--bn^^s$1VbWKO{xU=Q;M-httK(wp^ODm(kXt zJ2LM4kmrpnJ*IvO#~uV&YYtID1+Gs6?TNFG0y}m5%^wPm?R&FVR2!!(45cvDTH?+F z#+R#nGzT|id?rncF5b0no{Up2(JaBj!{G8DzKfA}+HKR);Vj+l=Oe||RIzn5U9zo6 zCY{YqM>Z+?DW5|uO|XGX4rvh_6?iYA9kFzoe%Uj&Jw2^GU?*3zBcIV$LP3%2>ae|-8dxZlu+Mqo&!fbxN60C5Zg>(kEef-0jA8CGP`2xzSlsQhJ$ z^wchD;R4s|uR=%yfDV={<96+Z=Fu7 z&s$yA3s#~N{vU_yRf1rNlP9(o3jAV$7)YITkfmr>2gJslDh=8ofWmp5T3|a(M$=q` zL-@1^-%O1)f^MUvk-~%qBrG<{!sX?kPvFAy>%BRQ?X`UGi^?%DX?a8fp1{x{uLB6t z4$;;jX8;g%1f9{Z%R|>Qh{`X=)~+t0X-d12>Oz&M;TbgW%-CaT(uL5-6&fuB^iPC2 zV#?0$)g>3=c{VB+I@gODr3L4MeT0bT;0>|GD`5HEy6u)~$SDN;L0;WM7AP{=IOu>2 z85N09cl)U@2H}jKNl+A=tpWz`KV7T8d|J8tO?{KLw^$f25+LEkK~D#SyJ08USGrDU zClN~cP9Rq4nM5r0h$m3eE98+Hc9f{qn%S%AF-&PtQWe(LEe^99N*Bq0`C1EGOc8#t z+C6o~Gdu2wUbuL*9k&V(PjJS^Gf+;i5y<_{_JGOy%!g|;N7>Qsnae9zu1PsG+9qi= zpAj`vHHM<1R1tX!V`zaGZh`{^!ix zbESXIoPxj`wEyILM5|C~+J{`4c6njwwe-;++d{L25DH!8%g@)|KT|j9Q_U`$Ct5u0 zP{@?eUCfw+8uS8e{GC*Kbjo-YeZl`4uc;;dS-m>9ym$jH5x;qjZQ}@wie;HXXabP7 zT8A}PzDil8z`cj%yI!FR-)p{EQu6=787?1qa2bBLpksDWO5|+0gs_x%>J((PhH-5T zWQo_8*RU<;>T3iq46kJ3*VL{5uyV(y^Mmeq%bb4`)~H+pg_k-YCv+L1#Bm{3kAqk; zC-N*!BIR{D^@fmXq+9JN5(aFb3V=c+f(Jc?sMn^q9);QIDl!TSZZpg^>8)S10vMf+P zYGF~@{0uw#yvX5{t5s621xawty!lP_-D5h$aCsXp33Qoe8cluDucE#@3duKwWU=<; zaR)w?YZodfzYi%>I9?cFTSqgytV$Tj>2X#d=R1M7mpNr(WH&uYX|s_8q%B zX|%xVXYW=&KDB&*zIx+p>|>a&)uUb1WhG%d8}r|BA$tx3cOo_+9);*I@0`3#Lk&BL zrt}kgLX-jQSj}Ccm#^y$QF4ZtG*Kq%>ajE51fX~odnjmvk}AYt!jclr)fyMez0Qx0 zk(H#V#K!D~g!X>f#P3yyL_e1JN2$&i^w9L!Rsv%uNl+kce&Log;5X zyLqPa^}ko%pQ~QFUOn;c>eabW%;bPQdT_^)NpNjJRi`zYztI96pxX4Dv#A=PJG zc6#S)V20!sT{ZSzNFG~r<-zRQ(TmmN4?xn@n43v;5I<-hwhY%Uovj={hPD55=AD(L zZy{>Y$?qyBKdr$SP5WvvNQ?geG#l%<`;)#oNTT1+a5-H%=;MaKjo8r$WE>tXOy$dd48TFW zNOk@nl@G7u%q8Mvws5iXD-71XVip(J2*r3>1;=<=3z@amg{#4JiTo`Q>>Lv1epEg= zMMWZ|t1`&H=%E6;IzNkG>aH8uPz48ecfG*w4g+lNa1|2T$#`AhER2C!3pdGQ9;NGqzGJ=IjcQ7e4Iat@;tPkpwZ`R|BG1ZV$*h0hcq z5R%stA-HKvI%q?uw8EVKLE!D1a)X!Eb^^%%{7-ix-V=Av|8yte{7-kh9si*v>5-iO zG1oL}O+8XQrNOymd?7(G~m_zEy_eA`V*x38D3Rvk2&rTCxizCPcu`sPMK( z{v$;UAkXxk*L->;=h;c&-*iW-ywG<7NkIK>Ht$g?aY+0?6^p2tcMej~b{%&i{$x5p zRiwNc1tc#VaWAbgIHF;N!qG9<`v6qvMY>i^oyh?u;GO@ba_Er?^qt~T~PJ;YpC^td3BXqxI zA1mAGLZW78K~I%?iPByqqOj42OjpRy$Iw8ZFbh#rJ8fnpX3s+ox z`%UG{hq#kc3(G9zLF{z4@uKiV#m`RH(?{#DkL|B~x>z~(BLvh^=_{r|xb)r?f^S6+IrioyE7ie>M(?VHIEe#zym1mo+{Q{z zm^Y@beRg$u={6pot1ew#z5S!S3@}~S@h4V$?isg)oDRy0qfzhJAi!o9baXO(daLEw zB+0ahYBj{5&fw;P0w1na4i&TGgeKfR;`tOyGE%*yX>}{CXK(u*z~k}o@lHDyJHNy2 zEneXbpS{}|tYl#l0*TWQr>4?8i?;^w`YgMfD^Npp_teA)tS6o-=#}D;jt-)xa}O1t zK|#fY1x=-MyyCb|ZoWNU)@+PJV%u8o{;RqH0|{cfFI|CPD0oJMuDiGfR?pwk7Lof% zD+go)EGtQY&I#4~I@>_fY#6z{XFS8$bL=N!jiMv3xvKyS>PSngmArG6Pn*2J57ptZ z>>+y?wnDL*v&@>CnNUnMQU$rBaowE^0=I|qr0b7SdT$u_Y!Nb59(VCK_B z8_`}EM5jZ0ogx{Dk~F{O#$XNiy|;Sx+pnshpB1^P@%C!{=!`0Ms7Zi#Gi!co!&z#|T8(`{~VRkQo#eo3wUCBNEVQnUNy z-lEpNMPEBd%^D|4WM>{m=9x6=zb~_|x(_o#@dl(UD2@^n#%K*Xx#r|IoCL1t^F(}ur2@QVwbQefN`8lVeICz1Pq z15;JGdydRgO-I_OJ|s%OX2Da@f|y(=p9&IeZ2Ama)l);wJ9q=h_wbFjPaSFnfd_CP zNe_;@Z_~m@NV1pmHG=+G?5I=?p(E&>n%s*QS5|cK#R{$GGM0D>3mtgnouy6#95uZ} zcwm3JvRMuh-8;~9wOPb^3KoUk3-l;?9r*Ry&ZxI;z(p8qpjXh#rnLF%t2eK)5Q)c+iN;+tOvoZFH%Rjt z&>N*4ICSzsJX8}k75#8>rARUwcL9b>!i;-o+6SEsrz9IZA;~UDqH38OSXI(XBIvYf z7oC6*9F?%&YCH%B`|(^!WpNfSSXK#u>bv7OdT#M2))!boo2=3q>t5LU`(lMirU+57H0Zq!5BrjF}y-P>95Z5!mElYK6 z^V_yGk6ld$X`xfU@VECt+xwuNdfV9As2vVs7c)o-sUl$JMXjxbZ=_rR66ukuj@D?!=h1ViP}S9uDbbpL$2TXfhdz6pFq0nrnlQ6 znxTTI*Va)Bw2#VVi;F?XK_ z50>x#|Lnc#a}-CmDDJrtFJ8R+KEL<5+z~U;402n$Kpwb1f$+2L0OATUbLY)W5RJMJ zJ+!)+?iRN09Y+X*Kr#Yh54&X+8w|r332!VC%m4C5Rcd|W{t3T4xmB*Uw1mJ|6<*X; zxtu(C^5n^rXPHacDo8%`G!~QsAUc6Beet<>g9c%2HpDBCn&c-c`I15_rwMwg|NH|M zzW$Kv{DUBZ{2|rw2QF6OZ<{yacUVqBFWNq|!-7#}3HD$p$>L7fN)bguDqAR68#1}C zNd=fMf)aDV=ElbBniMkpK88$Mb7Ks$B4@-%x(bR(gateAx$(u*Cl(%^L_2TFT0^5O z1CR=7HJM^z$gKv%r3Y@2ORgAsH8*}30#6u$nTndiZOG-!G$xlZ5t&?qQ`yEiL>APw zP?;!a*-497AV!i_P%I!Vy@QmshD2KAj#{h^0i!5A>EtB06p)i{a4nY%Cnr)iA*WJ6 z38N%aQASDDVrD{;%NU()r4pQwHpFte<#kPjB>Lc5_k0>fZDm4@tUa&b6DR%)oEccw zhOM9tOBe!_Ov1^J=n--H6nX>S$U&-ULuX?n`6Lx4SJXJJ z1Be`t?#l*>I1EWt{1Fj=*gNt1=w?tp^Uvj8Supn zw)2xwX|c`Pv}u!LF!|F)L+Hvu#ZxA^iBr2%E#4V?x z=-Fv)DoGe;IF??$S)2WWT-uA7yvRs{KHz$rXk+t5hEi2`zi5dyE4(~RGhES;F@bg(Uxf&w|Ft<*5iidCu- z;>$7hsGy*P0>hzS_P z$vLf(k!|sSHt_c*8~dVE(_6GyVT2Hm)}x7<*u$twtAuFnyQhvP>?B7m-mt}}sbdS#Fj#r35%94`K?3_p zdS!9bg^M6dfllcI44{ZV*n!n)8NZEb5X&A|h3{v}RO6*hJvWWz2qfmQ2@tEp8DkZi z*>eH2Wo))#`fkD&jIYADWm;mEp@k89iNS4z3jO(N!42x*Um;Zl#Kpv=QW4XQNPPAx zoV^pSVK3N2ahXd?{7VnMrPKpcZ-Gmh7?-^=o5n5fe$YI!$~?Db1m0t$*5h1qqBnn3B*PB;^GHA-oB7Tb#edIi0b5z1tQ%;aeBQj?uM{u$rg@B2r%l7|n?|Ots>bR-6)v zVDyjCGvJH(2_Y1T00lvfvcbfC_#fRy^W&er&rdY0Kgh4~_`Emu;Q8arKd(H0JV8XV z+Ovx~+0TJ8n*pXg^~odS-aFANka{)JkVffIQ({&4_$JpJL!FV zn}m-O73;7goV~p;ciR8qWNr4GKYo63=KA89RhN&69f>Q(5T??sYjOk;lfSJ5n}w6ZDj*g{FV9W` zF~WWrI$Ks8kVrihycVj1xW?hN$!M|q;Afx)0Rbi(JX3tw5YR)SzJ}`=jgCtpM052F zBr_w~bytXJKpkNu93f6mKt zy|KBY3G6=_8{5|GKUU@WZ~yzhzwu9Rys;xY;Pmcx{+&@OynEw+pnsduzkf#m!tXER zHQCkQ-$(z18W+*O|5GXQv*7*z0;3FY`*t&ET^x3ckdFC{pZ(JtpNyq{z0~!y{0an3n6Nu7B(4I{3RirQPcb zTe^OG@Sj^ZI=fM&?Y|4FB&_i@yV95R#99B#+x?@~e{*9;v%3CU+d7)p_W#v*UJd(C zfx@EG=S3H$hMhvexC!s!S?tMdYb}v8Ko6ca& z_LbV~I9YwZ^lHY5#Rpi!bnDe)TUlCk3)t?CAk3bv2rCl=aIgxYt56VL$zGH^5V10X zq~>{IpB5`Up@?Lxr^jsLIefa%(O9d72&xx8Yj%3Jm@+hG(}QnoPoE+LE?zzs3ysQr zb8U=-OG|`aArg*897$2D!eK$LQVip2PbD;xubeB%)BydRlbyru@dSw?{L%f|tuL3( z9wBI!5G)(xrpu*5VISH%-o+6(_)S3uFX(mQl#SK9UB%pJb5l7e*Rj4C&;&e)AhzL2y?N4c#+u556lHn}%4SxmREAq+LK10BMIKR2 zqK34I7?L)XOK8|&Xo)GeNr($+3SzfGoMg8avZ()@^=jU{qUpW0@=;v~ybS}R&t!8F zHf?po!kV+kW`neQ1c~rN@vM;O_GbN_`md{}Cma&RoRaeSdxV-p>~6sN2l%Hz`)ECa zt%5>G0&1uk?RvYZMv6Od6ZdU%lTyU+5D*XK2M?r-pKWuZML`#aC&fP(AR@*EOo)gY zy@42ICM6meXkwy)+-xG&(ig|!G(ZH{sKf;cjz!Y#qdI&cCxpre2UR|}&bVpABwR!L>pirgqV-Iu zMk*1>U{wP+Lj_zk=H_6YP$geP2yaFs>hJCD?R2QXG4+Xm_Nkb6K`Lz%C+%W1hC!Y$ zc>pj`S@R@?F|yd@3{i;axT(BEM67#9AzLaQH*sqvm@wb~9>HA=7ZVwW#QB7D4;G#m zmI35Ld9Em5tCokQU{NaH6QW5)#*kXcv!oha%BmgVDb~=@O%VhKuu+M)-1nn>dmH#* zRnn>cuHC=h+ti4ViQ{6Nn5#`95%~=Mjm^ZGf{-~Wmar5ZkQy7DHuP_5U%6EL4=G+q zjQ-kie@lORT6;1NYPI86gZ|o#{Xt*NjnH8wUpVZR6LyFsacFGu(o;m9{^TbM4@boj zT84tTWijc$;PWo<>`d<1w7~QFT zw;B6xYYgTg>D&k77CJ|h;XNET4cs#FMuq@R2a?j{``b$=rjpLczwPPT-qZg(w(>~m zp|3vjpUl8-1`-g75L^g8L2&VWb%ZX#rn=C=Iw#8zk>ujj(+dyh7amOnowtQ|-lnY$ z5hqKdSG~Ef5>x4}ZQHsjMwTwzSv>p0!qaQ;!=F0iO`H;=yf!uZU*nZiIpn%I3o>}O z!PX*jXwJ6Ppg$UaNAq%Jnx_(nFhRskQfKs1qK44XQeEFbDd%#zAGsjVRI&ie zQmkP?dB}7OG+tw}CVAH6U~A=&7SQktvI(r9x3Wnw2A$Ol(FShC7zRAsrCPg6$u6B? zU8-GMl9lC81)wyBCvFXVa4YKGdrA#4G?_p>vAeEn<{T)UmG^Iw|2Cw+5f)wPb~Z2W$pe^>%XnBrKv;R|C>9S*7pC^ zcwPb+2U3WkHc@DAa~%?@R@mI|PmDwQjO;qSS7 z{;3(zmtQ%v0*1c|XIW-g)jd3ap^(bZRIr=pr=-Pm$_rDaeuNXOZrN2T6&O7FJ7ZM@ zcAtAV@4hdLLwsSX)K@CF3@_#2Z=0R*()6hR%||3B6PcywkH={qRGj0p_Ux?p*%i>+ zo|#;n{~k2bmyfZM%+25V{}3XwL+mI_ZqpqSF>~?Ky!YfBF%IbIRw}@4a9_5FSP9Ag zNOG^Lg>VW?hb5~7Bebd6rO}D!k0(hqf*=UN)tigcCl@Ckc{7(uaUDi--RUrZVzx$# zTLK&C?8LztJmt5Ydpw<>Sn*GyaZ_Le1DT=9;3(S}I=_DEX!n5oCky&1a zB)Tr)6xSu1;yNG?eBlePw~SCTGzm^Yvw#y83LmNMhJLqhYk>d3)NlDpS8ga@qySYY zA5nDFnQU=n2k1`V-q&^B+U%sdx_9*K3^K)*3Vak;IlKU#Kw-abo%5H!IH}!xc5Un1 z8(B6SKuE?4hnd7Mc5mC!wX=I~_qJXAd*9pE)g1+H__h;diJ*tB#+%kk)2u4Ry-QWbd;CP%3Yf3pecbrf@$Q&n) zPuO=5AM>U@LbT`|{|-X4;6W=hK|+yW8Zj6|aX$Y1@nyu~7v^s+jb5%jJ3*S&w+e`6 z!(w@`4Q>+b%f~N~!qko*f6`2a=q42svre1|DU9_l*hMM_*aBh{YKf~z4IAZ6ijIJ; zT)y5?mbQef+jKiz7Tm_w6bUo67o;$YjKSEJH3p)!18?j$!VBp@y&@Bo;{h%NMh_Pv z21X;lmN<J#tbQ9ZVNeCo3a8}f)nKuG`CJY&v8 zgSHk0vo<00eKj=Ty*LJQtT4>oyzzU6kn&YIki2@8qsyL|eZ%2ChhSQ?2#I0R0XlvlA0khg__AS}shOa4MN>1@p!fZ8fE&@c<*V;eA8-Do&Oh z*d)XUU-}g2)W~dL^@}_gR^W%ShlHFXP?1>F*vCl{+E+R|i=`su9Wj(FySd>3Hyyy0 z`M=xxdK&~lJ3F(vT*x@GhX>gxBm&#)H0npIe-VbA$>k5{b5h^f*r68^$^;FqYjBzy z98uqxd9Q2xyL09u%}B>k0sgq3n4!~A1m`~GTbh%XI*)-a5er$U z5mlL}vLHq1l3K%j8GEcnGI5J^08eIE@<23y$7Xa4o*;RGp)`yOm&OAOj_UA}4Th&= zNSX5Xh_fq`rfJ#B#F18C80yFjEiM1F!9(omf~{b5s85PtkvQF+y6EQn{K=XB(V->wtyt!_jrc*;$vN zNzMC9>8$?MB)Q_2!v!*WJgQe90}9gwvOtopTOjKuD>h6Z6E{3e?aFaYI4~|xk|WK< zlIB!&ya;Iy`iPR|sH!BkGzaBlN^{BBs!=z>lsS=-opiH99IEhNn@smRVVSIKIytcw zG%NV@E55^opy-UG#x=)(T^8lIO)iTHRbLrdG$~B4lq?zt_*Y35E<}Z? zV!Z2cHeZ0e*{EfM6wKS?8=30d`7e&Wj5&V~&T=C4E$o;I=B>`5@aPECC40G}2B$Ty z9M4H&Xt+?#A1aKP4hcxg4$FT>CYtkS^}FIb3J)hFEAc3kSa!ks2#tmEIg<{kyzz1l}hf<7QC z8?b##CviwP^aLggULhwicrs)sPn$M$sSH0`$%iRRjRa>YT)vL;6t0)>OclTueXhb~ z<)vXFGy>7l-BLWq>UW1wHVVtpJu5;*TKR3M z6>!mX@TaA$u8V-j7$)b(pqZ%MKk0q^Otb}vsgvMWV^kdxT$QQn2;DeLRZx)cW)%ey zB=FHPUp(mM-pf`FK%LFjeBapkg7>v}_NqUAhGfK1%`V_{jgC!t>qMl@k8=W`|KfCj zRu5CO`Fl$vDzH%y{k04xVOi2;1qDEqhZ7V)^_J804*Inj5@9>}x8Blq5MmM@tW&_< zWrolpztWAGNaqzY6_A=aG+f04VF~m!bJ7~BSY!NR+N|f2&>E<@Obbg@bOGcciY`HY1sJueuf$54mLrG9 z#`3!JhN5*ftP0hN1Ql0TQ)5$m;7tm4C3#do3atmHD4Vp;sW0rR1t%tx^`FKo6h8lH z;qS2P4^mkEX}V5eGmz$gCA~o4QJ?5=BWx>hG+c-r^YCc04O6iNVf&_PA`+#|57{iC zt)%*V0x8t2TbYj=quE&b1+3n-ETV87E@LTGBS(mj&7ANn*=0ZsE(zVmEf1h3!C`5Wh&#A%madnf&9}u8^GtW{=s(VSz}tnhRr}vcWo%#BIjvS_QNqAg%o-s3gmqrowQT zz^3zfouZ0@NKSs^Bm7A3emGGhyap@+q<&+Y_yi(1`hLE80JnmV$2cI3&R&C?B{PFn zYM^%2yQoClNr+(1BrxbhHEYzQE(t8aXT4Cp83k6pvc=sG3yjZNGT`vUym;t>`L}{6 zfn~k!j0;*q;W+|JmV3^)z`VHo%?P*$z*oPp94z7n3W>4EUmJI2dgcg&BHoT1Z#S-I zCzjwwOFk~#E5%Y?q48tRQpfODfOow@SRfqig`&aEBwscj_|>rY=sWN86C}zcxaFOD z7KmX)%+DSk+{&%!RFAwa^&@o?uy@5RLk<0icAiQv;wQZiyVHg!n%O)!V&36pL~$f7 z{!q#Z@;UYLqo+6#>a@&! zFGqF{phgaFP)vNS+FJcR}jnP4qrTexj2#SYiz$ryNB z>z^LziHcA3VU3Q^34WVkGL%j!>w-k)BoRewN1xzqR%JYr+V$H!^fg*5)U^{|eeFOe zFwkeISL3GX4quBs^90c^iowkVNHf zT7ug03t4>Tnc#oo1&3vf_e#U%3J!TIHEca+_LqQ{5j^PY-4JI2@EV{Z*l$Gx5KDnh z{ZOczyJ?Xry!|7=lryHuRx_qRQ}4+8{mi zH7+3iZ4K#yzAk*hD1v_U3Xx>5wGC{gH!zM-y4N^_g;wm_n4S_7p4H#QOemtjuLx!+ zEn>1K+ak6VCLM&{G4+=qq_uHpHC+xX>rMbs?ECGYZ$&v?&n~Jg##P%(>j;1SlYe5y z|AxRkKyxoL{&!PjM@M60Ya5RL-O{`k|8rHIwfNsZ=_g_Q@4EOv%=q8J`fqP)Z(NK2 zy&8`g|GQ;ld-KMoj!e^r4UH`uI+{1G#s7ZopIGs~m$myxt^bzx)|Mu9|HtwF*Y^Jx z|Geb*-{{v+shH_=E5ikx2`oHLcn}mIyc`f~cnOGiVk^NDkZu{SbJqCG5@oJECHIpqCOI4!Pk#eyrLCj+X8?Usv)L0oa}=z z;yk`1@sX(AJGwM>Y6aqnE1YVX@x*uP+1^bV;BBGES(V}7@9<#ctU^wGv#jvo9M_O( zbnKKEnQ^)Kc(MBmdWqHn{7K?sSE+`DMhJnAV)k*EA}t2?(w)iL?2#bq=jTV4E}fB7 zyl>L*uz=V!YedUrhA8lTtS-`)ZaUyGnRk2pw)Av&Z`%p!KBw;Z(-Q%XaClh}Q)0Z) zz;{IlzBco;Hgny3IL<%efOHH2G(wpWr5ire;3W)4N_MxPt~nq=%;E0p>g(FEcUxay zZy)3{WpHxUOX3mIU_RIs81y>N#+I(n`xj^Z(>J|S=ag{m%2?t=S`CZ+A|Nm*CZRDT zWU3kw1_E`znJT*jZvHS>({cAU!515+fm~;HeX5JxYDb?C`dXa7Bw}pw@-dF#kRZ4E z1O-lHhOng=HDpOD)J1A6i?VQP=bgR%d$;|2&+dM+?KNJ!++LAuxTzbPbUB2lqCA|( zbE*~XL*vO-T+4hUfp~X+SO1>fd_{5ZX<^RPC1 zlLq9)t!ooucSHfiyjqJ#0*ow_u0o;oJ~QHh5me*>yct$7T%%4bP=5bMCrhg0N;Jlf zwM8VH=Bw#cgSw93VaWh!d#EWPiK6&mCmA7ma8J(Zl%TN1rLa$GrdlEm3Z|R*6U$8* z9_oStjD#NpynKLVH+wLC1Pl}OZ_>VO;MBqSAonR_Z~nw6+IMJni-)KDv$Ou>5&!ND zarKy{?n5h66xU;I=AQTDoOk*vT5JB<`#4bdILTU#wt8`6!V*cToF4>?jt!{Q9KAWVF)}bdKS1x{ZFMvGqQ$igbx0~(V)n2> zce~kK0a37Nv>7KgO+0KmiHjE;CD;K# zS&~zqER7z4!0=aZBH>Dx$PI@#gq9+{0~QZ^@)fkuu{Le$yVQSO{p(2wEe0}3GT@X_ zCOG{*obx}rO|dd}oL77P_%hBPPA9rQUoE)8eXu%g#@b|-H%Sn&Fn^xH^5EOr7k?*9 zm2@dBPXikiH*o!PAS`nRrxJF3_=ZRCuEDs3P483x@A9?h52qX|kqHA%849Bb;IbY; z5uYBZs%js={xtYeW9^gh&2$*AJ^6a^*8GYe#<7xTKM7TAIF6$oyW4Ui=Rr!4^iU}k zU+n|Bh`qQ1I^Lm-XLJE8A;)wHcgk5xYGDNr>mtmF&+B4k@yKq2{KcIvESJkk#c4e;ft zfAuES);##uo4;S1os|m7Lk;GLi0i_Lxdd-uZCW)!yfO*=Xg}Q#MxtsxBeMfB@bp#A zq4+S67)X!9_@|OeQbKgjEo4WS;j4<3bC%!~dp#_<|hX2|3AU+Dyf*2NQKn#IT zAFDP)6BHezp1~%bSX2$sTXG_`K%fQi9*=srPD1v{3qevt{kTE~wD|FTfAk1idei_F z$o0wB-e*@FVM~&jM)D0Pp3=l3NUbS0UN6OlM2OS3y&EU|?`|Wpf~;=3*5q}~G{E#^ zh0d+O@oBUt8f!20`=3|-Xg=Jbv;4yRY0d(Eo+BT<@q5BM$WM9ts`uG7RQl%?m8+Ss z7ATU!k>Zsfgz;C%Y2vKv(2PMe}UZjnT1bhol_>jgJu{A!;E~A;HjN z=!N`=M@uKBR^CDb_L_a*!O_L7G-$dU;=4pJR7qg10WDUB2L{mo$$U};UkrR_Cg`0U zwi30=GTo&L9>JI-T<@z^6hT_QUfIrKII+M3OvNpiOJ#Iq(&mG>u}F*smz9;&q0TEN zJ%NxqRPPcyfOZmB9o!=wrAzUYmOFV5Xs zL6v1T_ov|s_RHE;&Z8Aw9g!86bf-dWkA1fj=PD~(=vl_1nN#=LQX$7iAZ)aJ(1xv+ ze-j38D7M1aI;OqVL<90^tw8>dAl0eUyt4-AXpm>ua=Rm(8$@jiFk<22$0(YMdLEmw zN@wX11*6~n0C<})L-58j6Y$i3*cRfUkj5b0k# zQJcAkwppDd4aY}#>fUc*)3`srI*gFSZ+qQ2AP-@>qTCF(8jF`n6Z&K4Wx=pJg&!-! z#{@*JaWL^o{lCvA1rCPs6v@GGsoLyKo$?Z~ED=XM-SLY>lGM%0V+pMGrati=9#wRN zq{fTn?er|g&Tszt6aMtq@=<`giP4p<9$=MYuHj6;!ZezSz(c|s4wnbPO9MwRTKxW& zKYCNix=4y0$PXSMg_iz)(O9AgEU!Sy_?KqTS~C_al-?(07M`3|3eX^l@Rh+VpqF71 z{;Oh>T>XP>!}tSY0D%6>mjK#3ySjJu?DWUa@7}X(Ti;&%%NBu+`4xzEaCjF4gECo7 zrRzQqXUo`&5AFm7Z$7-<;q?|s{RYHL8-j0eQ; zQlT8{5v+2+=;tqgF;o+<5mwns@ri;8iE~uD^=WP5Tc(O7xKLHGC zy!bzYuW3@}|2JaC|7mP#X=`h1?{FHMTUtBT;{UA9vljp7C;cRg|5G0yh#CJ!SpRL! z&1><0R^<`n|1`HXwQXo=%Cv24-q5xY{aB0t^V&ag*8lQ$|ETrf)Y8<_q3-|9?JaBj z|7tuhIsOlg#sd@y7UNZSDvgjZiLtHBJo%aedK)UQTQv zLsTDfIwQwtIhaqPC_Z9j8)E>U09E+NYY*UKnh9J*pB}xkJSq5CDb7*oWi2_T;(KoE zH7zuTuW7pq1$@Rg-OmQ&OL{+xo44H0!Z+!DwkiBhHYks#zZc%exRfPn$a}g8VhBo1 z$biNjsyM)UUaU09=i*=km(S-SU?92!8Irh@BWqtQ`Wzd0A*vh0tiVwXIl1BaD+t6zv><{&H@yX26{KeYL4R7?Z1OS7TxgyvusBpR9cByHd zs(?}(+%lXmRC|iiEwX}*kxfE!s@(%6aGs26G$@)vlfB=98-XP9MMUUT+?8%|SjHQ( zP+459*kNWRR{+z=l3OJEBET>4g%$p5lf;i0&J_O`K&Mcpj>||g(x}TwFnv2dhNvzm zjzbD9Je%>)KBAQS=_D9)jsM*Yokm1Ww#nw}3=ifVW0~#lVYd)gfOVx5(Jwj#q7m-- z$mDEhX2m8>d2Q*^jTb>DITy?-Fr657Ax=69f#lWe>GI2=2!fYr7NX65C`=(;+2Ip? zD#3jp@4yRBp1~h?rKxl1-U7QLGbop3t;Bl_liY0k4ExUa8n`aV=d3$R09xYRW%edR z+{xOriyRZrA5Z!huKC}ci^8Kw>LlobH}+la$XSW+s?uzRka6t&IH(`CGY6%@@D{Sf z2~=#wge~xehjUCy1M^TZ+Qdq&bmC|bjHyBve3_0)aFIB~%P3w9Hg-VTSbdg&Nd=s{ zrze#7950_{QMj~tLGFk8~#63k_#zDk6n@Iu{q%R*MbJ)`zal#^ti_(ZV zK4~RtNy-#bR7T6L&$aQ_ad=*7f>$uA03MRV+R%-NFD(r!$?{|sp3cmoJ?QPlua2Pq zy$BhqS{fWIU>S-YaHu5J;Pe!$?x0(SSP|&`k4ds3*oq-p0b?S=gP+NSg^->t;9|4| zXB&YfiHs;H0IWpCs{EBs8J!^`(dq(w(1f(B|8%SPOW<;wFAkak)2kxpn%$Q#<}$te{)B{>pH_+q;hIT% zjW8jkl=%AVg@+UV`A6PoS8Df;dN+<~w@~$@l&~^COKu4ZbwJutiC7F(voX_Bh-BcD zB%08KJ&Xm>o19>0WhB1CrF_mgkS*p4*aeQc!?BU`*x4K>#D2TMLE!$1d~BejLC5Uu zNTHOK1pdIl-!_g;t#NABue`NHfB_ZN#8YqfkvDf%@vC$qd{cUh@s7^1+$8%o3~2)} zog^fAG|VMN3~Vrc2ZlU;PZ@O`n1rW(XB*RekSj1UwIm`>Fd5py;!BE(y?O~z2xf&r z$nD40|J)@((d$bkmC>g|&>_}i5a95X+<`I`QJT3MG3Vl_#-m-1#A=I`OsN52!Sdiw+o#gL~sDQYuWaHBNuof zM6f@bFX#(Ntg#J1W&S3i&L=BDIrYtZxc*7tdd$TJ+}EsQVeT?vQYVe@D4_p^$ev`H z2#<-DKk0`GwSHQbII0iOC`vWZ-po=J`?@BluF0t{PfjH?xR>yhHDUEfE37_$JWdTC znNCDxQvUSU-t;~4Moflelx%CyE-jsyBuN+4HZLC|1mhjK46gBbk+Oik*Svej)V3CH z%q=`U`TX%^T5W2!cJv9v{zYQWzM*^-1XYNq#1Rg^0vQ&5@Hh<4;@t_UeT#U*p%s~R zr-NCB#&WfGUKi=H;SZ@HvVY_EWCY-`jtBI@rAvK_Od?~-JEyim!4ocnM$PmxWKq+| z;z*=xlIF@tnn*CJ&3>V8#HOfd+O~_W57>`Bcc4_xRctb&!P;BFPtx#dIyg4zODmaJ z7URuHG3m_hiW|1U%KyW)JKfjmiRb@sNQAwT{=dZkzqzTorK6**4g3E#wXNO%uFA9K z|NoPIlKKDF#|L8i{|oECy`!~d&HsNj9?}25y{Wamv3X;rV?%pO`-YZ|_BH?i*ZzsK z{+GA=N3H*6{6k*0S zCg@MBWggf+o9fECZwcu^8^TqbKoU%!SmqH@iB1vn6%p;BIy5Pks`iy8IR3 zl>(C9ota&jJHGVk1p3E6eR~O%>yx7T+L zsRFAgu}o4{Q?QYwG%LRD@bxWDOtf2Fbb@zKFp(90X^UsGA&3ZHMB7Hyq8HX5mP|(I z5bnN&2-O8hJV)LUKP2)-c0~$&$^sA_k?=HsGAk>gw}KK16ve8#v;?XRdW@(;Xc;3f zqU2VB(lkYFMCqLhCetcvBgeuKo)(ZFuNFoCo1X9*1`cZ~7RDvj^07EoNTw$P=fW{8 z2&)okiDq@Ox^xi-JmOhQbFA?cE91wPuK!?3s{&Hhufz$&8gcNB7GG9WLJu6dkc8&$ z93GNi>_)&gI9picg<=`uy|-A9!U!9BhN^Te;IEt0ymi*9fL4P>U2g8r!YA6Sq0VM; zsfkrU@h&z17~va>D_W zgQuoJ>M0}y56gS-;M>~l{K{+}%-0Yi%Xg8Ljfqr*nr9&prbG4BCxruN%6y{^~Sdn8B16cKE&3>u&l(xq%l8;Cb^n2iyBXqu~$yW*)8e0E%I!Y0?1RL zJp`wzxP?tQmg1crI83 z&a-C!`;&f>+5gtZ2jbiRvi0AA-mTgHuEwL-|88twa zKaO)wXM3Hqc@vr4aDrl_G*)|d!u#a5KYbOFmyF+!B&%I+IbX_c&kyCRYozt+krr4+ zfMF?)PQNsI8BMPkMMj|>*^a_SY7DmkES~sn#q7RTIC{_%x1!kWN~YIDsB!<9vk*9{t^vZxGpe#?qo6c^;R+Vv}Va-vbW2=OATv`AOL$k zS%c26Hi_L@`N941+UNzc(xlbDJnsP{bykLlT)gnt7{yCs6ro{W6c(b|(i`*5@U^t? zu42D-p9({*xL6VL+YO)|bT zu9)NJX^hjG=e>`=Q7>7OyQS2`U$IyALh4iB-Tc1CPvbRCZKAEiXD=SUY zuTccO9Ln$~zFfL=gHr}t2@7+lz4`mK*;(`ny=W8?FXapokws#8Q1vB@#GUIlfT=A(!P(+8tN6{voOm#Ms;$KffZ zaS&+rY?lLBB&VmkyLz_&F4bu|*HcD@b__r~+B!zP8-GK*%y{}0(fB3;9kL6u;RJ3R zRt(C;HCWjr4qjIn5EQF#_Lbf2!FLQp|LwMIzfLgrPj1H--P!+M{Nei3<8g-zkvL1m>H!=rnQ#pIJgbHO@)z^aNxQ#uv*j2q zl436qjfyzHTa0m{Q2of|BEdk=h?SD@`DQQ%lcyQz91`4I3}9bheD)9Lm?rqBi@O4w zpdhv~dJ=IO9cV(}t$X{I2O?-UBq4wrsOG3{rv<)2RU2I$p>A>j4){j~SYi0Nl z%;lJPV53cWhYu~q1j5il_$*{AA#Jv{LR|$kWBmuvwtk>g$fY-guc{aek0@c{F3uP+ zkb_mDE=-~kqdGV|EmCr7_EOy=1r?i(%|y+2Jsbp{*z1)q)1@(89+PiuNWU@IO31aS z3VT>)cm=@4%g4xJ*8u7AP89?wOiySr?9UIHPZkA%%sHzm1Oi%vaqp5Jg9bn7AS*4A zNakfw@nSY5b1)HVo8H*Ckj08d8sn{#;m2y6in2mAYt!N-M?}*iLlCN2o0btB)w;sD zlv%QZpY*6vALj}>UCtqG5iPaTH)}Ib(ZUjS=7COX{B!TtNpJEz?C)nL{ku0;R^=pY zX~wm;k9=H#HKFdHv}NIx%-(cL(N~s(-rCId#hD9BUmUC5{Yp$JVdk^wxB(<)Gdfwm zAS0fH<6prlrUZgO{bRW7Y|3;it?b1&6@&n8I>h<--+k{(T(4^<$9vO#`ph#{NJbq#D8sR zTC@LLooCJe<0t(j^Z%%i55)EV2-bglV{7Z0|Ho=PlK)3bN884Y8#3+98#c6cZ0u-T z^Z$75pE&D(dAonq`fq7#r}5u~{lBTHb#4D&jps+^|B~-U@%oe7F<)oI^0HRRK$r1`7A0ujUW#}fbu z4*nb5L#r{gQ9+^!sMsw{W9P055NLBt2}*FE6cQOE#lcC%s=1q{Xl@c5StxNTUBgOL zYpX#X5vRiRoDS$3uBc($>qLWYP>!g0%j7HDh7Li*eTyyxki}P;99dYj5P*+B)0-VO zmoOy(X(ycw+?Hh;;F}GNyj;E*lw;0A>fj|eB2^>7#}P6y)dTrTXXn1*d?6>%eWLjh zP^qm=v`7{n-bu1Hm1XJR;fqTW>)K#qWL+B|3%@o^8`k^LIIVebWDGnxrCz+`wmN{mCQ#*0@DC-%;tt6OKZh!9ZW14{K*sE%;kl-;|TDD`6pn2H-3qXpf-DxyhIaE1}Y&R zLm=JH;Wzd*jbR}=R0+;4BiPP?G^k$(>SlvyzTz-?W493>SD;fm)At1vL5QfKS((3? z_pY2obM4Jd@vqEq2|5u&wVBO4ob(V2 zRy-WpvyU5^5o0}3m8yU1bQ;aqXW5Wu^lzvctrhj27XxRrv2pkB&ky9?;=qU*Q~tT} z#nUGcrob%<8*ZbYCZ8xH;<~XKI0oLfl1$i0%SOpjFX41cKAMsRw_H6CxQSZ@r-sdME2qrVuH9T}< zibKK@in|UEB9cN|gN|JBQwRaGCu?s``(!3x_E^xW8<7bE7x8VdKTEPOa`{4j5b+ob zH}+26@J>z;9^;SB6L%3bak1G#`t#x`wdZ8A}=;OjqrnF2xp{0@9;N^JWGoX@52&& zV*@Q53Z7WiY6NuUm^VMC=pjskK0WrUlDMLI_c=F{o2n8^M8p;F&rO0_Cvn~gU@+Dg8t9vrCxV{g z#B(qEliLb#_y9HuSahhaQd?*P))kf)RJ94azXoa9sDu71Bxrwq^Ttj40lOjdpzL;#F__0@`uka%f(^ zxwCNYyf{m=Z{CA%DLp?qUVC;C!Vo+j^=_R+M^e%Z99U*|mj- zb6}`{;VW@%pqj)+$h-H=!sE-{)J1g4a0EO`>>b02U?zD77tb88-TD$WOQC%7S8w7J z`GmUQz>zVcfh|0|LmUJa=1)_%)El?F(_>6kK$9B!mp?-CB4Wa*HS7|s zH`?(0;9GKHvQYK_IfA&k9|vJ`aKp)39u*pqKFn~U-YS((%hheD#vY^=Jm=g(cH|Yu za!;k3Kq+g3b@YmZ!P;(lY~iTw7D}hZsy5z?&G`7SUlOE*4Pvu?7E@=FowF6{z*bQ3fUh-ublzgnC>P6Rt{5le7K4jA62ljQU)@>EJhgO?o1HRNI#d!2QJ5)QS1GYSUjTPQ(k>u@{v{mK z=$p;jn4@o1q!D6gPdWg*hMbzTW|@Ko4VBd~j!~XUim1*`#*7Vxvxr(D*`(o9sk{v> zab*EZ^BE|9ID|@3>4&HcL(93%zj#$ut&p5|vYIZ|plwsPX_M1tC_X^+AeM2ddO{@7 zqT{y$R%~V%U?~KZYM0SW)0@7%IQNW5tiT*+M!m`N-i>2O6y-875r9bmvERD;c9#YY zx>fKbcy_e*;5&bM&KtkKIRCx>@eQMJ*;_2+i!R|&a;6nlVtcky?HZ^eo`+o~Qr`Gw z^iRq}@o|(6^fnD&LGi1m_&NydU7GQF$#!uD`}3_Y{VQh(1Wx*Q=y_|$j)06v&ENmg zfs1h9KjfmTa;T86rc-Z;!^bWFP%2Y!i-Y8Ttl6TymmogL1*%mt zUUeC01(fw8Zni8a=@CQ~7gz)a?e~p;6d{I8oG_l9!$bBeO@9oP)q=_U;Q{!sd^))} z|5yL>s|fg|3*Re<1ez#UyNSs@d6n4@sWlKXf1Z2m=Z(#|w;G$A#>P(kPpSbAr-4H$ zKtdaIi#cdco`zsho17+X4rmo41dUCG{+;zsQxp#fYcz6LY6oU?YX&UhiXkbgG28GS7snbK$=}8sl-jlzA$OZWm@9VN_IsD6pCwI^Nwviu>3lGo2)FNAwbeYpwa~)7i)4M%F*fL-qg3u zVrfF2tJgzw|MDg|Lc1ShsuO9eF-`mgbk^nxr3DjCsZ=Z0q=ss@ldN9EDR~%0U4ut` z+8@CQ;I-7zy_8wxf4olmKChf63kSdQ=R;bxK z{^A$Zp0Ra`-?~0CZ|<9j4L|%aDBX8 z9WS9l){=_{1#Vff_|-fr6RiO^&WNG`SAyQC%8eM>Z?0M3=OS)CBC7KYUL-`G64c#+ z3|8gAvZ=8O*QmxnhJHr3FwWYbK zjphGqY;0?CASGbiTKtz)d0vYDx4oqyx0=mmtJzG?E(BF{CHI4>TdY6~G4yjVj{rE7 zMgJpN@U$j^0is_+s6?Mz87?3pDrYN4y~^ndh*fA8l9lrV`9s+#XZ8?R^}2HyQW)ZR ztn1(|!_R$x7Cg~E7%Cv{FmSK}G2LJ~{}ug(*!%z*0NPs3W3W`kKJ9P=)93DY%itIh zQ47dsU%r^j^zQo;+92vn$w-Rw4}{QH&K51;wpqwW5-?DY>AO6vSC?0}bpfRrCAv0~qrR{G$HOR8^!+rz8=oDsj<>$r$I7SS1Hl6eTS>ej2rw{z~(= zHx{l7K^-EYbG@G{&qlqREA4Gyd5}wOi=idPxF0mBlBn6bt;!29e>% z22c&5bEhzqeQ2Ag1gFv!Sw?XHkH3u6>5(T-WSo2Z!%ArUe2IxmTqu~jE!gN!z1 z9#2SfWqA119f*{J=ZGjcI297l6EdL@ASceI61s0~arc9^A2Kp%!4UNQg0A8yO`)M? zg~@&KR#&1W1X?2&b=jNxXyM`M+UQs?{9<0*%PLVVO?|R7dSq$z^5WH-{5=+AMwoC0 z2#mj^t*e)T%qTzt+?41?8%f9(BiJW_+-8a(Lilo_U?O~>o81GYLnbi>a5Z*&;mKdQ z{!997GjVd>C%>qPi=o(N)y$XHkSw97)Y0Im2D*5(R!c0}lsRQVDln+4A1B1Th*K!F5b-6GrMGl(Fp`1zE}?4jur+6Pggx0o=kgBw`l$wlHk=69*2;S61)t< z6h%TrO&gDdh+WtU6OVO%w!joXcND~zH%A1ovFAwVn`pyy0&72~lMGsgPd|2x2X4BE z^g>!noZu6=+Qb!t{m@|2XQ&o#18AubZ4GFsNF(curhd`A#j_AJR3w&c^yW*?a3dFf z7l|&gck8EFL*Z@Na$zL6NRCx!S2>%n7_6lXMR%6)9e;Orq!LwBu*rxhxplaV7HYLG zTRa$5w9kD%Th47QWRV;gi8xX?5(FhJR`4O(osgtC#uCuN+?;4f5~h&A>y8H9NPY@4 zo~MxLN5wIGbP~ltlSF2a3&UhGeaqAtK+=6y5RUZ5r~wM)8JglmXa@6k6Ewm+0%OV1q;ZoAx;+Ds9P3r}R>a2MDS2kI~7W zSN<`23M}Vv#pjQYc+dXse}0pYuXpAadXqA#qzrJ-LWa(jnUcP1>&bRAO-b2$361ZQ z*sSA=X!6)7kJe0YyCd4amu`S`&+orLUmKh)~%`w{G$SW?K4r;Ss z`A5f0bijC4KmeFuz8<=sc&Bj@Q9L-P30GD|sYC#!R?nREN2kL#XW=acKW3| zTg__NwiwR5GCQ!rmL{Y=*~z#S&O7RIv>!5T+jVcz4co1m6D(`UqJj^+NAWyL-x$4@ zku++fVi*^eR)v|<#)xUbF6cu0;XIgScoa<#%#golg^fbN4(;Ai zM9}fI=|c8IQH`WVRBqs@&Gys7O7O;&Pb8`Xw7AId0AP8M(CpfsN3b4}>XO4YXF7?$ zO|*FAO17=mpm-M9=4OX{r#~x}Z3%NTxx*+?m2Y&NN*C%6a$o?+b!oY9=aTuafphVjsj>rIyT`gjCRj~rC zlc|`T(h7pA613&6yl1>)K4 zl;LjOt|ksUj?!!S27`h)vR|X#Smpv~oE1tZjS<`e4+88gtJ!?9@@sb_ovMnfJ*A!K z;32KAm-v;5QT&o12|j78tfELR&w(|G&{1vM!nV<8g-y2hotdxe&2A_CHD)Vh8cl@ zAQ=}K(Q-@!jp}~{CB>Ug$&%t<2R=mauW!)!@FMPz!~_;LX3|`pTQh~lxH((b6p#S|!?2W3i(EIi4O-wxUup+hvJUnkY6rhq+eUS$t zIZ`GK(MuQy2@i>;=}$cMW*>QTXNe{StzTueNO&>KA|)JjPNfuNJ2v1!;UaxxXiU$) zj=m;a8i23fit{F5Q^l1x2~CA%)d)IeX8?1rWXH60Ye2YoGbywm(t!1Y&Gbus>bsD# zjUn`!N_;5a12xah^8`f0t *H`|LyWaWf=Z}y0AKm6!SazF*whS7c^y{yGo633=8F8HQ~!t>Rkin;bKO>=rC5?*+YO-Lg)bdu z40cg2d_3T{a$=Db;$~K@=oe1O5teJbKbHR-Q6w-C1`n^S9Ar)?b^wC|caV80p=!9y zdlK7|70oe{qYiATjH)8XbR}NqOI8|mnILtdL>UMc@0O#6(~ANV^m%Zr((VQ5uP70@ zLDChOO*psNK!6_LobH7mfdj^R{6#AeOJUgr)}YeMWc8WJ=}wO-k3-3cDh_~( zM^6EBPzn$Ch!TrOqCyjs0F((Ig=n&XsGW3jd=Bi8jSIpUc^N@|Q$`w2q=jn@UQs8P zNVcH=OZ>X4)oweQ9b;sWZ~-(ni1M!nUPS|#Mh#{G%JN~jhE}INJ`=!k_3O}?taoW% za>L7dY?9<2m?Sa(KxGp^q9NjLn%ZeJ31H<&l1b#~6FffluM0zF&VSsF$|m&b8((jI z9>4;@BfbGWS_I$9hMro|T8>)8F+YplXg}u8*+0)cKOfjXkHgs9^}upBHYYo4<-xAD zpYV^i%{ROLeo5`o&capz$dA&j(UA(;r9KP6HD1IFxRc&vuCxPg^Zf-vN z7d`F;%pidI|FL?tH2wxpt*s`ltSq)?=U3OCXRL9jpR;LoytQ^N-lm!-!PhK)D-@U2 z{y$bPn!Bc~A7fs|1AaQgmsrgovm;lt4___Mh4t?qtQqO>pVDBVPh+>)K3y`{19bM; zY+5+HNH_6nRCLp7`fN@*@lI*1b2@c_V+q%-YlY@iU}iKd9J-QDw-E(%Xs+!Ovp4U) zo#Olss5Pq1*aknhd3ojFs?(W1CHJ7A?L?n?GiTd^1Mgc7>4k!Cc6?eN<{KSO$v86a zmZHUhYHS0YTj4aE2YH{a*QNNOhB#K3d))(=io&&`3w|2!@8I)9qV_7#*Ud@Cl{)s{ z%xL;9F4(JP**N7 z(pvggeOppU0UZ+qZwxmPS7-0Att+tEoH6e3_bkYUkC@%u_M1&Wcr z+`)|WLZ0*ALGfbOAP>}vCLrDriS}^us_j94($P+#Wuhx;UyXVjs*wha)|mRgAnb95 zzi2)P9|^ZeIe|lBrXa`z94vbg&H(BLzT&k^#Q6pe4Q3n=!iM~uKyEce0#LsNFsRgJ zckIuk0Ye)8NgCQb7*i6+R}*eUW-;iHn0vZ7^{a`gaW{m5_IC|wgI>1h-L5Y%WgS49 zvA8RkFB{h;FAJ{j4}fxfbL2Yvo%|C>raNay;C4hSEh^+=?`U`L{S5E(wHojtyFGPm zC^2+D>*U*s^u001<8`ZMd6^%mM2)SUW8;QO8na_;X2@!~b)+~EYfFFcjKpJ>Ce==;2d0!(y3=DJmSvGXOeP{NGaD@}A_$~;CRryf zQ8ZG{T=V@Z%rSb_ zeY*1>{Y{jYR{j=H#1ylB)GWDaVgY~D95+A1PF-1JW(GHvnHw&o1FrtYI{vHYBfjd< z+|cAodfHi^Yd866qME7D{~=XU&xu^0B&Fcjt~I_XIyUsUuxHvT_pY=MT1&}BLG%58 z5lnaG81*%y>HmUBPvqT?!>EaE9tne3>akNk(>x<~PE9gCBE(+fM`R#Uvx@;FRKFw0Z#N00@adBFs z=v&F=u(X|fR)8nm2#+MY|3=>t3mid;tnm_627q!zT!p^LUqOAI;(d+RGurB%%%cv+ zp6EZV^{*TGeMWjdK;0ic??^xd`k2WDQ3VhVrHtaL69t2KkaiU@y=s8f?VA*+kQ4S$ zMnMYRd}~;u9;`rlC?5XaWOr|+dD}dBuh13;njX&iTV4$AacX+O%BJ?6hDujp;=~6ZJg+{6MP(pPL64CJZ^8vz` zt(FP-+zPOgg3}`q`YRZ_hqZJNYw^Zm+C6|Grt5M|VKRD^$H^!JxCa87r15IJ#v>jJ zqF3Em>0>Ii36hdSyIX9}DAOG8BvZW<*iE}lp&c+{Y5I^#?{+acS!MjZsY(Tp6@#PBnYF~N-_n_Tvch0d}wHl z7X>4AhXA=%r+|+~fB>R4zJqa6EJ?hc;;0n#vwlw`q0)ta)SZU8&WC;;oM#LE|ytHto`(NuZ%m~9leM?Y8tUYaGb1vOHIDuU7mk*uz>CNDGxeEvbL?tBvxS=vWQSP zH{LRsT&v%|CTG3mJwnTmf3l&25(BW{`lq8eavwTZ`)fBA4agx2M@(r*aG=13{#zov zwsk{R9QKNwDT|km4XB*H;;VGF;wE-yGahRF<3SG{1a8thc8Dq%f|vi2rYmUjj0(TE zVP2yd>MqnpF!e(cd-ru)k9~cTCQE%^zyQf%p!9`JbinxP&|XlqXcfYN+8RT2o?AO_?AQ4Ia%5(^We{(^Xh_qt({qK_&vvy@uvK@lvD z@`vB*Qehlouoze$W()sS!FHuxd=C->EsxXLwz?2PuvJ@W0tI#kYkJSRzH5e3NYor? z!k4-ip<|wJ_i#ubJKr6Ae4oZ6aA0gZn@`ui+l{F#>MPc0x)7$eE3q0u4rG>)0@c#! zONpKN<@ZZf_gk>j(HUof_eStL##8HPvw5xi$gs)%K5qo07PAeq2nh@}ej3?WTUrD@ zLzjn}n_lHB9)7)TpM^O=cRA<#z;#!?ukW^&wpKa~KN`{E%|GVmmu42zR#vv>W|r%# z&1eAUPre32zM9L|_~t(gK>UYKJM-J?oy|Wr!w(j#Kfl%l)cp}HS`yn7-1REQ3!O~K{Ht8KMr6Sic6h9c_?Bxq%nO%b zP*M|v6$YOQ$!I2pFnBmuk#4&TOSKF*N<+BOG_Q_)i~UXTJkU>jn05nMX9F|~C*}!I z$wTbW5^)FaJPY%qw6jQ0#c;0xc?e@?Bp!uJBCgs6JyQayU#*1)b7lm@m$<%^h+EF^ z5o++5@xGy@5u)g~kAJIyLvX&;BP40!R2qsRfZA%tR)Y<{nAKv;sdc< z3}@V9&9s4}>ADaA*3JD5x3|^4-*ovY|M9F}#X3sFP~YD!Y?>pb^q}lch|di#;2pPocl0 z&wrk;fwb9YrrLzP?Bo&%?V&H~Fj?`!-Zp_yr_3w;>(l_rpXe%tBjg-B?@g)6}9BG1|+0FIed6_cyi+!c#9)f8U!fK zYyG~uL`w*)P%-s=RUys6gCKdTtR6M-bEMw}?hiUjsRC1pfGlB=C#X9{b8fmUB+iqbG@trc+p;QLgUR~V4STF zGAp;R4;MeqqHio*@2Hy$tR`OezET!7$KRPC&Q=hfW6q{@(@?Pjlp!hrLdYauBs!DGX<^nXR^MsZ}6f6gI5a-1wY zXJWE_z?%^RupUIy^~-}t5e*s;@2p23BO;+G)~tYl9Rlg%H7Hz-Ma~A191fY{BYW0? zn#AbcpyB~0X829g(&xp4NN}Yi;1Dk$QJPHAziiLUp{`b;*CY7wdSPUoGO+1GNB6M5A{G-?M66qH$O3A6->waR=1W59VTvjLwaO zO-M-p99Wu3C*KJ>d|6!$D0&PHxp4Qlu@B#Y0K=0vZwWW#E2ZG-+ zpyBz&2AB7vvz0*f{iB{@(pdt%))DJwx+Qj zJG52S)|M~)75AO(&-mh+pJlQ5KL@td45LjW&m;~1ja)VUAT0Z8IT}u+fBi|$s9Wie}AmH+t>JY-x=%a z8R_XuUp?-$-y7#V^h4hR+w*Z+Q^)m7R>h|10aWcs>ZT~vc1JNjaL0%Mq4m2Mk+yqH z|7QUfH@l`p_lMTl@3SLh4xkUHze=SbLTy*pZ7r|n4Ja#vP?uyJ!w zshC@fv$h6AGt{D4^G)_9Qfj-o-h@?nH>`)0fAtFPFm%_FC?lg(;#XyTgxw}OR=a+) zpQ8#m;6Eq#r!5n=Oufpzam9Phpqj<#wRVFQRa|+`B$P=@8gw=$c|2KxBr)Vxb{6l^ zl(d$N=x@{aBjw}HknQn>WE%M;r%F-Zq84iEB%sA!;9h^RMws6nsi^tvT49$DJ2Y>U z9xaOlS>*cUzi_-ibyy{#!F=T32&}lPyvO}w0U6G(tTHMJk^gv2Y#r(2Xni2+MsnegqRTXO%XaIbj&^U1H4@7- zH4V4~DYTS2maUOOo|Z`HSP=lpBzd0lJs4`y5CBh|Zm;-JhPUtDEc{!0BUk37UCAsP z#7qBvJrqc%ObbNCm2vZ|?a$9`NTb_$DmOqsuy>WoTk!J`R0%(N95aK4v)N>(T_AOD|fi;Hnx&))AaL^WIndpg=7|metw_q=8G-fA6#X9EZ0Q@m1X-tF`tClhv^C8_~`UY3t5NzmS)+U5FFt7ht^eON? zS?GK1iTd^qVip(x4nebvyn3RdkL4usNJ_@(To_US*4qaD#`D$xo26&_V5d*!kjO8x;g3F z#IUR{RM7%Puaq{znE} z9WYJ2TA0lIuw@Q`!*YMxv46hiv$M%o6__I(3h8&%G-2n;bE=0hRn{D%0;%lQ#9bITkE_<~DfpHqT zFeXq>i-NQUn$kxGGbHGAPxD9nm3nGJ5Ip`fF21E#_!Ujwrd{%1Ka+=)#(Z@VYkyV1 za(^2t4) z`Y*Ux01f{;V_8UvC3LB0_z|##lwbMwYE!y`&uSRI!TvGJVM%4!mWX;o;$pF;HeLz`;&>}d?4ZSm;;7BG6 zt`<&37puwUt643B+V7kH$B|(gRdd=Qux8DpI;-`-+iRoa+tt>p5{m}%<{H%ZI22=| zS?J{!00dcdxhZ!IC=uQ1(IZBE8q5-xr_m#N7majFnQ4Q0ozJ*lFZ$ij31=d1bOp%#P)6?a8`{BBS zSIe{c>T;*U)6#Z_El3BE6k48+*87grfChr}=@hC{-sWTB&)3I6(;W0^;Z5|8d_dIy zvIpRn2dY94!+n3P&N^K;dTh6vt30*X5jMZ-)*JrA`y25jX$L8il6DU@{Q0_LPV&Of zc_{|Ml+(WCmNrL1N*xOoGm3<@{SoNS+z6hYAhwV{2iM9ugplIy zQ_PNYs%P=7V{U9NfP$-5q7))%-Yz5Fu`VRtO9jG96ED_^2qGjgf%vB)0u; zNO>F4y{0LabsmzdjQ3+Q;{&Q`y_B`7LK};QxL=Yzq`CI>i`MtC*!N1r`k6y8g#CSW zRL+CSa~$GKwPQQnc}iMJKViU5lqg^b2_%XRVupyujGhSv?k{oyhpXZY#rf?vH9PI2dcU~6@>w_)hu!v*(4&ZfM4rv0_ zuV^Lm{r=~D>|uxjN-{R=rIM)@S05EkH$5V~f`VDPO zEKU&hgU7qFqz-ab>oQ;Qofwp~I8Nf*^vQuczsl5h(eX-X9}>(l%J5C4yaN6-z`sZ~ zJNY(j(KZhHE2(*NEJAeDuu9^OO?e<13OI5|dUuhQwfM(UMC<9yF}_C_Tug}whuM4mOVHwh!#-?d8 z#kCtFUHJna-r78GP3}uy)?Sc!)#;1@W%Q%{;hD8elsJV;mFp_&Nnq;?AYrWXVf9YE z0$h64K@FIca6@^Q;MEC_3zQntODHoyKs7*=QLNmlxb}+M*At-)IMuJAts(V3rI`uh);W9Z zm1J5Oxc*EWAhlpJQQV7Rqr~dRGc}s?;WnAz*j{!NH83}=DSxM(4c+v_p-JF8G)($i z72$_dnUC+YR*B=J(;ULL13J{|$O3OL!>AY+vlb!JU}hX*(Jve_iafHgpR)jpGxL-5808A_+fh3{sBuEm5%-mCGn z%O~{)O?NW)-6*@c5^;!XHMUlI4Nu;vdMKHoHKMzj2fY@B2-NC0Ww`0L zUW$D2EMo}x$~t%n|MoBd7dG6#apKRRJfUVNa(O|%&=^XjP1#Ix^9Z2DNcC7>H>47=Ok}`0zyMAY zh7hx&nz^4v`Oh^M8=iv)5RAV^!4{;C4U&5tbs^grR0 zO1xZO26wb&Z++<_hUGG2qkYbFzs&4tNp84gI)@yd^fq6i=)(f0vuw8EA5vosl$s;1 zAg#BuWoy3b8)N*8-xw!p_Ip8a3I}DN0883Rk4!GrR^nI3Z=4Ic#n1EcKYS7}N?Q z9lFVnj9SN1ipR7&?MFmO>RqgFw^UVp{CfL3e7dYlnqJtI`(=@Ym1gu1twXdLQ2+Q< z@8<8IWn6GsRarK$13T$s>j|MEM)ca5BHZ~zr`i;-+w%w-aJW>KzB9Lhi>KyIKtHWZ zlzJTbDrjJ%2%uEti&2y!a4?%?!z-{#P-Bds2B>I;%JnYo6+krKfXFjcI|!8>zvWBh@^v3CZofT1N0)%^^R zzlWj^l<2`?#=k(c5sZ-HA ziv@)T%EofflA5b5wJ9&gR9&}Po^{|5m}0Vao&%F3=EKHWlQ0%em2?a0%M}bX^$c!w z7Z_uvJ(%}X^-GlC;nbixnukCZNu`M|;Idz|4>XjZihg6aoI`l7y`K6f4fMO*w$%OH z!WOPL3IK_!AUAT1WMKNs z7AAQ!rrQ;+jDx+JCJsubR60K$Cuc`+fhNIAJNLz{xkey(wAcCJGp-=i=@vGMTb4#8 zL}LwnB%Y11w09n5V*icQgHy|ZYrxyGFB7Uq6>z4W*e*nY(rL{+B*&>GdQ-rWz<~yC zwTFK4bFQ0q&0}@5#B+xvZFa3pQ=GsKfG06WwgP9YUKzwjVVMlW zg?LqyPh7X1vt}vS&}kEKp(m10lh_B{R}>ry0v+mN4wp8Ysp;@&SH%cd#Zn3ejVIJu z)s87(j)b2IoEio+xR*!?Zaq@vj$`rB`7$LAEnkf2_)`5>b!;_m6FYErgvo0{3fN7E zVn;v(3`q+U2+Er5+PHy7O?Wf|Cbo&zU|bGi*^>j=2vJnDABvWOSe6>g6gIxW(Jv}2 z!{Pcq_r!qxAG>~Y0`KMdA-RzL9UFSozfm-r-dRagK>GnAP#QXPIQyDTH9S4AyC`ibQM4ut1YkynqVg%+j_rapV(c$stB7i;#SGizt z?B=zroi}S=(HS^ew7-AWJXfwMVrUc_Pb!^PbXsOkJo*1vZe1%oMJn;CAdOnLgC>lm zALj)I@p_w>)Iq8Gms6^mmrLnIZ&*S;AYmY^`Z4MSn*Y7BA*p2?MN0i~(A1#vzSreI ztRrSRNKT8_C}Ij3Ph%^ebi2akcDY=w!!L4Z6b7AY|0?}{h2xz@sCt8w06yC8x{GjO zJ}I1MP-JzJ8fd=mRmB+IaXn!|CE2(z*7xeyAlV>2cAhH7)naaG|W702U}pUMXYm0lxi*(Qrz? z!`(RNf&gGm8aBm{cb_#d*1-3>p%^O^wq{&Nw9jbEw`b(@%?^xP_1+QWFQ--HiGvN2 zy?{*BS=)VfK(90RkVN&n7SAbdJdBec1S*gd^Y$4(3|)QR zaJ(52v*ZBQ)6-tAQ##Ra>q6z15U#=WC-fsdx`W~l5uT$iesp3XB{SGwPk|i9t$I_C zAF5fvXgrEdiMT_<*!KRQX_{9Z5-M2}=CLqMQYkJcs@2{(v}>=#L=4!eN!)=oe6P5P z`D0igM~fkb-+3Gc4Ir%G5+GCr&Gw00J zzrC(#5z&C_`p&McNd_qixHh9TCWkVBH0$t|!=Ta$U;Y`pd-}BJ)M{;bB`6JC9r}V9 z<2QaWUI#9X%+Dc^@TR^Q9k;tre?}%jwRj2RYHbZ(b=6jf+eL)=z6_5a z>8XEG-ykl%s@fKs`6cl5H&b zbz+qt%MCE3mB_kBM2uF|0vG^+?V*T7nm-7n2SP+geo6;N!5CW8nUl35LLq^@7AEjt zk!de}HVUJ`Av272fwaN(IDsKcNzFMu)`!v9bf(&IKsAH=E)a&S&ex`2;PHlRu#mZL z?;LsyTV!yru2JaS$1A9C(TBz*5QK!`_6$O<3WAXO;IatLBa;>sOM4|K{v&y072G7& zd>Vr>T`t#{IUjWvA`+R;GbOvrW1o!n23MNl@!vPh4e$g-g6-N>wT;3af zkXcWhoE=Yxja!yoy)24s{a_VGPN@&xS_>JqiD(q=ltJs`wwu<_UR-k{P!?Nzb&4p0 z7>J*S0l>MC-pFts^;T|p&~TUC)&RCQG|3n@qhiOpXgbWkQMiw7pdaiCK+>5LdZ#dCY$UM&+Ryt@aba&cnU6a zq1wq>0_#Vi%dyeb-OD4)Xi{C%+QV`%wXE7xoZm@Ib*MZCEwv}rWcQlrMnRPJ?Az=) z=bfqBkbKV;p5ix0pr>dfe?nfmH(Mw%6&%I;9P32^HgzPt)jzSWKVvYZ--WWUgY$&Z z;=Y0+Vn{Mu!PbE_IsN6x^VMop7A#7U$R8j$gL%{k_CUb}i0Nl16viHur%r>?EKV*u z;weMhX{l2nihJ=0O}c``AgqzaElRP`lzwvZzwJhknOpoPG+S%*BQgTWtXbN?3dqr_W+Ix`ti1?Nl*Axzh5g>e&csZ;059kP$r zI{1MOrBLu_!z^fvdv?i!(2>~QPr zeEek`UIG&XfeZFcctj2e_0y+|h$_f-ta~8TkGtBtBY*(&(iYCg#NP=7%aWDUL>Po= zRQ=F!lo9=!6oGX*n(AR*)Xnu!gxn_*&42 zc9^tv_i>KwXEmTg--tXRRSD{GuyIZ^+FeG4o|blLI5mc4L}gtqpOohbp|(qo2+31f z?<|~>_JR2elt95-k&n7c)E!`|q@MD9IYcpD`}87xgjENu)DL(g%zaCxSh00Jdz2ClNdl+FY|A9q=4LOMx@^+k6HNY9hQW>h z*=>KSnxR~JFf`5-9fAqlN#L(_{!Aa)uR6MMM+Ai_`Md+i2iJ;2{n~kmr~)<>Wu0lL zSYZs-BToUL(gFCnfl3<$+eB!@oE)(F}OHJt8QCT*qwQ zbb9GJ`tY(=c^YIk#bO<`j%RjJ+Q7BHkgBSj*;(KD)pEGf*$NIx`~0-l+ig%`?|`sY{?ipHQDx>U51|x60Oe>^pRG+&|!}g;6~nj-kv_t1A3N zj$qpeFGK4_X9zLAYEH^R*ZrA3UB$rUoMr#Y(|`aGSjn2N^UR9-l*>~KD;N<)Gn3Ob z?TMH&J>V7Q*F}nU zwv$CsMN~e|bdtH%#oKzis39x3sXp&3M$O31KhKr{eW{|TY+=MGBCr*wxNpQ%##qa{ z?`?$W-%5dE1eJrXoy0Etp|`iS9Akx;eURPPj^u)wztw8~tcsgsq~naQBc_8b=Mx(6 z(gh{GYI>>As@9hGw3_LB7(CTTD|GNVf-zAq%IP*_dz;bHo%fpSosGc$YpsrIOmG%` zMXmUtqm%Q7^h9lEBcS11QQG(ntD9Tql&OPxyR6fgYll2( zod$0Acbm|2d35>M1@_W{F_2vqFhr~nVd{AYXUU)}b7+_txNSUDO!rj9uFJM2>)qz+ z5XUw*g29G%3sqrLeR0=2)%lVTEZ@b3u+J$}23L}RQFRL4zzoj^dYLOLUgx8h|N0*- z3u}g6YXiPB{!^uxrRclU(%rxis&Jf00e1=6^U*lAnLSN~2=}hzNC+%r=A%$X@pLnr z0cPFix?a%2NHN*-4J~4(0u6D{9U*Wa%+Nq)2Jq;{(sJ;!k=#glHygtOm&H+z*n43>Hu|?MBIG1;i z%Y%{kH{qbzp0LLGF_JwuIuP0jHTdg-Zoh|2)OJ<>+2ABar0zpf)c3>78y51FrgulNLlIW@3Ea^CoRg%Ys{*=_wWMDF2+n&R` zU?!5`;@kN?$-%H*AoI#?)Y8Z+OVIADCc1L^)!ukORSPQE{3S}IP_^Yq?w`DrI7u9{ zLZv0Qhzl?-h%{qXnN5|zxm2!UNo<8By^K<@Eu2T0ru4awa!{j!_a{17E%d$H$@6>i z=PF?6-?h~g^-~G-^2>F$Ur0YeN!xL%_Hjv>l-SwbMmUo=OOg)n!wO_C94M7EP2@q@npVlI?0TN^xk%dFH) zEEEmc*-*0eYWX2+1m9rQuaQZ2 zzF79?sK9NfPs3v%xYb@(*gm(uKO;5Rc&LNut${`&>%6biv%4KW$*w1M*^@Mj1H#Oe zyDlJ(kwL_h=(=IL{(Xx-7Bc#?@2+7~K2z;#35pS;Cv16el+(3ZpL$p!QS=Hde!D!3Sy zgeK-McYEao`CZ?w;oHif>z{=$DNNKW#{z`9N2f9C+joE~j(w0}MZzD9Z5Zv@4djv`?*MJ}XSfRye5V%`=+hGPW;VfNX*ssbnrBO+6R zJNI}w@P5!iXxwPs;R0iekAsWj20O^o`G}3O&`JqGQZP4~>|ewTv0znvaw{b=@p4nV z=lAZh72>%++q}Dswz(dl6$!%bgZa}4!j478mAPwDF>BXLhvtXJlu*-|TBGwBquW+5 zbAk*!&CTyH!K%!!-&W#tVS7$Sxtq;2G+8 z6z)~jAJ0T*vo6kqcXM_fv+L{WIe)n0hU7D{ccVfQFE^-M=G0C;hb&0%fxN{ZIkAMgETe{e|8@_8~6ry1;KAO?~S7qeFEsKmGB8j;I53l^?7fWmXc&Q8hR7 zeb)1J*?)J6zx@6~xdTG55xZen{~gMF(-t5$KmRU1l*k|1xdH{!d3?c- z8Y`m`Z}(x6-$I@ySIAz}VOVxpEJ778_43WLg}uht*yQ`t+!DHt8P%0irZ&O2<>1}$ z;|uqM|9Ox-KWYo5^|8bI`fxoJkEmk)f!{@CZ`n!9`zaAS_ik(U@}l(|5Jh- z{`|cYVamkp!r({{I#p@#JUMbY*>m@hb3#rgeMI%vNw1tfYAUT<7k=h_I;hkazh&?m z08p|&aeT*Eg!Cc5Pg>cI($&{;=oAza@^Hv;;X|m9F^InYb}%7wZB^mJ zs8De{(k_db&Z|RYgT))Fh>_#`N0|>yilYFk+Q1weW2)%emYFszCuLi@qy0CK*x5>} z%4z0)L$`Si@3106Ym zEM+z#LX>iM4L#q0URZ|pK8sW?68JV|xUH+JMdzE&)#c|bK}&d?R}Tstv`Pq_rr?r4QsVT zvvjD{Xc75p`%u%CFIg?!B^w5XK42Up9il+K?El)$+U-72{G((0lcO_?>|w8OY+w9K z(kUM&0OTSZ3TSlezOcE2?JXYqb=$SehgG1-^Ddg)h zUvZhKz4ddlsxg2R5bZ!C$AZagd{ZS1tdW?CakN1FH1<~Qc$m?5U_Zw|I6vA6lS zdj^wA%fjfX&fLK?RCw0aZ5=>(R{R=5#e?VP;SMao^Kyiw7{iDsn6KvX^*1gDZ*i8O zP03UUX%95g>x>XrVc(1>D(qSTADNC5E=W};+%M#+n8;Z>6M<2<97fi|trtBUf}Y(& zj5Mw`mGs#BL!xxjdgN$V*zA&+q{%y^EmT#CBey}vvQN}9ukMJWwc0wA}cGNzy8PQYKp!#{wI}_jA-o*H> zB6(=!cjl%J&sR0&D=p(Yy=X$eVj3e{Sn2Ab0ll+??|mky3PaRz5Z}0HX)34r@MlYY z(P{)^M&Rr7r*0_f2Tj>V0br75tpUudHpb3Jv<7lr~X>q74*KJ4Y0Eb)$|`{(RTviZhW z^S<$l|L+BP>X$eNpPt!IU)4|cwMeJ+&s;=k%a4Th^;t}bhaqp*R`|B;o$Ofi*U0be zvGQlw#kc2aC;IRBMNbEJmd(vim79;zN32y7isLBsJ>g{t%)Jd}r`zZ@USPj7#rXrG6r= zvEyvUXfvOacFrsR{vmgXmRaJHOOs?{zT`} z;^-jQn3IKE^wfdBd^3fm9N3^L<{ z+t_c0{8(VZJS;K5z-$y8s?DNI!ek22H4s`C{VW*mxd6*HgC4d6(%LMS6r=XrfzLiu ziQ|hvV65j81%DjnUZWJ&6}eF+%J5_#G#7hFA8Bv6cW;FfY>$|{0>j=x{%{Ly#$s?% zx?D`Qr?cF>zo^DUH^X6x<(R1_^0eng6vUFM&YHUwI9Y{Gh+{ly(@pIv|Y*Jk@m zyz?=W2xU2MySeYu*m+%<kmX~CeobH9igJnh%5vtPx$}$kq;M|=o}(sc{b(bNe)(ND;{Oc zsV;ID_3Bu)mfpqWo3IvFqgx2>O%t^yk~wLob09nZ9t6uZ6DXwoCYl!81!Xz%mK10x zsVs!PaFD@gX8<9#GNw|4xlvTx=2yzc17Xn37y|fSJbi+-qrluQWyawaS7Jm8yBj+0 zj5@nhPNRbK{W#ttI(Zy-Xd5zIuKZNR(Bb(^%g{_1(?vr7!PzeP;{d(Iyl{ZC^<+5? zmh)ywq0*lqXwR~>!VK$);;aVRl}_89t_>soJFTX*b*SbOPlce?K5Qjn2=_i`i1TI} zCa|`DB{+Fokx79FCtZ53q&dOUIc2=9$e#pN~T#9}|9!PZD$Vfe|2 z!5sYQ$ym-sbfS$A?skfI=gxQthlS|B-xDV=gz`_Al7P% z04R7M--y^u@Hij@SqJzk;yS#0U&$Ks# zeJGj1+xxp$HVH~GLS^}-eoujQT7({&8fPZCe^XvH&p4wBSAhGdTd^0xia)G!BM~k& zVB^@RQXq6Vw!p`lwu?pO`8Jmq{5o3~Em75bkW;CSc4-!8y~h*AlCqbB%h8+jE*=%3 z7lEc@LUP$@I`8GiCPfC?;2X-LHa1j{xX(&OrE%H0H<*sOjBd9nZN{$*9or`M3AgrDP?A{e4ZkH<@Z_f{W?oP4) z;p&~DD`~rM(bz`EcBea5$Lb^gTekkElXZGTR*b5q}X(YeTQdvx3lHXPc8fo*ZoR$mo2~v&gXsRwA-?5~HVwr8xbBda!H!o72OX^u zO)EN2nRBnnVILk^ik!h+T?VBsii6hQkSTRVN06n3UwY3chygdslz80VBP7UbD!J;w z&c;p`qUIX*hd*26BchdX3(wpH^0ziyrTug$a()VWN4P*dVLU5SGRlm|u&272CL0u# zCiW`}dXd;tEC%B*q*b0xV-V(qNQTQ$!E%Umgh;;Z&}3Az{Z-6K@IExuLpxp|eS)?? z9=zx&6@ubg&JWh=@5;upG^@VM4fdV@U0j29PbYl^lSD-3Dc6oWlWz0%rpP05Uhz|2 zDwuMWM<(8r+K-tzc9po<*?bOacvymIz+PPyBAf;BCoFB0mG6bFBvrGYtZD=8f7L3?WbWARTN#1C1FJCMbri1 z7lq=Tz0$3;D&)2g?1B4`Vs^#%lFtSTGb!`OmVSoc6QD?yFt@D7MOF`RnEv?jNlM;4YWvMYacm_5l z-7az=vDC|{l4En2fjEWDb9LZOJk~q1=)wMhUU#S$H&D4* z&-3f=En9E@#2x^-E97&m|2GgW4p`aDgMB@8E3mhD>+%tk#C!RoTm6Xn<4YCPNoE0~ zDuGUI#l=Lxa_pEC;LlXa!Z_v!aaWd1iNdwpZk^X*qfgGJ_xB@!)KfuQ;bZpMpT>Pa z(}DZ1yYA059>K(QQ;a>hZQwUw(b~CmM1Lo=KdX2UQe>)8QL5$fwav2tv7NyyEn|!* zu4bLT6@hxvuL@RX$@m=+Ui}xG)JAE`UgEGJVbbhFk{!Q&RDN}EBy?WR^{=hETkfVh zJCyp#%R^+(g!ti4Dxu8fNGrcjbFx8e=W&)oXZ9=k_9#FG209pXELFU`Bio-~6B=7Z zDkqwfvdjo@w>(#Bx4V+Iy4rDaUw!{=Q25z@5M$6_h|fphda;r+DYoU)ZflTNt#G=M z6I|#&nxnp|y+4U&8A&L_Gh{_omTT*T8z^8!KY|do3;71VpZvSJ|T2kw>NM3^!GJ4y3rco z@a-C%_vCkr3SBM@R!Z0Dc9fP=XAq*&0vS;=j!3`3ft*F2f)5=pIlFA>&V zkgFo*UuR>k#b|u$%_QsBeoSAN0_6LmQ^Uv+cCI}csK`!Eb={Vna#fpc{vgj5PQ_sU z${XfqNoKoZ`%B=LZ`?w|$gB6;AL}ED5Vya}wJzr?WAz~m#FM9o^E8c8^|QCg1^K3| zOPw(@<&APA@m(g}Wqd7%A4Tr%JIw*EL=dFQzRq72;Q^noT4YjJBg$+C^(`a~rq~WI zs5W`Ga!iZ{-E~ie2p=$K@x4_$L{6?(c16+%hu=dt^Gl~uq!a&GiF~lIs$`B~p<(A6 zM@vB9Pa7e=lD~RRP=8a+QDxjJnarxz=5MY05QWC~(;CqaMeiZvonBo>f0YqkNi!In z+dYA4Bl_+AYcrZ?^E3iS=A5(Qm9fGzduF>)kz}!o zQQcAQHl`8vSoN4Bwma0CqLh(cy4C!AWgCkf89WKLi6W37I@y|9)9?eR1hHUM6q<@33-3LzkMVx?OmMR zAwR%&|KQ_-;Cs$HHA|oo{2>$bSF|S$PL9tGVs}EOGIju+-85*;(Eqmu(CJC*{FP>@ zwEn3zsj97!o-wfbCPc(c()11+#!#B-kifQRC{;dzKJXU#JseXE<6a^09=DPW9`OhUMJ zZD%NfG&bBdYVIeh3vfIAtoIEl+U5=1PAm2X0yP9+&bM2?P)A|?H-PG_6}X-Mj6r85+(R{nY20@6Oy3*t4}60Cb$* z88K@}Z&1*6)9`%A#l?YK!@t_z>^8jKx!P=ZnXhY8@N{{)#`6YA4|9R+T)yE{H$7DV zWgbq=_m30Q>8YF}RRPVWEM0@c?X^bF^O>HW9a$x=AVk0?RWyOH0$?1oP>-@#(}a1JgGFT~h2t|%n_N<{gi?RM9>HcL%|cCE{6`xqJR{yG+$74d*-3b4+AMyX7+&6`T?- z>aqrxpM+)}+{Q!C3|z1U7|z9!X65$V`q?H0p?rS(7gGx+vc8)cC)O}4y9Rd({oWj- z>{8V>S_I0aS25rnjc%M7>KPh8wT2QmQ@fm=d9IC|D+0lDg*)78vK zk7co~x~a7!@!GGtF*!cCq!>*XsUEm%WnCBl;Uw|Lt?VllCI@#jb;ENVED z7foIC*aHE`fc|`5+&>V(7#xN3bi8lFyFyF9<9d4NRj7mq`nTG>&4$sg^wxKFz_J1; z{qnvJxVqj0XmRCC&RMhTLOnKaw(!@YN8H@rt9;T~6ndaSt3cv~hq3zfrAzV0VOJ{@ z-}0LP=~y9QXxxH2u0NUR1BMygbnVg3?^%y_D8c1;dp$E#0OHH-=p!8F?7Un`-YnRll1R=gM%U z3nf%<((wt4GcI)A2@>bOKx~S>v zI1v!}D3I$4*nZsJOqUpc2jsQ4cLJOD;0u6wBTB5cBRCS$2#Bevqr^2?B1n0`lauHTIdFpa1tN0L0kq*f22aSlBYl{nsm=Dkw2BBOj1-xOV=q9?%>uCD9dWN*asHJo=UQFq8omLJQYzqUf0hY=|>Y2)2pJ99q#22uRgf5q+r}w;&`D0nm;)=Q8Ii@cPa*^z`@WAs$EOD zyT7n;KJUT{dUi8EKjt?C#l=Jv>Jm(g2l{DU(EoW&O7+sy+MZJPS-c&Z8OQrI*c>&7 znHB?>AoP3f!Ng24e3gqll}x4{arN@;vZq0EX#V6tq4;@m2e0qjH#PQwW{ciAwD&~5r{c9Ms!3Bqsf)Hr zs>!wsRX@~@ua}5F1~s%Qy18_LchbW!gwH`CuzNjRCt)s5QHDdlk_ZTzVVX7aOx?}! zq#CJdDI*ERxAGFZBkhF_WaZKEArN9_lRtgYwPCp&zP3`Wlwr+_4@vtM=;TfgVdmj` z&A82dQ<{sPKFJ|Yz!Shc$bs)~wmhv{tJay@k+b?1d6!`1HOQnOLM?-Z3(=%K1bStLsCOMur0} z7I{nB_qtVC0{Y(1QS8q(BI_e$@*Vpw7`1Lcsu-1nUcTYGGFaOW$29ON|Bwvx&M=6h z|K7)u_ca2iJ`-D6sK6c<-ih-nbC6l8MTy<3FLuOm^Yb(ey4hA3W+|3uX>NW^*TjPJ z%1k1oqu5ALQh`&K2ru%OCFrb$kxagB*ePbbJ6r+acd55%Q=?3X&pt$$v#yCE$DAltjUzi47* z$KCNDcWXI+$tzg~mnA(G4z%0d18J5IiaiLNei zBq`s>YGftAA|Q=q=>V?_hBF6uGGIy{FjMWkjr3Ok*l(bOr?ty%D! z!)+rbe3T40obGJ&SLvBsX$FuH4!NyYHYiF*Bv!WYjH#)S(!JwWlVXRfXy*-ZoJR6Dtg*j;jz752g`eLqx2fU36X_#n_&{pq2JCqTCkv zmzF3L{V|=s_A%_UStWXFj&r zzS-J&a{LST8~|e%S*=gmhEp~V9wcb^p6yh}lPS6L4!jc8{IdM-{OMSd)BBWz|K^1&kkc`4~>( zJue03j%K>8t!5I?C)V@7k#`BhCC-+Yz&QN5GlJdo=jt#1GMLO_l1IHA?6nh7#<(@E zh?%a?`6%kL`<~Ji8VJ3`1&?W7@pG;ozq3z5gf6xs+5=yaz|SVMu*Yxb**E*`Lj>bh zDj&ViT)b^-7)Uhcld}jjEB|_Twj%5#tXVAnXYu4Zgmv-OmF1B4)(Kip_HJ-H?N0y# z*L42!jOyT3aFe;gw6Lv+q`{4hFuXVoDNRd>4P89Rr3vA0dqWnQJ{W0*lNkuP zLklvEFku{VW;B@)3|`i)CsxZxQ`J8_ghTdtN@h|x3r#U(zMlF-L$GWm&MM-&=x9Fn4uNzbM z`FCCg^g|fN?%KrSTSPq335DnFvTcQ>u9MYGp+V!%^V4NX#}^{&TVthQf^&dtQrYY0 zE3>ffc@gmP4^{l1JfKYTB~atO;jtUk(x?Fm{83ZR0jFSHwyqeXtMl2o8PiDAF5jLUKQ)ylVb;qpT0Fgki70>h2#7X2BvUz+A<_vnU=k zZ1JV*yKwEUjIz1IZkXnGdWTT<=FfRnluiy+R_E9}8*cBPA3efIJ3$}el(f@fjf~xk z_&4TkkSTV6@ff&-m>0A0?!}d}-q&n81T$ef&H%1e1V+E9s~>LY8e*-PikL;p@%E)x z5?|dAYS+v`gG#lLl%3jxeGLXN#e0Dd0iQ67=HceGgZcx((|A9YTd!|D<4}DLq|h7B zbr*4x%&EmPhp;`ixWwvkEu815DroxzW8)|{^%oqc+!tcu5(?~5t?ZEN z1=O-3Xml$)Fk)6N zJZ7TVJsmIW*L~q&fPyGAaM-0o>3VeqFw^K7qFiaw%Q3zct%#Pa;$W&~+qI2L8%zCk zQ+>$6XCS^Evp)1vWV$LVB9g3xoA)qjzS4@sWSy6aXF{S3bU&|4RT=bx=EpJ ztH%OGK$)HEQ6+?IAcpmO_BTFj&|c2Zz#1U(1T4~H_r}}x<*Cg(p!s0;vAcBZrO3&a zJSnu8v)Hpuv9#&y8PpD60z|IAe9i^XH_60Z^p?iA zzW>bTVR9JM)LBlr*Sp~impn;ilX?7+%>w&(J9Q;jnk*ao@QLST`f(xST}^sEF!#Xh zs`T&im^s_o#_|sSV+9}8(VG(yg-d-@(gP)x&N(G-7CuO%a-l^XV}uoH?$m4)zoC~ z`Zn_B88@)ID2&W}#e=^g zSFLHISfQU)&U-+9cEnD$(v%dOcBGoOvU^Ty=wOiDFLhg}nixgGF-LFEa>$>R7c$N8*#B=#RbKrLv2~6D;Bw&NC>X3iJg!5!gZi z*8})+$iuqj@N^Y_wre*Jz2591RHc99I?}`&xhjn^XS5-DbdFh3vlK*PaHsYi#R3!4 z_n!oHS#KZDq0(SgZ~Fv~!vJ-4jWZ2(Eiv-^h;0Z_`ByOUx5rlR${Qqf%VRD@sLcKt zaV_F4{een2M|?qo2m@TbaB=4mgPs)Gxeh!F65>j2kBMq&l0cQ3k2Yoz*&vO9F2ym^ zX3-qqknM!fp__AgzJfEA+;9;eUnI|pyzNgd`C3#oO;4x#F1u!zhZY?MG<4pu5yc3d zT|FU6==Wrvyw5OcSGKUfj6xd_gRyAgC6(3<*?}3P^9C-5hjata|5!^7w9nU0j*8lPDY7g^1MklS zhMobSX%P4_-N^^<4wQRr_!I!ml%!v%C`@~vkI)Ckb`if3Rah4v5kI9-I!=0YeZqK! zysqjqa49GcLYEVh6c-Lvq%07N_4%Kfr+l2BJyN6?Qff(6wOFd{+jlTRhfvHh9(*Jr zmMP$IqOU4YFQgxV5cUqwDjS~p<0#<&Qkt;u^E;fM=En9^!E-M>NZ43xz5~z|X5R1j z=QB>srFWQiBCN-IgFcT_W2?VyL7-bbC$boK_Dy=*H1n`)Z}=kZ*Qu z%HHq`io$~y>ee@w5Tbp(U5wq`lVoLU&4yHC)e@`0;3w- zka+KLprIYtdOtjLj;H8$nr3J#ofxM`(bc@4w))YX6H52*^sbZ(56q<90&`#3#4 zAW3Vg{ULnFSIgOdjj?fYjhs6)1aWRbh)CKeMIzJb`}Zu}K4y`k!0J<<1Y}P+H`RVF z?|7;i))GH=%YJMtfqp`f4*|t6(2K36l3V{eZ}fp!2L$avf?&`s`byxITN@FunM%%q zl?2E+JD)B+h|w8l;o&9g_3G4(KHoFMo{zPdQ z--F)~A{FA}(5aLi+7bie`Qb<~WQ$@uR<`wu^pmWyzig@5VB{DqyZyUf*Kjk*)VgaM z+8-}ncKcY~!EyV=A~eM5b|<{Ny$xSaJQ~Kf!h&RzZ)FTTgchCrxlspJK9Q zA1ZPe;{KqC!&j@z?yyHGQW1Haf%UX_>u_pE)rSu4I)gofy{}&vGC9=CY3=j@OHTIO zMf@^&tnT`;*Y7ZYdwv~IQaZrE4o0AFk9fn?Vl3;Zgkuw_vMmU1z>CV~@Opx&lY$IR zv)!;6$?O#8onxxf7KJY&0WP+sQGF_Z7e#N^BoYnd2|}tU<`XSYEVmIBZ=J`03-MQ{ zGKFnqK*j&xNTGKEJgxvL@y`Aq5Mu&73!DNCc+xoHjTRs}L^0g*5M!=uBN zBN~7-@u&Y2!17&or$&d;5bL4}blIJgD{-g`9b}|IsA4B_k(J$ZlTGD)q#HZc)JJdB zCS@&su_(6N7rR_#vA++zU33jqWYAYI)6?jl({u}8R{Y4qT<w@u1KF_ zCF=~96mg6Vk4EW486q^&IDbnlbhH$|uI2}>aRa72ZkroZ30iKNN&ZG{&e7oV4R@=z z{|8)rot@G&rc$gqK62s3@e4K95H^YCl=xxh!mO;UxY=CjDcQlY@B{Uc7vD~RnJkF|L7HJv)>VZyeKsfyo`hK_7)mFw;RH26_CzZlx z`uZzC_y58?mB1?{*XE9|_W7_TpsiFE^#O437SRB+)q*;`)Wkpp+2mu84v(qvQS}BC z-j7^xp~P_=f7;9Y4VeL>4^|ynIleS9z6YAoy&}zo$~0%tj^o6ES%vQ6`b*IY3fLSw zFs%0-Y-)VYFR|fgmgei+nO~Gor^iIwV-UM%y~{0+Q`P(MMC{tB=F+Ov399AS_}b*) zB5W}Pv0-Yf|0y?8+Ecexmg*&Ux*Us!B`rH!DsX1Utr{Y67N0%<9HcX<&H zEjzlwew=!xJx-{&si6!gghI$#FM9yp_ntW%cOWDbstt+ed(2TeGz}4$kB{^=1WTD@ z757DoL&?-)fwNXz!<3ABWIwIX<#`}jR7!h1{1Df7nSU9pXm3G`VTOZR|0y>NHoMw9 zt}o64EG=1!EwU-X-3=1<0xmGLQm{U7My1KxGg?F|axGKKl<@B$9?t^`!g2ALP6o3? zGrWXQFfO-Hg>TMeuo1c^c8VsKza8CR%}p%pJA~dN%GYBkpPl5V_}qV@a{mham)-LB z9d+X^-RKgbXD38m(EbIgCR4vNH#$6@UcnJ}F8&@dyqKf5S5~H2?A)45{<}jDmh+BM z<);Kc3T=O#Lrz`!I1R6`OKY1GI5q8v$yL@{SkSJ5So-m-LA?uKR;ow|Jn+BBwHS)!Qzc7&6?c1}k1! zOU!xRQis`8i=JAIS@bcHf5QdBpyZPmFBheZ93gPgy|Yzqi^3`r-P%d?Q!e0<2~;m+ zGpKEo)Q6QR;?7A)7;Io2v>M?vjMMy|=1KqZ&Q+M%Rg(T#3$$HY zGJw1&;Yi4gO+^qY3ap%>bF57&UM)SAUL|1BxDI>Bw*;X<%t7mTH@fZ=XG$x)}nUQVU_b>fFq>Bw1O$@z;VP`@K^V0bs3 zhTph@;R0I_wMWPNVXIiT-{#U=zStc(>{$GBYxq%7cUf0Uw4lAtZHXIDPT&~(#a6A|k`|&p6Q_5J5{C-v z!j!kw&&gfyhg5`!_Og7XDw|9l(^GhIhzQEXV!mBhmXgtJCa&awZZB%nle|W!;xCeT z5hx`-iSgesNzB-;IRwkX@?s9}SiW4yBqi+WzXQix1-WT-w3=`?%q!l{p zoVGqc0#Xc1d5OS$ue)lGh|`{#)JK$_lgBWR_~gsH?1+#0Gg#xGe>K`FMLnIOlXwq# za1GZX;g7#yzLJkh;}oQ?Kl{7JyEDpjJ|#g%xNLGTh#z!5hdJ~2z?k!NUWp>Zo>xAd z#)C-ufR_9I`tn5u4%SU)kk57HH^dNZPTYs6jcXCE;K5Fl`2uU6=>q(L=*3vxX3;;! zqqZTbMVRKB>_E}Qo1YM%)olmmkXi9@I0=`r-9*IDpxP%tG95i)izSpFun$#$rdfRx zQ1$({8+r+*4Hs@@i>t8|?-35^$6!cJ$jV06@i;Fmwpzwo&14ec)b0qu-r9Z=IK^<0 zPTxOmog}{va8*J0V0gJ6m|R;C^`p1AzLFzBuY-6Ww}3kDfO>;qZxXP_vMw zcFoc{^6%#)D%gtnrR|nis}05`ym047Z7e=pQ}y%j=xD;68`NnNA>HT}{*p0(n~S6z z9n6AIxY@&M8rxajBF(sO7efR2R{l(uuk^ye`D7lpf8-qZzlZ?iVAf(rRtxtgqe=eK z%wOuBADwiQw8~Z-0oZ#uRV_0}wct-=_zKiui#INV=TaO9GnEus2$Qf&UT5|*m;Yim z)75c3#jv+H@pG)>;o#xr%9jQ5VpC)ZL!hFGUGe^{S0hHFi|sKPWjNM~vtjCwI^eq( z9;m5%El%l^pt_UdB@L&v!@WD$_J6-*YL!cZ{be@kFpVbD(SULGEUUKMwX6kohHyU) zgUf(buKlaI&8yGyu;(vICqj*zOFcJoWuEFeX-Nif2CV_?dX*iLN7(LzG$IDDc<9nO__K8k>AUu6{n- zDMp{a=@XR&7EgA02qhD9XROptS*8LrL4FRo;#~8WlIfC}ijo_owDjeIt0m2`Tf8Zq zZW`&{^|;dT5U2la{qqG=J&l54jOrDe&VlO@*5jY`lgNv;=i&`DXT&|~`5=X&RQ%v% zgSblp9|0EjuzG5ht;vK4GpF$XI+69b1?D6Kh0eymtRJpqeegaoxGw;8_;&c?K zGV+^~=6>XvF&IeAh+j+mBV!~R_@eIdGSPYlDF{NGA_w?W6~alf0Ro9nmi5hCga?5D2b+LL!=bUNEK8{ zg)6`DI>q_Y<=03|&8XQ?el&;l&6T3{o_PGm_+cn-hgM*X3r%GWNBMbFA#D`dPj0g) zO(TQ#ZEckm0kLnI{6s+lNS!NG@cMwdQ6W6>C_!i>liM`K_#YE0St-pijeUE=$Ko#n z?i%_#Z7q>ps&N^F-yEnAwjZz52yhT#5~zzh%G%#`oX2HWZT*!*k6) z`$lAlmlWMKa0#2Fb9}v?317{?0~HkDp{e$aG^Sr*YnX5LDlh&omp^d*NNzoc^!DwW zIgUaBf1we64&IA$)7FD2f|D787eHtsV3srO_`JBNsR=>Y^{hSw>mOi96}1YL`06Sl z`QKRjVXQOlLuG3|^Vdyj>1cK-WOrW<-rxPkq1V)dd$|XjX+(i>Z?Ld8WVq?22RQjq z5$}JXey)aj{f*SNC<9}f0{%NWy_BK3eeLE%x6KA>-Zk6{fT)q34^$8_YySo7`T6Wp z?6LMrqH!DgO2YB@WEDL;qvSN~8Q)pz@)ZIS*8Fdf0Rfh=7eb7Mp8`bJ=KuLrmRNz= z?o-g|9Xc9BUxGzxujr3&IS$PXTDe0Gs4-PU0_TOR$=_QJMQ57YJ*;QBzhuxpePE__ zdwAjSlk%3Y#s|F!kE!nE`I*Tg-jqpUH8Q|rVO^i`fq<;wQQQ9x3nJP!?7 zbs)Q80zL$3C6tGcSEKzQ<%rqMWssY{z0FAuU^?1@wFaDdP<|8woE#;5Vq&=VA`v2R z*8Hi}vu-F<4X)|nq{9w~~+PRzFa7#z$FKBWB`tGOh06U8;F z?|=q@SVD^bD%|742 zmq4my^CR%5``R-QMDo0R34l1^PN3@&K+aaK3?vZ?7jFR$Tc{6c~28~e6NSRW31AXJgpGqbdc{PV*Gb*E>$BP5%}*ijk38YkxT8<_|mYggDkl00)(SKFE(Yt$LX)T zs};^iZkPGp0pp8ji-!N%|Gw1JxncP#yk9TZj+R`!`1^2XdimcLuo6tUTi& zOwnWNBI?^1rhT(|O!NMSx+6-y3z?&<{!#KYTiTea*ckI=0nypr9To|FMn6YN?No`o zsT*Gq_XCkH1_B_zJ#G5_^|@W&JClAVm*I;6`jb z)&h}-q{Ze;8&AaEuZR4>_*wHUK{`Q2#{2xJTYSB(rc;a0 zv0YqP)|pRv3hFn08L7yjyfcCUeJG<6M}A9{FYP~f|LN)n5va71wGP3oY2|^VeH!DT zD1G-0gjKa5ZMlK4^XT30Pk?8_{dT9SIEYm|D2Ndb7iaYML^c=;c5qO32DulpU(O9*1s2K6%>HWze3gFSv>j=@2aB7x4cgQntz>wb@ zjpCqM(4(dC&Hho;JN+Y;c2D_M`^w^pp2Y6>NU%SJ1b(!!3bULo6rrd1Z5{2zf`E*R z-ES3^!}hHiyx*>tM8mtIEd(rs2n@3Et~n|SrPtMG2G7$%#nlW zH1mI(^U^);PJ2RdYu8hsO|uET9P``J;kNabAqO806j8MR^q)uRzJXA{N>Ie4@qQG= zKw)kU$F46pi3H??Q-3`n7VgR}NDRA@PMEIJeJV1n?}U5hLGnf95H?r;n&~mq}c3}J1eN!4VT^W_&!q$`ORwjqPgX{zS)KQ z!OpXLvt={#b&Ii=3)=*xaB|ykQSbg)6x?%p<%k4@ujV!4B$gAngX^EUMj*250%(^3 z8g4aeTB_PqY-!e*$Sn|Wf*hS-h+xV^lW=O98and5&&33_<(NxGJsobLVYQwrf+pfYzY-& z>^MqCDQsh9jm6v`hx0qVt5U204}u%|3HA)CyN~wD{s8f{zR9R@4Wk{ zZ`91L{=qO9+i(CQ!R)G*v0K8%rt*#Lh^Vk@2%{;3wYg76=8x z%7$|BZFVv4f3C(Rl?7(0 zbI#=a3dU85Ue?WMVA!bE?yh|TnE;-`@PZLu;k&Vp3U=(OG8m1U_vZv4qF=TG_D8vW zHqzW-5(<))xEc~14fj*;z^ixKD&X)eF>7*zG%rM<*0}TvyI-iUC2ic8S3?Eo99E)m zVTD&M`!JwR9PD5V62Tk@A-`3$Hb4-G!&m(^pi+;9ToFvUAA&TjH;Ky_S4l*UCW(%( z(9SO1=Bsl#SNrVM79ftJ%h8_IRI80mtbW*^Gl}~H=BD1>5W7q_`{#X@|L9BC%@*%F zhrn0*B@kQgaljKZ45|qzOSS&03(^97c7votEkIQ}eh|t#_#cMOz4`>S80>!sP|c^e zD|U9)I@OsfmKD1yt^>~(0n(q{eb@ityg`OtlJ5ZW+5gFh8hU^cbJ5v2xCtK6e3e^M zN<*_@H^`hMKMvXHti*l1Z#voEXIK~L>A<^G(+<`U9UJAYXIOf@>q7VFb=jWB;_!kZ zG?SRtb2#>?xA$K|?1Fb7^Dw@ik=WUP?nNGM`HM`*|&R^Fadsjz6wX2Xf+qUK)afmOt6| z);tBTD%w0wYV5=sM-J;b`*PeOzp{eZI;`dbb6unPp#XA%+jx*>ou09)f=GSFvKeiC zep~gvG}-mCf7KlXChUZpP?ng71>Tp=XcCNR1KNYSYssJu;uROoV7JEZ221CZj7eFw zg+BfcUUTHa{suwf{HuB`|taMZqW|T zt?@gcOYuBiC~!U+BmqA*o&-w4PnDqUkt}|w`LHND(N72A_h9R+{eY~TpuK5kJlobO z=hJDD-F{ABXm!Ak0bJ4J`UKv+562s1{Swp{tEvKS7>N%Rw5rlWO~WGX5QOK>bX)gA z3-Gak&tq8aR7+6nA;^>%_3U8{iHgp}2wiavuCk6nSxu8SLg!}^fL5{Jn;Rt!2GBo+ zrlUgCvAgza@SaK5{Lp6bYht-V=J|O5@1=KBAcp*tUNu>u)Z3>Yr$Xzm?Z*TLp=p$U ztk|TowL-L3=)*Z;taySh7y?3IkW|IykC`4A1+bq=V*54nVU6i&3%EH584D?ubX-Ke zG1(>xhF&`) z78CUj2pgZefVK)jq_hkkhwto~g7)JnO84u^F8kP6qtkA8?ps~I(rS_wa6tdlvML(V z@G&4JL49TTGeSx@{PBs`LXL~jpO?DEPwRZ>N%ud}#)e#pP~PypTB)tMohN!9=;BJV z_ST)mYyDmgq9&|>*#4-v>wwzF>~II6zNaZp5Tl=p` zQSt9>qtB|Gf-M#h=3tlfvw^qycK!kXvZ-T**dU|-Sf{}Df{IQB14%x(0AH?9yi<-= zTH1w2_QyUtx@b@giq-jpzPa@?0pfpn z^0fpRQzuK=ajmiwVW~rCINqjwlaPUpXDe_yx_P!ty3t3haIm>E(AKnqDR!>SdWyPw zFqyrJt*A3MLmN11At?INLNh`R9rqHuZb`U9SjT}6Vn}p#A(4Tgj6$yGv_nCCaoxVZ zerx&jdcJTc5bU_Rr(^n7yl~_oNlW`sZ5n^`mA6r5D-!c>t!1V^!j~AR5Lacl$KWk5 zbMV-0;OAv8e_6w3F_z-j)qoM$=YDcNH@Ihj_)(+0cAnLMDZpmuC{Pt|N}_oO_e!Fe&Pk+So8?OJm0Q;81V0cNW$UoQd+6bD&{~sM<&H86N3164g zBkWf)@>cS_)iBHd$8 z`kA2y%6uv6ZH$r21vUe0#+B*{6;e5JGXo%w*gYBr>~epiD~R%dM^cEOlPvVpLM_x= zRJj!!`ud;fqQ5gSwc84G%!^I1pD*3ZRVbS3KP)(_XTs!FP`kJbQ$bY7D;Kv7&FB%f zR)Ou@HPteHnV;X5{M;oof%~~eZ_ww$T)nKx#C<=g$yVRXGh*#um=B4!LXsy?SbDg# z$xSqH7Tbtp<(H|YVCrIR0@E21_OZK~y0&-QIBgT3+fr39m!K0ej8!3T^hqNRCLM_@ z16HD~UgGWH`1lZyzt$w7fFxZ9(9BW)J>_FK`ST``bb>8PzSRrVE-i6;mb!SuTW7ai zZS;-Z5uLw%Znj!=)z!U#8GL_{R)R&U=J4F&_RjB@PCvUSKXa-C#j*4pjnAAjH(+Y> zsbm9cpH6uz&+G6m8IQF(7Ohky5Jy>YCzMuFX~4-1o5W$k%Z1y!@Q(mBF$=PvlUrIK zIpYEe@#0OlqpJl@cFCdr7`g&g9Ww-I{}%;aT;!nTHnDo=b=(uqr{W|Qic6F+LXh_f zZ?9yOw2Uly4*j*s*l#76*lDHMq0gHv=A}K3wVV)Etn|iO1&)q&1zCalk0GmlXxBhj zVLO?zS((2lsk|+OX@lO_b24mOc&o9c(Zkd;N6DeCeaFHmy}zP4rjcW}MY#=X)nKg? zDG+`B^k?+!{WF0Pqr;%+9-TMCyWj(q>)DK{IotM;OREK~|C1 zVTrVcBp_{=LgS({rgZExB&RU2)Xmsch1T9mDE*QZgEGj)%nyT?zPlHk@yKP1u9a&V zBPvmLx{J2avwx!Kep43x!02xavpC%3C69~F3wISH#VdCOlTdoztNhKM1 z+x~*2aUl~@IH0CMe6?uEuNLV;@^PwFv6F|bmZ!fU>m`v;i#K3MCK|<-weV^SxFB`N2Z)74SMMSes#C49t4=oW~;mZ@f>vAhQR78%9S zCrcfg894IJhP70%bf$FY^_p|%E__RTS%-O_yq?)ME)(9R_sN;zj}$Wc(@e5RvMtm( zK7t)aXc4ZIlkbFzU^@iH#y-11+I7s&GXE2mlEz(x8oIW~=vmsXBm&Eu*}mA^eg>MRPv7z zdRkCSA~p3Nkc*}1Zy<-fp;9q*ewBCWgF#OsvU#G2u%L#r1^(Qb9K*l1+%JF=ukMtA z_=GuiJah~4O*2r|_e)?l^W=~I`}LL*%hnRewxcfhDWF(eK2YD)v>B*N&Y+}Ys(ItQ z6cJ&v3jn^4?Uk+_UXyy$5{e@w?7FZ|=jSYNbL&+6VAL$`Za4xh^B_1&``%yf*2cM) zX}hLIb2X^@fbXE17aln(dPF8@)}vI7BzjOWsii$Q`|HnOv95@A=Yu@RT5MzFnO7VK zpS&T4*tY(`oa)8Gs!eOGH2}tz$>l;p(11Y3$51O4+2ma$&=AWX4%2~250-yp4rz&c z{cjefHRp%_zT&?%Tt3*(*J(-dS}$WegF0@a#Vq#$J}&^ZZm3HIzh)h=PK^$sI7B=9 zvp&e;%wMSNu9Ua(^W$IWcaAJN6U5fPHfvVKK^qY$i)SU)*aXsX&hIN{ub6pxQAo@w za8Q#aYiGh?Sa;s&c;ZE)k2ytim;Qb|KmYCcqf3JgNnL zpJ41)!0LN$X@bZ;Y^y#?JEH3~)P_{hNqVe|TaIZNv*f&5z_ICNGHWqOw&Ze;F$mJ>F$v3R6;rj1Sx4my1Tn) z=6>h*JkNiv_X{891NYps&ULP{_t|Hki1tOjiTL!<#Sin(S4@YvlVF8eRGHIN_0L$+ z@5_cODGjzTRvPGwJ*AU}%n9-oia(4?rOWp#pzyW;Yewul|K{P8&p^4Wv;(ku2RKg8 zwg5pt3VQF>=^vzmA80)gm+C!&C~n?K-U62j-QErO`EONO1kCPfFTo5BBL_pb~wAmC$xf5U4{ zV`E}?>nFh}FGw}qX` zyG_3hqKM|?>@yRxKG}#fCypZU8Db7jmgz-;wpLR0I}iWGpcI*>s&b(5m!j~?T0lh# zm)=2^a5-r8Z9=B8sr5E5?CaG+6a60BEw<9foM=CubfiO)xu6MZ@Pay=UYFPxv>WC~ zF?==jWiY8S-8;o4`=V_lMD7*iZrjy`svB!{ne}6X!GPi8yv*WDHfkJ^qbQ!wSU=D) zU(1lg=gfnD79l6LNy%rq(>1qG^MG2If4*rj(t>*gncZ%|YUer?ra+SR-LZ#d-K?eh zqArC}QLk@0La@+qT=d%94&I>|v2OQT&||bYH@t{0f==T2z0X4qfP(zD4r%Ow%XIQIlSQ6vW_!NT1E}l=t8`cmMpHi-T>PPfTBUCZ2LdGvQ^rWeJfP1VWiU z62$K&yol{>wtvWH+w$Dmz_xQ>X|Hp+z;xSjIT^-Pqh_z0E)ZwZq&G%IIjsET^Da6K zE&Fmcl8q$HMQY3P6?SH&liU{3#``R+3X+Cb@h4&EbjT#zcE>QZ-rI`1+f1*x_W$&b z|1*&C72ocrzi}~rciSe_2*FAA$ToAV*R2^NrdQZH1p=!6prh!vUpbaas5mfieWhED z7IW?J`mznh5^ma2y?KEzhZSB(j%EJbOBjJm;tx44pA6`-1h~mS`eDh6oHQCoLs#{-`uogU3 zb%VD{zeNGulC3m`(?94n?(plQ*uTjvVfu=i<2t7C&$Xb`QM=*hqh|YKQ)^U=w#VFV z%q~G&-)q>2Fuay^c718`k8eHs^{WV=rwvmW#ru0^; ztv5P#u~SpQ!>XM^Qj-4j6;zWYSgf1Vqb-`gGpKRASBoW*n_cI7ck!7YFpLb?+{2+u zS0Pm=X_q$9r}=*Q7V;mx%$iVN*Wf9=f%;kgQ8fJ7)F(?3Z_BVr0k zB7R}+>|{KB-pNv1t1K{I=5j zYcf|te$8)_k_PeR?D|5%orIkWUvXr4?)KuTq+j~BPxs@0l1su=L=_80sR zE|-+vgn76iJ#Lg{NyZT9r_|VivL>IY-mjMbNrMq*$mshVDKAw@qG=7@DDt3D z&!@fKH!Yk_?1ky&{~F?A`sf7s|NOmLPCUZSlxnJlJQRDKCB(|{6D0+{Ds#!_W>9*0 zPp>$qA2MhHuyJEn#Uh1d{P^EFf`z5j&s?IhFZTwqq{@Wa(hYgVVs2l*LI_JTSQ=>b z6kSKZi(k*7=#)w!i)VI@^2a$hg$A4)SP=gngh#Ak>U7X6jr<^gA{WvIUy?5&>5^F6 z6@a4!2cJgj(4!bmPpbkS@_WlD8?k;V(ksWCh2ua{2% zE5*VBV$O4t$G*P~wcOP`P!#yguH)ukGn?8jZ;RQKT3oIy6|+<`S@_?I3!SBJyt+M|b9f;7JW5;;B{3HdK2|irum60v?!TmTA0}FQsa13ko>}?DN zqP+q!m1H{qdXU^z`Gf4lBE%}&0{UZw6V)^Yx+n))%sqz@ePl{G2>&|5ZOr`58UyGt z${;~*tB%@#U}-m%=lIRp0<57@agvT2fNA@T6hqs(34T@w&XX!J7}Wm45qI&H$k+ovb5AiDj~rm~S3av4K@vSdGF{o4kc*6-}HT zb54Bm2j0KKvK}(u^_LQ!-a7x$_8|3cVZa0#870qg@9P=|OjNqi)D+X2T2@D@QZVOX zG7F$FeXbo~?x*KawfJE|;6%VUzn;!oThpx{uDKB@AaxU6<|e7YQR%%Mfa!4}Jt!lM zO5UC@+7}V%IeN)|P9XHb`eZB3!`93F0~tY35OqjKF!hwTQAZACS9uoA#P5<3+u^r! z80ZZb2&UpZWAyXQM954`XGJ__;w`>*=(663$V$2-nL0p6>DsD4?nFo6q@QpF zHA6ml>=j|+!Y?w5!oKLI2fQ8`G8fp$C1UU|*~E%btfe@*JW#fS0gH3+7w9K%w2*MK z7Py(l=;uVi>z5imVI-T#V_KK|Gu`oh?aEI~l#`~pm2-J)X%GRyJR2yo)!)sIUS~o* zOa^hH9BU5qdBmQxmN+>bkAy1OQ#_`BE10TuJZ2Kx$DUczKmA-|2%jYa-ET96nc9C+ zKN4daN^1TsHrnie{$J;E54|yzKk~w_n40w(JjhO!JQiX+NH|VunZd70sAmrjml{eq zYO?u%cIxwEiG{%Nc4D%S_tuK*pv3kTtzh3C}CoImzY z*u6gW>*zmcUnG){umgBSW+GP_oI6%nm&|lHd)x} z{yB*H3DTu`d-+yJIc%9@+|>+Sq$p$ey{T3xuk!mK;_WvpbkO(Tk5w~sUmqm1w9Mv5 zO+*bl3P}BuV{JC;ys7u{GG-+d><%V=D$u~8=*E8$<9YF*GRAUhUe_eQ zFW01I{S+CqxZ;1KE`vw^AmQ6?5555pz%hihyU4=z4H#S&Q)<(L zEL!HR7zWtuW%!To{44UDqH;D$t5`Rc9udbxH z%T1jOdq4ATw?_Q49loBlVU-m#Gqr(G%3H!uWZ!DF-H96-s@{>LT`pQE^r#Q(7H3-} zDdxUaEfl;vA*YDyZ z4y621!2!$yw}A3l7ob3eNo(~mA8i0|+y^_45TC%EP0IO`Co{^w5YIj*#p!?JRQV*8 zkv05@ZwH5uKQ7xH>xz)h29qGyUFRh>D3fs607}`Vk2X79Mg$$3i$SF0rxc!Vx z9r=B2ibUR9mMt$KD8?51e$nN~*YzziPBlN2OED5@XCi_9^zWamC^Om3U)voxOlqYU z)~q6D#jTKkCF-6ed%g|tY%UoJeUW4djl-!(M*2x{@byK~Z}O=~0Es;)(egY!KMz^v zPvIt=HpM`G>GJ80G(Qo(@J_eNX#YeA8`B-_Mg+KJ z?jSya&ZnN8_T3d6YKy_mmGd)iq1ME?X1t2=JMP9!$`BF-`SXROW>UU?hYB_KB;OZ_ zn-zo*+J4Q?<1f;{PqncdE19+TNr4Bjsri={Plf2SJ+RI8B~J6s(!3V4 z-GWF=eDiw5l%M5rn;Iw#6SW%EBHMO}qQoWmXq+ch5_Po#mDcv2X;o73b!A43)V?dXGAfJBufjVT9)@h2janJ)}7=vn@|_2AON;hCg}XtgfbFN~2aonVayY$ogB@yEddBd2Z!(V{bvPn~nsHet8)d|J460)E zRIZlfaYX^iP& zJ2B{L;QBVbwva=oQ|ey>M3jA>0NbIE5D8bm;BG6FQtWLTRh^S(PYTymh(L>}p+c)H zsQt1d;2XCMzDbPVcaKEu(_pW}8|Hy4h|dw9$KNAt6V!fIAgmI`5e?K!eNf0YIBWwI zi0t-0by`*=k%OF@gp{=VCyF!9pgp8qiJBXXCNKw4E9K{Yem*IaNlpsOVi2aYRY=Wg z-WmA$W~e3i!I7h7cOjWM`i+RI!{?M;T6e4@Xd1-j895kf0g0j%ZSj@eWP)6w#;TFg z-0CMIBRO)QgbUz7Gk6yTY+__x19t<(muD{gB+6baTMe=VK*?!{Cfq;|X0!{UP?r6E z3}&Jk0IpsH3*o5XAEnJLI<+g8PoBL}h#zgEz zDzUDn*)r|bL?~{Ne#1c4UOb_1`R22TM(lheV3EsjpoFUzLURI zANnB9At{O?sIBO0v6G(A9Jx;?W+v|!Ak%a4cd*n#Y9%qwip}hoh$IL5yWWD2O0xEy zSZ0}vEj9{MgmF0<{>tSv)$>wf%~)gxneE2+jKWavJmh0B-%I<)E!^D8@v&$tyI(Hc zc=DVN`nb`~99dxr%Rzhplw^9MTDq{_(*mzAh9_2lsS^;@pWkh=;vr|?fs{V@Bq`SD#d&eU4sYJ zcA%}}O1(L4DkmoCp749K6B2KybcYyGp9|TP^}da5aqL}V)TUVDK4aPZwo(vLy-oyN z%))N?!q+Ce5U%L%{!6!H%K9w-V}vU_ED{E{M@a^(|y08Q=Pc-p_;W%9SUs&6Q?P z-d+M_Dtt>k?smul53~B|URCdL2_Mi(LK9e zwL+$*XG22-+h0 z9GmJ9976YOacquOjbU(&tXJQT{Qj7O69o;&^l*~DF`B0ZMN{eb&TkXybFcH<-?E42 zo(t@4Q{`)nfsfR^r;CHH_4*Y%F-vyxpE*Fe$UqyX&GRM6pEtfu!h#ucd{=brr@j}L zmpXrzha$W%Y3`$HOxWfN%wkv@wXSCUg~J;pJdeoZY<1y&gPZ^Mc{tew`LfI~oWFg& zkl8-b!sl>cLOu)d+zr3F`Q!c9UE!4?8KV^VOP6qUz3IZYn#Z5VpKK!2{7}@c{tZaw zi;9kB{G#Ky-#gWh;c#-OVgpx56nvfuXzBB%yM0`DHO?k@IW@Muh^Kf8P{$7uyOIHK z`_SR*lF}~l){4ICM-$>}q-LO%3mCE308a6q1jK2+1uF>=#f}@2e=>FNJm4RcXaWS zGyF1al-WOsKiY{lXx*;MY;;EY*CH3EtXgC3eTK{hBdwx& z?GUQ(MOFD{yj(SbcxYWj-AqB2n(*-?ZlevKkz?gxZ~`1UJ2PT60g9I>6Mo_OMheCT zYqw$arC8ca7Jn%HtCDY}_FXdqdX&cxTjq|e+LfX_P;+!IJp@Em-(|IIJMcNwOs4g`QX zK2jO9wf?Dd6!b?!Q!F$v@&7szB&ffdz4c6mX47aSKrt&WK|2PvNA*zJau0I6s;2)quj`k=J1I zd2j;u7U&7(v=rEMZzZ__#r1+=lP(=f|4`zGPJY;iR0CfdNusa?>_R94yX->>BcyNx zi2H2e1xOr%9nY?;pLT|1ZLcu>HWrZ%q=D@(SO)-ktn8OQ=s7mp(|SJo%g4IoAk_&} znj6q3`1U0$=>a$>SPqDFH5A7||GJyP%OwohgA0=Y5?!$OW1{Q7TM&gB+CI1k=`+yJ zsFwFk&NtYn9Rey2I`E*C-Da8Xj(KG>EKEy6U^nZCi*RTDb8;xZd-g7<+AxT7GBEMA zAXHZC=aFc}w?Oh&Ke)6^kA4KIaa*(wV2mC3Cf(eN~h%B z?Yyt9G1-j!#qS*t2l^kJ-a(%{hU*Whr>-Y=t zFwkCwRXDuDe{TJZeNu1-Tbci-)0Xkp87-*|M^!ln`!j^z@*`g(Qa22QYy{qfgGlgi zz+k$1;N14Y0&sStLAjl z*svbB$NCBb^3m0v0p@*V-AAD(fbKC-fk7IsU{crzu%uwJltksz-XWMAll%eFPq_&hqWhg$s<@PBl$yAsZ7 zZu~uhOcU4MdKl~n&|)ls84|VtCd6-z@&9}L5e$OJ>%uvhcR*0EFJex>s&mnWDcgb= zi-)rh4mk}O1X`Qt)<3~TxM&dXd)W)$$3KYxoKZo(+US)pOB}wYSz+0&HEuQawbC-9Pp->(87!xHqjB5#+O5r4{b1U&V*^S42{PiBpJz<$(5VdU8Q2`@e z8JbAw>*)`SjOg8`#iJ5Kkx{K+j=XNGHp80x9dM5tz;zl-nz#$ z0r%z(mj?r=jo_e-;J}z&7V=TVwTa(qP1Ogn@4Ulci6o#Po1iGiJMYyK`zyZvFyG_t z`1ILmt(uE_*q%>p>eE7t0Tghis*Q~r}+HOz&{YnY`DwXQ2m-n<7sxAQL>4wII#)Z2I(vq~~3wU229i=+|5p{6l z`YUC|Pn*Q7lzG27u~?2T%y{!g@4jZrY|M8$St&a(XbUh$4C;L*^(|gPTO-0v8Rm+# zPo;f3nppu}&rNPEH;d1#)jiAoT^F>T`)0Xjt$)N3bYKeqJ`@^ONd7Zs6XeN@%S~SX zD&s-hi)?pHf+wo+==Gp4mIQrJGV)3?H&M?NEis4^D^V(v5Z3v`{pHd__=kMPG=xWj zE+`FSERQ`QW=6K#w;Tg^o$i^sDgWK?!OX~KNvbb!zwZF6Ru(5tzek2jw20TMsh?}M z7V{k8eITv^nIj(Z(V=+?iGefib+Lya(Z50$t*DXTn0#*UPi+UnvN#fi z=3f>@i19N@aK*gnxqQ*z?zXEV0q2wDO8+yprwdv5a}^;^gO_8PSK6ZWuvk z=7)Cy?5VHaaBpoU`_?RT>?lZMKjt0&RTEXCqCSGA`jtkv$VN1tJh@qa)*p||T6zzu zBmWRBNB6Pf1&`ifiy%W~CZh`C48zuf97Dh7*S^nFSdAy0qM?ApG}v=a`MZ!si56_L zd6Ij5SQA^32Sa_KrLh#Tu3{RXi65Ynp;9MCex@U}r}JZfbX++YmPE!AjdZ^z_!2o=9cfJ??xynKkp=KJ^v z`b+!7gNBWfiUbsZS)_t4PxsrSeEze%@$&~=w^rSLIjO&riA#scm{k1g`}ovNh(D(u zuKRT-Hy0@IP606i{3VYAEpA4;H z&azv3s*>ND?Q*Fex5`FdsnTJ|KK*9hrN)Qex;46o>wESh-^V7v;Rn0!$G^D73UcDr z@-?i?^ri71#7-CjwE5dHXf)aCdQmD0FnPCIS z+Yt7WPb*b&U^G1Ps_b}leE$k|d-Y_w+Zlc}=)QZ#w>wzv#q8eTwbv{js1)=_bz(7& zhFEivN6nHT^ZbH&#B^QVyyC~JnV@@_{>;F`CkqV#?c>nS#h>dRizmpEID?tcKhmaZGA4CX zbE6Y@2>tNM8*frOQe&0u|850)1@e3_w8pzB+Z4~3UKbAdfg9LAw1mAZQY#256Nml0 zo6|hq)4W}T9W|c+IX-?uW0_p}&czAq5edZ}-Y=g^3*yt=adtwUu7%%y+5h;3@MNrK z4E{%(jq#P-h+)lBeHryCrOTIUA?Z{6!BTiHYtz}Rt;b-yVCcS{bIx1s)k52$aEHx| z{c=X08>fYOTE16z)>>XRyp^qc^U`cfvfmFGvUFenvee)Y5WCN3*zpV|bjr_M zhek=Z^?Tv(+M1XT*&QN#4b&Hp%sSos;*a1%8V#&*FKu@+T8QE65fA6d7F3Vmq3sZ$ zf}3CkSeV^7ge#<8BY8qejo`!$iUhzaY5f3LMSg4A#hYn5&y;v|wGWoqg7Nav+4a^t z9V|%EJPPI{QAIn3(AHz$L*k)L%lEuD93=YsBUNJQ&xz^#e52o9KP_;@6TR;cG~<-( zlH`5<=@sTM&LJ_g zaiqwAH$*&TawjF|U&wNS(~1&bv9XqWVA1=kX;Fs1ctT7{(DzRx|0SY$*-K>9m@ zUEvInP!l<-x`t9Z4{Vw4Dcpk@9T4~j^*{X6mkOWX5tph1Y_Dh&`=RTI&#tEkfP(mN z^M-s2aYA!{6jkaO$4`**$QnM|295NJGH|bPNBL*9c=HCz^|TKzA3^hkgmbkpI9z z`6`3Iuo7aKK6BBW0L!#CI}k*%DFFvjn*BHiQDUEfdwQKt0MDSM=vmq&J@}}%$()nP zWud%YX>g_Dfn^~(a;wJ9AAz~w&NYn?n~)P%<@)WG?eVk#y>2U180xj3kxI{Wb1iT% z-4oKmSHxuHyG=^e@ixLKvKv1r?6@xCv>QuWBuqg}5Cv?nAn%(>N*`3*iTra=@ll@0 z4AT9S`m1jQIjo78_OK9g-+e4KJlg!maxdKa{oNftV(;s!YK(rpw6$7T3LCwN8gZ3! z3cTOSLJBJ^#3kBkS$+@g{gQ24C<#?lGqU;+KqDpe&r2Uj{N0EB5R3~2!9t{f_1O&( zxHkgqW57((0a+%J2k;Y$4y@ofA$(gR8Iuf`D+*YJOOXSe;d2FBQ10xeTEKznKeBOk z7$~{cN@WRtU@-UGb@1JO(T(D4Y2G>fuWig&1iN$zkonQ|f}=3=8sZZc4hK2B8wR%V z(VhVb-26KL0X2;$fO;rPieVCB$$q*Au5P7vWj}S|$D$<@|<&`t)}|87c!` zZxQ$^90vJTko_0=f-u`}iw|ahJ0{?#8ayz?F5DScptX2R-kZ zP+|S zi+JdfndNr1+c~GI+gpi7q@EkguK6;BnMmIck#bm^|9BpMUixJ=INk<7$`DO@qm#DZ zaj74IFV5COZrXYawsJnBAKXIFJYd1_0u8|n;06N)=ByUV0;?PG4K@a5cR=LPFW?0| z!2*b*bdIIaQF7@dxY&>WWb*&;Y>hsH%%_4#_U>`XvYpP_q0vao$^52iZ*J~3n-4t-o z?=u7BMs3q-DgVjZ%qKg0A4119p%*(z9O<#FRhxA9R1rJt$fvB zuB%Boy-&b?vD9Uv)U(M~Y$ycr8cBeB(9$VqVUr|ddcoNza3{xP;Ai7b!@TVB29X6U zeA_)+PUR28G457d<*b>IA6$moL0J`-v`XeT{g0wSbLq1FC4=}n;9ms4I?R;_2{^KAeH9mOH9?T5i_1%-;)RwS_+t|VjlT+Y-BPsFGG2$pP*W|oBA5wrf#^yVhaXX07= z>Mr!Fd2lF_KvAF{UAmZ(^eC4+N9eTe3!H{rZ-1_rz2L$-z;@=I_$$V<3Fk!LHH4LN z1-QaOc}B2KlW>rj86fl<{IU2!EqMdp$~{E}h>)K}4=gf0m?QyJu#g#`0+D%s!AX_$ zb_$f`L!W68XX+7X+PT>tpB`H5tbnOK77PClyPZoxLr9mod2K%8_if~-gZ z539W$M^5kjGa7o$Pa@3syfltlKVY1TU3m*8!*P$?f@Ez&WB`Z_B0rrFqdg(0k;y0U z|I3Iz;)qZ2`3Jz~31RIvYWSH*C4>inBYxU5K%xUyyIe=C;K;}PFN`zqfTRNbC1CsE zeB2OP)%Y`y3rV>NvX76BAYqDJB?Es#7H`gPo&hrKj6BQ~FjqbBh}s7StA$p;z=%8l z7H*OW2#*2;CxAE2*fU_mG~%|IkCt=tk(m)eg~0xg3Xy;t6idTPGz1qARAT2Q5c_x- z@9`9_W%f{?_0iH_OKz>uLwqsCTBY66<}YsQ4uM3FD{@K>cfza4^t`LV7mw4BGy{&S zblM$K$j0E*V><(j_Kr$+geNYx$Q&$f!!(9Ut@%qoPd~07Hqd*HL%*+WqCKU{GhAlq zsyJ54Ja^_H>YM!!rcQ5s^RY}2mA1_X%6_diH@}XcN7D*OqTh}X}{>DQy^s11@139R%Zw-yi3kC27!rk=ETrXVg|WXtd02a^2Us#9?$W*dAEd|*77sddqGe_sO zKm(P%5zuav1k92yq(7`%1uc!UZd?ssAC1Pmd+ru@ukG;B$!|Y>!yv50HSF3q{^s;n z!qV%*w}SM#??|2%Zu^{0Gv_&^>9LJcx5>Kuy+B&dWcqQ<#n0_0s`$0k5X9yx*V}%g zN|R4Py{XBZnr;gnDSXH`Ij#*%uPIQY48(ZRN>xmj}N2#0@vn+ z;y@%Ea>*2X1yM)|0qVHxDIeE8gX{$FpC4&=v6Zj3DX+pSd?GAts6u`OsHi;FW-c;L z^fq0BurJI~k);r=)eQatK=vNkn(l>fW1r|i)#3~EUm{f-ir0xRUu9gb{u*f^lxqIs zD2YEc0DKJ%FnraBD*nK+9f*z~713&b0MQ7wVLTcf4DWRs0N!0|H97^ya!6Tgw z7Wwa&mNh3%gO8xCZ_dF-*Cq+^3dMv8a+lzRUSOUUa{`P=ucCmX8x7(I5EusIq~TA1 zVMyDL&R@6vwx}pS{&eW7U?IY~0yl%E763%Gw>k+f`R)nZSBKV_!q$BkaGxRfo#17^ zz1TXunSyIk6SBWt*hGyIAfGBj{lWYiSocGiZg-oodt*m^X*!3qVZ=yY5s1}CzujF2 zMKLm5MLNh$>ofdE$3wT<`Ac{`xxvqxk0-JA*xd-NFPxR zPk^^@kN<0N-C`ivyZ&J{)qLa;Z`yxOtMik=2k9axe8fglale;DNN~~tK$bm|JbUcA zTU^C_O9ZPc2gWi&4uup`83K!lFv35H0l^xHeBOyl&WG z{A^d{_AegX;^x$xNyv3gg!9;A5onC3yU+Hu8CXtxtU6p{d;w&Nue6^0G_2 zw%dHa%3RZF?7;D9!4^Ep(J^G@6VvMbiV`Yuejw^7jq_Iw@-RwN-Xr|uflAPI#^{JG zeCVor2ymKtIu`{LQk7N#(BPY77>Fbu{Tf0ml@DAFYmEW7BK%O6!0PcBV#ciz7w77% zpJSC@F|G8K6fMKSzrd32;ps!%h9?{ad{cx9lU3Aaaoy!x^7XF^jiH8a;b^z zaW4zz=Kn;iV@KK zQU4l}7B10f|Dsptv!Q1{QsgLJ=sO|Zz|$z?dRu*>tE5AR9NuQ7fx@y0R0hx>DUdOm zfq9x_Vw7&p{mEoKeID#0lg6b)Wcr3u#m>Cvv*zA;X!1yD*olSW=Wkk0l4oB&UPG0j z#IFv%Fb^2I8U{*$W`9FV(5yU+HI>C5Y52=yzwKx(rDjfcNS%fk3lo05H_JFrGno+8 zz;$3oOYW6u^Ss(i>w0K*yY@ZZ^nCDIa>BAgNKpyW($c0S#XEDb4#3)ydghJQ+ge1(I~TS0Fb zU&(D*o&XxT1Df{=tGwN=M`2dKBb<26ilBk+5_9c3;=YY-O*@Kwitu+LqaE zR(^0LEotn89+()7VS--jV|jh3Qqe(rk^YS~odrLnP$WKNKn=4Uhda4~syUPo->#Kb z9rguQw+~(_ zf+h|^)p*tEez`^?6iI!wm{9N?KLSOX!&wp$=oX+wsAL)x-yxI%s1#SAH(Q&sO)USY zV}BEc0BXyqyN_zvHIU~=5a~8TzQjrLfM~vn)dJ4jmx;Xl#Q$I+Jzpx|HM+!8su(k$ z33hBS@@UUO^rxP2pOkzZOS~Kxi-``sLbNV+kOCIzEkrxAjD#g-YSS0MSZs>givb#4 zU(?MLLcYMyUo73Lv-@8qWl_E!Yylx4->*S|XXke*8|rXwLGm#msMp{N@FP$R8xRVt zy0TlGKFL8I(?=T<&6QuV%X=K}f|f4@%msDim+cQn%-`CZiU{*WbcN(fs%UY=(5XeGGC_hP&AoV9P^s&8Sz!wTwGa6<2)~)2?9T3gWif zVJ>Zg5UzD&_PRRouf(|6EhXQePjIKv=@Zt3D7oe)RLQCCrPf3v!bQ)I5~H%N;Asu3 z-AKNJ$yf(WkZ03nb#ed}V@OXr_ex#R?MwH$^Bg`fwYXQ4)3nv7fyp0G0G1uB3l+1G zhrKje%DYP|?L&aBf({jNZ{oCe3sMlWW5T3m;ZwQf)32&YyG}_oY(J$ftjI%*8C#mD z-6Wrs$&idsf^{g&4UH}K;$M5D3xpZJ_ej=_&34kg+7t>b*nKgwXVC&v3q@V9N3^VT zXUPQYkqTRY)RUKLz2Q{eQ@P z6$Z2+^i(2(gbN7pAoLHuz3I}Mgz@)OuQv_#uN~FQjQ3g8r-M@w!rmKgCrR|t#>S;M z>f0xGyIMo+{G((4^mrCBN);3TJcmCsb(&GZhi-2gv=3BOQ=n(K`Bw>1RC!y?dtD=I zR-HDeHYZ#jwUL>G#_O_PY(53=J=-G7s}NPWNtl&J+cHLlSSKC=ylRAgc6puz$31~4 zV~3_5)%c@;UHt!nDQZs$-0;7|jc_LB7WffC+<<2K!x=};gy&a@4*-88)*Z<2|JtYK z$+G=Vw7XxS&ON=9HGlyWRDPy?2GYr&WVP0@F$7dF7D)uwhbK>qj?Iq_bWf3xW1zB? zSI6>}+mb;TN%QCHwuy1b^i9@8;13&WC5i-p?4%X&U!lHr^JLU&JYj7R$?DBtc>?ns{8qb-; zPpO9yY|n?9>>%G4=QJA3KH!p75V`qcu^|{~?EBkZ(yNk+IqQV9dBa_FM-(G6N-0%t z+(XwXp?9mJIjuW~-$tJp4nFFLQ?o-lR$lB8+A^f$vbAQ0ka_ZM|61^=yqo>ORn8>M z(yYF_Sp05xf$~Bt?V{My^0C)~H0uO~4)r&jcJRFf-1T}0?DPx>>rMh>vGVHBR!qtP z;O50z3JjER?+-OsSU+bl{ksWOIu4?}KF>Ki#(nx*T5x}vXb_+7bxOKBsQgyG9Ogwd zb1TJN!15GBncQ}dR>Uxb%?9aGUCj>l`oYUkgy8sd4&JPx6!$5WDjFwBpub0@6n~e$ zO{*)IKXMGpT4~!&&z!5kwy<_;s{x^HBYAaQ`wrRCI8^nTOb?S9QI|+ff9+eQSaWl| zAhB^PL`E%?#;wz=7(`b;Aeqe>CQbObK_2`T!!ojdXkaCj@odCEXf?r$@OdHkWUC0_ z*IKwr&+GL1ZlDy0Epznas6f&cexi2i+?N~CQL}qRfKVHUp{$rgUeuwF& zLti*m5{gRb=<=h)Th zMxVm#6PtKmmV0P){esY0@i*dmoA?tr;%Taj%62m+n^?ld3(x~`)8}jdkr;nf_&6T2(}IN) zg{lGx&{^}@ZM*&Yw*Hx_i6#I~M!;^hH$-Z+#l7;r#qoW2B*NK`oWInxETO$fr%F+e zxkAtme^MvIS@WTqB;>{dKjWQwsdAs`P&lP*NTv41siCoJoFes{;cp%{B_4mFMxIp@H~Yk7$~SJ=UJ#2@Gv;P)W^7W}w_5E~sg@AoGAk6$ zc`29{A1NGX5v?YJG4zq)zZ{ojVdNB<55L;7-nf2~$pu&Pnag=R%`d55l=hOwel{}m zJZyKA0;CqP)4ID~YQuqBac!|ppyCl|R40v-FPQ=oW6>@FXh<)BV1ihH|JCFS-O+|Z zhf?}rU~uqp?K1-2m5?3ijI+1XhZFOGQ#KFK^T=0byQN`cIM31tyZZ& zRvwlSCElF%x==PzoT$43iDP+0xC(~Z_e9VxNS)e_5K8)B|$4+d9?O_>h(?yAR!0Hdb z+|U`#&LCCfG~u%>8R8}+oS48jWH6pz7PE4OJ3O4y2b=wkb1a}FPB%Q6xG~3mx$!kC zt-2hI+Nb}IskaKNs(Zh`>F(~9ltvJckVaZSx}~HWBsL}8AqY}}(%m85jdXW+_g?SP z=li?f{~6bYqs>}#-gAu47}JOB&+Xv$U`F-Dm!B8KURK$XUk5IFYb@@XUndYwGMVhX zw{0td-gLH_jAMmcb7Qdk?xhh0oK%qf=nLSR4_C_+RW~rKX1ESPmPU%7iFNy=F0kE5 zeAYBcDNtqJGwEY77q`_cZlA4NmD!l?ng~~KYL{CT<#0nnj=X?tnsit^_|x3$`IFZ% z9!8>YTizS5;GTuL_$Nz=926pKf{>OaC9}Aln{JHgv zmY_s?)ku&CrX`N|yNASV7c=vehkl~cLO#LrDYLsU)Itjb9TkMJ@}z%YbM5JF$rw{R z;aZ<-3ev~!gB+%gzh@kA@;f9xkS}4Ntzp8wdxY@6@}f8zt?C&F_;a%h{Mi7y8?ErR zfoN&aSOdk2f83hYUz@@)8Nv>Y{vX?#vL>e;vY)%=w-t>%_geHp$o zkn6`1?WcL4?}c}Tp0h+cU%vG^mN>p4oK?;ftv(Glbow3hZRen3Kf?#|yNc_JbT4j? zTv7X?rw<#Oi$7Owp~H6QEi>1)7pm(VEb$4O1OofqUX4WK>&Psv6yog3r;O^QMr|CR@i~AI zP`4=-lZKw6SQ-Fkptu^udFFlx`H*R^al+C3&F2qmF8`L>`>gupTPDWviO1>|e#9w@ zr|$(>O`eb942TNMAMaf&=w0;0DwGLm+`UL-Yv56N3;EJ%BKqpnto>13u!!KTjo&9? zCz8VFBfqckK%@jRk9QU!Q(K*d!B@vk-P&I@2e(_?@=f#8gpH!}4Pt1Bdi8(WKjMoE z!zVu@hsX=ZQ4zNX&ket#`2=KH(RDH=ssiXIzyspHZ-?no1=Q4Q0q~JL|Coe+BaJ*{U=&H#2<=>uzX=PX5o-Ik|7krLc?#{yG51f1gRQ*J+#>IQ0(%`CLB+uQH0N zOQ)GXt{?C_L9K=M1sF>wQ;>8Ma5U=cV6YCjZdv)$wQpZXcwhfB6}>)9&SOjA^)a-n z-jOrpluoSmaDrOHmf)+9bb1L2{YO=P#59?v*Ou*)%gcP-79MQ*rR*5d%a|Orzu$_x zK!YSDsZ`{RcknM*#`Iw7FSD45CoTcUaD*vf(>5E_{2hiKV&T(9|K|_6M*$@vJd~re zq;>031pz--=fcJdHe=#hoQ(P#z0#Jk);PC&iZ7BCKBuAz$0|= zpfiEzkr%PIB?%9$#o*9hl-%i zpi((wDAIY;OwGRYVO&-UfYBtCZE zrhdZVPE&KxB^Zzyyvza(8ZuzDK^i(Q1->50D|mpBZA^9;fEJCDmzzl`wo1F8>%QaD zr~LeC&ZM8C;bKrQrJ=-2ynOP9RMBUsXtr1#il7NhME1z>*+R-tviZeCqk0#LXxeHQMm_X+CWjT6*SoJfHzgBJeq4zr^ z)KNjWzZJg$2H6;q^>GYmu*L&Cew}`VX36o9#r6mi9YAcsP!?#b-(-Of^^q88t6xFe z(!kk4pATFqV2Oa$*RvxbIg%DF!fA@&U;Q6CatoL@21`Q)+oL)jPLW$EZ>W!|)C>gm zRvc^L-u6YCpq>{`<7n(|-87=b$Zvi$@JFEid_?E`gZXZ}*5ZWR>$vz}swP7dX1&w( z#iC&LP&n#O=le?M<3{0ksbBh#wg+`6SYt4IlJ#zvB|6tXF1m{(k0}ieab~=v@*km9 zn4?vt`d2} z0F#+5#RDLNKS2Xzfd&!aP1dpxfNS8@LmMM-ZWW06*OM;+;77#1qR4L8dc4Fl55o8h zpgursirb2x`d}t?W&sl7JbpmPNn% zFJJL;{rc&qF(G@zGw>m^R3n9WYv0=IQ(`AE%rZXd+T|co^Z)`A9zqyjgSMfcHAQxnFO%$iDMcU=fSeacVfRkyIziHgTIVk6N~{ z8qGrrNu{0gK%|lS>TQSB`f}Be=y_zE;~g^ziw&gHl%%+fntcu`G&F{MFK8Y<5tko< z_Fw&W33p_q&;Q`Hbf|ZJzf_;^56Pfr^GM{Y2&~uzZXUYFqfxFWvwE`4YT=7h`^0EH zj@|YL5^>6cBS=u<7dkQ6nLafWTc#5T$p#>;jkOFI!#{%mivOPm9$y6b?qgjlK~5W8 zdlLwlKw&b~e@SYF4}LOx^Ph))0u?(i(1hlK;Ku;9r|=OZ*+_tu{MKeNg|%G2TD)D) z*t)yqLvvPq{9^x6XaU@T(+$`k2u;nZd0gQb%-r`*$MI$j`R0-wT@BuytB$AOrN>al zFBjFl6GGZKVVVYK1@UR3U4482)pSG+KS&uLH(Z?<{g2dbb>pI!VS-FoR>tdG3p%r0 z!b7{i-r9)FPs_ZMsGDoUZzNn;VjZ7bra_~HOS^1ydOO&zYW-hsK%1*h07^aQP6rsp zn05S0?73g1>$uP2TswR?ZRiG`T)gZ)G@ie2^)7FE&~)?C^mALj&c?1{M7t6Y{Qluv z^?FTtfN!nv>f&#_&Gv54VNbsuC{sD2i4!LgP6A$iO7TO_8iGKn`taTA}O`}l>&(t9L_I}E1hlHKZM`QJs?=H=VBS3C9 zal|Js1-66Yl&x!<5gPWb9{m6&vr>a*0|OnFHbd3XxO(^nH!pd-pY;9~EPrsVj-P%W z47x>}LGX-%2WrA50IuT-dI8@GvuNsm1x_-DS4*_lZ&Mf~b}FKY?drYfdzIw9WwLUs zfZT$ZKCf&j0Tc3F4YPUOEi}slh;Aaojfu97+8ar za~jtHOiclBL?a&SY7NV-jxwr5yq74qT&q*KU&RJuo1+%}* z=ctjxIr@N;pdLdqQ}t_X*AFD*oFPT!{9C2w7E#EhD&{7i_6~-}jQ5n!@v!|wkc7aJ z@c)gRxPoFeeqk`IVz!C&pKl%)j%5i}8dxCh*)^N{pK6aF}E1`<(+Po4;_NxX9Tt5iH~sJ z##-;p0l`cqLRo2RQ_GFEy_+nlH($e;=$N4j47lK?4C<=Ye|6P-P*!UCNpS0$2^{7N@oGCSCg z^TeJ7dz5zu=lNGra7`-xEX?ZnI21;bs1|9X@vqCQ(cpv)ds&%iV%G1Yo?mI*^~bJM zImg)hw~*=(d^XbHT{-eQ(i~6irT9l`Te`2^_YhMj{*E7ar}}(Q&0#v{^N5& zjlkrdSH%t>*X!LW5`IEeCrlcSe6#w0+x;wfbQq=sCZy=VX#u^3@_|~}tkCC{Flsf_ zettp<)*tm{=%vuEZn8M9QGKkF_Rzg``N;ZkCITS_?%e*6(5vvGY|bXtiVwE*;|l;9 zh$JA1Ydsn*SpkuocVmCCPFRP&r`S@x!Jf;(YWBhO^?qn*!^{j!!TD8;9*k4ssx~^! zui~$@sG)piqRmFWHzJ-psm-YPbNrM^u$63B7lFYzd-XH2(kvsPTO)2>JA!_Q)v~lh z`iyz{hDC-=8&>dAujUafI$CNk6dT>VIOk#apTzX$pS^1VX;2k^3F0u|xd^1t1c0%f z9Wd7v%fJriQY3)#c91P?zt%p6=r=Htp2}CnuRWYn!I0fMfC5SzFy#Cl1iBpGK4BZJ z9ghPVL)2eJ##WYQV}7-wjR$F@FM~kHGzD($m)+!R>8GWRgp>pr*rQT^gj1DbmwaW_ zD(`$Dt1?`XLr+@@Nk?zK7dTOREE*Zchsw}tu;O<49pAE0C$yd&$93(AH5&3 zZZWSlGhfS~q_0J8cJuM~>&@&d?BOb`B}G?nNAI322L!BlQ>bON;yrgPfC zD>t7x9v^cof;-xUHH?sIFC<-V`)s2o5qyssGC$(0H2`Lt@y5U|`oH;+YP0RtE9~(H z$TSF>$T9wVk(+=aX;XO)vKWTuj#O8s9|5oh#{sNRJ@6#|_`J%TQqeD#g0azd5AP8A zoX;lb&T z)&dv0_b z;Z`ah45XM$n*(wT3yFJJS3n)|JK!(KC2(&!@5yH!58=l}eG>Bx5Re6sn-)Cpvbko4 z@&i`BUQV{M^d2YPoXONixCK>aX{7HS-S*mo7vXqggF^i zpxVd6O>CZS_X%vFtAuMG@WRRP{jEF5C|9j!8Ug)VyI=A#(jSk6_;U zBcHVpliqmA*KYC@tuYM?P9a}0x`458P~q)s(Q34*eV>Mt*I{J#gHOJb+w&>dJFQcq zbt;Se#de;QOZ29j`YS)0!jlENy$M&-wF-{XWE%wwYqun&3Qnb;ee2ySQPkRo5RH|F z?Gvu#_3BO*!?ZWbSMZ6ut?{$1&W*5NX7V~4l-m6;;agLrzvQQkC5k+%Qpu*w$oZrZ z+g2g(p)8T-=2Y6CpQMOh7uL7)&~9&ddEH|@>9e9rzkWlzeb{_4dkgzhR{f_fw+4Jy z)lBlNq=SJ}gvq!MMynua`LbLDc1{`9*fgZ@of8+U zi2RqQ4Ppwfh>45BAC|8hk#H!ow?0mn{N>=c@1t^4=kD`!-d%sN%$u^}*7(h*r}KHF zz@R-~6ob7xV2mutVtv<8(y}AgHnG@V;fm|QhX5xr+Kk+sfirj`?)a4`pS$td_BC~T zHy&zbeAuFG`8fvc;$M8?mD_1HtLdU}Qe6+Z==XB-ajN3JBOcPw5}wRIjh{A5Cs`%8 z1U@h16gVk&2Q_N7HlN0O`U<8eRXZB-i{vsAM`GJdQQF#yG)Gtv2SOQxrN|eoas8SlErW+z8CWg%U`-M( zpDSN7`&l=>PtAb9Qs&uBZC#fGSwNkV164qsiybBWk^_IV^kNNvbUVTNmEG&Gn=Js* z3cU``QF&DvP~j)rJ~%ge?E?s$0XN7i6px&xfEEgyjW&EwqMe(`fCwe?N7F50{_hj> zb1Tv%qX)wFG`+raCh+Gssp(O99=$)*9Bw;#Q}h)|!y58nd`6e(Ms#&J!##_Ck)Pml#F=1sBFY(Ry9Xgz`G z-V*;MaR~?rfJfK$xZWD>Ect-U#oa;dUthdEiR4h7jfs-e$^vv~keua+UQv12cvw!w zlyDC6iaob_g<`T?s@G=OuXE9lPwzYwJ#ATB-y`0~lZ_xf8Th&BXwm;8g7%4~4m#g^ z9fvz#2DDtfH=M<+AWlZ!EgmWcZQKQqTX10-=FFbRLn+|v<%tJ z45yUEa4gQqK7S=KOqt~vaA8|`J5a)jK+1wTZz{&(M9A}l1bfBf7wsG}ip~eD1T036 zW5>Syvdvto_p?DXqOdL3;+23H0~`{Ir|<=Lz#=qj^9;nsdY@Gs$DzdLL4Q5+m{Xu$ z7PjEYwftexV0%8q=dH*=K79nt9pon@F{eWU2_uPAqh0sl#_r-@* zt7b8ZJdT`$?aA$IPOSpI$o9M2xfg%tP`rCLipZP_8!u8%Lv zQ>?n##hO_hB>a=xrRCeytq87Aisd=My!+|DZZ0ww*E!bgv{M>!Z*L^!sT94Dt6LOW zzUSWNYya<~7>M(7zZr;u4Rl$9`lM}ntnYiKfrSGf-c6-H8D)9M6)wrEI;E@xdm`|e zG9;72iwRY54eV0&@1D5(O#CF=J?QE9bKV0HuERHg(9{xCvRIYV`)1r@jbgapvQ6Cw zY=vO_^1phW#X_sK%RVh%i~e&-q-U10usJ)*#KG!iy20WOUGxJM!te0^7km4f4^8z7dHcBc{{}m_tjzQpT)@KKRtq# z7760#Z5!dn0H}Pucmxjasn>*fK@5r8ZB=X4V|I9xKd;`|;CQ-5;$+XX6_Z{h^ zhe9A~th6W$kLxG8IVdKl0?<@zB9cqXDzUFp5@>+^E1@gmWE~vph?u^Me zb=J~e*)P&W^)XVB%LMD~m=lM`Qw2ZMEpy-D6%c-Sfwm{9>d`5 zv}#f#CXpSzGUKQ;$03mpURQWCEMAdx|Cn8CEkDeiy*^#a?&X4INIRF>%b8p#{l;)x zt6Z%oCL_S}L3F6A`IA%(d%1zCpJ98lI@_r62lgA^-h2i3blEPYLQ>CP0=3~|S691h zmdE*eC-p~19J2SdyY_}`C^tv2E#Jhq0dE{b&IRtrwhk~X5oQLg(m>mcaPEK;>$5Fj z5zL020>uLm5Oz7mPkn~qb94Nm+(}qIxB8LKBnR_5XXj@8iwq;`f~|*2I1U--ZDw0v*+<{=)1QwvM^CAN$PoVaMB~cRg-5vDs%!4&|%?-U@`He)TIG3X%+XHJ1)PUWM3Au#rPLXr_Y^Y1TIN0c+hUsdKIN6-o2aM|2ArrlkAj3c(knwlg!xBWQXE-Z~cU-I^Lvuycj+BY?RS&=KVu1-rvo9)Oe=O*fGaaUq4tAl^GkTkeqTt;eDh2@r-O@Ny1^42cxhciba)$Sw{H$Ppqz`^po)-ja5Rqjfv6$S>X4VC23wdk*-y1#vQyYczuEuoAIqYKeH(mu*&j0k@C|ZSbfM&KkR5jsm2>{Ql)FeWCe7 z5Q;ZwqrejkSge#MjJJ=~*Wl?~O>#P9Owm7CKWocfHcBj8 zF}yQdm!SH@nTw}7uM+uZfD29dT4%zlN~k~I*|4p{R8{4$ST{$5)49P5r}Wh%rHfWFlD+}k5@o?*{*e4?`b<(ZV5l8RILV`a~vM&dl9 zIiCE;&K2<-Wr@~$)xjEbS)^9yZOCJ=VC-YR!;+2(5{=BGX9_$ERH-g4vAlS4T6Ie|Kp@ z!}~bxPH+SNJ-g#^MS8ClWz=W|4Gh+bt;?nvJr`a>V$2$)qNLBo62VpG7EIy&Wk~)} zj_(11evo4YEfJ&k0ss_I<=&8@dN7#|Xw%`I{yX^oixB6Z0meF@cB+7I55^0Ho>!kf zz`Wz0d)|%wK_EJ+fDebpG;IX=bd5l|2B_gNh`NZAvB7*`CHVP%LAyk7dU1Vo@=30t zT_L~9N9{NgH#X-d{8K7S8t*(DKMj?v^3U43oN9|{Mwk_2djFGP(+~TAsU&fAS$SmE z#BR54;q)nj@OK>K(1FBIj76LnSS1}io|cIi$9DugimZv;1e~sTVf}Ik)E4;genkBA zdkl4*B@vw}G5HajYrxxZ{hVre9eLlGsdubel} zf1(0_ml^Elp7>vp+=z3Pk|Q>8JhYw>OExd+YZqgShGL7@ELlBfxAu)H2VjL5^uK>C zf69AmV#sPP@TTX>E)A_>vnp__`GjRgkfbadi918&SL}OR!7rxyydEWT;Ut<)7|qU4 zqviM5)Z|Q=TYKNarNexI52L`UN6}|>HMY$gyM0YuWD_Zz5Z9y~C2z{o)<#3%6;>W& z_h9MeQslgAyFD$CoMo?-Qtz+r&kg@vy0iiU`uT_Djb({X3`MB(eBy3=wJgMz9hTEW zoLi0LnTKz0|9F&ThHI=Sils%wzP0u{i~o788qwH|c-CCcs3coQd?R>pv}_kXQWTKATT@}SAq z0Lu7_#M$*g!!oc>*)glXEV?enb|F}B8`?3(O{;}MP7?J|>cU^Xf7_GA=MAD?w#;?c zD#LfEa2Un6Hz?$z?S?!t7PN`i`ET#|6kQqgeNZvNOv*lb?FaY3bWYb|L>lr9rH7(*`R9ul z(oieUgk;OsMB=uuoGWn-oH=1XFBL8xYQd!V{a}QTiG1U|zLH5^a+iA82?NRp*eQnw zk^(}tOzgR^{PUXZoXM?CxK2+V#6Kj>6ohg=^_i?)QwjD$oe%>ok+RZX2I@FrG?Tlp zy$lp;)X*glBf<(5#;B%5kT8Sg&zT#F{q=61&QEQx#z(Wfx-#LRg2PC|qKt&?DG+v9 zA(l78H>+cw&^2itw6@^G5G-tEmtr$NWQm;Y!L5>yAG%z}42mT`pT;r2YP^;ecK{+8 z8yGHt7zTK7W&-J=5pY@EyHyx8))* z1Q)9t2P*LZ?Vv806J?kKM=bcgD=}c$oWow*x;=7TNb>W;QE-w-D(l&|kTK*5b0RH< zTnVCrpn@ChiL8*T8qTjH?D=eQfp+g8CF+dB1AmXBClA zPkH0}tEnZ#w?Ld2%*Z;nx-aD(9B46bEGup=JYPJRZeK(^ov|I|(W|sEw>3pf+K?Hj z@F6!U?uhfVW$y6yF_40?W0|>`*?AhI42Rd7P z8E`A-l5cXCct3NaZ;Y0 zS6{Hi#fzzz{0{yeOZgB|$ow(j2AG}f!Cx!S0D0qR#!X!QeqfL7A9b#EUv2{^jlokr zK{W2MFT@@YiT?68nJCrEwhSG;&GYRr<2P%p0DC#q{$k9zG7KDe6W=}=LHtXAu6O}y z$bAjXjz5+@fyza%T-%HQqNqVa@W#hGGrvFETtrqaU(T%1GA)(Kj$t7|Ids-pl%(lQ54UXTEgSE>*A|xvR%>Bvh?oPX7ngz_oFf3jpHgR#I#HS@!qnQ12}#-LIWxxGSVyv zgTXjsUwZE-Vw1;kw2l0!$l)Z@$u}k8JZZwey(epZ4E+88WA;Moxy_xLkzHF*>+yzL zkiV!`XPCrj3KAE1D6WPQtCII8Xp`maHk5AMUG1Lh?qcm@Jg2L!7nhIeUf24`XJ$g@ zL#({XH&H5>g~s;uJ505atiGNaW%$z49E8SFTO6ebPpIXp-@Z4$zrQ^@2MzX=SADCfo?yEKJ!>s}oedq}KghyJCkqBDkV0F)stIX(9 z!}^#ysWF|;5nz#1&n}s3;&~Il{enm1P#>yCua<8RJ|42;pS&(}GVt2u*~qw9!1wX0 za<-HI=4BFn9^-|5PHZ)T%C|@TZUbRQnEmM0lL=3nS>_)d8%6RT5BYcGK}u(M;y;^gb<_XiLX>`81C5GgH1Pz4S;Wn_FNO&;lT z9Oyax^)rT(L{x9ht|-ver3ksLZ&ha;s<&yBCQUrvyIH_5wOp^rVr878anwIvbj%;i zaVbVx8{>;f58g-1d+K?_Iw-Kcq_P**^5`p-n7IpGXT%YZ{}hCJTytoeE1T_9H_~;C zYhCrl-$I=WAGXEOkVoY`e^&PXA{KQKrodBqK02%Qfb_bu^01=&XIE_7I0{8)o^=~B z!;-S^Z@1JFrH02?XD#KTUeQGuGkrHx{#hjE5iOysw10k^st|vx(5}m1!NMYvqEyX ztF^_OuVR9H&}5Jftc+o6gL=o_(IaYjSXOl)`jX3md>kw}q7v2ix2*PFY+YQ@ey9;N|pup`(+?n)Em1zJ{?ZvI+xP3}AhEpQM<=52lcT$X_`Z~qgMlSYE zNq*+l+#&jpW{|vIN>PX;?HX~66@}0H*n@crLIHG|7&b3NrNkm<-o*=anB*8YxW;U$ z8O|q-m8VmpDH9q4Sr0G z75!{!x-OV%7GRFrP)BbHU!`e`@~VL&hIoo7#)_^SO*D~J_o48N2^Cn zR0yJYhd!db`$YZ4iAwjXN}b97oJQLraF7(+us_}9dXh{uo${r!g;%oB-RbT0RUP$o zmfNRSWN8`z30~imb@@&AU5-I+5jyd;7W{^FE&EydtyAo@eIQc?t|WZ>Q@1NxBOSee zV7y0%*r|0c#pp$2<;H8zn_V$IC54J*#WYz#$aT>a>sqsNhiO8P`GlI_9WnLCqS~jF zvsElKMg;?|M zqf%w^6~?7;E9l7PP{-fjIfU*L%&4Nz@#g(8-j-^{uyQ_r&3$E3u&8aw3;E%TqL?0? z(s=vrVTU2g$o*iF-)vI~=|=e8mKC$<%5rb~;#9x8%zWXPCmAcp5VnO4WiN*Gs+pCj z!w}Q`)}TgP{PE$D(%++Whkq7@=&9%;B4Fkcw@>$~--uA>Zt2zx50qQ?L*i~mfUwklvuQKh}(lhbk zu&T$=R_>m@ZWjG5P=D!ZP4JLb>dX2JI>n<%0|){IMP%m^+O;x%)e32KYg+QoEdE*$ zz8jH!W2*@{X+ZfJF2OW6<^OwAG-Nxh3yCejNLb+Ww)ydNa&I~I#{fpu-H6fHwQwB8 z+|=o;FSN5)m~o$;$vI)~xsr};J^$R*ULPTwREv>^1V#|Mod)Wzdf(IkvKzfnavEE2 z#SX2@@;Km=by`SLz>e0l{GFhZr{Na{V1yxE5nF#n{g12~a!J{muAYm0r%9gApV6SU z?Dow=??BRNYqG3~Bj5h__h$thJbe;JW^Gj7J$WW^!roTKFK}0)+4_^0O?4(>QEVR8c&3Qc+a<5`he|EP9 z8h5i|S{9jFnf83&4bwvva?543q{eTdOc!?0U9sOWAC{9(vw+~K{q2q{yz&jk=y>HH zxf{kTA~9$}stVWSopZ%OA=c#PRXS_LDsE%RN{yxxVTV;Xa{4-NTG&Ow;Ct+WHr)H-CQ|he?l{72YE!>buKo}wdL{o-_=-K1Y2sEkX?x1xI)N!B zLCg{1sXZ+9Uhodnjn7F45PWfhjZHsWu~cwZZ>D4fdyNCoew8|h2y&2hL(Y)lfLIYs zjtiq(sFexT84mC^8Km>6z~3N}ZHP&2tZOGJx%cVtliopUL>l@5{t_s`y%8PQixCX= zn)$+30evi$C(bz`d!b)BZRVgscQ?hwV{omNEk$uMc#F*J~}s6Jx%TFQH7b* zolBbE*qHknWO{=pvY-AiF6$RjWTO#qr#!l1C*h@m4JOLns!nk=6 z864hrbGd2+u@J*GLmz@hDia%VCY!pY-)aj!s;1v-_)KnFxz;K;D9sNo2N?Px8B`iO z%~IEC;OzK3RyAUcQ43c0nG;uqp%GyipfaDQ0h<47*eSSfBiaFfSIz;Rt)non#Kb_g zFyL7rWvjvQ%?iHdX1sPbP%HM|R}8u>e{&KLtR4Vk2FO5;eaK>x^~gh|ip!f~_<+1z zBb7v}AbJTx963H>KbIi-^9@By=>mI^!QGZ*4tN^U_tTnQH}9{nlMgzjDVkQ)LKCF+ zW@N^HlO%Jb_r@O>$`j+Tg6>%3j3%i>OQF&>tAo_ zxIU3IqZaEdY!K-F-jbbI91NnuE2#( z^7IOH3v@>^LOwT@cs9TEEJ3d7amvB@Krfw2Ueu<^{r1+%M(>)3)m>G_q5KAlDQ52L z(qaC5szlf;+ntm|itwa8NBW$lB%=zYj@hI;*QUq-D*krD8G9eKE#SqRZuGkp8`G9^ zaIU?G>D;g}=lx?US`YU@-YvOGhlX#HWKE4WovhNp+A$&>JOT;1N}M!+<)g-lKf92FK)#Uf3cP-LG3_htIaSF-PU4+99Ap7&wzwGdHV30M!APW#t2B3F+M7?2 zI3;7rJ)lhX9X-L6RXCOCMQ3k*ixVs#>aNH2c-~u*h2F0gtnaaN{o|;dNzu%qmiOr| zIIUE`+7N+~?OJrIN;no4KZ*7wXLoI_$5Rt0#r|5?r`-X7DL$<8?(|$c4wwe1-!^Zu zg5x&!VR*wQNX3(ElW)59g)Gf#K{e+yy_af}D-w4ax++dQXO0b9O7 z#J|m#zaNaYZ$dy%sC88#=caH;di7BjQB@b}=I46C$iyE3DHD%-39+2t zCuM57avm2c&3jZ|M3TR^*1D+kG@}X5*zb~jbOo`)mYolu)T_ZJk)(Wq?d_Dok)FY$ zsO3}=pXql8S*60;cPM2KM&f)_}$ z>8Ad#3y7{#?r-N60P$X3SniJJds=QYHQXIB9W9P`OF4%l56XxphBsVfNWc8P%WP(C ziyg%`Xcg?_?2IMfkXkI?KN9}n+OSp zPfr>c{JrQ0yyr*h9$@Hrl1k`s)S~;7d=$~+`+SP(iyjQ4gzKVhEdU(+Gp} zQEgg=F`J{j-coMgIsHZUa`+whkeuZl5FoJpt-7Kd5CHpv|+s z3OL9+w*WYiOm&E610M@)^mxdwuiHFCUrho0FQ3U*0lDA}<$LOgJE$1D{Kx?$BvUH` zqLa~9%BgH7tI|8|7uqTtAD?qDP4z%3{+q&N8gcxN87;8Vjo(8&bL^+KaatS7SfHt) z*?j64D>@HSEx)&VRx27zXLWf>-*s~zE3tAzhR3(FwwDpol~{X&lZae_Ty@(+#Yzv6 zJblLZI&LN%db7MgUMlKPCA_pciy650-c}fE`5XbaC3BR{sfZx312>Ftzv1zmpE&)+ z%syx~a^nIPje;7FPh6J6_@oq$#Pjp!_I!G@g}c5Qk7S+#H$L-$&GwT#44AQh(O%KA zMwZ&qKDo!M+6vobf9Z1}b$Pe^EKLi+Kk(wTpF)4{Dza$w-S zG6mx>*Q+f)4~;g*)+g7-ho?uO+dxH|FjaB>u&&OHFbuBbZu41ax;Ljh z9RZ*4RiHHEo-BJ|wk=X4&36(L^1X12ZuehPGv>NT{<7pMLP>TMPEG4bj_ct&2 z5!TNdaz%Tob+q-t;3JWS+a$`yhk6*V-qbkvnUH?YRSxQO*da1eoF>=8H{SS*)^Ypr z>B1yqy)^D;NX*}M*zJ>Sd*QE9-IjxkI+-g3{SjdiHy{UU*4Mh52c&Nk@++t@zqC(# z+sty5EZ-Lz8hAP_>yYw4cXjo;RlieCchm?u9#s-3i*%GJ6Pb!4Cw|BRETkn$L-`G; zUqy4A2$a|xD^K@L!(MPqM^9tk(*Dtsz_fvxK37csVANr(h5@63q2~Yh6&xI^%Wviqht69^qp+*!*;Y_9% z-_TxoU{(NkYNf^UVz*#n7PV&k(%?y2bCeVqF~wsljD(3_H}o@5jQ4ht-BF2Ooep0Yi8j!gufp)G$fiKPD8qrHE3t-`^CeS*5zdsqHiab%T@!eC~{an`5`;GYht8sOO zv6+~xpwlYOH^`qqZj%3eDfCL^NR)?buR>D3KE;yw3jOvxQK8}YpN+Mv z-jpili$9E-MQ2b2!0Kvwo$P=Qg10ve)6r8&xdZmy>dNz@j40_{OIaw5mr+%3I_}#S zBSpX1;T41Ljb9O?IZ6>no3vcwt0t6;YG$ntH>y!Y+-ZnadN zd>6<}ZCWRU+R~YX1meqHNoEoR-T8D!NvyUc~9@WN_zwCe8cA!On_8H6jSL;o&ND&P8i z`dfMB7;?A^zw!)>TvJDPJ&J)JUNbEKVpD|CjDg1FnNiZ6YGTmE<}9*OAX))}{b01j z0(6}Rp8~51TW@-*hjuyqN{Fc<;e<&&SE#L~9msjkILpPAYLpXYMI z@F@UqqU`lt6mr*MLZzL-qR;cGh}_Dv*bLhVVKqzq-g+ zBx!|TWw-pu_hsQ527QY^e4w(v)-n(`pW~Yx!?aQ@|NRBq@S zuH{Tv0$yKwnRz)r{q8;Tu-$Im(NJS65=^Iy+Fgl$7fQ&|MHt441s8}j{&;SIl*&M? z>iQVh{{5K#hl>NXrqej{wCFI`f~EhD#ZVV;m7l2fek_PEBUL>Hk00pFH8|pjLA^Y* zdi7tin%#=gYDY`zMli`w8IQuwi2K1_6hB~d{TjRsVNeGxqhhTDp>e`_CQ3}O#8?g5a)#nS zg!pk`LPSM|(6YIaod+3`D_jDn9!j*)vpdvKTCIihsqR^1=zHJ{prfuwmWM0#)E zaQ|QT^$i8M1aI8+zw%MKx`FH(#0mm3SDq?vW=9;3G3UYfqgi&n@XQ&%MIQPbfL@^*h)$bN+|I z0`z1K0+zX%_rK`#zgQC5|G}R=|8Gh1&;Q=*w4U?7X8Ax<=YMGZCzG+5fBx5sRCE3p zNd}{#=(a?%t1IlE|Go3c`}yB`rGI1nFIuzyigfOTd2#yU@ei6SGQ|4 zoASkTrPVp?IoVjV5z;Q_cVX)MuKMKr^5v)a0y=q$=XQQ4WbJoyYILcrpdNBNyGhSw7%V|0C9a zA{p}azm_CY|3mS=!m(f?8BK)aUH>W64;^m;bFuZ%F^! zjLG7w8ZR)!`qe(qC7KGebwO6 zWWpu4=C}>{;L@jpufby%LSmfGl26TkTe9J4>Pha5CVu=;aPKX=(6y5rwg z=NBv2j~NfaplN20bR#$6nr4Vtcw_`V_(;w2aoiq8qR}hDZ()GEb;r2#@)q%WLf(O% zqvD;TvXVYjufuGoX|mJPKj{z7n9q(}16^-CWpx$=1gCF3;Fh+mZVLL|Hla8M-WcE5UkC{=yQXk&)+WJ zTC6NzEdM;`#nGuyl-YKBE504;g2D*Gf;46uc(IMyMscD8mMI(m1eh}ckmhrUDQ!MS zxVxFpQE{r9g!vr)MCNk_2)oVQcnHWw%~4^uYF40!&}!HC4H_oE_~8TJbh7wAAP1$W zro}>MFa<3&QW`CFKCdd7;8a%LKpS5DzrwL_621i-ii9IcU;l4S^6mfcbz-c4+vLAV ze88*!m%aX@p|Jn{M@tgr{}l;EL)&7(L^zg+gu9Xn-~Rv3C&v2UWa-~<{l}8wgj4>5 z3cmbrMf!l-(IB;bz*R2YEG<2#oL|uIe2*XFX!B67t&oSBKsQhpKH%`qhECQI)&7gPGBEcXq1qrQF@wDJy9~o=^%}cQhJG*oKL}6bs^koRn5F;towM zihoK^2&$0QM71vmGC`X=irQG$p9-RSSXAK~gkLE+z@rM1&^bIIk{fZqC92>p;d8oo=od<$qEf{U{QO#8bVy zV6Vx6!y}+CUBuUa;60G#rRw~A>D7Gs(hJi#mjQ~XsPagM=5dRVccLK)rVj?;00nzd zAygjy=L4?#>gV$Ld*xqm;o*fAOQPx0%B+6&irK`31oU|c4?4$XrJR&4W_c+igSNsU zGg~-a2NR0utq+*W@kFjNaXaY+kO;HXdlh+y=n&Y&Dw z)U={Wps-0rHvh(bf!=n90f0z|%AOP?0EQ1!nT`=gLvLip%^@gXIaykMR$4q=TDfHn z1f*kt32r$Hly(W8SDA>2*vQ_^S;UH)8VCB$ULqC6}vht{n zLyzQwUKLn3yMaDtV2tRDOW8aSUd%^%J3jj$zl1E>~avpikdgyLMMUeY5)gs!;$2#ZB`W5k5i<_@*d> zRn^|k`iPFp05MVDa*F2o9)e>yJSjk$;FuyAV+2Jq-X(!4fan%*9lj_z7CG9dKvtNS zcu=;${Jah=s-gWaBOczU0^rg97fJy2GZG5>`~OxZ-~RVrC(ri34e$Yv_CI3%M-t(P zZ~tpWqWpiu+alW%;b15cjc-fD;!)rJ_s*vV_PYlAhXK%GAOFL}T#;GPNx{w`?Be?;cjJA1lxOTc3LZbS9wNQ1+!w zvE?Dke^!wkHf1WG+pH;D3Vg3QmHM7t#}AP(sy$24w^bS_Uwh;INk2U)1_RVKfTwy=9AH zB50xoEJ{d%pi~WdGf`_Sek&&xu$f6o>j>G~&m9ZB6p+zwdSOYX98`AMk4b zwby?n6p#D%-&Q30{pawuSY%r;u?+^|j)s!H{r8(@cY96%3)|5 zBwbDL2k5yBEhVY~O&5mlXj=V1Ewi=1fyN6;&bDD5#2MYDr4i`OikcO)ZjNDFvCNM= zv@mybf89Is|FHEbBaR70S%V(bc~RA-cqx~bi^zS8%PF~3t|-f>U_n$RL6*KE28iP( zRS7fySo=p_LR5w-oNmb($Y(6PjT& zgNFX4OJM9uuWpyWU1*bXV~R0<H{#%jQ`hQj$$Cj~8u>>@)|0iS7xUc`WA~l--PrFhnA(Ke~?j`0R^Gs>b z{8k7SzVy5^s4xGc@kHF3{{$qIP$V9TMSc0-ip29=<(HqU|8oO~T>bRJ>g|VZyNCLE zM*6sso?QcdT>r=1zWpOy-#_|?M~1mC&{0f5`-1C04E+l!^98r7|F8Y~Mo_VR2L=YX zo&zKMQ~mn@n7w@J>bF5=SsmH@5^g~=D(r%<=(8R`g!LR)PJRuzmIj|L_q0D&)m9kmP)6@#5O;7iRmAyg=&`>}@X?*OC6ceT<&Mt<8fo z<*U!uu0A!Zf;~~=iZI&2PdDj`@SIUfOL-xur3z`KamNK&COWOpd{M zQ6uyE@rR|=i-C+4DT~K3WdiWfoG+g}sn1`xC&(<6k}`!axW5hW-)95IWzBb6^y#aB z>YF>LobjbdVknEFfio@24T5+4V7;^Enj&;H8eQXzqe=Tb8b)H=UfM5mRoUt|GwV7 ze?abeBxRCHheJMsaL~bJX8%@e#OB8ca+EPZ4pdSW3CmG6H)~k!qHKImWeQaObXr*G8S3G1wDVZ zJbeLcX~dcygZn#3UtSqQY3WjV_NIPswYqSOfFla3JZ0honWsl^<(UimsdE^)^1n&U z@~R*eM6YnFcYXl`SU&Y{8c0q_0X8H|r82@)!2>o|Z|Td=stXJH-A4ozP0<87h0gXp zz&N_Bzr3nXpRh=@sKVHZDOJcF@-R=U7r}tQ@PsxcVL_Bi%K}*2%`w#HzpdRl0i*$z zb`F33lk?VbxKy$F3p7BDUN<%U;p0^^qH)<2v;NKMUYuQZcOl|fX#Gj zIXKC~s9Lo}EzZ?Z2OdSP9TfGx4!J-|08=y|c38PFU%qt`(Rsz1J3p8p@{SW-{2pph6?1bGwWN%NusHtbL}qHN6u z4Tq&z7z`Y6H{RmP0qj47*IWQH3?6fW0MyNM<2+!x#A-xMd()~=+jX=mTdGlF4OOGH zD!S;L9S| zk%&Ni1*VV`3#5w$P07-Y;vE^2+acgAWzMJxHf8ShSh}1yRRSvnEM1~TRj{@RNHsvR zHZTT;0%Vlu{$*s;(I2e(?kpb*aH<=dB4jp6++`)`oyV{USi_l9a^zrweaYI1AFJ~( zk+<7*_%(p3XH*;m9Rk}5AtOtmj<4<1*LFOKEDtIXxtco2wYAAIjTT*oK+|vBS)08% zxTlelqlrQ`l|R%fW7*&y&xW$WJq|VwF%?oyC50vLek)&j;nZ1->h-i1vZ6zGft5!+ zv|V~}&8_Xxx^0sc>9*g5aV`TYVP|HiF==p5W%(%(aO)G}D8oi@{XxBS)b5ynzVYIF0%L_B* ztM|gB!L!{jD+z=42^}ueSPMK@=v!* z%g-EohnHhRBv`jN8Ku=FN{t^CGUFm(aI2MHZUsgaZGtZ?-mM-zzHU8Oaf|usfHi@|F`2d0I%G`cciB3$UqkLgNZh9cCG>VV3At zlFclExD=0=0kvH0_~la%fYiaNIjjLNsJ?uuugsWWWTBt|Fk-q`%Pfg?cp93a5iEyE z0B9}nUY`eXy)vsGpRO!D@TJSTg;a*kJOAD39 zS1On9!L6gFoV~u&2U#f?%KGu&*RDPe6huvvEtQ@m9)F9oDO(&u2zL+?xwaq_mnlOA zc3|NdFomKQy?j%*L$zBT^EBDa!Yi*j|3qKC?0rWB7CZPmvk4m`1g*ZY+(fn2&PI)O zS2tZ1N@YWv4j2L^U~usOJYWtWj!9^6U|7>AaL4!b6WU?NjhCNFVEA^0@ zfkeN1vi$qQ*THDKu77;wX8yU(b2*BvIw&gJ%%bU8_<)pE@K?HK>!% z?-8fSDmXF}$rG5haHuGIqp9ZW;8$At7OVzhDJY|gmYNhtCltkXMU>~3^lJ}+0RIe^ zqbt`()2fR@R(UrH)ouVNqmTh0H3pVvQLS|<$E9(29r_dmhs;W$5Lj+K?m=ZnV3Eqo zMg8%tts{c06bhW>ZbU-$s;^E$UG!aaqT5vV;ouLZ5oP8_{rEk7^&(K_Ee6xpzi+s2 zXatIq{SLzkUg!bRiV@%(+oFI&#She~+5yg0dk(m$tpPV}G2o`71=yOAt5y=I)ei#J zW`8T7;DG~u!)+bA_wU=?Gt%*SAE4%a`#*h%3*Y4%KCpB7sJ0JMt0o(YW*@4rvT7o0+#-L$DU^~JCETD*Zq-@|vcnNTh4@emubcAOAIj6SpeI`}>Bl% z=YOnxOVer_lGUk?_Yds_+?vU>`C69Ehr)sURmp>;7*LY9$A5eG)9aW1jn+S0>@mmsk0cU) z{MVMG?SBEYZwBVt(H;(l+O5D~JK9lTu=c;~Y}*c&?LjfEaiDaru%mrK)AHS&oml~_ zWiwQ|qK6ccOaRvr0ykKgDCR6Mfy@N#(WS zazO7BbJ|X;vF+okQq1>fcCw=Qk1C3`z0)jV*MYj@7})EBOk9PBC3&QviYkxW*xpGL zv+El3wlf+G2NT;n&7XDwRg?v&MGOksM5ocaP801m3TNtF0|%lck~MMrYH_ph3gfY( z@Y)VF^qp1|Ic}E;1?A%+gz~=A5mc^x`i_3?G7c?Q`F**ve3Roj{lfXmFAMl>iS3kZFZt%3Ffh083GC7>4Tt6G4UWogZ0g>dMGE2iLry$msl_g=hK-@4C$n1 z<%WXcNH7XUmB_P8P82mok*yD4*Aa}wws$(RO^PL^&!mt%yb6*_(^L9?c%B3Uhdlj5k$+U+D!aqq#Wf-Gea+k3MeIc(vvvHtYzAAGYN z5tRJKdVt?NdZT@S4$$L5sOWYp=G$;JNf*DOB!4NG9x=_`Ebsktsn zN)W!*4R{bbWcJX@gMeA{LSFLJDT1*#?#pY(8a~~#8EB)@%~0fP@0k&i_>FXq$p(jw z2dTJ@((-W_FnHGC{tO<%WNC_Djv*74lKebnE6BCv%oWCfG28jY}A_NIG>=+ z7^gqKryqZ=pFQ5}AmoC0JLjS6xF9dc-qtX^O>DMb14?#OIkXPqGfE&!#seBVs{&vwISFyxLI4<3dev9PG=(4fpO@#|i{n)~TYGg=cyR zdZ4`sRJD^$65QY6jiIkN!@c9l_xjTPH#=GA3e#83v&J4|KjC&O*{qT)a05baJi3ma z7-pC>2IHX*MoC_V4Q?3i-HI&Z*HG89k6$mxSB7;s#A>+CT$9Gd1E9#?knJv9Q+U^rZ36{AX0k5kruM`Z{rl+)})l_P9UP9qcD zxm-@sP=kZ20$o)V6&ei-?Bb{je6Z2|Gb+-o@T^74yD+V(Yg-}1UBPI*5x1&zy7vh* z?0JZ`ch;Fg&xpW;(4u%GEf+IlJz6n}yrtx_y|XTU*5{2+ kwV6{ed7Qz|ENd>{b8q@g$WMOq)BBPBKOT 部分 --> + + + + software.amazon.awssdk + s3 + 2.20.26 + + + + + commons-io + commons-io + 2.11.0 + diff --git a/backend/pom.xml b/backend/pom.xml new file mode 100644 index 0000000..71fdaa7 --- /dev/null +++ b/backend/pom.xml @@ -0,0 +1,205 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.1.6 + + + + com.designhub + designhub-backend + 1.0.0 + DesignHub Backend + 设计师任务抢单管理系统 - 后端服务 + + + 17 + 17 + 17 + UTF-8 + 3.5.7 + 4.3.0 + 0.12.3 + 5.8.24 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.springframework.boot + spring-boot-starter-aop + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + com.mysql + mysql-connector-j + runtime + + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + + + io.jsonwebtoken + jjwt-api + ${jwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jwt.version} + runtime + + + io.jsonwebtoken + jjwt-jackson + ${jwt.version} + runtime + + + + + org.springframework.security + spring-security-crypto + + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + ${knife4j.version} + + + + + cn.hutool + hutool-all + ${hutool.version} + + + + + org.projectlombok + lombok + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + com.aliyun + dingtalk + 2.1.67 + + + + + org.springframework.boot + spring-boot-starter-quartz + + + + + org.apache.commons + commons-lang3 + + + + + org.apache.commons + commons-collections4 + 4.4 + + + + + org.apache.poi + poi-ooxml + 5.2.5 + + + + + software.amazon.awssdk + s3 + 2.20.26 + + + + + commons-io + commons-io + 2.11.0 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + + + org.projectlombok + lombok + 1.18.34 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/backend/src/main/java/com/designhub/DesignHubApplication.java b/backend/src/main/java/com/designhub/DesignHubApplication.java new file mode 100644 index 0000000..1ff0922 --- /dev/null +++ b/backend/src/main/java/com/designhub/DesignHubApplication.java @@ -0,0 +1,36 @@ +package com.designhub; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * DesignHub 主应用类 + * 设计师任务抢单管理系统 + * + * @author DesignHub Team + * @version 1.0.0 + */ +@SpringBootApplication +@MapperScan("com.designhub.mapper") +@EnableScheduling +public class DesignHubApplication { + + public static void main(String[] args) { + SpringApplication.run(DesignHubApplication.class, args); + System.out.println(""" + + ╔═══════════════════════════════════════════════════════╗ + ║ ║ + ║ 🎨 DesignHub Backend Service Started 🎨 ║ + ║ ║ + ║ Port: 8080 ║ + ║ Swagger UI: http://localhost:8080/api/doc.html ║ + ║ Version: 1.0.0 ║ + ║ ║ + ╚═══════════════════════════════════════════════════════╝ + """); + } +} + diff --git a/backend/src/main/java/com/designhub/annotation/RequirePermission.java b/backend/src/main/java/com/designhub/annotation/RequirePermission.java new file mode 100644 index 0000000..29a8b60 --- /dev/null +++ b/backend/src/main/java/com/designhub/annotation/RequirePermission.java @@ -0,0 +1,30 @@ +package com.designhub.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 权限验证注解 + * 用于Controller方法上,标记需要的权限 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequirePermission { + + /** + * 权限代码 + */ + String[] value() default {}; + + /** + * 权限匹配模式:AND-需要所有权限,OR-需要任一权限 + */ + LogicalMode mode() default LogicalMode.AND; + + enum LogicalMode { + AND, OR + } +} + diff --git a/backend/src/main/java/com/designhub/annotation/RequireRole.java b/backend/src/main/java/com/designhub/annotation/RequireRole.java new file mode 100644 index 0000000..7ab287f --- /dev/null +++ b/backend/src/main/java/com/designhub/annotation/RequireRole.java @@ -0,0 +1,30 @@ +package com.designhub.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 角色验证注解 + * 用于Controller方法上,标记需要的角色 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequireRole { + + /** + * 角色代码 + */ + String[] value() default {}; + + /** + * 角色匹配模式:AND-需要所有角色,OR-需要任一角色 + */ + LogicalMode mode() default LogicalMode.OR; + + enum LogicalMode { + AND, OR + } +} + diff --git a/backend/src/main/java/com/designhub/common/Result.java b/backend/src/main/java/com/designhub/common/Result.java new file mode 100644 index 0000000..36e2ef1 --- /dev/null +++ b/backend/src/main/java/com/designhub/common/Result.java @@ -0,0 +1,110 @@ +package com.designhub.common; + +import lombok.Data; +import java.io.Serializable; + +/** + * 统一响应结果类 + * + * @param 数据类型 + */ +@Data +public class Result implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 响应码 + */ + private Integer code; + + /** + * 响应消息 + */ + private String message; + + /** + * 响应数据 + */ + private T data; + + /** + * 时间戳 + */ + private Long timestamp; + + public Result() { + this.timestamp = System.currentTimeMillis(); + } + + public Result(Integer code, String message, T data) { + this.code = code; + this.message = message; + this.data = data; + this.timestamp = System.currentTimeMillis(); + } + + /** + * 成功响应 + */ + public static Result success() { + return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), null); + } + + /** + * 成功响应(带数据) + */ + public static Result success(T data) { + return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data); + } + + /** + * 成功响应(自定义消息) + */ + public static Result success(String message, T data) { + return new Result<>(ResultCode.SUCCESS.getCode(), message, data); + } + + /** + * 失败响应 + */ + public static Result error() { + return new Result<>(ResultCode.ERROR.getCode(), ResultCode.ERROR.getMessage(), null); + } + + /** + * 失败响应(自定义消息) + */ + public static Result error(String message) { + return new Result<>(ResultCode.ERROR.getCode(), message, null); + } + + /** + * 失败响应(自定义码和消息) + */ + public static Result error(Integer code, String message) { + return new Result<>(code, message, null); + } + + /** + * 失败响应(使用ResultCode) + */ + public static Result error(ResultCode resultCode) { + return new Result<>(resultCode.getCode(), resultCode.getMessage(), null); + } + + /** + * 失败响应(自定义码、消息和数据) + */ + public static Result error(Integer code, String message, T data) { + return new Result<>(code, message, data); + } + + /** + * 失败响应(别名方法) + */ + public static Result fail(String message) { + return error(message); + } +} + diff --git a/backend/src/main/java/com/designhub/common/ResultCode.java b/backend/src/main/java/com/designhub/common/ResultCode.java new file mode 100644 index 0000000..62b9604 --- /dev/null +++ b/backend/src/main/java/com/designhub/common/ResultCode.java @@ -0,0 +1,59 @@ +package com.designhub.common; + +import lombok.Getter; + +/** + * 响应状态码枚举 + */ +@Getter +public enum ResultCode { + + // 成功 + SUCCESS(200, "操作成功"), + + // 客户端错误 4xx + BAD_REQUEST(400, "请求参数错误"), + PARAM_ERROR(400, "参数错误"), + UNAUTHORIZED(401, "未授权,请登录"), + FORBIDDEN(403, "没有权限访问该资源"), + NOT_FOUND(404, "请求的资源不存在"), + METHOD_NOT_ALLOWED(405, "请求方法不支持"), + CONFLICT(409, "数据冲突"), + + // 服务器错误 5xx + ERROR(500, "服务器内部错误"), + SERVICE_UNAVAILABLE(503, "服务暂时不可用"), + + // 业务错误 1xxx + USER_NOT_EXIST(1001, "用户不存在"), + USER_ALREADY_EXIST(1002, "用户已存在"), + PASSWORD_ERROR(1003, "密码错误"), + USER_DISABLED(1004, "用户已被禁用"), + TOKEN_EXPIRED(1005, "Token已过期"), + TOKEN_INVALID(1006, "Token无效"), + + TASK_NOT_EXIST(1101, "任务不存在"), + TASK_ALREADY_TAKEN(1102, "任务已被抢单"), + INSUFFICIENT_SCORE(1103, "积分不足"), + TASK_STATUS_ERROR(1104, "任务状态错误"), + + DEPT_NOT_EXIST(1201, "部门不存在"), + DEPT_TYPE_ERROR(1202, "部门类型错误"), + SCORE_LIMIT_EXCEEDED(1203, "超出积分消耗限制"), + + FILE_UPLOAD_ERROR(1301, "文件上传失败"), + FILE_TYPE_ERROR(1302, "文件类型不支持"), + FILE_SIZE_ERROR(1303, "文件大小超出限制"), + + DINGTALK_AUTH_ERROR(1401, "钉钉认证失败"), + DINGTALK_API_ERROR(1402, "钉钉接口调用失败"); + + private final Integer code; + private final String message; + + ResultCode(Integer code, String message) { + this.code = code; + this.message = message; + } +} + diff --git a/backend/src/main/java/com/designhub/config/CorsConfig.java b/backend/src/main/java/com/designhub/config/CorsConfig.java new file mode 100644 index 0000000..2234fb2 --- /dev/null +++ b/backend/src/main/java/com/designhub/config/CorsConfig.java @@ -0,0 +1,40 @@ +package com.designhub.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +/** + * 跨域配置 + */ +@Configuration +public class CorsConfig { + + @Bean + public CorsFilter corsFilter() { + CorsConfiguration config = new CorsConfiguration(); + + // 允许所有域名进行跨域调用(生产环境需要具体指定) + config.addAllowedOriginPattern("*"); + + // 允许所有请求头 + config.addAllowedHeader("*"); + + // 允许所有请求方法 + config.addAllowedMethod("*"); + + // 允许携带凭证 + config.setAllowCredentials(true); + + // 暴露哪些头信息 + config.addExposedHeader("Authorization"); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + + return new CorsFilter(source); + } +} + diff --git a/backend/src/main/java/com/designhub/config/Knife4jConfig.java b/backend/src/main/java/com/designhub/config/Knife4jConfig.java new file mode 100644 index 0000000..534997f --- /dev/null +++ b/backend/src/main/java/com/designhub/config/Knife4jConfig.java @@ -0,0 +1,32 @@ +package com.designhub.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Knife4j (Swagger) 配置 + * 访问地址:http://localhost:8080/api/doc.html + */ +@Configuration +public class Knife4jConfig { + + @Bean + public OpenAPI customOpenAPI() { + return new OpenAPI() + .info(new Info() + .title("DesignHub API 文档") + .description("设计师任务抢单管理系统 RESTful API 接口文档") + .version("v1.0.0") + .contact(new Contact() + .name("DesignHub Team") + .email("support@designhub.com")) + .license(new License() + .name("MIT License") + .url("https://opensource.org/licenses/MIT"))); + } +} + diff --git a/backend/src/main/java/com/designhub/config/MybatisPlusConfig.java b/backend/src/main/java/com/designhub/config/MybatisPlusConfig.java new file mode 100644 index 0000000..ec551c7 --- /dev/null +++ b/backend/src/main/java/com/designhub/config/MybatisPlusConfig.java @@ -0,0 +1,29 @@ +package com.designhub.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * MyBatis Plus 配置类 + */ +@Configuration +public class MybatisPlusConfig { + + /** + * MyBatis Plus 分页插件配置 + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 添加分页插件 + PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL); + // 设置最大单页限制数量,默认 500 条,-1 不受限制 + paginationInnerInterceptor.setMaxLimit(1000L); + interceptor.addInnerInterceptor(paginationInnerInterceptor); + return interceptor; + } +} + diff --git a/backend/src/main/java/com/designhub/config/SystemConfigInitializer.java b/backend/src/main/java/com/designhub/config/SystemConfigInitializer.java new file mode 100644 index 0000000..52ffe3c --- /dev/null +++ b/backend/src/main/java/com/designhub/config/SystemConfigInitializer.java @@ -0,0 +1,31 @@ +package com.designhub.config; + +import com.designhub.service.SystemConfigService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +/** + * 系统配置初始化器 + * 在应用启动时自动初始化默认配置 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class SystemConfigInitializer implements ApplicationRunner { + + private final SystemConfigService systemConfigService; + + @Override + public void run(ApplicationArguments args) { + try { + log.info("开始初始化系统默认配置..."); + systemConfigService.initDefaultConfigs(); + log.info("系统默认配置初始化完成"); + } catch (Exception e) { + log.error("系统默认配置初始化失败", e); + } + } +} diff --git a/backend/src/main/java/com/designhub/config/WebConfig.java b/backend/src/main/java/com/designhub/config/WebConfig.java new file mode 100644 index 0000000..a1deaf2 --- /dev/null +++ b/backend/src/main/java/com/designhub/config/WebConfig.java @@ -0,0 +1,38 @@ +package com.designhub.config; + +import com.designhub.interceptor.PermissionInterceptor; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * Web配置类 + */ +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + private final PermissionInterceptor permissionInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(permissionInterceptor) + .addPathPatterns("/api/**") // 拦截所有API请求 + .excludePathPatterns( + "/api/auth/**", // 排除认证相关接口(包含注册) + "/api/departments", // 排除部门列表接口(注册页面需要) + "/api/hello", // 排除测试接口 + "/api/system/health", // 排除健康检查接口 + "/api/system-config/group/site", // 排除公开的网站设置API(登录页面需要) + "/api/system-config/key/site.name", // 排除公开的网站设置API(按key获取) + "/api/system-config/key/site.logo", // 排除公开的网站设置API(按key获取) + "/api/system-config/map/group/site", // 排除公开的网站设置API(Map格式) + "/doc.html", // 排除Swagger文档 + "/swagger-ui/**", + "/v3/api-docs/**", + "/webjars/**" + ); + } +} + diff --git a/backend/src/main/java/com/designhub/controller/AchievementController.java b/backend/src/main/java/com/designhub/controller/AchievementController.java new file mode 100644 index 0000000..7284e91 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/AchievementController.java @@ -0,0 +1,53 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.entity.Achievement; +import com.designhub.entity.UserAchievement; +import com.designhub.service.AchievementService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 成就系统Controller + */ +@Tag(name = "成就系统", description = "成就徽章相关API") +@RestController +@RequestMapping("/api/achievements") +@RequiredArgsConstructor +public class AchievementController { + + private final AchievementService achievementService; + + @Operation(summary = "获取所有成就列表") + @GetMapping + public Result> getAllAchievements() { + List achievements = achievementService.getAllAchievements(); + return Result.success(achievements); + } + + @Operation(summary = "获取用户已达成的成就") + @GetMapping("/user/{userId}") + public Result> getUserAchievements(@PathVariable Long userId) { + List achievements = achievementService.getUserAchievements(userId); + return Result.success(achievements); + } + + @Operation(summary = "领取成就奖励") + @PostMapping("/{achievementId}/claim") + public Result claimReward(@PathVariable Long achievementId, @RequestParam Long userId) { + boolean success = achievementService.claimAchievementReward(userId, achievementId); + return Result.success(success); + } + + @Operation(summary = "初始化默认成就") + @PostMapping("/init") + public Result initDefaultAchievements() { + achievementService.initDefaultAchievements(); + return Result.success(null); + } +} + diff --git a/backend/src/main/java/com/designhub/controller/AdminTaskController.java b/backend/src/main/java/com/designhub/controller/AdminTaskController.java new file mode 100644 index 0000000..3c25e04 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/AdminTaskController.java @@ -0,0 +1,155 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.entity.Task; +import com.designhub.service.AdminTaskService; +import com.designhub.util.JwtUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 管理员任务调配Controller + */ +@Tag(name = "管理员任务管理", description = "管理员任务调配相关API") +@RestController +@RequestMapping("/api/admin/tasks") +@RequiredArgsConstructor +public class AdminTaskController { + + private final AdminTaskService adminTaskService; + private final JwtUtil jwtUtil; + + @Operation(summary = "分配任务") + @PostMapping("/{taskId}/assign") + public Result assignTask( + @PathVariable Long taskId, + @RequestParam Long designerId, + @RequestHeader("Authorization") String authHeader + ) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long adminId = jwtUtil.getUserIdFromToken(token); + Task task = adminTaskService.assignTask(taskId, designerId, adminId); + return Result.success(task); + } + + @Operation(summary = "转移任务") + @PostMapping("/{taskId}/transfer") + public Result transferTask( + @PathVariable Long taskId, + @RequestBody TransferRequest request, + @RequestHeader("Authorization") String authHeader + ) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long adminId = jwtUtil.getUserIdFromToken(token); + Task task = adminTaskService.transferTask( + taskId, + request.getNewDesignerId(), + adminId, + request.getReason() + ); + return Result.success(task); + } + + @Operation(summary = "批量分配任务") + @PostMapping("/batch-assign") + public Result batchAssignTasks( + @RequestBody BatchAssignRequest request, + @RequestHeader("Authorization") String authHeader + ) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long adminId = jwtUtil.getUserIdFromToken(token); + int count = adminTaskService.batchAssignTasks( + request.getTaskIds(), + request.getDesignerId(), + adminId + ); + return Result.success(count); + } + + @Operation(summary = "调整任务优先级") + @PutMapping("/{taskId}/priority") + public Result adjustTaskPriority( + @PathVariable Long taskId, + @RequestParam Integer priority, + @RequestHeader("Authorization") String authHeader + ) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long adminId = jwtUtil.getUserIdFromToken(token); + Task task = adminTaskService.adjustTaskPriority(taskId, priority, adminId); + return Result.success(task); + } + + @Operation(summary = "获取任务池列表") + @GetMapping("/pool") + public Result> getTaskPool() { + List tasks = adminTaskService.getTaskPool(); + return Result.success(tasks); + } + + @Operation(summary = "获取所有进行中的任务") + @GetMapping("/in-progress") + public Result> getInProgressTasks() { + List tasks = adminTaskService.getInProgressTasks(); + return Result.success(tasks); + } + + @Operation(summary = "强制完成任务") + @PostMapping("/{taskId}/force-complete") + public Result forceCompleteTask( + @PathVariable Long taskId, + @RequestBody AdminActionRequest request, + @RequestHeader("Authorization") String authHeader + ) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long adminId = jwtUtil.getUserIdFromToken(token); + Task task = adminTaskService.forceCompleteTask(taskId, adminId, request.getReason()); + return Result.success(task); + } + + @Operation(summary = "取消任务") + @PostMapping("/{taskId}/cancel") + public Result cancelTask( + @PathVariable Long taskId, + @RequestBody AdminActionRequest request, + @RequestHeader("Authorization") String authHeader + ) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long adminId = jwtUtil.getUserIdFromToken(token); + Task task = adminTaskService.cancelTask(taskId, adminId, request.getReason()); + return Result.success(task); + } + + // DTO类 + public static class TransferRequest { + private Long newDesignerId; + private String reason; + + public Long getNewDesignerId() { return newDesignerId; } + public void setNewDesignerId(Long newDesignerId) { this.newDesignerId = newDesignerId; } + public String getReason() { return reason; } + public void setReason(String reason) { this.reason = reason; } + } + + public static class BatchAssignRequest { + private List taskIds; + private Long designerId; + + public List getTaskIds() { return taskIds; } + public void setTaskIds(List taskIds) { this.taskIds = taskIds; } + public Long getDesignerId() { return designerId; } + public void setDesignerId(Long designerId) { this.designerId = designerId; } + } + + public static class AdminActionRequest { + private String reason; + + public String getReason() { return reason; } + public void setReason(String reason) { this.reason = reason; } + } +} + diff --git a/backend/src/main/java/com/designhub/controller/AnalyticsController.java b/backend/src/main/java/com/designhub/controller/AnalyticsController.java new file mode 100644 index 0000000..8f998e1 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/AnalyticsController.java @@ -0,0 +1,44 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.service.AnalyticsService; +import com.designhub.util.JwtUtil; +import com.designhub.vo.DemandStatisticsVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +/** + * 数据分析Controller + */ +@Slf4j +@Tag(name = "数据分析", description = "数据分析相关API") +@RestController +@RequestMapping("/api/analytics") +@RequiredArgsConstructor +public class AnalyticsController { + + private final AnalyticsService analyticsService; + private final JwtUtil jwtUtil; + + @Operation(summary = "获取需求统计数据(根据角色返回不同维度的数据)") + @GetMapping("/demand-statistics") + public Result getDemandStatistics( + @RequestHeader("Authorization") String authHeader, + @RequestParam(required = false, defaultValue = "month") String dateRange, + @RequestParam(required = false) Long deptId, + @RequestParam(required = false) Long userId + ) { + // 从token获取当前用户ID + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long currentUserId = jwtUtil.getUserIdFromToken(token); + + log.info("获取需求统计数据: currentUserId={}, dateRange={}, deptId={}, userId={}", + currentUserId, dateRange, deptId, userId); + + return Result.success(analyticsService.getDemandStatistics(currentUserId, dateRange, deptId, userId)); + } +} + diff --git a/backend/src/main/java/com/designhub/controller/AuthController.java b/backend/src/main/java/com/designhub/controller/AuthController.java new file mode 100644 index 0000000..a54fe03 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/AuthController.java @@ -0,0 +1,130 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.dto.LoginDTO; +import com.designhub.dto.RegisterDTO; +import com.designhub.entity.User; +import com.designhub.exception.BusinessException; +import com.designhub.service.UserService; +import com.designhub.util.JwtUtil; +import com.designhub.vo.LoginVO; +import com.designhub.vo.UserInfoVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * 认证控制器 + */ +@Slf4j +@Tag(name = "用户认证", description = "用户登录、登出、刷新令牌等接口") +@RestController +@RequestMapping("/api/auth") +@RequiredArgsConstructor +public class AuthController { + + private final UserService userService; + private final JwtUtil jwtUtil; + + @Operation(summary = "用户注册", description = "新用户注册") + @PostMapping("/register") + public Result register(@Valid @RequestBody RegisterDTO registerDTO) { + log.info("用户注册请求: {}", registerDTO.getUsername()); + User user = userService.register(registerDTO); + return Result.success(user); + } + + @Operation(summary = "用户登录", description = "支持密码登录、钉钉登录、Casdoor登录") + @PostMapping("/login") + public Result login(@Valid @RequestBody LoginDTO loginDTO) { + log.info("用户登录请求: {}", loginDTO.getUsername()); + LoginVO loginVO = userService.login(loginDTO); + return Result.success(loginVO); + } + + @Operation(summary = "获取当前用户信息", description = "根据Token获取当前登录用户信息") + @GetMapping("/userinfo") + public Result getUserInfo(@RequestHeader(value = "Authorization", required = false) String authHeader) { + log.info("获取用户信息"); + String token = jwtUtil.extractTokenFromHeader(authHeader); + UserInfoVO userInfo = userService.getUserInfoByToken(token); + return Result.success(userInfo); + } + + @Operation(summary = "用户登出", description = "退出登录,清除Token") + @PostMapping("/logout") + public Result logout(@RequestHeader(value = "Authorization", required = false) String authHeader) { + log.info("用户登出"); + // Token是无状态的,前端删除即可 + // 如果需要黑名单机制,可以在Redis中记录 + return Result.success("登出成功"); + } + + @Operation(summary = "刷新令牌", description = "使用刷新令牌获取新的访问令牌") + @PostMapping("/refresh") + public Result refresh(@RequestHeader("Authorization") String authHeader) { + log.info("刷新令牌"); + LoginVO loginVO = userService.refreshToken(authHeader); + return Result.success(loginVO); + } + + @Operation(summary = "钉钉扫码登录回调", description = "接收钉钉扫码登录的回调 - 使用OAuth 2.0流程") + @GetMapping("/dingtalk/callback") + @SuppressWarnings("unchecked") + public Result dingtalkCallback( + @RequestParam String code, + @RequestParam(required = false) String state) { + log.info("钉钉登录回调 - code: {}, state: {}", + code.substring(0, Math.min(10, code.length())) + "...", state); + + // 使用新的OAuth 2.0流程登录 + LoginDTO loginDTO = new LoginDTO(); + loginDTO.setLoginType("dingtalk"); + loginDTO.setAuthCode(code); + + try { + LoginVO loginVO = userService.login(loginDTO); + log.info("钉钉登录成功: {}", loginVO.getUsername()); + return Result.success(loginVO); + } catch (BusinessException e) { + // 检查是否需要选择部门 + if (e.getMessage() != null && e.getMessage().startsWith("NEED_SELECT_DEPT:")) { + String[] parts = e.getMessage().split(":"); + Map data = new HashMap<>(); + data.put("needSelectDept", true); + data.put("userId", parts[1]); + data.put("dingDepartments", parts.length > 2 ? parts[2] : ""); + Result> result = Result.error(1901, "需要选择部门", data); + return (Result) (Result) result; + } + log.error("钉钉登录失败", e); + return Result.error(e.getMessage()); + } catch (Exception e) { + log.error("钉钉登录失败", e); + return Result.error("钉钉登录失败: " + e.getMessage()); + } + } + + @Operation(summary = "钉钉用户选择部门", description = "首次登录时选择系统部门") + @PostMapping("/dingtalk/select-department") + public Result selectDepartment( + @RequestParam Long userId, + @RequestParam Long deptId) { + log.info("钉钉用户选择部门: userId={}, deptId={}", userId, deptId); + + try { + LoginVO loginVO = userService.completeDingTalkUserSetup(userId, deptId); + return Result.success(loginVO); + } catch (Exception e) { + log.error("选择部门失败", e); + return Result.error("选择部门失败: " + e.getMessage()); + } + } +} + diff --git a/backend/src/main/java/com/designhub/controller/CheckInController.java b/backend/src/main/java/com/designhub/controller/CheckInController.java new file mode 100644 index 0000000..f77ea9c --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/CheckInController.java @@ -0,0 +1,84 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.entity.CheckIn; +import com.designhub.service.CheckInService; +import com.designhub.util.JwtUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 签到Controller + */ +@Slf4j +@Tag(name = "签到系统", description = "每日签到相关API") +@RestController +@RequestMapping("/api/check-in") +@RequiredArgsConstructor +public class CheckInController { + + private final CheckInService checkInService; + private final JwtUtil jwtUtil; + + @Operation(summary = "用户签到") + @PostMapping + public Result checkIn(@RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + if (userId == null) { + return Result.error(401, "未登录或登录已过期"); + } + CheckIn checkIn = checkInService.checkIn(userId); + return Result.success(checkIn); + } + + @Operation(summary = "检查今天是否已签到") + @GetMapping("/today") + public Result hasCheckedInToday(@RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + if (userId == null) { + return Result.error(401, "未登录或登录已过期"); + } + boolean checked = checkInService.hasCheckedInToday(userId); + return Result.success(checked); + } + + @Operation(summary = "获取签到历史记录") + @GetMapping("/history") + public Result> getCheckInHistory( + @RequestHeader("Authorization") String authHeader, + @RequestParam(defaultValue = "30") Integer limit + ) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + if (userId == null) { + return Result.error(401, "未登录或登录已过期"); + } + List history = checkInService.getUserCheckInHistory(userId, limit); + return Result.success(history); + } + + @Operation(summary = "获取签到统计信息") + @GetMapping("/stats") + public Result> getCheckInStats(@RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + if (userId == null) { + return Result.error(401, "未登录或登录已过期"); + } + Map stats = new HashMap<>(); + stats.put("continuousDays", checkInService.getContinuousDays(userId)); + stats.put("totalDays", checkInService.getTotalCheckInDays(userId)); + stats.put("hasCheckedInToday", checkInService.hasCheckedInToday(userId)); + return Result.success(stats); + } +} + diff --git a/backend/src/main/java/com/designhub/controller/ConfigController.java b/backend/src/main/java/com/designhub/controller/ConfigController.java new file mode 100644 index 0000000..9e4835d --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/ConfigController.java @@ -0,0 +1,271 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.entity.*; +import com.designhub.service.ConfigService; +import com.designhub.service.DemandTypeDepartmentService; +import com.designhub.service.DepartmentService; +import com.designhub.service.TeamScorePoolService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 配置管理Controller + */ +@Tag(name = "配置管理", description = "系统配置相关API") +@RestController +@RequestMapping("/api/config") +@RequiredArgsConstructor +public class ConfigController { + + private final ConfigService configService; + private final DemandTypeDepartmentService demandTypeDepartmentService; + private final TeamScorePoolService teamScorePoolService; + private final DepartmentService departmentService; + + // ============ 需求类型管理 ============ + + @Operation(summary = "获取所有需求类型(包括禁用的,用于管理页面)") + @GetMapping("/demand-types") + public Result> getAllDemandTypes() { + return Result.success(configService.getAllDemandTypes()); + } + + @Operation(summary = "获取所有启用的需求类型(用于下拉选择)") + @GetMapping("/demand-types/enabled") + public Result> getEnabledDemandTypes() { + return Result.success(configService.getEnabledDemandTypes()); + } + + @Operation(summary = "根据ID获取需求类型") + @GetMapping("/demand-types/{id:\\d+}") + public Result getDemandTypeById(@PathVariable Long id) { + return Result.success(configService.getDemandTypeById(id)); + } + + @Operation(summary = "创建需求类型") + @PostMapping("/demand-types") + public Result createDemandType(@RequestBody DemandType demandType) { + return Result.success(configService.createDemandType(demandType)); + } + + @Operation(summary = "更新需求类型") + @PutMapping("/demand-types/{id:\\d+}") + public Result updateDemandType(@PathVariable Long id, @RequestBody DemandType demandType) { + demandType.setId(id); + return Result.success(configService.updateDemandType(demandType)); + } + + @Operation(summary = "删除需求类型") + @DeleteMapping("/demand-types/{id:\\d+}") + public Result deleteDemandType(@PathVariable Long id) { + return Result.success(configService.deleteDemandType(id)); + } + + @Operation(summary = "设置需求类型的可接单部门") + @PutMapping("/demand-types/{id:\\d+}/departments") + public Result setDemandTypeDepartments( + @PathVariable Long id, + @RequestBody List departmentIds) { + return Result.success(demandTypeDepartmentService.setDemandTypeDepartments(id, departmentIds)); + } + + @Operation(summary = "获取需求类型的可接单部门列表") + @GetMapping("/demand-types/{id:\\d+}/departments") + public Result> getDemandTypeDepartments(@PathVariable Long id) { + return Result.success(demandTypeDepartmentService.getDepartmentIdsByDemandType(id)); + } + + // ============ 品牌管理 ============ + + @Operation(summary = "获取所有品牌(包括禁用的,用于管理页面)") + @GetMapping("/brands") + public Result> getAllBrands() { + return Result.success(configService.getAllBrands()); + } + + @Operation(summary = "获取所有启用的品牌(用于下拉选择)") + @GetMapping("/brands/enabled") + public Result> getEnabledBrands() { + return Result.success(configService.getEnabledBrands()); + } + + @Operation(summary = "根据ID获取品牌") + @GetMapping("/brands/{id:\\d+}") + public Result getBrandById(@PathVariable Long id) { + return Result.success(configService.getBrandById(id)); + } + + @Operation(summary = "创建品牌") + @PostMapping("/brands") + public Result createBrand(@RequestBody Brand brand) { + return Result.success(configService.createBrand(brand)); + } + + @Operation(summary = "更新品牌") + @PutMapping("/brands/{id:\\d+}") + public Result updateBrand(@PathVariable Long id, @RequestBody Brand brand) { + brand.setId(id); + return Result.success(configService.updateBrand(brand)); + } + + @Operation(summary = "删除品牌") + @DeleteMapping("/brands/{id:\\d+}") + public Result deleteBrand(@PathVariable Long id) { + return Result.success(configService.deleteBrand(id)); + } + + // ============ 平台管理 ============ + + @Operation(summary = "获取所有平台(包括禁用的,用于管理页面)") + @GetMapping("/platforms") + public Result> getAllPlatforms() { + return Result.success(configService.getAllPlatforms()); + } + + @Operation(summary = "获取所有启用的平台(用于下拉选择)") + @GetMapping("/platforms/enabled") + public Result> getEnabledPlatforms() { + return Result.success(configService.getEnabledPlatforms()); + } + + @Operation(summary = "根据ID获取平台") + @GetMapping("/platforms/{id:\\d+}") + public Result getPlatformById(@PathVariable Long id) { + return Result.success(configService.getPlatformById(id)); + } + + @Operation(summary = "创建平台") + @PostMapping("/platforms") + public Result createPlatform(@RequestBody Platform platform) { + return Result.success(configService.createPlatform(platform)); + } + + @Operation(summary = "更新平台") + @PutMapping("/platforms/{id:\\d+}") + public Result updatePlatform(@PathVariable Long id, @RequestBody Platform platform) { + platform.setId(id); + return Result.success(configService.updatePlatform(platform)); + } + + @Operation(summary = "删除平台") + @DeleteMapping("/platforms/{id:\\d+}") + public Result deletePlatform(@PathVariable Long id) { + return Result.success(configService.deletePlatform(id)); + } + + // ============ 类目管理 ============ + + @Operation(summary = "获取所有叶子类目(包括禁用的,用于管理页面)") + @GetMapping("/categories/leaf") + public Result> getAllLeafCategories() { + return Result.success(configService.getAllLeafCategories()); + } + + @Operation(summary = "获取所有启用的叶子类目(用于下拉选择)") + @GetMapping("/categories/leaf/enabled") + public Result> getEnabledLeafCategories() { + return Result.success(configService.getEnabledLeafCategories()); + } + + @Operation(summary = "获取所有类目") + @GetMapping("/categories") + public Result> getAllCategories() { + return Result.success(configService.getAllCategories()); + } + + @Operation(summary = "根据父ID获取子类目") + @GetMapping("/categories/parent/{parentId}") + public Result> getCategoriesByParentId(@PathVariable Long parentId) { + return Result.success(configService.getCategoriesByParentId(parentId)); + } + + @Operation(summary = "根据ID获取类目") + @GetMapping("/categories/{id:\\d+}") + public Result getCategoryById(@PathVariable Long id) { + return Result.success(configService.getCategoryById(id)); + } + + @Operation(summary = "创建类目") + @PostMapping("/categories") + public Result createCategory(@RequestBody Category category) { + return Result.success(configService.createCategory(category)); + } + + @Operation(summary = "更新类目") + @PutMapping("/categories/{id:\\d+}") + public Result updateCategory(@PathVariable Long id, @RequestBody Category category) { + category.setId(id); + return Result.success(configService.updateCategory(category)); + } + + @Operation(summary = "删除类目") + @DeleteMapping("/categories/{id:\\d+}") + public Result deleteCategory(@PathVariable Long id) { + return Result.success(configService.deleteCategory(id)); + } + + // ============ 团队积分池管理 ============ + + @Operation(summary = "根据部门ID获取积分池") + @GetMapping("/score-pool/dept/{deptId}") + public Result getScorePoolByDeptId(@PathVariable Long deptId) { + TeamScorePool pool = teamScorePoolService.getByDeptId(deptId); + // 如果积分池不存在,自动创建 + if (pool == null) { + var department = departmentService.getDepartmentById(deptId); + if (department != null) { + pool = teamScorePoolService.upsertPool(deptId, department.getDeptName()); + } else { + // 部门不存在,返回一个默认的积分池对象(余额为0) + pool = new TeamScorePool(); + pool.setDeptId(deptId); + pool.setBalance(0); + } + } + return Result.success(pool); + } + + @Operation(summary = "检查积分余额是否足够") + @GetMapping("/score-pool/check-balance") + public Result checkBalance(@RequestParam Long deptId, @RequestParam Integer score) { + return Result.success(teamScorePoolService.checkBalance(deptId, score)); + } + + @Operation(summary = "检查是否超过周期限制") + @GetMapping("/score-pool/check-limit") + public Result checkPeriodLimit(@RequestParam Long deptId, @RequestParam Integer score) { + return Result.success(teamScorePoolService.checkPeriodLimit(deptId, score)); + } + + @Operation(summary = "充值积分") + @PostMapping("/score-pool/recharge") + public Result rechargeScore(@RequestParam Long deptId, @RequestParam Integer score) { + teamScorePoolService.rechargeScore(deptId, score); + return Result.success(); + } + + @Operation(summary = "创建积分池") + @PostMapping("/score-pool") + public Result createScorePool(@RequestBody TeamScorePool pool) { + return Result.success(teamScorePoolService.createPool(pool)); + } + + @Operation(summary = "更新积分池配置") + @PutMapping("/score-pool/{id}") + public Result updateScorePool(@PathVariable Long id, @RequestBody TeamScorePool pool) { + pool.setId(id); + return Result.success(teamScorePoolService.updatePool(pool)); + } + + @Operation(summary = "检查是否需要预警") + @GetMapping("/score-pool/alert/{deptId}") + public Result isAlert(@PathVariable Long deptId) { + return Result.success(teamScorePoolService.isAlert(deptId)); + } +} + diff --git a/backend/src/main/java/com/designhub/controller/ContestController.java b/backend/src/main/java/com/designhub/controller/ContestController.java new file mode 100644 index 0000000..990191c --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/ContestController.java @@ -0,0 +1,122 @@ +package com.designhub.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.designhub.common.Result; +import com.designhub.entity.Contest; +import com.designhub.entity.ContestParticipant; +import com.designhub.service.ContestService; +import com.designhub.util.JwtUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 竞赛活动Controller + */ +@Tag(name = "竞赛活动", description = "竞赛活动管理相关API") +@RestController +@RequestMapping("/api/contests") +@RequiredArgsConstructor +public class ContestController { + + private final ContestService contestService; + private final JwtUtil jwtUtil; + + @Operation(summary = "创建竞赛") + @PostMapping + public Result createContest( + @RequestBody Contest contest, + @RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + contest.setCreatorId(userId); + return Result.success(contestService.createContest(contest)); + } + + @Operation(summary = "更新竞赛") + @PutMapping("/{id}") + public Result updateContest(@PathVariable Long id, @RequestBody Contest contest) { + return Result.success(contestService.updateContest(id, contest)); + } + + @Operation(summary = "发布竞赛") + @PostMapping("/{id}/publish") + public Result publishContest(@PathVariable Long id) { + return Result.success(contestService.publishContest(id)); + } + + @Operation(summary = "结束竞赛") + @PostMapping("/{id}/end") + public Result endContest(@PathVariable Long id) { + contestService.endContest(id); + return Result.success(); + } + + @Operation(summary = "取消竞赛") + @PostMapping("/{id}/cancel") + public Result cancelContest(@PathVariable Long id) { + contestService.cancelContest(id); + return Result.success(); + } + + @Operation(summary = "删除竞赛") + @DeleteMapping("/{id}") + public Result deleteContest(@PathVariable Long id) { + contestService.deleteContest(id); + return Result.success(); + } + + @Operation(summary = "获取竞赛列表") + @GetMapping + public Result> getContestList( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) String status) { + return Result.success(contestService.getContestList(page, pageSize, status)); + } + + @Operation(summary = "获取竞赛详情") + @GetMapping("/{id}") + public Result getContestById(@PathVariable Long id) { + return Result.success(contestService.getContestById(id)); + } + + @Operation(summary = "获取竞赛排行榜") + @GetMapping("/{id}/leaderboard") + public Result> getLeaderboard( + @PathVariable Long id, + @RequestParam(required = false, defaultValue = "50") Integer limit) { + return Result.success(contestService.getLeaderboard(id, limit)); + } + + @Operation(summary = "获取用户在竞赛中的数据") + @GetMapping("/{contestId}/user/{userId}") + public Result getUserContestData( + @PathVariable Long contestId, + @PathVariable Long userId) { + return Result.success(contestService.getUserContestData(contestId, userId)); + } + + @Operation(summary = "获取我的竞赛数据") + @GetMapping("/{contestId}/my") + public Result getMyContestData( + @PathVariable Long contestId, + @RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + return Result.success(contestService.getUserContestData(contestId, userId)); + } + + @Operation(summary = "领取竞赛奖励") + @PostMapping("/{contestId}/claim-reward") + public Result claimReward( + @PathVariable Long contestId, + @RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + return Result.success(contestService.claimReward(contestId, userId)); + } +} diff --git a/backend/src/main/java/com/designhub/controller/DelayPenaltyController.java b/backend/src/main/java/com/designhub/controller/DelayPenaltyController.java new file mode 100644 index 0000000..0423787 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/DelayPenaltyController.java @@ -0,0 +1,72 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.entity.DelayPenaltyRecord; +import com.designhub.service.DelayPenaltyService; +import com.designhub.util.JwtUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 延误扣分Controller + */ +@Slf4j +@Tag(name = "延误扣分管理", description = "延误扣分和申诉相关API") +@RestController +@RequestMapping("/delay-penalty") +@RequiredArgsConstructor +public class DelayPenaltyController { + + private final DelayPenaltyService delayPenaltyService; + private final JwtUtil jwtUtil; + + @Operation(summary = "根据任务ID查询延误记录") + @GetMapping("/task/{taskId}") + public Result> getByTaskId(@PathVariable Long taskId) { + return Result.success(delayPenaltyService.getDelayRecordsByTaskId(taskId)); + } + + @Operation(summary = "根据设计师ID查询延误记录") + @GetMapping("/designer/{designerId}") + public Result> getByDesignerId(@PathVariable Long designerId) { + return Result.success(delayPenaltyService.getDelayRecordsByDesignerId(designerId)); + } + + @Operation(summary = "统计设计师的延误扣分总数") + @GetMapping("/designer/{designerId}/total") + public Result getTotalPenaltyScore(@PathVariable Long designerId) { + return Result.success(delayPenaltyService.getTotalPenaltyScoreByDesignerId(designerId)); + } + + @Operation(summary = "申诉延误扣分") + @PostMapping("/{recordId}/appeal") + public Result appealDelayPenalty( + @PathVariable Long recordId, + @RequestParam String appealReason, + @RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + delayPenaltyService.appealDelayPenalty(recordId, userId, appealReason); + return Result.success("申诉提交成功"); + } + + @Operation(summary = "管理员审批申诉") + @PostMapping("/{recordId}/approve") + public Result approveAppeal( + @PathVariable Long recordId, + @RequestParam Boolean approved, + @RequestParam(required = false) String appealResult, + @RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long approverId = jwtUtil.getUserIdFromToken(token); + delayPenaltyService.approveAppeal(recordId, approverId, approved, + appealResult != null ? appealResult : (approved ? "申诉通过" : "申诉驳回")); + return Result.success(approved ? "申诉已通过" : "申诉已驳回"); + } +} + diff --git a/backend/src/main/java/com/designhub/controller/DepartmentController.java b/backend/src/main/java/com/designhub/controller/DepartmentController.java new file mode 100644 index 0000000..3921788 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/DepartmentController.java @@ -0,0 +1,114 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.dto.DepartmentCreateRequest; +import com.designhub.dto.DepartmentLimitSettingsRequest; +import com.designhub.dto.DepartmentScoreAdjustRequest; +import com.designhub.dto.DepartmentUpdateRequest; +import com.designhub.entity.Department; +import com.designhub.entity.TeamScorePool; +import com.designhub.service.DepartmentService; +import com.designhub.service.TeamScorePoolService; +import com.designhub.vo.DepartmentOverviewVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 部门管理Controller + */ +@Tag(name = "部门管理", description = "部门管理相关API") +@RestController +@RequestMapping("/api/departments") +@RequiredArgsConstructor +public class DepartmentController { + + private final DepartmentService departmentService; + private final TeamScorePoolService teamScorePoolService; + + @Operation(summary = "获取所有部门列表") + @GetMapping + public Result> getAllDepartments() { + return Result.success(departmentService.getAllDepartments()); + } + + @Operation(summary = "部门概览(含积分信息)") + @GetMapping("/overview") + public Result> getDepartmentOverview() { + return Result.success(departmentService.getDepartmentOverviewList()); + } + + @Operation(summary = "获取部门树形结构") + @GetMapping("/tree") + public Result> getDepartmentTree() { + return Result.success(departmentService.getDepartmentTree()); + } + + @Operation(summary = "根据类型获取部门列表") + @GetMapping("/type/{deptType}") + public Result> getDepartmentsByType(@PathVariable String deptType) { + return Result.success(departmentService.getDepartmentsByType(deptType)); + } + + @Operation(summary = "根据ID获取部门") + @GetMapping("/{id}") + public Result getDepartmentById(@PathVariable Long id) { + return Result.success(departmentService.getDepartmentById(id)); + } + + @Operation(summary = "创建部门") + @PostMapping + public Result createDepartment(@Valid @RequestBody DepartmentCreateRequest request) { + return Result.success(departmentService.createDepartment(request)); + } + + @Operation(summary = "更新部门") + @PutMapping("/{id}") + public Result updateDepartment(@PathVariable Long id, + @Valid @RequestBody DepartmentUpdateRequest request) { + return Result.success(departmentService.updateDepartment(id, request)); + } + + @Operation(summary = "更新部门积分限制配置") + @PutMapping("/{id}/limit") + public Result updateLimitSettings(@PathVariable Long id, + @Valid @RequestBody DepartmentLimitSettingsRequest request) { + return Result.success(departmentService.updateLimitSettings(id, request)); + } + + @Operation(summary = "删除部门") + @DeleteMapping("/{id}") + public Result deleteDepartment(@PathVariable Long id) { + return Result.success(departmentService.deleteDepartment(id)); + } + + @Operation(summary = "设置部门负责人/主管") + @PostMapping("/{deptId}/head") + public Result setDepartmentHead( + @PathVariable Long deptId, + @RequestParam Long userId, + @RequestParam String type + ) { + return Result.success(departmentService.setDepartmentHead(deptId, userId, type)); + } + + @Operation(summary = "调整部门积分") + @PostMapping("/{deptId}/score-adjust") + public Result adjustScore(@PathVariable Long deptId, + @Valid @RequestBody DepartmentScoreAdjustRequest request) { + departmentService.adjustScore(deptId, request); + return Result.success(); + } + + @Operation(summary = "获取部门积分池详情") + @GetMapping("/{deptId}/score-pool") + public Result getScorePool(@PathVariable Long deptId) { + return Result.success(teamScorePoolService.getByDeptId(deptId)); + } +} + + diff --git a/backend/src/main/java/com/designhub/controller/DesignerController.java b/backend/src/main/java/com/designhub/controller/DesignerController.java new file mode 100644 index 0000000..10094ec --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/DesignerController.java @@ -0,0 +1,54 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.entity.Task; +import com.designhub.service.DesignerService; +import com.designhub.vo.DesignerStatsVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 设计师统计Controller + */ +@Tag(name = "设计师管理", description = "设计师统计和排行榜相关API") +@RestController +@RequestMapping("/api/designers") +@RequiredArgsConstructor +public class DesignerController { + + private final DesignerService designerService; + + @Operation(summary = "获取所有设计师统计信息") + @GetMapping("/stats") + public Result> getAllDesignerStats() { + return Result.success(designerService.getAllDesignerStats()); + } + + @Operation(summary = "获取单个设计师统计信息") + @GetMapping("/stats/{userId}") + public Result getDesignerStats(@PathVariable Long userId) { + return Result.success(designerService.getDesignerStats(userId)); + } + + @Operation(summary = "获取排行榜") + @GetMapping("/leaderboard") + public Result> getLeaderboard( + @RequestParam(required = false, defaultValue = "score") String type, + @RequestParam(required = false, defaultValue = "10") Integer limit, + @Parameter(description = "月份,格式:YYYY-MM,不传则查询当月") @RequestParam(required = false) String month + ) { + return Result.success(designerService.getLeaderboard(type, limit, month)); + } + + @Operation(summary = "获取设计师进行中的任务列表") + @GetMapping("/{userId}/in-progress-tasks") + public Result> getDesignerInProgressTasks(@PathVariable Long userId) { + return Result.success(designerService.getDesignerInProgressTasks(userId)); + } +} + diff --git a/backend/src/main/java/com/designhub/controller/DingTalkController.java b/backend/src/main/java/com/designhub/controller/DingTalkController.java new file mode 100644 index 0000000..275af9f --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/DingTalkController.java @@ -0,0 +1,174 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.entity.DingTalkConfig; +import com.designhub.service.DingTalkService; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 钉钉集成 Controller + */ +@Tag(name = "钉钉集成", description = "钉钉集成管理API") +@RestController +@RequestMapping("/api/dingtalk") +@RequiredArgsConstructor +public class DingTalkController { + + private final DingTalkService dingTalkService; + private final ObjectMapper objectMapper; + + @Operation(summary = "获取钉钉配置") + @GetMapping("/config") + public Result getConfig() { + DingTalkConfig config = dingTalkService.getConfig(); + + // 如果没有配置,返回默认配置(仅用于前端显示) + if (config == null) { + config = new DingTalkConfig(); + config.setName("默认配置"); + config.setEnabled(0); + } + + // 隐藏敏感信息 + if (config.getAppSecret() != null && !config.getAppSecret().isEmpty()) { + config.setAppSecret("******"); + } + if (config.getRobotSecret() != null && !config.getRobotSecret().isEmpty()) { + config.setRobotSecret("******"); + } + config.setAccessToken(null); + + return Result.success(config); + } + + @Operation(summary = "保存钉钉配置") + @PostMapping("/config") + public Result saveConfig(@RequestBody Map configMap) { + try { + // 处理enabledFeatures字段 + Object enabledFeaturesObj = configMap.get("enabledFeatures"); + String enabledFeaturesJson = null; + if (enabledFeaturesObj != null) { + if (enabledFeaturesObj instanceof String) { + enabledFeaturesJson = (String) enabledFeaturesObj; + } else { + // 转换为JSON字符串 + enabledFeaturesJson = objectMapper.writeValueAsString(enabledFeaturesObj); + } + } + + // 构建配置对象 + DingTalkConfig config = new DingTalkConfig(); + config.setId(configMap.get("id") != null ? ((Number) configMap.get("id")).longValue() : null); + config.setName((String) configMap.get("name")); + config.setAgentId((String) configMap.get("agentId")); + config.setAppKey((String) configMap.get("appKey")); + config.setAppSecret((String) configMap.get("appSecret")); + config.setCorpId((String) configMap.get("corpId")); + config.setRobotWebhook((String) configMap.get("robotWebhook")); + config.setRobotSecret((String) configMap.get("robotSecret")); + config.setEnabled(configMap.get("enabled") != null ? ((Number) configMap.get("enabled")).intValue() : 1); + config.setEnabledFeatures(enabledFeaturesJson); + + // 如果前端传来的appSecret是占位符,则保留原有的secret + if ("******".equals(config.getAppSecret())) { + DingTalkConfig existingConfig = dingTalkService.getConfig(); + if (existingConfig != null) { + config.setAppSecret(existingConfig.getAppSecret()); + } + } + if ("******".equals(config.getRobotSecret())) { + DingTalkConfig existingConfig = dingTalkService.getConfig(); + if (existingConfig != null) { + config.setRobotSecret(existingConfig.getRobotSecret()); + } + } + + DingTalkConfig saved = dingTalkService.saveConfig(config); + + // 返回时隐藏敏感信息 + if (saved.getAppSecret() != null && !saved.getAppSecret().isEmpty()) { + saved.setAppSecret("******"); + } + if (saved.getRobotSecret() != null && !saved.getRobotSecret().isEmpty()) { + saved.setRobotSecret("******"); + } + saved.setAccessToken(null); + + return Result.success(saved); + } catch (Exception e) { + return Result.error("保存配置失败: " + e.getMessage()); + } + } + + @Operation(summary = "测试钉钉连接") + @PostMapping("/test") + public Result testConnection(@RequestBody DingTalkConfig config) { + return Result.success(dingTalkService.testConnection(config)); + } + + @Operation(summary = "发送测试消息") + @PostMapping("/test-message") + public Result sendTestMessage(@RequestBody Map params) { + String title = params.getOrDefault("title", "测试消息"); + String content = params.getOrDefault("content", "这是一条来自DesignHub的测试消息"); + return Result.success(dingTalkService.sendRobotMessage(title, content)); + } + + @Operation(summary = "获取钉钉部门列表") + @GetMapping("/departments") + public Result>> getDepartmentList() { + return Result.success(dingTalkService.getDepartmentList()); + } + + @Operation(summary = "同步钉钉用户") + @PostMapping("/sync-users") + public Result syncUsers() { + return Result.success(dingTalkService.syncUsers()); + } + + @Operation(summary = "检查功能状态") + @GetMapping("/feature/{feature}") + public Result isFeatureEnabled(@PathVariable String feature) { + return Result.success(dingTalkService.isFeatureEnabled(feature)); + } + + @Operation(summary = "钉钉OAuth登录发起", description = "生成钉钉登录URL") + @GetMapping("/oauth/login") + public Result> initiateLogin(@RequestParam(required = false) String redirectUrl) { + if (!dingTalkService.isConfigured()) { + return Result.error("钉钉登录未配置或未启用,请联系管理员在系统设置中配置"); + } + + DingTalkConfig config = dingTalkService.getConfig(); + + // 构建回调URL + String callbackUrl = redirectUrl != null ? redirectUrl : "http://localhost:3000/auth/dingtalk/callback"; + + // 构建钉钉OAuth URL + String authUrl = String.format( + "https://login.dingtalk.com/oauth2/auth?redirect_uri=%s&response_type=code&client_id=%s&scope=openid&state=STATE&prompt=consent", + java.net.URLEncoder.encode(callbackUrl, java.nio.charset.StandardCharsets.UTF_8), + config.getAppKey() + ); + + Map result = new HashMap<>(); + result.put("authUrl", authUrl); + result.put("appKey", config.getAppKey()); + return Result.success(result); + } + + @Operation(summary = "检查钉钉登录是否已配置") + @GetMapping("/is-configured") + public Result isConfigured() { + return Result.success(dingTalkService.isConfigured()); + } +} diff --git a/backend/src/main/java/com/designhub/controller/ExportController.java b/backend/src/main/java/com/designhub/controller/ExportController.java new file mode 100644 index 0000000..5f3610c --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/ExportController.java @@ -0,0 +1,55 @@ +package com.designhub.controller; + +import com.designhub.service.ExportService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; + +/** + * 数据导出Controller + */ +@Tag(name = "数据导出", description = "数据导出相关API") +@RestController +@RequestMapping("/api/export") +@RequiredArgsConstructor +public class ExportController { + + private final ExportService exportService; + + @Operation(summary = "导出任务列表") + @GetMapping("/tasks") + public void exportTasks( + HttpServletResponse response, + @RequestParam(required = false) String status, + @RequestParam(required = false) Long publisherId + ) throws IOException { + exportService.exportTasks(response, status, publisherId); + } + + @Operation(summary = "导出用户列表") + @GetMapping("/users") + public void exportUsers( + HttpServletResponse response, + @RequestParam(required = false) Long deptId, + @RequestParam(required = false) Integer status + ) throws IOException { + exportService.exportUsers(response, deptId, status); + } + + @Operation(summary = "导出部门列表") + @GetMapping("/departments") + public void exportDepartments(HttpServletResponse response) throws IOException { + exportService.exportDepartments(response); + } + + @Operation(summary = "导出设计师统计") + @GetMapping("/designer-stats") + public void exportDesignerStats(HttpServletResponse response) throws IOException { + exportService.exportDesignerStats(response); + } +} + diff --git a/backend/src/main/java/com/designhub/controller/FileUploadController.java b/backend/src/main/java/com/designhub/controller/FileUploadController.java new file mode 100644 index 0000000..2ee3c63 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/FileUploadController.java @@ -0,0 +1,184 @@ +package com.designhub.controller; + +import com.designhub.entity.FileUpload; +import com.designhub.entity.User; +import com.designhub.service.S3Service; +import com.designhub.service.UserService; +import com.designhub.util.JwtUtil; +import com.designhub.common.Result; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 文件上传下载Controller + */ +@RestController +@RequestMapping("/api/file") +public class FileUploadController { + + @Autowired + private S3Service s3Service; + + @Autowired + private UserService userService; + + @Autowired + private JwtUtil jwtUtil; + + /** + * 上传文件 + * + * @param files 文件列表 + * @param relatedType 关联类型(可选) + * @param relatedId 关联ID(可选) + * @param request HttpServletRequest + * @return 文件URL列表 + */ + @PostMapping("/upload") + public Result> uploadFiles( + @RequestParam("files") MultipartFile[] files, + @RequestParam(value = "relatedType", required = false) String relatedType, + @RequestParam(value = "relatedId", required = false) Long relatedId, + HttpServletRequest request) { + + try { + // 从token获取用户信息 + String token = request.getHeader("Authorization"); + if (token == null || !token.startsWith("Bearer ")) { + return Result.error("未登录或token无效"); + } + token = token.substring(7); + + String username = jwtUtil.getUsernameFromToken(token); + if (username == null) { + return Result.error("token无效"); + } + + User user = userService.getUserByUsername(username); + if (user == null) { + return Result.error("用户不存在"); + } + + // 上传文件 + List fileUrls = new ArrayList<>(); + for (MultipartFile file : files) { + if (file.isEmpty()) { + continue; + } + + String fileUrl = s3Service.uploadFile( + file, + user.getId(), + user.getRealName() != null ? user.getRealName() : user.getUsername(), + relatedType, + relatedId + ); + fileUrls.add(fileUrl); + } + + return Result.success(fileUrls); + + } catch (Exception e) { + return Result.error("文件上传失败: " + e.getMessage()); + } + } + + /** + * 下载文件(需要登录) + * + * @param id 文件ID + * @param request HttpServletRequest + * @param response HttpServletResponse + */ + @GetMapping("/download/{id}") + public void downloadFile( + @PathVariable Long id, + HttpServletRequest request, + HttpServletResponse response) { + + try { + // 验证登录状态 + String token = request.getHeader("Authorization"); + if (token == null || !token.startsWith("Bearer ")) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("未登录,请先登录"); + return; + } + token = token.substring(7); + + String username = jwtUtil.getUsernameFromToken(token); + if (username == null) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("token无效"); + return; + } + + // 获取文件信息 + FileUpload fileUpload = s3Service.getFileUploadById(id); + if (fileUpload == null) { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + response.getWriter().write("文件不存在"); + return; + } + + // 重定向到文件URL(S3提供的公开URL) + response.sendRedirect(fileUpload.getFileUrl()); + + } catch (IOException e) { + try { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + response.getWriter().write("文件下载失败: " + e.getMessage()); + } catch (IOException ex) { + // ignore + } + } + } + + /** + * 获取文件信息 + * + * @param id 文件ID + * @return 文件信息 + */ + @GetMapping("/{id}") + public Result getFileInfo(@PathVariable Long id) { + try { + FileUpload fileUpload = s3Service.getFileUploadById(id); + if (fileUpload == null) { + return Result.error("文件不存在"); + } + return Result.success(fileUpload); + } catch (Exception e) { + return Result.error("获取文件信息失败: " + e.getMessage()); + } + } + + /** + * 根据关联信息获取文件列表 + * + * @param relatedType 关联类型 + * @param relatedId 关联ID + * @return 文件列表 + */ + @GetMapping("/list") + public Result> getFilesByRelated( + @RequestParam String relatedType, + @RequestParam Long relatedId) { + + try { + List files = s3Service.getFilesByRelated(relatedType, relatedId); + return Result.success(files); + } catch (Exception e) { + return Result.error("获取文件列表失败: " + e.getMessage()); + } + } +} diff --git a/backend/src/main/java/com/designhub/controller/HelloController.java b/backend/src/main/java/com/designhub/controller/HelloController.java new file mode 100644 index 0000000..7f1a9a8 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/HelloController.java @@ -0,0 +1,50 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +/** + * 测试控制器 + */ +@Tag(name = "测试接口", description = "用于测试系统是否正常运行") +@RestController +@RequestMapping("/hello") +public class HelloController { + + @Operation(summary = "Hello World", description = "测试接口是否可访问") + @GetMapping + public Result hello() { + return Result.success("Hello, DesignHub! 系统运行正常 ✅"); + } + + @Operation(summary = "系统信息", description = "获取系统基本信息") + @GetMapping("/info") + public Result> info() { + Map info = new HashMap<>(); + info.put("name", "DesignHub - 设计师任务管理平台"); + info.put("version", "1.0.0"); + info.put("time", LocalDateTime.now()); + info.put("status", "running"); + info.put("description", "基于 Spring Boot 3.x + Vue 3 开发"); + return Result.success(info); + } + + @Operation(summary = "健康检查", description = "检查系统健康状态") + @GetMapping("/health") + public Result> health() { + Map health = new HashMap<>(); + health.put("status", "UP"); + health.put("database", "OK"); + health.put("api", "OK"); + return Result.success(health); + } +} + diff --git a/backend/src/main/java/com/designhub/controller/NotificationController.java b/backend/src/main/java/com/designhub/controller/NotificationController.java new file mode 100644 index 0000000..742abc4 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/NotificationController.java @@ -0,0 +1,70 @@ +package com.designhub.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.designhub.common.Result; +import com.designhub.entity.Notification; +import com.designhub.service.NotificationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +/** + * 通知消息Controller + */ +@Tag(name = "通知消息", description = "通知消息相关API") +@RestController +@RequestMapping("/api/notifications") +@RequiredArgsConstructor +public class NotificationController { + + private final NotificationService notificationService; + + @Operation(summary = "获取用户通知列表(分页)") + @GetMapping + public Result> getUserNotifications( + @RequestParam Long receiverId, + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) Boolean unreadOnly + ) { + Page page = notificationService.getUserNotifications(receiverId, pageNum, pageSize, unreadOnly); + return Result.success(page); + } + + @Operation(summary = "获取未读数量") + @GetMapping("/unread-count") + public Result getUnreadCount(@RequestParam Long receiverId) { + long count = notificationService.getUnreadCount(receiverId); + return Result.success(count); + } + + @Operation(summary = "标记通知为已读") + @PutMapping("/{id}/read") + public Result markAsRead(@PathVariable Long id, @RequestParam Long receiverId) { + boolean success = notificationService.markAsRead(id, receiverId); + return Result.success(success); + } + + @Operation(summary = "批量标记所有通知为已读") + @PutMapping("/read-all") + public Result markAllAsRead(@RequestParam Long receiverId) { + int count = notificationService.markAllAsRead(receiverId); + return Result.success(count); + } + + @Operation(summary = "删除通知") + @DeleteMapping("/{id}") + public Result deleteNotification(@PathVariable Long id, @RequestParam Long receiverId) { + boolean success = notificationService.deleteNotification(id, receiverId); + return Result.success(success); + } + + @Operation(summary = "发送通知(管理员)") + @PostMapping + public Result sendNotification(@RequestBody Notification notification) { + Notification result = notificationService.sendNotification(notification); + return Result.success(result); + } +} + diff --git a/backend/src/main/java/com/designhub/controller/PermissionController.java b/backend/src/main/java/com/designhub/controller/PermissionController.java new file mode 100644 index 0000000..738ce48 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/PermissionController.java @@ -0,0 +1,73 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.entity.Permission; +import com.designhub.service.PermissionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 权限管理Controller + */ +@Tag(name = "权限管理", description = "权限管理相关API") +@RestController +@RequestMapping("/api/permissions") +@RequiredArgsConstructor +public class PermissionController { + + private final PermissionService permissionService; + + @Operation(summary = "获取所有权限列表") + @GetMapping + public Result> getAllPermissions() { + return Result.success(permissionService.getAllPermissions()); + } + + @Operation(summary = "获取权限树形结构") + @GetMapping("/tree") + public Result> getPermissionTree() { + return Result.success(permissionService.getPermissionTree()); + } + + @Operation(summary = "根据ID获取权限") + @GetMapping("/{id}") + public Result getPermissionById(@PathVariable Long id) { + return Result.success(permissionService.getPermissionById(id)); + } + + @Operation(summary = "根据用户ID获取权限列表") + @GetMapping("/user/{userId}") + public Result> getPermissionsByUserId(@PathVariable Long userId) { + return Result.success(permissionService.getPermissionsByUserId(userId)); + } + + @Operation(summary = "根据用户ID获取权限代码列表") + @GetMapping("/user/{userId}/codes") + public Result> getPermissionCodesByUserId(@PathVariable Long userId) { + return Result.success(permissionService.getPermissionCodesByUserId(userId)); + } + + @Operation(summary = "创建权限") + @PostMapping + public Result createPermission(@RequestBody Permission permission) { + return Result.success(permissionService.createPermission(permission)); + } + + @Operation(summary = "更新权限") + @PutMapping("/{id}") + public Result updatePermission(@PathVariable Long id, @RequestBody Permission permission) { + permission.setId(id); + return Result.success(permissionService.updatePermission(permission)); + } + + @Operation(summary = "删除权限") + @DeleteMapping("/{id}") + public Result deletePermission(@PathVariable Long id) { + return Result.success(permissionService.deletePermission(id)); + } +} + diff --git a/backend/src/main/java/com/designhub/controller/RewardRuleController.java b/backend/src/main/java/com/designhub/controller/RewardRuleController.java new file mode 100644 index 0000000..b25db21 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/RewardRuleController.java @@ -0,0 +1,92 @@ +package com.designhub.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.designhub.common.Result; +import com.designhub.entity.RewardRule; +import com.designhub.service.RewardRuleService; +import com.designhub.util.JwtUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 自动奖励规则 Controller + */ +@Tag(name = "自动奖励规则", description = "自动奖励规则管理API") +@RestController +@RequestMapping("/api/reward-rules") +@RequiredArgsConstructor +public class RewardRuleController { + + private final RewardRuleService rewardRuleService; + private final JwtUtil jwtUtil; + + @Operation(summary = "创建奖励规则") + @PostMapping + public Result createRule( + @RequestBody RewardRule rule, + @RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + rule.setCreatorId(userId); + return Result.success(rewardRuleService.createRule(rule)); + } + + @Operation(summary = "更新奖励规则") + @PutMapping("/{id}") + public Result updateRule(@PathVariable Long id, @RequestBody RewardRule rule) { + return Result.success(rewardRuleService.updateRule(id, rule)); + } + + @Operation(summary = "启用规则") + @PostMapping("/{id}/enable") + public Result enableRule(@PathVariable Long id) { + rewardRuleService.toggleRule(id, true); + return Result.success(); + } + + @Operation(summary = "禁用规则") + @PostMapping("/{id}/disable") + public Result disableRule(@PathVariable Long id) { + rewardRuleService.toggleRule(id, false); + return Result.success(); + } + + @Operation(summary = "删除规则") + @DeleteMapping("/{id}") + public Result deleteRule(@PathVariable Long id) { + rewardRuleService.deleteRule(id); + return Result.success(); + } + + @Operation(summary = "获取规则列表") + @GetMapping + public Result> getRuleList( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) String ruleType) { + return Result.success(rewardRuleService.getRuleList(page, pageSize, ruleType)); + } + + @Operation(summary = "获取规则详情") + @GetMapping("/{id}") + public Result getRuleById(@PathVariable Long id) { + return Result.success(rewardRuleService.getRuleById(id)); + } + + @Operation(summary = "获取所有启用的规则") + @GetMapping("/enabled") + public Result> getEnabledRules() { + return Result.success(rewardRuleService.getEnabledRules()); + } + + @Operation(summary = "初始化默认规则") + @PostMapping("/init") + public Result initDefaultRules() { + rewardRuleService.initDefaultRules(); + return Result.success(); + } +} diff --git a/backend/src/main/java/com/designhub/controller/RoleController.java b/backend/src/main/java/com/designhub/controller/RoleController.java new file mode 100644 index 0000000..2118355 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/RoleController.java @@ -0,0 +1,79 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.entity.Role; +import com.designhub.service.RoleService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 角色管理Controller + */ +@Tag(name = "角色管理", description = "角色管理相关API") +@RestController +@RequestMapping("/api/roles") +@RequiredArgsConstructor +public class RoleController { + + private final RoleService roleService; + + @Operation(summary = "获取所有角色列表") + @GetMapping + public Result> getAllRoles() { + return Result.success(roleService.getAllRoles()); + } + + @Operation(summary = "根据ID获取角色") + @GetMapping("/{id}") + public Result getRoleById(@PathVariable Long id) { + return Result.success(roleService.getRoleById(id)); + } + + @Operation(summary = "创建角色") + @PostMapping + public Result createRole(@RequestBody Role role) { + return Result.success(roleService.createRole(role)); + } + + @Operation(summary = "更新角色") + @PutMapping("/{id}") + public Result updateRole(@PathVariable Long id, @RequestBody Role role) { + role.setId(id); + return Result.success(roleService.updateRole(role)); + } + + @Operation(summary = "删除角色") + @DeleteMapping("/{id}") + public Result deleteRole(@PathVariable Long id) { + return Result.success(roleService.deleteRole(id)); + } + + @Operation(summary = "为角色分配权限") + @PostMapping("/{id}/permissions") + public Result assignPermissions( + @PathVariable Long id, + @RequestBody List permissionIds + ) { + return Result.success(roleService.assignPermissions(id, permissionIds)); + } + + @Operation(summary = "获取角色的权限ID列表") + @GetMapping("/{id}/permissions") + public Result> getRolePermissionIds(@PathVariable Long id) { + return Result.success(roleService.getRolePermissionIds(id)); + } + + @Operation(summary = "启用/禁用角色") + @PutMapping("/{id}/status") + public Result toggleRoleStatus( + @PathVariable Long id, + @RequestParam Integer status + ) { + return Result.success(roleService.toggleRoleStatus(id, status)); + } +} + diff --git a/backend/src/main/java/com/designhub/controller/S3ConfigController.java b/backend/src/main/java/com/designhub/controller/S3ConfigController.java new file mode 100644 index 0000000..bed8d26 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/S3ConfigController.java @@ -0,0 +1,132 @@ +package com.designhub.controller; + +import com.designhub.entity.S3Config; +import com.designhub.service.S3ConfigService; +import com.designhub.common.Result; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * S3配置管理Controller + */ +@RestController +@RequestMapping("/api/s3/config") +public class S3ConfigController { + + @Autowired + private S3ConfigService s3ConfigService; + + /** + * 获取激活的S3配置 + */ + @GetMapping("/active") + public Result getActiveConfig() { + try { + S3Config config = s3ConfigService.getActiveConfig(); + // 隐藏敏感信息 + if (config != null) { + config.setSecretAccessKey("******"); + } + return Result.success(config); + } catch (Exception e) { + return Result.error("获取S3配置失败: " + e.getMessage()); + } + } + + /** + * 获取所有S3配置 + */ + @GetMapping("/list") + public Result> getAllConfigs() { + try { + List configs = s3ConfigService.getAllConfigs(); + // 隐藏敏感信息 + configs.forEach(config -> config.setSecretAccessKey("******")); + return Result.success(configs); + } catch (Exception e) { + return Result.error("获取S3配置列表失败: " + e.getMessage()); + } + } + + /** + * 根据ID获取S3配置 + */ + @GetMapping("/{id}") + public Result getConfigById(@PathVariable Long id) { + try { + S3Config config = s3ConfigService.getConfigById(id); + if (config == null) { + return Result.error("S3配置不存在"); + } + // 隐藏敏感信息 + config.setSecretAccessKey("******"); + return Result.success(config); + } catch (Exception e) { + return Result.error("获取S3配置失败: " + e.getMessage()); + } + } + + /** + * 创建S3配置 + */ + @PostMapping + public Result createConfig(@RequestBody S3Config config) { + try { + S3Config created = s3ConfigService.createConfig(config); + created.setSecretAccessKey("******"); + return Result.success(created); + } catch (Exception e) { + return Result.error("创建S3配置失败: " + e.getMessage()); + } + } + + /** + * 更新S3配置 + */ + @PutMapping("/{id}") + public Result updateConfig(@PathVariable Long id, @RequestBody S3Config config) { + try { + // 如果secretAccessKey为"******",说明没有修改,从数据库中获取原值 + if ("******".equals(config.getSecretAccessKey())) { + S3Config existingConfig = s3ConfigService.getConfigById(id); + if (existingConfig != null) { + config.setSecretAccessKey(existingConfig.getSecretAccessKey()); + } + } + + S3Config updated = s3ConfigService.updateConfig(id, config); + updated.setSecretAccessKey("******"); + return Result.success(updated); + } catch (Exception e) { + return Result.error("更新S3配置失败: " + e.getMessage()); + } + } + + /** + * 删除S3配置 + */ + @DeleteMapping("/{id}") + public Result deleteConfig(@PathVariable Long id) { + try { + s3ConfigService.deleteConfig(id); + return Result.success(); + } catch (Exception e) { + return Result.error("删除S3配置失败: " + e.getMessage()); + } + } + + /** + * 激活指定的S3配置 + */ + @PutMapping("/{id}/activate") + public Result activateConfig(@PathVariable Long id) { + try { + s3ConfigService.activateConfig(id); + return Result.success(); + } catch (Exception e) { + return Result.error("激活S3配置失败: " + e.getMessage()); + } + } +} diff --git a/backend/src/main/java/com/designhub/controller/ScorePoolController.java b/backend/src/main/java/com/designhub/controller/ScorePoolController.java new file mode 100644 index 0000000..9cad04e --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/ScorePoolController.java @@ -0,0 +1,102 @@ +package com.designhub.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.designhub.common.Result; +import com.designhub.entity.ScorePool; +import com.designhub.entity.ScorePoolRecord; +import com.designhub.service.ScorePoolService; +import com.designhub.util.JwtUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + * 激励积分池 Controller + */ +@Tag(name = "激励积分池", description = "激励积分池管理API") +@RestController +@RequestMapping("/api/score-pools") +@RequiredArgsConstructor +public class ScorePoolController { + + private final ScorePoolService scorePoolService; + private final JwtUtil jwtUtil; + + @Operation(summary = "创建积分池") + @PostMapping + public Result createPool( + @RequestBody ScorePool pool, + @RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + pool.setCreatorId(userId); + return Result.success(scorePoolService.createPool(pool)); + } + + @Operation(summary = "更新积分池") + @PutMapping("/{id}") + public Result updatePool(@PathVariable Long id, @RequestBody ScorePool pool) { + return Result.success(scorePoolService.updatePool(id, pool)); + } + + @Operation(summary = "获取积分池列表") + @GetMapping + public Result> getPoolList( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) String poolType, + @RequestParam(required = false) Integer year) { + return Result.success(scorePoolService.getPoolList(page, pageSize, poolType, year)); + } + + @Operation(summary = "获取积分池详情") + @GetMapping("/{id}") + public Result getPoolById(@PathVariable Long id) { + return Result.success(scorePoolService.getPoolById(id)); + } + + @Operation(summary = "获取当前活跃积分池") + @GetMapping("/current") + public Result getCurrentActivePool() { + return Result.success(scorePoolService.getCurrentActivePool()); + } + + @Operation(summary = "获取积分池统计信息") + @GetMapping("/{id}/statistics") + public Result> getPoolStatistics(@PathVariable Long id) { + return Result.success(scorePoolService.getPoolStatistics(id)); + } + + @Operation(summary = "获取积分池使用记录") + @GetMapping("/{id}/records") + public Result> getPoolRecords( + @PathVariable Long id, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "20") Integer pageSize) { + return Result.success(scorePoolService.getPoolRecords(id, page, pageSize)); + } + + @Operation(summary = "关闭积分池") + @PostMapping("/{id}/close") + public Result closePool(@PathVariable Long id) { + scorePoolService.closePool(id); + return Result.success(); + } + + @Operation(summary = "结算积分池") + @PostMapping("/{id}/settle") + public Result settlePool(@PathVariable Long id) { + scorePoolService.settlePool(id); + return Result.success(); + } + + @Operation(summary = "删除积分池") + @DeleteMapping("/{id}") + public Result deletePool(@PathVariable Long id) { + scorePoolService.deletePool(id); + return Result.success(); + } +} diff --git a/backend/src/main/java/com/designhub/controller/SystemConfigController.java b/backend/src/main/java/com/designhub/controller/SystemConfigController.java new file mode 100644 index 0000000..8d2f44a --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/SystemConfigController.java @@ -0,0 +1,87 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.entity.SystemConfig; +import com.designhub.service.SystemConfigService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 系统配置Controller + */ +@Tag(name = "系统配置", description = "系统配置相关API") +@RestController +@RequestMapping("/api/system-config") +@RequiredArgsConstructor +public class SystemConfigController { + + private final SystemConfigService systemConfigService; + + @Operation(summary = "获取所有配置列表") + @GetMapping + public Result> getAllConfigs() { + return Result.success(systemConfigService.getAllConfigs()); + } + + @Operation(summary = "根据分组获取配置列表") + @GetMapping("/group/{group}") + public Result> getConfigsByGroup(@PathVariable String group) { + return Result.success(systemConfigService.getConfigsByGroup(group)); + } + + @Operation(summary = "根据键获取配置值") + @GetMapping("/key/{key}") + public Result getConfigValue(@PathVariable String key) { + return Result.success(systemConfigService.getConfigValue(key)); + } + + @Operation(summary = "获取所有配置的Map") + @GetMapping("/map") + public Result> getAllConfigsMap() { + return Result.success(systemConfigService.getAllConfigsMap()); + } + + @Operation(summary = "根据分组获取配置Map") + @GetMapping("/map/group/{group}") + public Result> getConfigsMapByGroup(@PathVariable String group) { + return Result.success(systemConfigService.getConfigsMapByGroup(group)); + } + + @Operation(summary = "创建配置") + @PostMapping + public Result createConfig(@RequestBody SystemConfig config) { + return Result.success(systemConfigService.createConfig(config)); + } + + @Operation(summary = "更新配置") + @PutMapping("/{id}") + public Result updateConfig(@PathVariable Long id, @RequestBody SystemConfig config) { + config.setId(id); + return Result.success(systemConfigService.updateConfig(config)); + } + + @Operation(summary = "批量更新配置值") + @PutMapping("/batch") + public Result batchUpdateValues(@RequestBody Map configMap) { + return Result.success(systemConfigService.batchUpdateValues(configMap)); + } + + @Operation(summary = "删除配置") + @DeleteMapping("/{id}") + public Result deleteConfig(@PathVariable Long id) { + return Result.success(systemConfigService.deleteConfig(id)); + } + + @Operation(summary = "初始化默认配置") + @PostMapping("/init") + public Result initDefaultConfigs() { + systemConfigService.initDefaultConfigs(); + return Result.success(null); + } +} + diff --git a/backend/src/main/java/com/designhub/controller/SystemHealthController.java b/backend/src/main/java/com/designhub/controller/SystemHealthController.java new file mode 100644 index 0000000..42be246 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/SystemHealthController.java @@ -0,0 +1,30 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.service.SystemHealthService; +import com.designhub.vo.SystemHealthVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 系统健康检查控制器 + */ +@Tag(name = "系统健康", description = "系统自检与健康检查接口") +@RestController +@RequestMapping("/api/system") +@RequiredArgsConstructor +public class SystemHealthController { + + private final SystemHealthService systemHealthService; + + @Operation(summary = "健康检查", description = "用于部署后快速检测数据库与关键表状态") + @GetMapping("/health") + public Result health() { + return Result.success(systemHealthService.check()); + } +} + diff --git a/backend/src/main/java/com/designhub/controller/TaskApprovalController.java b/backend/src/main/java/com/designhub/controller/TaskApprovalController.java new file mode 100644 index 0000000..7832732 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/TaskApprovalController.java @@ -0,0 +1,93 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.dto.TaskApprovalDTO; +import com.designhub.entity.Task; +import com.designhub.service.TaskApprovalService; +import com.designhub.util.JwtUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 任务审核控制器 + */ +@Tag(name = "任务审核管理") +@RestController +@RequestMapping("/api/tasks") +@RequiredArgsConstructor +public class TaskApprovalController { + + private final TaskApprovalService taskApprovalService; + private final JwtUtil jwtUtil; + + @Operation(summary = "需求部门审核通过") + @PostMapping("/{id}/approve-demand") + public Result approveDemandLevel( + @PathVariable Long id, + @RequestBody TaskApprovalDTO dto, + @RequestHeader("Authorization") String authHeader + ) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long approverId = jwtUtil.getUserIdFromToken(token); + taskApprovalService.approveDemandLevel(id, approverId, dto); + return Result.success(); + } + + @Operation(summary = "需求部门驳回") + @PostMapping("/{id}/reject-demand") + public Result rejectByDemandLeader( + @PathVariable Long id, + @RequestBody Map params, + @RequestHeader("Authorization") String authHeader + ) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long approverId = jwtUtil.getUserIdFromToken(token); + String reason = params.getOrDefault("reason", ""); + taskApprovalService.rejectByDemandLeader(id, approverId, reason); + return Result.success(); + } + + @Operation(summary = "接单部门审核通过") + @PostMapping("/{id}/approve-receiving") + public Result approveReceivingLevel( + @PathVariable Long id, + @RequestBody TaskApprovalDTO dto, + @RequestHeader("Authorization") String authHeader + ) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long approverId = jwtUtil.getUserIdFromToken(token); + taskApprovalService.approveReceivingLevel(id, approverId, dto); + return Result.success(); + } + + @Operation(summary = "接单部门驳回") + @PostMapping("/{id}/reject-receiving") + public Result rejectByReceivingLeader( + @PathVariable Long id, + @RequestBody Map params, + @RequestHeader("Authorization") String authHeader + ) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long approverId = jwtUtil.getUserIdFromToken(token); + String reason = params.getOrDefault("reason", ""); + taskApprovalService.rejectByReceivingLeader(id, approverId, reason); + return Result.success(); + } + + @Operation(summary = "获取待审核任务列表") + @GetMapping("/pending-approval") + public Result> getPendingApprovalTasks( + @RequestHeader("Authorization") String authHeader + ) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + List tasks = taskApprovalService.getPendingApprovalTasks(userId); + return Result.success(tasks); + } +} diff --git a/backend/src/main/java/com/designhub/controller/TaskController.java b/backend/src/main/java/com/designhub/controller/TaskController.java new file mode 100644 index 0000000..4eea057 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/TaskController.java @@ -0,0 +1,400 @@ +package com.designhub.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.designhub.common.Result; +import com.designhub.dto.TaskAssignRequest; +import com.designhub.dto.TaskAuditRequest; +import com.designhub.entity.Task; +import com.designhub.service.TaskService; +import com.designhub.util.JwtUtil; +import com.designhub.vo.DashboardStatsVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +/** + * 任务管理控制器 + */ +@Slf4j +@Tag(name = "任务管理", description = "任务的增删改查、抢单、审核等接口") +@RestController +@RequestMapping("/api/tasks") +@RequiredArgsConstructor +public class TaskController { + + private final TaskService taskService; + private final JwtUtil jwtUtil; + + @Operation(summary = "创建任务", description = "发起新的设计任务") + @PostMapping + public Result createTask(@RequestBody Task task, @RequestHeader("Authorization") String authHeader) { + log.info("创建任务: {}", task.getTitle()); + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + task.setPublisherId(userId); + Long taskId = taskService.createTask(task); + return Result.success(taskId); + } + + @Operation(summary = "创建流水线任务", description = "创建包含多个子任务的流水线任务") + @PostMapping("/pipeline") + public Result createPipelineTask( + @RequestBody com.designhub.dto.PipelineTaskDTO dto, + @RequestHeader("Authorization") String authHeader) { + + log.info("创建流水线任务: {}", dto.getTitle()); + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + Long taskId = taskService.createPipelineTask(dto, userId); + return Result.success("流水线任务创建成功", taskId); + } + + @Operation(summary = "获取任务列表", description = "分页查询任务列表,支持多种筛选条件") + @GetMapping + public Result> getTaskList( + @Parameter(description = "任务状态") @RequestParam(required = false) String status, + @Parameter(description = "任务类型ID") @RequestParam(required = false) Long taskTypeId, + @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page, + @Parameter(description = "每页数量") @RequestParam(defaultValue = "20") Integer pageSize) { + + log.info("查询任务列表: status={}, taskTypeId={}, page={}, pageSize={}", + status, taskTypeId, page, pageSize); + + IPage taskPage = taskService.getTaskList(page, pageSize, status, taskTypeId); + return Result.success(taskPage); + } + + @Operation(summary = "获取所有待审核任务", description = "获取所有处于审批流程中的任务(包括需求部门审核和接单部门审核)") + @GetMapping("/pending-approval-all") + public Result> getAllPendingApprovalTasks( + @Parameter(description = "任务类型ID") @RequestParam(required = false) Long taskTypeId, + @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page, + @Parameter(description = "每页数量") @RequestParam(defaultValue = "20") Integer pageSize) { + + log.info("查询所有待审核任务: taskTypeId={}, page={}, pageSize={}", + taskTypeId, page, pageSize); + + IPage taskPage = taskService.getAllPendingApprovalTasks(page, pageSize, taskTypeId); + return Result.success(taskPage); + } + + @Operation(summary = "获取任务详情", description = "根据任务ID获取详细信息") + @GetMapping("/{id}") + public Result getTaskById(@PathVariable Long id) { + log.info("查询任务详情: {}", id); + Task task = taskService.getTaskById(id); + return Result.success(task); + } + + @Operation(summary = "更新任务", description = "更新任务信息") + @PutMapping("/{id}") + public Result updateTask(@PathVariable Long id, @RequestBody Task task) { + log.info("更新任务: {}", id); + taskService.updateTask(id, task); + return Result.success("更新成功"); + } + + @Operation(summary = "抢单", description = "设计师抢单") + @PostMapping("/{id}/grab") + public Result grabTask(@PathVariable Long id, @RequestHeader("Authorization") String authHeader) { + log.info("抢单: {}", id); + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + taskService.grabTask(id, userId); + return Result.success("抢单成功"); + } + + @Operation(summary = "提交任务", description = "设计师提交完成的任务") + @PostMapping("/{id}/submit") + public Result submitTask( + @PathVariable Long id, + @RequestParam String deliveryPath, + @RequestHeader("Authorization") String authHeader) { + + log.info("提交任务: {}, 交付路径: {}", id, deliveryPath); + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + taskService.submitTask(id, userId, deliveryPath); + return Result.success("提交成功"); + } + + @Operation(summary = "审核任务", description = "需求方审核任务") + @PostMapping("/{id}/review") + public Result reviewTask( + @PathVariable Long id, + @RequestParam Boolean approved, + @RequestParam(required = false) String comment, + @RequestParam(required = false) Integer rating, + @RequestHeader("Authorization") String authHeader) { + + log.info("审核任务: {}, 通过: {}, 评分: {}", id, approved, rating); + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + taskService.reviewTask(id, userId, approved, comment, rating); + return Result.success(approved ? "审核通过" : "已驳回"); + } + + @Operation(summary = "取消任务", description = "发布人取消任务") + @PostMapping("/{id}/cancel") + public Result cancelTask( + @PathVariable Long id, + @RequestParam String reason, + @RequestHeader("Authorization") String authHeader) { + + log.info("取消任务: {}, 原因: {}", id, reason); + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + taskService.cancelTask(id, userId, reason); + return Result.success("取消成功"); + } + + @Operation(summary = "获取已完成任务列表", description = "根据用户角色返回不同范围的已完成任务") + @GetMapping("/completed") + public Result> getCompletedTasks( + @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page, + @Parameter(description = "每页数量") @RequestParam(defaultValue = "12") Integer pageSize, + @Parameter(description = "关键词") @RequestParam(required = false) String keyword, + @Parameter(description = "排序方式") @RequestParam(required = false) String sortBy, + @RequestHeader("Authorization") String authHeader) { + + log.info("查询已完成任务: page={}, pageSize={}, keyword={}, sortBy={}", + page, pageSize, keyword, sortBy); + + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + + IPage taskPage = taskService.getCompletedTasks(page, pageSize, userId, keyword, sortBy); + return Result.success(taskPage); + } + + @Operation(summary = "任务大厅", description = "获取可接单的任务列表,支持多种筛选、搜索和排序") + @GetMapping("/hall") + public Result> getTaskHall( + @Parameter(description = "任务类型ID") @RequestParam(required = false) Long taskTypeId, + @Parameter(description = "需求类型ID") @RequestParam(required = false) Long demandTypeId, + @Parameter(description = "品牌ID") @RequestParam(required = false) Long brandId, + @Parameter(description = "类目ID") @RequestParam(required = false) Long categoryId, + @Parameter(description = "平台ID") @RequestParam(required = false) Long platformId, + @Parameter(description = "任务子类型") @RequestParam(required = false) String taskSubType, + @Parameter(description = "最小积分") @RequestParam(required = false) Integer minScore, + @Parameter(description = "最大积分") @RequestParam(required = false) Integer maxScore, + @Parameter(description = "关键词搜索") @RequestParam(required = false) String keyword, + @Parameter(description = "发布人姓名") @RequestParam(required = false) String publisherName, + @Parameter(description = "发布时间范围:today/week/month") @RequestParam(required = false) String timeRange, + @Parameter(description = "排序方式:latest/score/deadline/hot") @RequestParam(required = false) String sortBy, + @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page, + @Parameter(description = "每页数量") @RequestParam(defaultValue = "20") Integer pageSize) { + + log.info("查询任务大厅: taskTypeId={}, demandTypeId={}, brandId={}, categoryId={}, platformId={}, " + + "taskSubType={}, minScore={}, maxScore={}, keyword={}, publisherName={}, timeRange={}, " + + "sortBy={}, page={}, pageSize={}", + taskTypeId, demandTypeId, brandId, categoryId, platformId, taskSubType, + minScore, maxScore, keyword, publisherName, timeRange, sortBy, page, pageSize); + + IPage taskPage = taskService.getTaskHall(page, pageSize, taskTypeId, demandTypeId, + brandId, categoryId, platformId, taskSubType, minScore, maxScore, keyword, + publisherName, timeRange, sortBy); + return Result.success(taskPage); + } + + @Operation(summary = "我的任务", description = "获取当前用户的任务列表") + @GetMapping("/my") + public Result> getMyTasks( + @RequestParam(required = false) String role, // publisher 或 receiver + @RequestParam(required = false) String status, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestHeader("Authorization") String authHeader) { + + log.info("查询我的任务: role={}, status={}, page={}, pageSize={}", + role, status, page, pageSize); + + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + + IPage taskPage; + if ("publisher".equals(role)) { + taskPage = taskService.getMyPublishedTasks(page, pageSize, userId, status); + } else { + taskPage = taskService.getMyReceivedTasks(page, pageSize, userId, status); + } + + return Result.success(taskPage); + } + + @Operation(summary = "提交任务审核", description = "任务发起人提交任务给部门负责人审核") + @PostMapping("/{id}/submit-audit") + public Result submitTaskForAudit( + @PathVariable Long id, + @RequestHeader("Authorization") String authHeader) { + log.info("提交任务审核: taskId={}", id); + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + return Result.success(taskService.submitTaskForAudit(id, userId)); + } + + @Operation(summary = "审核任务", description = "部门负责人审核任务(通过或驳回)") + @PostMapping("/audit") + public Result auditTask( + @Valid @RequestBody TaskAuditRequest request, + @RequestHeader("Authorization") String authHeader) { + log.info("审核任务: request={}", request); + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long auditorId = jwtUtil.getUserIdFromToken(token); + return Result.success(taskService.auditTask(request, auditorId)); + } + + @Operation(summary = "强制指派任务", description = "部门负责人强制指派任务给部门成员") + @PostMapping("/assign") + public Result assignTask( + @Valid @RequestBody TaskAssignRequest request, + @RequestHeader("Authorization") String authHeader) { + log.info("强制指派任务: request={}", request); + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long assignerId = jwtUtil.getUserIdFromToken(token); + return Result.success(taskService.assignTask(request, assignerId)); + } + + @Operation(summary = "负责人任务列表", description = "需求部门负责人查看部门内所有任务") + @GetMapping("/leader/demand") + public Result> getLeaderTaskList( + @RequestParam(required = false) String auditStatus, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestHeader("Authorization") String authHeader) { + log.info("查询负责人任务列表: auditStatus={}, page={}, pageSize={}", + auditStatus, page, pageSize); + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + return Result.success(taskService.getLeaderTaskList(userId, page, pageSize, auditStatus)); + } + + @Operation(summary = "接单部门负责人视图", description = "接单部门负责人查看部门成员接单明细") + @GetMapping("/leader/receiving") + public Result> getLeaderReceivingTaskList( + @RequestParam(required = false) String status, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestHeader("Authorization") String authHeader) { + log.info("查询接单部门负责人视图: status={}, page={}, pageSize={}", + status, page, pageSize); + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + return Result.success(taskService.getLeaderReceivingTaskList(userId, page, pageSize, status)); + } + + @Operation(summary = "获取工作台统计数据") + @GetMapping("/dashboard/stats") + public Result getDashboardStats( + @Parameter(description = "月份,格式:YYYY-MM,不传则查询当月") @RequestParam(required = false) String month, + @RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + log.info("获取工作台统计数据: userId={}, month={}", userId, month); + return Result.success(taskService.getDashboardStats(userId, month)); + } + + @Operation(summary = "获取我的进行中任务") + @GetMapping("/my-in-progress") + public Result> getMyInProgressTasks(@RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + return Result.success(taskService.getMyInProgressTasks(userId)); + } + + @Operation(summary = "批量更新任务进度") + @PostMapping("/batch-update-progress") + public Result batchUpdateProgress( + @RequestBody java.util.Map payload, + @RequestHeader("Authorization") String authHeader) { + + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + + @SuppressWarnings("unchecked") + java.util.List> updates = + (java.util.List>) payload.get("updates"); + + taskService.batchUpdateProgress(userId, updates); + return Result.success("进度更新成功"); + } + + @Operation(summary = "更新任务进度", description = "接单人更新任务进度") + @PostMapping("/{id}/progress") + public Result updateTaskProgress( + @PathVariable Long id, + @RequestBody java.util.Map payload, + @RequestHeader("Authorization") String authHeader) { + + log.info("更新任务进度: taskId={}", id); + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + + Integer progress = (Integer) payload.get("progress"); + if (progress == null) { + return Result.error("进度值不能为空"); + } + + taskService.updateTaskProgress(id, userId, progress); + return Result.success("进度更新成功"); + } + + @Operation(summary = "完结任务", description = "接单人完结任务并提交交付路径") + @PostMapping("/{id}/complete") + public Result completeTask( + @PathVariable Long id, + @RequestBody java.util.Map payload, + @RequestHeader("Authorization") String authHeader) { + + log.info("完结任务: taskId={}", id); + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + + String deliveryPath = payload.get("deliveryPath"); + if (deliveryPath == null || deliveryPath.trim().isEmpty()) { + return Result.error("交付路径不能为空"); + } + + taskService.completeTask(id, userId, deliveryPath); + return Result.success("任务已提交审核"); + } + + @Operation(summary = "审核通过任务", description = "发起人或部门负责人审核通过任务") + @PostMapping("/{id}/approve") + public Result approveTask( + @PathVariable Long id, + @RequestHeader("Authorization") String authHeader) { + + log.info("审核通过任务: taskId={}", id); + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + + taskService.approveTask(id, userId); + return Result.success("审核通过"); + } + + @Operation(summary = "驳回任务", description = "发起人或部门负责人驳回任务") + @PostMapping("/{id}/reject") + public Result rejectTask( + @PathVariable Long id, + @RequestBody java.util.Map payload, + @RequestHeader("Authorization") String authHeader) { + + log.info("驳回任务: taskId={}", id); + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + + String reason = payload.get("reason"); + if (reason == null || reason.trim().isEmpty()) { + return Result.error("驳回原因不能为空"); + } + + taskService.rejectTask(id, userId, reason); + return Result.success("任务已驳回"); + } +} + diff --git a/backend/src/main/java/com/designhub/controller/TaskItemController.java b/backend/src/main/java/com/designhub/controller/TaskItemController.java new file mode 100644 index 0000000..9a28174 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/TaskItemController.java @@ -0,0 +1,174 @@ +package com.designhub.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.designhub.common.Result; +import com.designhub.entity.TaskItem; +import com.designhub.service.TaskItemService; +import com.designhub.util.JwtUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 任务子项控制器(流水线任务) + */ +@Tag(name = "流水线任务管理", description = "流水线任务子项相关接口") +@RestController +@RequestMapping("/api/tasks/items") +@RequiredArgsConstructor +public class TaskItemController { + + private final TaskItemService taskItemService; + private final JwtUtil jwtUtil; + + /** + * 获取可接单的子任务列表 + */ + @Operation(summary = "获取可接单的子任务列表") + @GetMapping("/available") + public Result> getAvailableItems( + @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page, + @Parameter(description = "每页数量") @RequestParam(defaultValue = "10") Integer pageSize, + @Parameter(description = "任务类型ID") @RequestParam(required = false) Long taskTypeId, + @Parameter(description = "搜索关键词") @RequestParam(required = false) String keyword) { + + IPage items = taskItemService.getAvailableItems(page, pageSize, taskTypeId, keyword); + return Result.success(items); + } + + /** + * 获取进行中的子任务列表 + */ + @Operation(summary = "获取进行中的子任务列表") + @GetMapping("/in-progress") + public Result> getInProgressItems( + @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page, + @Parameter(description = "每页数量") @RequestParam(defaultValue = "10") Integer pageSize, + @RequestHeader("Authorization") String token) { + + String actualToken = token.replace("Bearer ", ""); + Long userId = jwtUtil.getUserIdFromToken(actualToken); + IPage items = taskItemService.getInProgressItems(page, pageSize, userId); + return Result.success(items); + } + + /** + * 获取已完成的子任务列表 + */ + @Operation(summary = "获取已完成的子任务列表") + @GetMapping("/completed") + public Result> getCompletedItems( + @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page, + @Parameter(description = "每页数量") @RequestParam(defaultValue = "10") Integer pageSize, + @RequestHeader("Authorization") String token) { + + String actualToken = token.replace("Bearer ", ""); + Long userId = jwtUtil.getUserIdFromToken(actualToken); + IPage items = taskItemService.getCompletedItems(page, pageSize, userId); + return Result.success(items); + } + + /** + * 根据主任务ID获取所有子任务 + */ + @Operation(summary = "根据主任务ID获取所有子任务") + @GetMapping("/task/{taskId}") + public Result> getItemsByTaskId( + @Parameter(description = "主任务ID") @PathVariable Long taskId) { + + List items = taskItemService.getItemsByTaskId(taskId); + return Result.success(items); + } + + /** + * 获取子任务详情 + */ + @Operation(summary = "获取子任务详情") + @GetMapping("/{itemId}") + public Result getItemById( + @Parameter(description = "子任务ID") @PathVariable Long itemId) { + + TaskItem item = taskItemService.getItemById(itemId); + return Result.success(item); + } + + /** + * 抢单子任务 + */ + @Operation(summary = "抢单子任务") + @PostMapping("/{itemId}/grab") + public Result grabItem( + @Parameter(description = "子任务ID") @PathVariable Long itemId, + @RequestHeader("Authorization") String token) { + + String actualToken = token.replace("Bearer ", ""); + Long userId = jwtUtil.getUserIdFromToken(actualToken); + taskItemService.grabItem(itemId, userId); + return Result.success("抢单成功"); + } + + /** + * 更新子任务进度 + */ + @Operation(summary = "更新子任务进度") + @PostMapping("/{itemId}/progress") + public Result updateProgress( + @Parameter(description = "子任务ID") @PathVariable Long itemId, + @Parameter(description = "进度(0-100)") @RequestParam Integer progress, + @RequestHeader("Authorization") String token) { + + String actualToken = token.replace("Bearer ", ""); + Long userId = jwtUtil.getUserIdFromToken(actualToken); + taskItemService.updateProgress(itemId, userId, progress); + return Result.success("进度更新成功"); + } + + /** + * 完结子任务 + */ + @Operation(summary = "完结子任务") + @PostMapping("/{itemId}/complete") + public Result> completeItem( + @Parameter(description = "子任务ID") @PathVariable Long itemId, + @Parameter(description = "交付路径") @RequestBody Map request, + @RequestHeader("Authorization") String token) { + + String actualToken = token.replace("Bearer ", ""); + Long userId = jwtUtil.getUserIdFromToken(actualToken); + String deliveryPath = request.get("deliveryPath"); + + Map result = taskItemService.completeItem(itemId, userId, deliveryPath); + return Result.success("子任务完成成功", result); + } + + /** + * 驳回子任务 + */ + @Operation(summary = "驳回子任务") + @PostMapping("/{itemId}/reject") + public Result rejectItem( + @Parameter(description = "子任务ID") @PathVariable Long itemId, + @Parameter(description = "驳回原因") @RequestBody Map request) { + + String reason = request.get("reason"); + taskItemService.rejectItem(itemId, reason); + return Result.success("子任务已驳回"); + } + + /** + * 获取主任务进度统计 + */ + @Operation(summary = "获取主任务进度统计") + @GetMapping("/task/{taskId}/progress") + public Result> getTaskProgress( + @Parameter(description = "主任务ID") @PathVariable Long taskId) { + + Map progress = taskItemService.getTaskProgress(taskId); + return Result.success(progress); + } +} diff --git a/backend/src/main/java/com/designhub/controller/TaskSubTypeController.java b/backend/src/main/java/com/designhub/controller/TaskSubTypeController.java new file mode 100644 index 0000000..b03bef0 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/TaskSubTypeController.java @@ -0,0 +1,81 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.entity.TaskSubType; +import com.designhub.service.TaskSubTypeService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 任务子类型控制器 + */ +@Tag(name = "任务子类型管理") +@RestController +@RequestMapping("/api/task-sub-types") +@RequiredArgsConstructor +public class TaskSubTypeController { + + private final TaskSubTypeService taskSubTypeService; + + @Operation(summary = "获取所有子类型") + @GetMapping + public Result> getAllSubTypes() { + return Result.success(taskSubTypeService.getAllSubTypes()); + } + + @Operation(summary = "获取启用的子类型") + @GetMapping("/enabled") + public Result> getEnabledSubTypes() { + return Result.success(taskSubTypeService.getEnabledSubTypes()); + } + + @Operation(summary = "根据ID获取子类型") + @GetMapping("/{id}") + public Result getById(@PathVariable Long id) { + return Result.success(taskSubTypeService.getById(id)); + } + + @Operation(summary = "创建子类型") + @PostMapping + public Result createSubType(@RequestBody TaskSubType subType) { + return Result.success(taskSubTypeService.createSubType(subType)); + } + + @Operation(summary = "更新子类型") + @PutMapping("/{id}") + public Result updateSubType( + @PathVariable Long id, + @RequestBody TaskSubType subType + ) { + return Result.success(taskSubTypeService.updateSubType(id, subType)); + } + + @Operation(summary = "删除子类型") + @DeleteMapping("/{id}") + public Result deleteSubType(@PathVariable Long id) { + taskSubTypeService.deleteSubType(id); + return Result.success(); + } + + @Operation(summary = "切换启用状态") + @PostMapping("/{id}/toggle-enabled") + public Result toggleEnabled(@PathVariable Long id) { + taskSubTypeService.toggleEnabled(id); + return Result.success(); + } + + @Operation(summary = "更新排序") + @PostMapping("/{id}/sort") + public Result updateSortOrder( + @PathVariable Long id, + @Parameter(description = "排序值") @RequestParam Integer sortOrder + ) { + taskSubTypeService.updateSortOrder(id, sortOrder); + return Result.success(); + } +} diff --git a/backend/src/main/java/com/designhub/controller/TeamPKController.java b/backend/src/main/java/com/designhub/controller/TeamPKController.java new file mode 100644 index 0000000..0b8c061 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/TeamPKController.java @@ -0,0 +1,85 @@ +package com.designhub.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.designhub.common.Result; +import com.designhub.entity.TeamPK; +import com.designhub.service.TeamPKService; +import com.designhub.util.JwtUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +/** + * 团队PK Controller + */ +@Tag(name = "团队PK", description = "团队PK管理相关API") +@RestController +@RequestMapping("/api/team-pk") +@RequiredArgsConstructor +public class TeamPKController { + + private final TeamPKService teamPKService; + private final JwtUtil jwtUtil; + + @Operation(summary = "创建团队PK") + @PostMapping + public Result createTeamPK( + @RequestBody TeamPK teamPK, + @RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + teamPK.setCreatorId(userId); + return Result.success(teamPKService.createTeamPK(teamPK)); + } + + @Operation(summary = "发布团队PK") + @PostMapping("/{id}/publish") + public Result publishTeamPK(@PathVariable Long id) { + return Result.success(teamPKService.publishTeamPK(id)); + } + + @Operation(summary = "结束团队PK") + @PostMapping("/{id}/end") + public Result endTeamPK(@PathVariable Long id) { + teamPKService.endTeamPK(id); + return Result.success(); + } + + @Operation(summary = "取消团队PK") + @PostMapping("/{id}/cancel") + public Result cancelTeamPK(@PathVariable Long id) { + teamPKService.cancelTeamPK(id); + return Result.success(); + } + + @Operation(summary = "删除团队PK") + @DeleteMapping("/{id}") + public Result deleteTeamPK(@PathVariable Long id) { + teamPKService.deleteTeamPK(id); + return Result.success(); + } + + @Operation(summary = "获取团队PK列表") + @GetMapping + public Result> getPKList( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) String status) { + return Result.success(teamPKService.getPKList(page, pageSize, status)); + } + + @Operation(summary = "获取团队PK详情") + @GetMapping("/{id}") + public Result getPKById(@PathVariable Long id) { + return Result.success(teamPKService.getPKById(id)); + } + + @Operation(summary = "刷新PK数据") + @PostMapping("/{id}/refresh") + public Result refreshPKStats(@PathVariable Long id) { + TeamPK pk = teamPKService.getPKById(id); + teamPKService.refreshPKStats(pk); + return Result.success(teamPKService.getPKById(id)); + } +} diff --git a/backend/src/main/java/com/designhub/controller/ThirdPartyCallbackController.java b/backend/src/main/java/com/designhub/controller/ThirdPartyCallbackController.java new file mode 100644 index 0000000..821cab6 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/ThirdPartyCallbackController.java @@ -0,0 +1,72 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.service.ThirdPartyCallbackService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + * 第三方平台回调控制器 + */ +@Slf4j +@Tag(name = "第三方回调", description = "接收第三方平台的任务完成回调") +@RestController +@RequestMapping("/api/third-party") +@RequiredArgsConstructor +public class ThirdPartyCallbackController { + + private final ThirdPartyCallbackService callbackService; + + @Operation(summary = "任务完成回调", description = "第三方平台通知任务已完成") + @PostMapping("/task-complete") + public Result taskComplete(@RequestBody Map payload) { + log.info("收到第三方任务完成回调: {}", payload); + + try { + String thirdPartyTaskNo = (String) payload.get("thirdPartyTaskNo"); + if (thirdPartyTaskNo == null || thirdPartyTaskNo.isEmpty()) { + return Result.fail("缺少必需参数: thirdPartyTaskNo"); + } + + callbackService.handleTaskCompleteCallback(thirdPartyTaskNo, payload); + return Result.success("回调处理成功"); + } catch (Exception e) { + log.error("处理任务完成回调失败", e); + return Result.fail("处理失败: " + e.getMessage()); + } + } + + @Operation(summary = "任务状态更新回调", description = "第三方平台通知任务状态变更") + @PostMapping("/task-status-update") + public Result taskStatusUpdate(@RequestBody Map payload) { + log.info("收到第三方任务状态更新回调: {}", payload); + + try { + String thirdPartyTaskNo = (String) payload.get("thirdPartyTaskNo"); + String status = (String) payload.get("status"); + + if (thirdPartyTaskNo == null || thirdPartyTaskNo.isEmpty()) { + return Result.fail("缺少必需参数: thirdPartyTaskNo"); + } + + callbackService.handleTaskStatusUpdateCallback(thirdPartyTaskNo, status, payload); + return Result.success("回调处理成功"); + } catch (Exception e) { + log.error("处理任务状态更新回调失败", e); + return Result.fail("处理失败: " + e.getMessage()); + } + } + + @Operation(summary = "查询回调记录", description = "根据第三方任务编号查询回调记录") + @GetMapping("/callback-records/{thirdPartyTaskNo}") + public Result getCallbackRecords( + @Parameter(description = "第三方任务编号") @PathVariable String thirdPartyTaskNo) { + return Result.success(callbackService.getCallbackRecords(thirdPartyTaskNo)); + } +} diff --git a/backend/src/main/java/com/designhub/controller/UploadController.java b/backend/src/main/java/com/designhub/controller/UploadController.java new file mode 100644 index 0000000..172d7a8 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/UploadController.java @@ -0,0 +1,171 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.annotation.PostConstruct; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +/** + * 文件上传Controller + */ +@Slf4j +@RestController +@RequestMapping("/api/upload") +@Tag(name = "文件上传", description = "文件上传相关API") +public class UploadController { + + @Value("${file.upload.path:./uploads}") + private String uploadPath; + + @Value("${file.upload.max-size:52428800}") + private long maxSize; + + @Value("${file.upload.allowed-extensions:.xls,.xlsx,.doc,.docx,.pdf,.zip}") + private String allowedExtensions; + + private static final Set ALLOWED_EXTENSIONS_SET = new HashSet<>(); + + @PostConstruct + public void init() { + String[] extensions = allowedExtensions.split(","); + for (String ext : extensions) { + ALLOWED_EXTENSIONS_SET.add(ext.trim().toLowerCase()); + } + + // 确保上传目录存在 + try { + Path path = Paths.get(uploadPath); + if (!Files.exists(path)) { + Files.createDirectories(path); + log.info("创建上传目录: {}", uploadPath); + } + } catch (IOException e) { + log.error("创建上传目录失败: {}", e.getMessage()); + } + } + + /** + * 上传文件 + */ + @PostMapping("/file") + @Operation(summary = "上传文件", description = "支持Excel、Word、PDF、ZIP等格式") + public Result> uploadFile(@RequestParam("file") MultipartFile file) { + try { + // 验证文件 + if (file == null || file.isEmpty()) { + return Result.error(400, "文件不能为空"); + } + + // 验证文件大小 + if (file.getSize() > maxSize) { + return Result.error(400, String.format("文件大小不能超过 %d MB", maxSize / 1024 / 1024)); + } + + // 验证文件扩展名 + String originalFilename = file.getOriginalFilename(); + if (originalFilename == null || originalFilename.isEmpty()) { + return Result.error(400, "文件名不能为空"); + } + + String extension = getFileExtension(originalFilename); + if (!ALLOWED_EXTENSIONS_SET.contains(extension.toLowerCase())) { + return Result.error(400, String.format("不支持的文件格式,支持的格式:%s", allowedExtensions)); + } + + // 生成文件存储路径 + String dateDir = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); + String fileName = generateFileName(originalFilename); + String relativePath = dateDir + "/" + fileName; + String fullPath = uploadPath + "/" + relativePath; + + // 确保目录存在 + Path targetPath = Paths.get(fullPath); + Files.createDirectories(targetPath.getParent()); + + // 保存文件 + file.transferTo(targetPath.toFile()); + + log.info("文件上传成功: originalName={}, savedPath={}, size={}", + originalFilename, fullPath, file.getSize()); + + // 构建返回数据 + Map data = new HashMap<>(); + data.put("fileName", originalFilename); + data.put("filePath", relativePath); + data.put("fileUrl", "/api/upload/download?path=" + relativePath); + data.put("fileSize", file.getSize()); + data.put("fileType", extension); + + return Result.success(data); + } catch (IOException e) { + log.error("文件上传失败: {}", e.getMessage(), e); + return Result.error(500, "文件上传失败: " + e.getMessage()); + } catch (Exception e) { + log.error("文件上传异常: {}", e.getMessage(), e); + return Result.error(500, "文件上传异常: " + e.getMessage()); + } + } + + /** + * 下载文件 + */ + @GetMapping("/download") + @Operation(summary = "下载文件", description = "根据文件路径下载文件") + public org.springframework.http.ResponseEntity downloadFile( + @RequestParam("path") String filePath) { + try { + String fullPath = uploadPath + "/" + filePath; + Path path = Paths.get(fullPath); + + if (!Files.exists(path)) { + return org.springframework.http.ResponseEntity.notFound().build(); + } + + org.springframework.core.io.Resource resource = + new org.springframework.core.io.UrlResource(path.toUri()); + + return org.springframework.http.ResponseEntity.ok() + .header(org.springframework.http.HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + resource.getFilename() + "\"") + .body(resource); + } catch (Exception e) { + log.error("文件下载失败: {}", e.getMessage(), e); + return org.springframework.http.ResponseEntity.internalServerError().build(); + } + } + + /** + * 获取文件扩展名 + */ + private String getFileExtension(String filename) { + int lastDotIndex = filename.lastIndexOf('.'); + if (lastDotIndex > 0 && lastDotIndex < filename.length() - 1) { + return filename.substring(lastDotIndex); + } + return ""; + } + + /** + * 生成唯一文件名 + */ + private String generateFileName(String originalFilename) { + String extension = getFileExtension(originalFilename); + String nameWithoutExt = originalFilename.substring(0, originalFilename.lastIndexOf('.')); + String timestamp = String.valueOf(System.currentTimeMillis()); + String random = UUID.randomUUID().toString().substring(0, 8); + return nameWithoutExt + "_" + timestamp + "_" + random + extension; + } +} + diff --git a/backend/src/main/java/com/designhub/controller/UserController.java b/backend/src/main/java/com/designhub/controller/UserController.java new file mode 100644 index 0000000..1440f72 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/UserController.java @@ -0,0 +1,129 @@ +package com.designhub.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.designhub.common.Result; +import com.designhub.entity.User; +import com.designhub.service.UserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 用户管理Controller + */ +@Tag(name = "用户管理", description = "用户管理相关API") +@RestController +@RequestMapping("/api/users") +@RequiredArgsConstructor +@Slf4j +public class UserController { + + private final UserService userService; + + @Operation(summary = "分页查询用户列表") + @GetMapping + public Result> getUserList( + @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "10") int pageSize, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) Long deptId, + @RequestParam(required = false) Integer status + ) { + return Result.success(userService.getUserList(pageNum, pageSize, keyword, deptId, status)); + } + + @Operation(summary = "获取全部用户列表") + @GetMapping("/all") + public Result> getAllUsers() { + return Result.success(userService.getAllUsers()); + } + + @Operation(summary = "根据ID获取用户") + @GetMapping("/{id}") + public Result getUserById(@PathVariable Long id) { + return Result.success(userService.getUserById(id)); + } + + @Operation(summary = "创建用户") + @PostMapping + public Result createUser(@RequestBody User user) { + return Result.success(userService.createUser(user)); + } + + @Operation(summary = "更新用户") + @PutMapping("/{id}") + public Result updateUser(@PathVariable Long id, @RequestBody User user) { + user.setId(id); + return Result.success(userService.updateUser(user)); + } + + @Operation(summary = "删除用户") + @DeleteMapping("/{id}") + public Result deleteUser(@PathVariable Long id) { + return Result.success(userService.deleteUser(id)); + } + + @Operation(summary = "重置密码") + @PostMapping("/{id}/reset-password") + public Result resetPassword( + @PathVariable Long id, + @RequestBody Map params + ) { + String newPassword = params.getOrDefault("newPassword", "123456"); + return Result.success(userService.resetPassword(id, newPassword)); + } + + @Operation(summary = "修改密码") + @PostMapping("/{id}/change-password") + public Result changePassword( + @PathVariable Long id, + @RequestBody Map params + ) { + String oldPassword = params.get("oldPassword"); + String newPassword = params.get("newPassword"); + return Result.success(userService.changePassword(id, oldPassword, newPassword)); + } + + @Operation(summary = "为用户分配角色") + @PostMapping("/{id}/roles") + public Result assignRoles( + @PathVariable Long id, + @RequestBody List roleIds + ) { + return Result.success(userService.assignRoles(id, roleIds)); + } + + @Operation(summary = "获取用户的角色ID列表") + @GetMapping("/{id}/roles") + public Result> getUserRoleIds(@PathVariable Long id) { + return Result.success(userService.getUserRoleIds(id)); + } + + @Operation(summary = "启用/禁用用户") + @PutMapping("/{id}/status") + public Result toggleUserStatus( + @PathVariable Long id, + @RequestParam Integer status + ) { + return Result.success(userService.toggleUserStatus(id, status)); + } + + @Operation(summary = "调整用户所属部门") + @PutMapping("/{id}/department") + public Result updateUserDepartment( + @PathVariable Long id, + @RequestBody Map params + ) { + Long deptId = params.get("deptId"); + log.info("调整用户部门请求: userId={}, deptId={}, params={}", id, deptId, params); + boolean result = userService.updateUserDepartment(id, deptId); + log.info("调整用户部门结果: userId={}, deptId={}, result={}", id, deptId, result); + return Result.success(result); + } +} + diff --git a/backend/src/main/java/com/designhub/controller/UserGoalController.java b/backend/src/main/java/com/designhub/controller/UserGoalController.java new file mode 100644 index 0000000..fd7a4e0 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/UserGoalController.java @@ -0,0 +1,117 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.entity.UserGoal; +import com.designhub.service.UserGoalService; +import com.designhub.util.JwtUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 用户目标Controller + */ +@Tag(name = "目标管理", description = "用户目标管理相关API") +@RestController +@RequestMapping("/api/goals") +@RequiredArgsConstructor +public class UserGoalController { + + private final UserGoalService userGoalService; + private final JwtUtil jwtUtil; + + @Operation(summary = "创建目标") + @PostMapping + public Result createGoal( + @RequestBody UserGoal goal, + @RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + goal.setUserId(userId); + return Result.success(userGoalService.createGoal(goal)); + } + + @Operation(summary = "创建预设目标") + @PostMapping("/preset/{type}") + public Result createPresetGoal( + @PathVariable String type, + @RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + return Result.success(userGoalService.createPresetGoal(userId, type)); + } + + @Operation(summary = "更新目标") + @PutMapping("/{id}") + public Result updateGoal(@PathVariable Long id, @RequestBody UserGoal goal) { + return Result.success(userGoalService.updateGoal(id, goal)); + } + + @Operation(summary = "更新目标进度") + @PutMapping("/{id}/progress") + public Result updateProgress( + @PathVariable Long id, + @RequestParam Integer progressValue) { + return Result.success(userGoalService.updateProgress(id, progressValue)); + } + + @Operation(summary = "获取我的目标列表") + @GetMapping("/my") + public Result> getMyGoals( + @RequestParam(required = false) String status, + @RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + return Result.success(userGoalService.getUserGoals(userId, status)); + } + + @Operation(summary = "获取我的活跃目标") + @GetMapping("/my/active") + public Result> getMyActiveGoals( + @RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + return Result.success(userGoalService.getActiveGoals(userId)); + } + + @Operation(summary = "获取目标详情") + @GetMapping("/{id}") + public Result getGoalById(@PathVariable Long id) { + return Result.success(userGoalService.getGoalById(id)); + } + + @Operation(summary = "领取目标奖励") + @PostMapping("/{id}/claim-reward") + public Result claimReward( + @PathVariable Long id, + @RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + return Result.success(userGoalService.claimReward(id, userId)); + } + + @Operation(summary = "取消目标") + @PostMapping("/{id}/cancel") + public Result cancelGoal( + @PathVariable Long id, + @RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + userGoalService.cancelGoal(id, userId); + return Result.success(); + } + + @Operation(summary = "删除目标") + @DeleteMapping("/{id}") + public Result deleteGoal( + @PathVariable Long id, + @RequestHeader("Authorization") String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long userId = jwtUtil.getUserIdFromToken(token); + userGoalService.deleteGoal(id, userId); + return Result.success(); + } +} diff --git a/backend/src/main/java/com/designhub/dto/DepartmentCreateRequest.java b/backend/src/main/java/com/designhub/dto/DepartmentCreateRequest.java new file mode 100644 index 0000000..3227e93 --- /dev/null +++ b/backend/src/main/java/com/designhub/dto/DepartmentCreateRequest.java @@ -0,0 +1,86 @@ +package com.designhub.dto; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 部门创建请求 + */ +@Data +public class DepartmentCreateRequest { + + @NotBlank(message = "部门名称不能为空") + private String deptName; + + /** + * 部门编码,可选 + */ + private String deptCode; + + @NotBlank(message = "部门类型不能为空") + private String deptType; // demand / receiving + + private Long parentId; + + /** + * 部门负责人 + */ + private Long leaderId; + + /** + * 部门描述 + */ + private String description; + + /** + * 排序 + */ + @Min(value = 0, message = "排序必须大于等于0") + private Integer sortOrder = 0; + + /** + * 状态:0-禁用 1-启用 + */ + private Integer status = 1; + + /** + * 接单部门允许的需求类型 + */ + private List allowedDemandTypes; + + /** + * 是否启用积分限制(仅需求部门) + */ + private Integer limitEnabled; + + /** + * 限制周期:DAILY/WEEKLY/MONTHLY + */ + private String limitPeriod; + + /** + * 周期限额 + */ + private Integer periodLimit; + + /** + * 预警阈值(百分比) + */ + private Integer alertThreshold; + + /** + * 超限动作:alert/block + */ + private String overLimitAction; + + /** + * 初始积分余额(仅需求部门) + */ + @Min(value = 0, message = "初始积分不能为负数") + private Integer initialScoreBalance; +} + diff --git a/backend/src/main/java/com/designhub/dto/DepartmentLimitSettingsRequest.java b/backend/src/main/java/com/designhub/dto/DepartmentLimitSettingsRequest.java new file mode 100644 index 0000000..5962017 --- /dev/null +++ b/backend/src/main/java/com/designhub/dto/DepartmentLimitSettingsRequest.java @@ -0,0 +1,37 @@ +package com.designhub.dto; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 部门积分限制配置请求 + */ +@Data +public class DepartmentLimitSettingsRequest { + + @NotNull(message = "是否启用限制不能为空") + private Integer limitEnabled; + + /** + * 限制周期:DAILY/WEEKLY/MONTHLY + */ + private String limitPeriod; + + /** + * 周期限额 + */ + @Min(value = 0, message = "限制额度不能为负数") + private Integer periodLimit; + + /** + * 预警阈值 + */ + private Integer alertThreshold; + + /** + * 超限动作 + */ + private String overLimitAction; +} + diff --git a/backend/src/main/java/com/designhub/dto/DepartmentScoreAdjustRequest.java b/backend/src/main/java/com/designhub/dto/DepartmentScoreAdjustRequest.java new file mode 100644 index 0000000..fee6cb1 --- /dev/null +++ b/backend/src/main/java/com/designhub/dto/DepartmentScoreAdjustRequest.java @@ -0,0 +1,21 @@ +package com.designhub.dto; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; + +public class DepartmentScoreAdjustRequest { + + @NotBlank(message = "操作类型不能为空") + private String action; + @Min(value = 1, message = "调整积分必须大于0") + private Integer amount; + private String remark; + + public String getAction() { return action; } + public void setAction(String action) { this.action = action; } + public Integer getAmount() { return amount; } + public void setAmount(Integer amount) { this.amount = amount; } + public String getRemark() { return remark; } + public void setRemark(String remark) { this.remark = remark; } +} + diff --git a/backend/src/main/java/com/designhub/dto/DepartmentUpdateRequest.java b/backend/src/main/java/com/designhub/dto/DepartmentUpdateRequest.java new file mode 100644 index 0000000..2d413dd --- /dev/null +++ b/backend/src/main/java/com/designhub/dto/DepartmentUpdateRequest.java @@ -0,0 +1,53 @@ +package com.designhub.dto; + +import jakarta.validation.constraints.Min; +import java.util.List; + +public class DepartmentUpdateRequest { + + private String deptName; + private String deptCode; + private String deptType; + private Long parentId; + private Long leaderId; + private String description; + @Min(value = 0, message = "排序必须大于等于0") + private Integer sortOrder; + private Integer status; + private List allowedDemandTypes; + private Integer limitEnabled; + private String limitPeriod; + private Integer periodLimit; + private Integer alertThreshold; + private String overLimitAction; + + public String getDeptName() { return deptName; } + public void setDeptName(String deptName) { this.deptName = deptName; } + public String getDeptCode() { return deptCode; } + public void setDeptCode(String deptCode) { this.deptCode = deptCode; } + public String getDeptType() { return deptType; } + public void setDeptType(String deptType) { this.deptType = deptType; } + public Long getParentId() { return parentId; } + public void setParentId(Long parentId) { this.parentId = parentId; } + public Long getLeaderId() { return leaderId; } + public void setLeaderId(Long leaderId) { this.leaderId = leaderId; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public Integer getSortOrder() { return sortOrder; } + public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; } + public Integer getStatus() { return status; } + public void setStatus(Integer status) { this.status = status; } + public List getAllowedDemandTypes() { return allowedDemandTypes; } + public void setAllowedDemandTypes(List allowedDemandTypes) { this.allowedDemandTypes = allowedDemandTypes; } + public Integer getLimitEnabled() { return limitEnabled; } + public void setLimitEnabled(Integer limitEnabled) { this.limitEnabled = limitEnabled; } + public String getLimitPeriod() { return limitPeriod; } + public void setLimitPeriod(String limitPeriod) { this.limitPeriod = limitPeriod; } + public Integer getPeriodLimit() { return periodLimit; } + public void setPeriodLimit(Integer periodLimit) { this.periodLimit = periodLimit; } + public Integer getAlertThreshold() { return alertThreshold; } + public void setAlertThreshold(Integer alertThreshold) { this.alertThreshold = alertThreshold; } + public String getOverLimitAction() { return overLimitAction; } + public void setOverLimitAction(String overLimitAction) { this.overLimitAction = overLimitAction; } +} + diff --git a/backend/src/main/java/com/designhub/dto/LoginDTO.java b/backend/src/main/java/com/designhub/dto/LoginDTO.java new file mode 100644 index 0000000..987af2f --- /dev/null +++ b/backend/src/main/java/com/designhub/dto/LoginDTO.java @@ -0,0 +1,34 @@ +package com.designhub.dto; + +import lombok.Data; +import jakarta.validation.constraints.NotBlank; + +/** + * 登录请求DTO + */ +@Data +public class LoginDTO { + + /** + * 用户名 + */ + @NotBlank(message = "用户名不能为空") + private String username; + + /** + * 密码 + */ + @NotBlank(message = "密码不能为空") + private String password; + + /** + * 登录类型:password-密码登录 dingtalk-钉钉登录 casdoor-Casdoor登录 + */ + private String loginType = "password"; + + /** + * 钉钉授权码(钉钉登录时使用) + */ + private String authCode; +} + diff --git a/backend/src/main/java/com/designhub/dto/PipelineTaskDTO.java b/backend/src/main/java/com/designhub/dto/PipelineTaskDTO.java new file mode 100644 index 0000000..b7ecb71 --- /dev/null +++ b/backend/src/main/java/com/designhub/dto/PipelineTaskDTO.java @@ -0,0 +1,78 @@ +package com.designhub.dto; + +import lombok.Data; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 流水线任务创建请求DTO + */ +@Data +public class PipelineTaskDTO { + + /** + * 任务标题 + */ + private String title; + + /** + * 是否流水线任务 + */ + private Boolean isPipeline; + + /** + * 流水线模式:sequential(顺序)/parallel(并行) + */ + private String pipelineMode; + + /** + * 子任务列表 + */ + private List items; + + /** + * 任务其他字段(与普通任务相同) + */ + private Long brandId; + private Long categoryId; + private Long platformId; + private String spuInfo; + private String taskSubType; + private String attachmentUrl; + private String remark; + private Integer priority; + private LocalDateTime deadline; // 截止时间 + private String releaseType; // 发布类型:default/recruitment/assigned + private Long receiverId; // 指定接收人ID(assigned模式使用) + + /** + * 子任务项DTO + */ + @Data + public static class TaskItemDTO { + /** + * 任务类型ID + */ + private Long taskTypeId; + + /** + * 数量 + */ + private Integer quantity; + + /** + * 单价积分 + */ + private Integer unitScore; + + /** + * 预估工时 + */ + private Double estimatedHours; + + /** + * 排序 + */ + private Integer sortOrder; + } +} diff --git a/backend/src/main/java/com/designhub/dto/RegisterDTO.java b/backend/src/main/java/com/designhub/dto/RegisterDTO.java new file mode 100644 index 0000000..7eb9544 --- /dev/null +++ b/backend/src/main/java/com/designhub/dto/RegisterDTO.java @@ -0,0 +1,52 @@ +package com.designhub.dto; + +import lombok.Data; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +/** + * 注册请求DTO + */ +@Data +public class RegisterDTO { + + /** + * 用户名 + */ + @NotBlank(message = "用户名不能为空") + private String username; + + /** + * 密码 + */ + @NotBlank(message = "密码不能为空") + private String password; + + /** + * 真实姓名 + */ + @NotBlank(message = "真实姓名不能为空") + private String realName; + + /** + * 角色ID(5-设计师DESIGNER, 6-需求方REQUESTER) + */ + @NotNull(message = "请选择角色") + private Long roleId; + + /** + * 部门ID + */ + @NotNull(message = "请选择部门") + private Long deptId; + + /** + * 邮箱(可选) + */ + private String email; + + /** + * 手机号(可选) + */ + private String phone; +} diff --git a/backend/src/main/java/com/designhub/dto/TaskApprovalDTO.java b/backend/src/main/java/com/designhub/dto/TaskApprovalDTO.java new file mode 100644 index 0000000..74943c3 --- /dev/null +++ b/backend/src/main/java/com/designhub/dto/TaskApprovalDTO.java @@ -0,0 +1,36 @@ +package com.designhub.dto; + +import lombok.Data; + +/** + * 任务审核DTO + */ +@Data +public class TaskApprovalDTO { + /** + * 审核意见 + */ + private String comment; + + /** + * 任务更新数据(审核时可修改任务) + */ + private TaskUpdateDTO taskUpdates; + + // Getters and Setters + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public TaskUpdateDTO getTaskUpdates() { + return taskUpdates; + } + + public void setTaskUpdates(TaskUpdateDTO taskUpdates) { + this.taskUpdates = taskUpdates; + } +} diff --git a/backend/src/main/java/com/designhub/dto/TaskAssignRequest.java b/backend/src/main/java/com/designhub/dto/TaskAssignRequest.java new file mode 100644 index 0000000..68ae11c --- /dev/null +++ b/backend/src/main/java/com/designhub/dto/TaskAssignRequest.java @@ -0,0 +1,33 @@ +package com.designhub.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 任务指派请求DTO + */ +@Data +public class TaskAssignRequest { + + /** + * 任务ID + */ + @NotNull(message = "任务ID不能为空") + private Long taskId; + + /** + * 被指派人ID + */ + @NotNull(message = "被指派人ID不能为空") + private Long assigneeId; + + /** + * 指派类型:force-强制指派 recommend-推荐 + */ + private String assignType = "force"; + + /** + * 指派说明 + */ + private String comment; +} diff --git a/backend/src/main/java/com/designhub/dto/TaskAuditRequest.java b/backend/src/main/java/com/designhub/dto/TaskAuditRequest.java new file mode 100644 index 0000000..a94af23 --- /dev/null +++ b/backend/src/main/java/com/designhub/dto/TaskAuditRequest.java @@ -0,0 +1,19 @@ +package com.designhub.dto; + +import jakarta.validation.constraints.NotNull; + +public class TaskAuditRequest { + + @NotNull(message = "任务ID不能为空") + private Long taskId; + @NotNull(message = "审核结果不能为空") + private String result; + private String comment; + + public Long getTaskId() { return taskId; } + public void setTaskId(Long taskId) { this.taskId = taskId; } + public String getResult() { return result; } + public void setResult(String result) { this.result = result; } + public String getComment() { return comment; } + public void setComment(String comment) { this.comment = comment; } +} diff --git a/backend/src/main/java/com/designhub/dto/TaskUpdateDTO.java b/backend/src/main/java/com/designhub/dto/TaskUpdateDTO.java new file mode 100644 index 0000000..a72ffa9 --- /dev/null +++ b/backend/src/main/java/com/designhub/dto/TaskUpdateDTO.java @@ -0,0 +1,116 @@ +package com.designhub.dto; + +import lombok.Data; + +import java.util.List; + +/** + * 任务更新DTO(用于审核时修改任务) + */ +@Data +public class TaskUpdateDTO { + private String title; + private String taskSubType; + private Long brandId; + private Long platformId; + private Long categoryId; + private String spuInfo; + private String remark; + private String attachmentUrl; + private List taskTypes; + + @Data + public static class TaskTypeUpdateDTO { + private Long taskTypeId; + private Integer quantity; + + public Long getTaskTypeId() { + return taskTypeId; + } + + public void setTaskTypeId(Long taskTypeId) { + this.taskTypeId = taskTypeId; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + } + + // Getters and Setters + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getTaskSubType() { + return taskSubType; + } + + public void setTaskSubType(String taskSubType) { + this.taskSubType = taskSubType; + } + + public Long getBrandId() { + return brandId; + } + + public void setBrandId(Long brandId) { + this.brandId = brandId; + } + + public Long getPlatformId() { + return platformId; + } + + public void setPlatformId(Long platformId) { + this.platformId = platformId; + } + + public Long getCategoryId() { + return categoryId; + } + + public void setCategoryId(Long categoryId) { + this.categoryId = categoryId; + } + + public String getSpuInfo() { + return spuInfo; + } + + public void setSpuInfo(String spuInfo) { + this.spuInfo = spuInfo; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + public String getAttachmentUrl() { + return attachmentUrl; + } + + public void setAttachmentUrl(String attachmentUrl) { + this.attachmentUrl = attachmentUrl; + } + + public List getTaskTypes() { + return taskTypes; + } + + public void setTaskTypes(List taskTypes) { + this.taskTypes = taskTypes; + } +} diff --git a/backend/src/main/java/com/designhub/entity/Achievement.java b/backend/src/main/java/com/designhub/entity/Achievement.java new file mode 100644 index 0000000..2fcfe42 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/Achievement.java @@ -0,0 +1,95 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 成就徽章实体类 + */ +@Data +@TableName("achievement") +public class Achievement implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 成就代码(唯一标识) + */ + private String achievementCode; + + /** + * 成就名称 + */ + private String achievementName; + + /** + * 成就描述 + */ + private String description; + + /** + * 成就图标URL + */ + private String iconUrl; + + /** + * 成就类型:task-任务类, score-积分类, quality-质量类, speed-速度类 + */ + private String achievementType; + + /** + * 达成条件(JSON格式) + * 例如:{"type":"task_count","value":100} + */ + @TableField("`condition`") + private String condition; + + /** + * 奖励积分 + */ + private Integer rewardScore; + + /** + * 成就等级:bronze-铜牌, silver-银牌, gold-金牌, diamond-钻石 + */ + private String level; + + /** + * 排序号 + */ + private Integer sortOrder; + + /** + * 状态:0-禁用,1-启用 + */ + @TableField("enabled") + private Integer status; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + /** + * 删除标志:0-未删除,1-已删除 + */ + @TableLogic + private Integer deleted; +} + diff --git a/backend/src/main/java/com/designhub/entity/Brand.java b/backend/src/main/java/com/designhub/entity/Brand.java new file mode 100644 index 0000000..2864d96 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/Brand.java @@ -0,0 +1,69 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 品牌实体类 + */ +@Data +@TableName("brand") +public class Brand implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 品牌名称 + */ + @TableField("brand_name") + private String name; + + /** + * 品牌编码 + */ + @TableField("brand_code") + private String brandCode; + + /** + * 品牌Logo URL + */ + @TableField("logo") + private String logoUrl; + + /** + * 品牌描述 + */ + private String description; + + /** + * 排序号 + */ + private Integer sortOrder; + + /** + * 是否启用:0-禁用,1-启用 + */ + @TableField("status") + private Integer isEnabled; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} + diff --git a/backend/src/main/java/com/designhub/entity/Category.java b/backend/src/main/java/com/designhub/entity/Category.java new file mode 100644 index 0000000..06fda36 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/Category.java @@ -0,0 +1,84 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 类目实体类 + */ +@Data +@TableName("category") +public class Category implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 父类目ID,0表示一级类目 + */ + private Long parentId; + + /** + * 类目名称 + */ + @TableField("category_name") + private String name; + + /** + * 类目编码 + */ + @TableField("category_code") + private String categoryCode; + + /** + * 类目层级:1-一级,2-二级,3-三级 + */ + private Integer level; + + /** + * 是否叶子类目:0-否,1-是 + */ + private Integer isLeaf; + + /** + * 类目路径,用/分隔,如:/1/12/123(数据库不存在此字段) + */ + @TableField(exist = false) + private String path; + + /** + * 类目描述 + */ + private String description; + + /** + * 排序号 + */ + private Integer sortOrder; + + /** + * 是否启用:0-禁用,1-启用 + */ + @TableField("status") + private Integer isEnabled; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} + diff --git a/backend/src/main/java/com/designhub/entity/CheckIn.java b/backend/src/main/java/com/designhub/entity/CheckIn.java new file mode 100644 index 0000000..a1e50e3 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/CheckIn.java @@ -0,0 +1,68 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * 签到记录实体类 + */ +@Data +@TableName("user_check_in") +public class CheckIn implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 用户ID + */ + private Long userId; + + /** + * 签到日期 + */ + private LocalDate checkInDate; + + /** + * 连续签到天数 + */ + private Integer continuousDays; + + /** + * 奖励积分 + */ + private Integer rewardScore; + + /** + * 签到备注 + */ + private String remark; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + /** + * 删除标志:0-未删除,1-已删除 + */ + @TableLogic + private Integer deleted; +} + diff --git a/backend/src/main/java/com/designhub/entity/Contest.java b/backend/src/main/java/com/designhub/entity/Contest.java new file mode 100644 index 0000000..6929de5 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/Contest.java @@ -0,0 +1,110 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 竞赛活动实体 + */ +@Data +@TableName("contest") +public class Contest implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 竞赛名称 + */ + private String name; + + /** + * 竞赛描述 + */ + private String description; + + /** + * 竞赛类型:monthly-月度竞赛, weekly-周度竞赛, special-专项竞赛 + */ + private String contestType; + + /** + * 开始时间 + */ + private LocalDateTime startTime; + + /** + * 结束时间 + */ + private LocalDateTime endTime; + + /** + * 参与范围:all-全体设计师, department-指定部门, user-指定人员 + */ + private String participantScope; + + /** + * 指定部门ID列表(JSON数组) + */ + private String departmentIds; + + /** + * 指定用户ID列表(JSON数组) + */ + private String userIds; + + /** + * 竞赛规则配置(JSON对象) + * 例如:{"taskCompleted":{"score":2,"weight":30},"fiveStarReview":{"score":10,"weight":25}} + */ + private String rules; + + /** + * 奖励配置(JSON数组) + * 例如:[{"rank":1,"score":1500,"badge":"月度冠军"},{"rank":2,"score":1000}] + */ + private String rewards; + + /** + * 参与奖配置(JSON对象) + */ + private String participationReward; + + /** + * 竞赛状态:draft-草稿, scheduled-待开始, active-进行中, ended-已结束, cancelled-已取消 + */ + private String status; + + /** + * 参与人数 + */ + private Integer participantCount; + + /** + * 创建人ID + */ + private Long creatorId; + + /** + * 是否删除:0-否,1-是 + */ + @TableLogic + private Integer deleted; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} diff --git a/backend/src/main/java/com/designhub/entity/ContestParticipant.java b/backend/src/main/java/com/designhub/entity/ContestParticipant.java new file mode 100644 index 0000000..5ba3091 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/ContestParticipant.java @@ -0,0 +1,107 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 竞赛参与者实体 + */ +@Data +@TableName("contest_participant") +public class ContestParticipant implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 竞赛ID + */ + private Long contestId; + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户姓名(冗余) + */ + private String userName; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 部门名称(冗余) + */ + private String deptName; + + /** + * 完成任务数 + */ + private Integer taskCompleted; + + /** + * 五星好评数 + */ + private Integer fiveStarCount; + + /** + * 提前完成次数 + */ + private Integer earlyCompletionCount; + + /** + * 被表扬次数 + */ + private Integer praiseCount; + + /** + * 无延误天数 + */ + private Integer noDelayDays; + + /** + * 总得分 + */ + private Integer totalScore; + + /** + * 当前排名 + */ + private Integer currentRank; + + /** + * 是否已领取奖励:0-否,1-是 + */ + private Integer rewardClaimed; + + /** + * 奖励积分 + */ + private Integer rewardScore; + + /** + * 奖励领取时间 + */ + private LocalDateTime rewardClaimTime; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} diff --git a/backend/src/main/java/com/designhub/entity/DelayPenaltyRecord.java b/backend/src/main/java/com/designhub/entity/DelayPenaltyRecord.java new file mode 100644 index 0000000..8e12bf5 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/DelayPenaltyRecord.java @@ -0,0 +1,106 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 延误扣分记录实体类 + */ +@Data +@TableName("delay_penalty_record") +public class DelayPenaltyRecord implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 任务ID + */ + private Long taskId; + + /** + * 设计师ID + */ + private Long designerId; + + /** + * 延误天数 + */ + private Integer delayDays; + + /** + * 延误小时数 + */ + private Long delayHours; + + /** + * 延误等级:light-轻微 general-一般 severe-严重 major-重大 + */ + private String delayLevel; + + /** + * 扣除积分 + */ + private Integer penaltyScore; + + /** + * 原截止时间 + */ + private LocalDateTime originalDeadline; + + /** + * 实际完成/转移时间 + */ + private LocalDateTime actualTime; + + /** + * 状态:confirmed-已确认 appealed-已申诉 approved-申诉通过 rejected-申诉驳回 + */ + private String status; + + /** + * 申诉原因 + */ + private String appealReason; + + /** + * 申诉时间 + */ + private LocalDateTime appealTime; + + /** + * 申诉结果 + */ + private String appealResult; + + /** + * 审批人ID + */ + private Long approverId; + + /** + * 审批时间 + */ + private LocalDateTime approveTime; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} + diff --git a/backend/src/main/java/com/designhub/entity/DelayPenaltyRule.java b/backend/src/main/java/com/designhub/entity/DelayPenaltyRule.java new file mode 100644 index 0000000..524b023 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/DelayPenaltyRule.java @@ -0,0 +1,86 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 延误扣分规则实体类 + */ +@Data +@TableName("delay_penalty_rule") +public class DelayPenaltyRule implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 等级名称:light-轻微 general-一般 severe-严重 major-重大 + */ + private String levelName; + + /** + * 最小延误天数(实际使用小时数) + */ + private Integer minDays; + + /** + * 最小延误小时数 + */ + private Integer minHours; + + /** + * 最大延误天数(实际使用小时数,NULL表示无上限) + */ + private Integer maxDays; + + /** + * 最大延误小时数(NULL表示无上限) + */ + private Integer maxHours; + + /** + * 基础扣分 + */ + private Integer baseScore; + + /** + * 每天递增扣分 + */ + private Integer perDayScore; + + /** + * 每小时扣分(用于按小时计算的等级) + */ + private Integer perHourScore; + + /** + * 规则描述 + */ + private String description; + + /** + * 是否启用:0-否 1-是 + */ + private Integer enabled; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} + diff --git a/backend/src/main/java/com/designhub/entity/DemandType.java b/backend/src/main/java/com/designhub/entity/DemandType.java new file mode 100644 index 0000000..3fb72ff --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/DemandType.java @@ -0,0 +1,75 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 需求类型实体类 + */ +@Data +@TableName("task_type") +public class DemandType implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 类型名称 + */ + @TableField("type_name") + private String name; + + /** + * 单价(积分) + */ + @TableField("unit_score") + private Integer unitScore; + + /** + * 预估工时(小时) + */ + @TableField("estimated_hours") + private Integer estimatedHours; + + /** + * 类型描述 + */ + private String description; + + /** + * 排序号 + */ + private Integer sortOrder; + + /** + * 是否启用:0-禁用,1-启用 + */ + @TableField("status") + private Integer isEnabled; + + /** + * 可接单部门ID列表(逗号分隔),为空表示所有部门都可接单 + */ + @TableField("allowed_dept_ids") + private String allowedDeptIds; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} + diff --git a/backend/src/main/java/com/designhub/entity/DemandTypeDepartment.java b/backend/src/main/java/com/designhub/entity/DemandTypeDepartment.java new file mode 100644 index 0000000..867b9aa --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/DemandTypeDepartment.java @@ -0,0 +1,38 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 需求类型-接单部门关联表实体类 + */ +@Data +@TableName("demand_type_department") +public class DemandTypeDepartment implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 关联ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 需求类型ID + */ + private Long demandTypeId; + + /** + * 接单部门ID + */ + private Long departmentId; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; +} diff --git a/backend/src/main/java/com/designhub/entity/Department.java b/backend/src/main/java/com/designhub/entity/Department.java new file mode 100644 index 0000000..301949f --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/Department.java @@ -0,0 +1,235 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 部门实体类 + * 注意:已移除@Data注解,改用手动getter/setter以解决Lombok编译问题 + */ +@TableName("sys_dept") +public class Department implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 父部门ID,0表示顶级部门 + */ + private Long parentId; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 部门编码 + */ + private String deptCode; + + /** + * 钉钉部门ID + */ + private String dingDeptId; + + /** + * 部门类型:demand-需求部门,receiving-接单部门 + */ + private String deptType; + + /** + * 部门负责人ID + */ + @TableField("leader_id") + private Long leaderId; + + /** + * 部门积分余额(冗余字段,用于快速展示) + */ + @TableField("score_balance") + private Integer scoreBalance; + + /** + * 是否启用积分消耗限制:0-否,1-是 + */ + @TableField("limit_enabled") + private Integer limitEnabled; + + /** + * 限制周期:DAILY/WEEKLY/MONTHLY + */ + @TableField("limit_period") + private String limitPeriod; + + /** + * 周期限额 + */ + @TableField("period_limit") + private Integer periodLimit; + + /** + * 预警阈值(百分比) + */ + @TableField("alert_threshold") + private Integer alertThreshold; + + /** + * 超限后的动作:alert-仅提醒,block-禁止发起 + */ + @TableField("over_limit_action") + private String overLimitAction; + + /** + * 允许的需求类型(接单部门使用,逗号分隔) + */ + @TableField("allowed_demand_types") + private String allowedDemandTypes; + + /** + * 部门主管ID(接单部门)- 数据库不存在此字段 + */ + @TableField(exist = false) + private Long supervisorId; + + /** + * 部门描述 - 数据库不存在此字段 + */ + @TableField(exist = false) + private String description; + + /** + * 部门层级 - 数据库不存在此字段 + */ + @TableField(exist = false) + private Integer level; + + /** + * 部门路径 - 数据库不存在此字段 + */ + @TableField(exist = false) + private String path; + + /** + * 排序号 + */ + private Integer sortOrder; + + /** + * 状态:0-禁用,1-启用 + */ + private Integer status; + + /** + * 创建人ID - 数据库不存在此字段 + */ + @TableField(exist = false) + private Long createBy; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新人ID - 数据库不存在此字段 + */ + @TableField(exist = false) + private Long updateBy; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + /** + * 删除标志:0-未删除,1-已删除 + */ + @TableLogic + private Integer deleted; + + // Getters and Setters (手动添加以解决Lombok编译问题) + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public Long getParentId() { return parentId; } + public void setParentId(Long parentId) { this.parentId = parentId; } + + public String getDeptName() { return deptName; } + public void setDeptName(String deptName) { this.deptName = deptName; } + + public String getDeptCode() { return deptCode; } + public void setDeptCode(String deptCode) { this.deptCode = deptCode; } + + public String getDingDeptId() { return dingDeptId; } + public void setDingDeptId(String dingDeptId) { this.dingDeptId = dingDeptId; } + + public String getDeptType() { return deptType; } + public void setDeptType(String deptType) { this.deptType = deptType; } + + public Long getLeaderId() { return leaderId; } + public void setLeaderId(Long leaderId) { this.leaderId = leaderId; } + + public Integer getScoreBalance() { return scoreBalance; } + public void setScoreBalance(Integer scoreBalance) { this.scoreBalance = scoreBalance; } + + public Integer getLimitEnabled() { return limitEnabled; } + public void setLimitEnabled(Integer limitEnabled) { this.limitEnabled = limitEnabled; } + + public String getLimitPeriod() { return limitPeriod; } + public void setLimitPeriod(String limitPeriod) { this.limitPeriod = limitPeriod; } + + public Integer getPeriodLimit() { return periodLimit; } + public void setPeriodLimit(Integer periodLimit) { this.periodLimit = periodLimit; } + + public Integer getAlertThreshold() { return alertThreshold; } + public void setAlertThreshold(Integer alertThreshold) { this.alertThreshold = alertThreshold; } + + public String getOverLimitAction() { return overLimitAction; } + public void setOverLimitAction(String overLimitAction) { this.overLimitAction = overLimitAction; } + + public String getAllowedDemandTypes() { return allowedDemandTypes; } + public void setAllowedDemandTypes(String allowedDemandTypes) { this.allowedDemandTypes = allowedDemandTypes; } + + public Long getSupervisorId() { return supervisorId; } + public void setSupervisorId(Long supervisorId) { this.supervisorId = supervisorId; } + + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + + public Integer getLevel() { return level; } + public void setLevel(Integer level) { this.level = level; } + + public String getPath() { return path; } + public void setPath(String path) { this.path = path; } + + public Integer getSortOrder() { return sortOrder; } + public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; } + + public Integer getStatus() { return status; } + public void setStatus(Integer status) { this.status = status; } + + public Long getCreateBy() { return createBy; } + public void setCreateBy(Long createBy) { this.createBy = createBy; } + + public LocalDateTime getCreateTime() { return createTime; } + public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; } + + public Long getUpdateBy() { return updateBy; } + public void setUpdateBy(Long updateBy) { this.updateBy = updateBy; } + + public LocalDateTime getUpdateTime() { return updateTime; } + public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; } + + public Integer getDeleted() { return deleted; } + public void setDeleted(Integer deleted) { this.deleted = deleted; } +} + diff --git a/backend/src/main/java/com/designhub/entity/DingTalkConfig.java b/backend/src/main/java/com/designhub/entity/DingTalkConfig.java new file mode 100644 index 0000000..a65f497 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/DingTalkConfig.java @@ -0,0 +1,88 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 钉钉配置实体 + */ +@Data +@TableName("dingtalk_config") +public class DingTalkConfig implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 配置名称 + */ + private String name; + + /** + * 应用AgentId + */ + private String agentId; + + /** + * 应用AppKey + */ + private String appKey; + + /** + * 应用AppSecret (加密存储) + */ + private String appSecret; + + /** + * 企业ID (CorpId) + */ + private String corpId; + + /** + * 机器人Webhook地址 + */ + private String robotWebhook; + + /** + * 机器人密钥 + */ + private String robotSecret; + + /** + * 是否启用:0-禁用,1-启用 + */ + private Integer enabled; + + /** + * 启用功能配置(JSON) + * 例如:{"notification":true,"login":false,"attendance":false} + */ + private String enabledFeatures; + + /** + * Access Token(缓存) + */ + private String accessToken; + + /** + * Access Token过期时间 + */ + private LocalDateTime tokenExpireTime; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} diff --git a/backend/src/main/java/com/designhub/entity/FileUpload.java b/backend/src/main/java/com/designhub/entity/FileUpload.java new file mode 100644 index 0000000..70920cf --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/FileUpload.java @@ -0,0 +1,200 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 文件上传记录实体类 + */ +@Data +@TableName("sys_file_upload") +public class FileUpload { + + /** + * 主键ID + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 原始文件名 + */ + private String originalName; + + /** + * 存储文件名 + */ + private String storedName; + + /** + * 文件路径 + */ + private String filePath; + + /** + * 文件访问URL + */ + private String fileUrl; + + /** + * 文件大小(字节) + */ + private Long fileSize; + + /** + * 文件类型 + */ + private String fileType; + + /** + * 上传者ID + */ + private Long uploaderId; + + /** + * 上传者姓名 + */ + private String uploaderName; + + /** + * 关联类型:task-任务, user-用户等 + */ + private String relatedType; + + /** + * 关联ID + */ + private Long relatedId; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + /** + * 删除标志:0-未删除,1-已删除 + */ + @TableLogic + private Integer deleted; + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getOriginalName() { + return originalName; + } + + public void setOriginalName(String originalName) { + this.originalName = originalName; + } + + public String getStoredName() { + return storedName; + } + + public void setStoredName(String storedName) { + this.storedName = storedName; + } + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public String getFileUrl() { + return fileUrl; + } + + public void setFileUrl(String fileUrl) { + this.fileUrl = fileUrl; + } + + public Long getFileSize() { + return fileSize; + } + + public void setFileSize(Long fileSize) { + this.fileSize = fileSize; + } + + public String getFileType() { + return fileType; + } + + public void setFileType(String fileType) { + this.fileType = fileType; + } + + public Long getUploaderId() { + return uploaderId; + } + + public void setUploaderId(Long uploaderId) { + this.uploaderId = uploaderId; + } + + public String getUploaderName() { + return uploaderName; + } + + public void setUploaderName(String uploaderName) { + this.uploaderName = uploaderName; + } + + public String getRelatedType() { + return relatedType; + } + + public void setRelatedType(String relatedType) { + this.relatedType = relatedType; + } + + public Long getRelatedId() { + return relatedId; + } + + public void setRelatedId(Long relatedId) { + this.relatedId = relatedId; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } + + public LocalDateTime getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(LocalDateTime updateTime) { + this.updateTime = updateTime; + } + + public Integer getDeleted() { + return deleted; + } + + public void setDeleted(Integer deleted) { + this.deleted = deleted; + } +} diff --git a/backend/src/main/java/com/designhub/entity/Notification.java b/backend/src/main/java/com/designhub/entity/Notification.java new file mode 100644 index 0000000..0905c40 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/Notification.java @@ -0,0 +1,268 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 通知消息实体类 + */ +@Data +@TableName("sys_notification") +public class Notification implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 通知类型:system-系统通知, task-任务通知, score-积分通知, review-评审通知 + */ + @TableField("type") + private String notificationType; + + /** + * 接收用户ID + */ + @TableField("receiver_id") + private Long receiverId; + + /** + * 发送用户ID(可为空,系统通知为空) + */ + @TableField(exist = false) + private Long senderId; + + /** + * 通知标题 + */ + private String title; + + /** + * 通知内容 + */ + private String content; + + /** + * 关联业务ID(任务ID、积分记录ID等) + */ + @TableField("related_task_id") + private Long relatedId; + + /** + * 关联业务类型:task, score, review, system + */ + @TableField(exist = false) + private String relatedType; + + /** + * 跳转链接 + */ + @TableField("link") + private String linkUrl; + + /** + * 是否已读:0-未读,1-已读 + */ + @TableField("is_read") + private Integer isRead; + + /** + * 阅读时间 + */ + private LocalDateTime readTime; + + /** + * 是否推送到钉钉:0-否,1-是 + */ + @TableField("ding_enabled") + private Integer pushDingtalk; + + /** + * 钉钉推送状态:pending-待推送, success-成功, failed-失败 + */ + @TableField(exist = false) + private String dingtalkStatus; + + /** + * 钉钉推送时间 + */ + @TableField("ding_push_time") + private LocalDateTime dingtalkPushTime; + + /** + * 优先级:low-低, normal-普通, high-高, urgent-紧急 + */ + @TableField("ding_priority") + private Integer priority; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + /** + * 删除标志:0-未删除,1-已删除 + */ + @TableLogic + private Integer deleted; + + // Getters and Setters (手动添加以解决Lombok编译问题) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getNotificationType() { + return notificationType; + } + + public void setNotificationType(String notificationType) { + this.notificationType = notificationType; + } + + public Long getReceiverId() { + return receiverId; + } + + public void setReceiverId(Long receiverId) { + this.receiverId = receiverId; + } + + public Long getSenderId() { + return senderId; + } + + public void setSenderId(Long senderId) { + this.senderId = senderId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Long getRelatedId() { + return relatedId; + } + + public void setRelatedId(Long relatedId) { + this.relatedId = relatedId; + } + + public String getRelatedType() { + return relatedType; + } + + public void setRelatedType(String relatedType) { + this.relatedType = relatedType; + } + + public String getLinkUrl() { + return linkUrl; + } + + public void setLinkUrl(String linkUrl) { + this.linkUrl = linkUrl; + } + + public Integer getIsRead() { + return isRead; + } + + public void setIsRead(Integer isRead) { + this.isRead = isRead; + } + + public LocalDateTime getReadTime() { + return readTime; + } + + public void setReadTime(LocalDateTime readTime) { + this.readTime = readTime; + } + + public Integer getPushDingtalk() { + return pushDingtalk; + } + + public void setPushDingtalk(Integer pushDingtalk) { + this.pushDingtalk = pushDingtalk; + } + + public String getDingtalkStatus() { + return dingtalkStatus; + } + + public void setDingtalkStatus(String dingtalkStatus) { + this.dingtalkStatus = dingtalkStatus; + } + + public LocalDateTime getDingtalkPushTime() { + return dingtalkPushTime; + } + + public void setDingtalkPushTime(LocalDateTime dingtalkPushTime) { + this.dingtalkPushTime = dingtalkPushTime; + } + + public Integer getPriority() { + return priority; + } + + public void setPriority(Integer priority) { + this.priority = priority; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } + + public LocalDateTime getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(LocalDateTime updateTime) { + this.updateTime = updateTime; + } + + public Integer getDeleted() { + return deleted; + } + + public void setDeleted(Integer deleted) { + this.deleted = deleted; + } +} + diff --git a/backend/src/main/java/com/designhub/entity/Permission.java b/backend/src/main/java/com/designhub/entity/Permission.java new file mode 100644 index 0000000..2c879d9 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/Permission.java @@ -0,0 +1,96 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 权限实体类 + */ +@Data +@TableName("sys_permission") +public class Permission implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 父权限ID,0表示顶级权限 + */ + private Long parentId; + + /** + * 权限编码 + */ + private String permissionCode; + + /** + * 权限名称 + */ + private String permissionName; + + /** + * 权限类型:menu-菜单,button-按钮,api-接口 + */ + private String type; + + /** + * 权限路径/接口路径 + */ + private String path; + + /** + * 请求方法:GET/POST/PUT/DELETE等 + */ + private String method; + + /** + * 图标 + */ + private String icon; + + /** + * 组件路径 + */ + private String component; + + /** + * 权限描述 + */ + private String description; + + /** + * 排序号 + */ + private Integer sortOrder; + + /** + * 状态:0-禁用,1-启用 + */ + private Integer status; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + /** + * 删除标志:0-未删除,1-已删除 + */ + @TableLogic + private Integer deleted; +} + diff --git a/backend/src/main/java/com/designhub/entity/Platform.java b/backend/src/main/java/com/designhub/entity/Platform.java new file mode 100644 index 0000000..f1aa524 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/Platform.java @@ -0,0 +1,69 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 平台实体类 + */ +@Data +@TableName("platform") +public class Platform implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 平台名称 + */ + @TableField("platform_name") + private String name; + + /** + * 平台编码 + */ + @TableField("platform_code") + private String platformCode; + + /** + * 平台图标 URL + */ + @TableField("icon") + private String iconUrl; + + /** + * 平台描述 + */ + private String description; + + /** + * 排序号 + */ + private Integer sortOrder; + + /** + * 是否启用:0-禁用,1-启用 + */ + @TableField("status") + private Integer isEnabled; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} + diff --git a/backend/src/main/java/com/designhub/entity/RewardRule.java b/backend/src/main/java/com/designhub/entity/RewardRule.java new file mode 100644 index 0000000..5815b09 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/RewardRule.java @@ -0,0 +1,109 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 自动奖励规则实体 + */ +@Data +@TableName("reward_rule") +public class RewardRule implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 规则名称 + */ + private String name; + + /** + * 规则描述 + */ + private String description; + + /** + * 规则类型:daily-每日奖励, weekly-每周奖励, monthly-每月奖励, task-任务奖励, rating-好评奖励 + */ + private String ruleType; + + /** + * 触发条件(JSON对象) + * 例如:{"type":"task_count","operator":">=","value":10} + */ + private String triggerCondition; + + /** + * 奖励类型:score-积分, badge-徽章, both-两者 + */ + private String rewardType; + + /** + * 奖励积分数 + */ + private Integer rewardScore; + + /** + * 奖励徽章(JSON对象) + */ + private String rewardBadge; + + /** + * 适用范围:all-全员, designer-设计师, department-指定部门 + */ + private String scope; + + /** + * 指定部门ID列表(JSON数组) + */ + private String scopeDeptIds; + + /** + * 是否启用:0-禁用,1-启用 + */ + private Integer enabled; + + /** + * 执行周期(cron 表达式,仅周期性奖励使用) + */ + private String cronExpression; + + /** + * 上次执行时间 + */ + private LocalDateTime lastExecuteTime; + + /** + * 优先级(数值越大优先级越高) + */ + private Integer priority; + + /** + * 是否删除:0-否,1-是 + */ + @TableLogic + private Integer deleted; + + /** + * 创建人ID + */ + private Long creatorId; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} diff --git a/backend/src/main/java/com/designhub/entity/Role.java b/backend/src/main/java/com/designhub/entity/Role.java new file mode 100644 index 0000000..38b8b7f --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/Role.java @@ -0,0 +1,103 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 角色实体类 + */ +@Data +@TableName("sys_role") +public class Role implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 角色编码 + */ + private String roleCode; + + /** + * 角色名称 + */ + private String roleName; + + /** + * 角色描述 + */ + private String description; + + /** + * 角色级别:1-最高权限,数字越大权限越低 + * 当前数据库表中无对应字段,标记为非持久化,避免 SQL 报错 + */ + @TableField(exist = false) + private Integer level; + + /** + * 数据权限范围:all-全部数据,dept-本部门,self-本人 + * 数据库当前未存储该字段 + */ + @TableField(exist = false) + private String dataScope; + + /** + * 排序号 + * 数据库当前未存储该字段 + */ + @TableField(exist = false) + private Integer sortOrder; + + /** + * 状态:0-禁用,1-启用 + */ + private Integer status; + + /** + * 是否内置角色:0-否,1-是(内置角色不可删除) + * 数据库当前未存储该字段 + */ + @TableField(exist = false) + private Integer builtIn; + + /** + * 创建人ID + * 数据库当前未存储该字段 + */ + @TableField(exist = false) + private Long createBy; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新人ID + * 数据库当前未存储该字段 + */ + @TableField(exist = false) + private Long updateBy; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + /** + * 删除标志:0-未删除,1-已删除 + */ + @TableLogic + private Integer deleted; +} + diff --git a/backend/src/main/java/com/designhub/entity/RolePermission.java b/backend/src/main/java/com/designhub/entity/RolePermission.java new file mode 100644 index 0000000..dff6a34 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/RolePermission.java @@ -0,0 +1,39 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 角色权限关联实体类 + */ +@Data +@TableName("sys_role_permission") +public class RolePermission implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 角色ID + */ + private Long roleId; + + /** + * 权限ID + */ + private Long permissionId; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; +} + diff --git a/backend/src/main/java/com/designhub/entity/S3Config.java b/backend/src/main/java/com/designhub/entity/S3Config.java new file mode 100644 index 0000000..5e23d27 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/S3Config.java @@ -0,0 +1,187 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * S3存储配置实体类 + */ +@Data +@TableName("sys_s3_config") +public class S3Config { + + /** + * 主键ID + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * S3服务端点 + */ + private String endpoint; + + /** + * 存储桶名称 + */ + private String bucket; + + /** + * 访问密钥ID + */ + private String accessKeyId; + + /** + * 访问密钥 + */ + private String secretAccessKey; + + /** + * 区域 + */ + private String region; + + /** + * 公共访问域名 + */ + private String publicDomain; + + /** + * 是否启用路径样式访问 + */ + private Boolean enablePathStyle; + + /** + * 文件存储路径前缀 + */ + private String filePath; + + /** + * 是否激活:0-未激活,1-激活 + */ + private Boolean isActive; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + /** + * 删除标志:0-未删除,1-已删除 + */ + @TableLogic + private Integer deleted; + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public String getBucket() { + return bucket; + } + + public void setBucket(String bucket) { + this.bucket = bucket; + } + + public String getAccessKeyId() { + return accessKeyId; + } + + public void setAccessKeyId(String accessKeyId) { + this.accessKeyId = accessKeyId; + } + + public String getSecretAccessKey() { + return secretAccessKey; + } + + public void setSecretAccessKey(String secretAccessKey) { + this.secretAccessKey = secretAccessKey; + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } + + public String getPublicDomain() { + return publicDomain; + } + + public void setPublicDomain(String publicDomain) { + this.publicDomain = publicDomain; + } + + public Boolean getEnablePathStyle() { + return enablePathStyle; + } + + public void setEnablePathStyle(Boolean enablePathStyle) { + this.enablePathStyle = enablePathStyle; + } + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public Boolean getIsActive() { + return isActive; + } + + public void setIsActive(Boolean isActive) { + this.isActive = isActive; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } + + public LocalDateTime getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(LocalDateTime updateTime) { + this.updateTime = updateTime; + } + + public Integer getDeleted() { + return deleted; + } + + public void setDeleted(Integer deleted) { + this.deleted = deleted; + } +} diff --git a/backend/src/main/java/com/designhub/entity/ScorePool.java b/backend/src/main/java/com/designhub/entity/ScorePool.java new file mode 100644 index 0000000..95b4397 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/ScorePool.java @@ -0,0 +1,148 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 激励积分池实体 + */ +@Data +@TableName("score_pool") +public class ScorePool implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 积分池名称 + */ + private String name; + + /** + * 积分池类型:monthly-月度池, quarterly-季度池, yearly-年度池, special-专项池 + */ + private String poolType; + + /** + * 周期年份 + */ + private Integer year; + + /** + * 周期月份(月度池使用) + */ + private Integer month; + + /** + * 周期季度(季度池使用,1-4) + */ + private Integer quarter; + + /** + * 总预算积分 + */ + private Integer totalBudget; + + /** + * 已使用积分 + */ + private Integer usedScore; + + /** + * 剩余积分 + */ + private Integer remainingScore; + + /** + * 任务完成奖励预算 + */ + private Integer taskRewardBudget; + + /** + * 任务完成奖励已使用 + */ + private Integer taskRewardUsed; + + /** + * 好评奖励预算 + */ + private Integer ratingRewardBudget; + + /** + * 好评奖励已使用 + */ + private Integer ratingRewardUsed; + + /** + * 竞赛奖励预算 + */ + private Integer contestRewardBudget; + + /** + * 竞赛奖励已使用 + */ + private Integer contestRewardUsed; + + /** + * 签到奖励预算 + */ + private Integer checkinRewardBudget; + + /** + * 签到奖励已使用 + */ + private Integer checkinRewardUsed; + + /** + * 其他奖励预算 + */ + private Integer otherRewardBudget; + + /** + * 其他奖励已使用 + */ + private Integer otherRewardUsed; + + /** + * 超支处理策略:stop-停止发放, alert-提醒继续, auto_increase-自动增加预算 + */ + private String overflowStrategy; + + /** + * 超支提醒阈值(百分比,如80表示使用80%时提醒) + */ + private Integer alertThreshold; + + /** + * 状态:active-进行中, closed-已关闭, settled-已结算 + */ + private String status; + + /** + * 创建人ID + */ + private Long creatorId; + + /** + * 是否删除:0-否,1-是 + */ + @TableLogic + private Integer deleted; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} diff --git a/backend/src/main/java/com/designhub/entity/ScorePoolRecord.java b/backend/src/main/java/com/designhub/entity/ScorePoolRecord.java new file mode 100644 index 0000000..8f6dfe1 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/ScorePoolRecord.java @@ -0,0 +1,76 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 积分池使用记录实体 + */ +@Data +@TableName("score_pool_record") +public class ScorePoolRecord implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 积分池ID + */ + private Long poolId; + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户姓名 + */ + private String userName; + + /** + * 奖励类型:task-任务, rating-好评, contest-竞赛, checkin-签到, goal-目标, other-其他 + */ + private String rewardType; + + /** + * 积分变动(正数为发放,负数为扣除) + */ + private Integer scoreChange; + + /** + * 变动前池剩余 + */ + private Integer beforeRemaining; + + /** + * 变动后池剩余 + */ + private Integer afterRemaining; + + /** + * 关联业务类型 + */ + private String bizType; + + /** + * 关联业务ID + */ + private Long bizId; + + /** + * 备注 + */ + private String remark; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; +} diff --git a/backend/src/main/java/com/designhub/entity/SystemConfig.java b/backend/src/main/java/com/designhub/entity/SystemConfig.java new file mode 100644 index 0000000..e89b606 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/SystemConfig.java @@ -0,0 +1,81 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 系统配置实体类 + */ +@Data +@TableName("sys_config") +public class SystemConfig implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 配置键 + */ + private String configKey; + + /** + * 配置值 + */ + private String configValue; + + /** + * 配置分组 + */ + private String configGroup; + + /** + * 配置名称 + */ + private String configName; + + /** + * 配置描述 + */ + private String description; + + /** + * 值类型:string/number/boolean/json + */ + private String valueType; + + /** + * 是否系统内置:0-否,1-是 + */ + private Integer builtIn; + + /** + * 排序号 + */ + private Integer sortOrder; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + /** + * 删除标志:0-未删除,1-已删除 + */ + @TableLogic + private Integer deleted; +} + diff --git a/backend/src/main/java/com/designhub/entity/Task.java b/backend/src/main/java/com/designhub/entity/Task.java new file mode 100644 index 0000000..3ba2b60 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/Task.java @@ -0,0 +1,517 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 任务实体类 + * 注意:已移除@Data注解,改用手动getter/setter以解决Lombok编译问题 + */ +@TableName("task") +public class Task implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 任务ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 任务编号 + */ + private String taskNo; + + /** + * 第三方平台任务编号 + */ + private String thirdPartyTaskNo; + + /** + * 任务标题 + */ + private String title; + + /** + * 任务类型ID(单个,兼容旧数据) + */ + private Long taskTypeId; + + /** + * 任务类型ID列表(多个,逗号分隔,支持多类型) + */ + private String taskTypeIds; + + /** + * 任务子类型:new-新品 optimize-优化 redesign-改图 other-其他 + */ + private String taskSubType; + + /** + * 数量 + */ + private Integer quantity; + + /** + * 预估积分 + */ + private Integer score; + + /** + * 实际积分奖励 + */ + private BigDecimal scoreReward; + + /** + * 预估工时(小时) + */ + private BigDecimal estimatedHours; + + /** + * 预估时长(小时) + */ + private Integer estimatedDuration; + + /** + * 需求类型ID + */ + private Long demandTypeId; + + /** + * 品牌ID + */ + private Long brandId; + + /** + * 叶子类目ID + */ + private Long categoryId; + + /** + * SPU信息 + */ + private String spuInfo; + + /** + * 平台ID + */ + private Long platformId; + + /** + * 附件URL(多个用逗号分隔) + */ + private String attachmentUrl; + + /** + * 交付路径 + */ + private String deliveryPath; + + /** + * 发布人ID + */ + private Long publisherId; + + /** + * 发布人部门ID + */ + private Long publisherDeptId; + + /** + * 接收人ID + */ + private Long receiverId; + + /** + * 接收人部门ID + */ + private Long receiverDeptId; + + /** + * 发布类型:assigned-指定 recruitment-招募 default-默认 + */ + private String releaseType; + + /** + * 状态:pending-待接单 in_progress-进行中 review-待审核 rejected-已驳回 completed-已完成 cancelled-已取消 + */ + private String status; + + /** + * 审核状态:draft-草稿 pending_audit-待审核 audit_pass-审核通过 audit_reject-审核驳回 + */ + private String auditStatus; + + /** + * 审核人ID + */ + private Long auditorId; + + /** + * 审核时间 + */ + private LocalDateTime auditTime; + + /** + * 审核意见 + */ + private String auditComment; + + /** + * 完成进度(0-100) + */ + private Integer progress; + + /** + * 开始时间 + */ + private LocalDateTime startTime; + + /** + * 实际开始时间 + */ + private LocalDateTime actualStartTime; + + /** + * 截止时间 + */ + private LocalDateTime deadline; + + /** + * 完成时间 + */ + private LocalDateTime completeTime; + + /** + * 提交审核时间 + */ + private LocalDateTime submitTime; + + /** + * 实际结束时间 + */ + private LocalDateTime actualEndTime; + + /** + * 优先级:0-低 1-普通 2-高 3-紧急 + */ + private Integer priority; + + /** + * 备注信息 + */ + private String remark; + + /** + * 评分(1-5星) + */ + private Integer rating; + + /** + * 审核评论 + */ + private String reviewComment; + + /** + * 驳回原因 + */ + private String rejectReason; + + /** + * 驳回后的最晚交付时间 + */ + private LocalDateTime rejectDeadline; + + /** + * 是否紧急:0-否 1-是 + */ + private Integer isUrgent; + + /** + * 是否延误:0-否 1-是 + */ + private Integer isDelayed; + + /** + * 延误天数 + */ + private Integer delayDays; + + /** + * 是否流水线任务:0-否,1-是 + */ + private Integer isPipeline; + + /** + * 流水线模式:sequential(顺序)/parallel(并行) + */ + private String pipelineMode; + + /** + * 审核状态 + */ + private String approvalStatus; + + /** + * 当前审核人ID + */ + private Long currentApproverId; + + /** + * 当前审核人角色 + */ + private String currentApproverRole; + + /** + * 需求部门审核时间 + */ + private java.time.LocalDateTime demandApprovalTime; + + /** + * 接单部门审核时间 + */ + private java.time.LocalDateTime receivingApprovalTime; + + /** + * 逻辑删除:0-否 1-是 + */ + @TableLogic + private Integer deleted; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + /** + * 关联字段(查询时返回,不映射到数据库) + */ + @TableField(exist = false) + private String brandName; + + @TableField(exist = false) + private String categoryName; + + @TableField(exist = false) + private String platformName; + + @TableField(exist = false) + private String publisherName; + + @TableField(exist = false) + private String publisherDeptName; + + @TableField(exist = false) + private String receiverName; + + @TableField(exist = false) + private String auditorName; + + /** + * 当前审核人姓名(查询时返回,不映射到数据库) + */ + @TableField(exist = false) + private String currentApproverName; + + // ==================== Getters and Setters (手动添加以解决Lombok编译问题) ==================== + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public String getTaskNo() { return taskNo; } + public void setTaskNo(String taskNo) { this.taskNo = taskNo; } + + public String getThirdPartyTaskNo() { return thirdPartyTaskNo; } + public void setThirdPartyTaskNo(String thirdPartyTaskNo) { this.thirdPartyTaskNo = thirdPartyTaskNo; } + + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + + public Long getTaskTypeId() { return taskTypeId; } + public void setTaskTypeId(Long taskTypeId) { this.taskTypeId = taskTypeId; } + + public String getTaskTypeIds() { return taskTypeIds; } + public void setTaskTypeIds(String taskTypeIds) { this.taskTypeIds = taskTypeIds; } + + public String getTaskSubType() { return taskSubType; } + public void setTaskSubType(String taskSubType) { this.taskSubType = taskSubType; } + + public Integer getQuantity() { return quantity; } + public void setQuantity(Integer quantity) { this.quantity = quantity; } + + public Integer getScore() { return score; } + public void setScore(Integer score) { this.score = score; } + + public BigDecimal getScoreReward() { return scoreReward; } + public void setScoreReward(BigDecimal scoreReward) { this.scoreReward = scoreReward; } + + public BigDecimal getEstimatedHours() { return estimatedHours; } + public void setEstimatedHours(BigDecimal estimatedHours) { this.estimatedHours = estimatedHours; } + + public Integer getEstimatedDuration() { return estimatedDuration; } + public void setEstimatedDuration(Integer estimatedDuration) { this.estimatedDuration = estimatedDuration; } + + public Long getDemandTypeId() { return demandTypeId; } + public void setDemandTypeId(Long demandTypeId) { this.demandTypeId = demandTypeId; } + + public Long getBrandId() { return brandId; } + public void setBrandId(Long brandId) { this.brandId = brandId; } + + public Long getCategoryId() { return categoryId; } + public void setCategoryId(Long categoryId) { this.categoryId = categoryId; } + + public String getSpuInfo() { return spuInfo; } + public void setSpuInfo(String spuInfo) { this.spuInfo = spuInfo; } + + public Long getPlatformId() { return platformId; } + public void setPlatformId(Long platformId) { this.platformId = platformId; } + + public String getAttachmentUrl() { return attachmentUrl; } + public void setAttachmentUrl(String attachmentUrl) { this.attachmentUrl = attachmentUrl; } + + public String getDeliveryPath() { return deliveryPath; } + public void setDeliveryPath(String deliveryPath) { this.deliveryPath = deliveryPath; } + + public Long getPublisherId() { return publisherId; } + public void setPublisherId(Long publisherId) { this.publisherId = publisherId; } + + public Long getPublisherDeptId() { return publisherDeptId; } + public void setPublisherDeptId(Long publisherDeptId) { this.publisherDeptId = publisherDeptId; } + + public Long getReceiverId() { return receiverId; } + public void setReceiverId(Long receiverId) { this.receiverId = receiverId; } + + public Long getReceiverDeptId() { return receiverDeptId; } + public void setReceiverDeptId(Long receiverDeptId) { this.receiverDeptId = receiverDeptId; } + + public String getReleaseType() { return releaseType; } + public void setReleaseType(String releaseType) { this.releaseType = releaseType; } + + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } + + public String getAuditStatus() { return auditStatus; } + public void setAuditStatus(String auditStatus) { this.auditStatus = auditStatus; } + + public Long getAuditorId() { return auditorId; } + public void setAuditorId(Long auditorId) { this.auditorId = auditorId; } + + public LocalDateTime getAuditTime() { return auditTime; } + public void setAuditTime(LocalDateTime auditTime) { this.auditTime = auditTime; } + + public String getAuditComment() { return auditComment; } + public void setAuditComment(String auditComment) { this.auditComment = auditComment; } + + public Integer getProgress() { return progress; } + public void setProgress(Integer progress) { this.progress = progress; } + + public LocalDateTime getStartTime() { return startTime; } + public void setStartTime(LocalDateTime startTime) { this.startTime = startTime; } + + public LocalDateTime getActualStartTime() { return actualStartTime; } + public void setActualStartTime(LocalDateTime actualStartTime) { this.actualStartTime = actualStartTime; } + + public LocalDateTime getDeadline() { return deadline; } + public void setDeadline(LocalDateTime deadline) { this.deadline = deadline; } + + public LocalDateTime getCompleteTime() { return completeTime; } + public void setCompleteTime(LocalDateTime completeTime) { this.completeTime = completeTime; } + + public LocalDateTime getSubmitTime() { return submitTime; } + public void setSubmitTime(LocalDateTime submitTime) { this.submitTime = submitTime; } + + public LocalDateTime getActualEndTime() { return actualEndTime; } + public void setActualEndTime(LocalDateTime actualEndTime) { this.actualEndTime = actualEndTime; } + + public Integer getPriority() { return priority; } + public void setPriority(Integer priority) { this.priority = priority; } + + public String getRemark() { return remark; } + public void setRemark(String remark) { this.remark = remark; } + + public Integer getRating() { return rating; } + public void setRating(Integer rating) { this.rating = rating; } + + public String getReviewComment() { return reviewComment; } + public void setReviewComment(String reviewComment) { this.reviewComment = reviewComment; } + + public String getRejectReason() { return rejectReason; } + public void setRejectReason(String rejectReason) { this.rejectReason = rejectReason; } + + public LocalDateTime getRejectDeadline() { return rejectDeadline; } + public void setRejectDeadline(LocalDateTime rejectDeadline) { this.rejectDeadline = rejectDeadline; } + + public Integer getIsUrgent() { return isUrgent; } + public void setIsUrgent(Integer isUrgent) { this.isUrgent = isUrgent; } + + public Integer getIsDelayed() { return isDelayed; } + public void setIsDelayed(Integer isDelayed) { this.isDelayed = isDelayed; } + + public Integer getDelayDays() { return delayDays; } + public void setDelayDays(Integer delayDays) { this.delayDays = delayDays; } + + public Integer getIsPipeline() { return isPipeline; } + public void setIsPipeline(Integer isPipeline) { this.isPipeline = isPipeline; } + + public String getPipelineMode() { return pipelineMode; } + public void setPipelineMode(String pipelineMode) { this.pipelineMode = pipelineMode; } + + public String getApprovalStatus() { return approvalStatus; } + public void setApprovalStatus(String approvalStatus) { this.approvalStatus = approvalStatus; } + + public Long getCurrentApproverId() { return currentApproverId; } + public void setCurrentApproverId(Long currentApproverId) { this.currentApproverId = currentApproverId; } + + public String getCurrentApproverRole() { return currentApproverRole; } + public void setCurrentApproverRole(String currentApproverRole) { this.currentApproverRole = currentApproverRole; } + + public LocalDateTime getDemandApprovalTime() { return demandApprovalTime; } + public void setDemandApprovalTime(LocalDateTime demandApprovalTime) { this.demandApprovalTime = demandApprovalTime; } + + public LocalDateTime getReceivingApprovalTime() { return receivingApprovalTime; } + public void setReceivingApprovalTime(LocalDateTime receivingApprovalTime) { this.receivingApprovalTime = receivingApprovalTime; } + + public Integer getDeleted() { return deleted; } + public void setDeleted(Integer deleted) { this.deleted = deleted; } + + public LocalDateTime getCreateTime() { return createTime; } + public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; } + + public LocalDateTime getUpdateTime() { return updateTime; } + public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; } + + public String getBrandName() { return brandName; } + public void setBrandName(String brandName) { this.brandName = brandName; } + + public String getCategoryName() { return categoryName; } + public void setCategoryName(String categoryName) { this.categoryName = categoryName; } + + public String getPlatformName() { return platformName; } + public void setPlatformName(String platformName) { this.platformName = platformName; } + + public String getPublisherName() { return publisherName; } + public void setPublisherName(String publisherName) { this.publisherName = publisherName; } + + public String getPublisherDeptName() { return publisherDeptName; } + public void setPublisherDeptName(String publisherDeptName) { this.publisherDeptName = publisherDeptName; } + + public String getReceiverName() { return receiverName; } + public void setReceiverName(String receiverName) { this.receiverName = receiverName; } + + public String getAuditorName() { return auditorName; } + public void setAuditorName(String auditorName) { this.auditorName = auditorName; } + + public String getCurrentApproverName() { return currentApproverName; } + public void setCurrentApproverName(String currentApproverName) { this.currentApproverName = currentApproverName; } +} + diff --git a/backend/src/main/java/com/designhub/entity/TaskApprovalLog.java b/backend/src/main/java/com/designhub/entity/TaskApprovalLog.java new file mode 100644 index 0000000..4a3dbcd --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/TaskApprovalLog.java @@ -0,0 +1,136 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 任务审核记录实体 + */ +@Data +@TableName("task_approval_log") +public class TaskApprovalLog implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 任务ID + */ + private Long taskId; + + /** + * 审核级别:1-需求部门,2-接单部门 + */ + private Integer approvalLevel; + + /** + * 审核人ID + */ + private Long approverId; + + /** + * 审核人姓名 + */ + private String approverName; + + /** + * 操作:approve/reject/modify + */ + private String action; + + /** + * 审核意见 + */ + private String comment; + + /** + * 修改的内容(JSON格式) + */ + private String changes; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getTaskId() { + return taskId; + } + + public void setTaskId(Long taskId) { + this.taskId = taskId; + } + + public Integer getApprovalLevel() { + return approvalLevel; + } + + public void setApprovalLevel(Integer approvalLevel) { + this.approvalLevel = approvalLevel; + } + + public Long getApproverId() { + return approverId; + } + + public void setApproverId(Long approverId) { + this.approverId = approverId; + } + + public String getApproverName() { + return approverName; + } + + public void setApproverName(String approverName) { + this.approverName = approverName; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public String getChanges() { + return changes; + } + + public void setChanges(String changes) { + this.changes = changes; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } +} diff --git a/backend/src/main/java/com/designhub/entity/TaskAssignLog.java b/backend/src/main/java/com/designhub/entity/TaskAssignLog.java new file mode 100644 index 0000000..4d4e62f --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/TaskAssignLog.java @@ -0,0 +1,53 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 任务指派记录实体类 + */ +@Data +@TableName("task_assign_log") +public class TaskAssignLog implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 记录ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 任务ID + */ + private Long taskId; + + /** + * 指派人ID(部门负责人) + */ + private Long assignerId; + + /** + * 被指派人ID + */ + private Long assigneeId; + + /** + * 指派类型:force-强制指派 recommend-推荐 + */ + private String assignType; + + /** + * 指派说明 + */ + private String comment; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; +} diff --git a/backend/src/main/java/com/designhub/entity/TaskAuditLog.java b/backend/src/main/java/com/designhub/entity/TaskAuditLog.java new file mode 100644 index 0000000..9105437 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/TaskAuditLog.java @@ -0,0 +1,53 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 任务审核日志实体类 + */ +@Data +@TableName("task_audit_log") +public class TaskAuditLog implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 日志ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 任务ID + */ + private Long taskId; + + /** + * 审核人ID + */ + private Long auditorId; + + /** + * 审核人姓名 + */ + private String auditorName; + + /** + * 操作:submit-提交审核 pass-审核通过 reject-审核驳回 + */ + private String action; + + /** + * 审核意见 + */ + private String comment; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; +} diff --git a/backend/src/main/java/com/designhub/entity/TaskItem.java b/backend/src/main/java/com/designhub/entity/TaskItem.java new file mode 100644 index 0000000..d49a76b --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/TaskItem.java @@ -0,0 +1,197 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 任务子项实体类(流水线任务) + */ +@Data +@TableName("task_item") +public class TaskItem implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 主任务ID + */ + private Long taskId; + + /** + * 子项编号 + */ + private String itemNo; + + /** + * 子任务组号(同组任务由同一接单部门处理) + */ + private Integer groupNo; + + /** + * 任务类型ID + */ + private Long taskTypeId; + + /** + * 数量 + */ + private Integer quantity; + + /** + * 单价积分 + */ + private Integer unitScore; + + /** + * 总积分 + */ + private Integer totalScore; + + /** + * 预估工时 + */ + private BigDecimal estimatedHours; + + /** + * 接单人ID + */ + private Long receiverId; + + /** + * 接单人姓名 + */ + private String receiverName; + + /** + * 状态:locked/pending/in_progress/completed/rejected + */ + private String status; + + /** + * 进度0-100 + */ + private Integer progress; + + /** + * 是否锁定:0-否,1-是(顺序流水线中使用) + */ + private Integer isLocked; + + /** + * 前置子任务ID(顺序流水线中使用) + */ + private Long prevItemId; + + /** + * 交付路径 + */ + private String deliveryPath; + + /** + * 提交时间 + */ + private LocalDateTime submitTime; + + /** + * 完成时间 + */ + private LocalDateTime completeTime; + + /** + * 排序 + */ + private Integer sortOrder; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + // ========== 非数据库字段 ========== + + /** + * 任务类型名称(非数据库字段) + */ + @TableField(exist = false) + private String taskTypeName; + + /** + * 主任务信息(非数据库字段) + */ + @TableField(exist = false) + private Task mainTask; + + /** + * 主任务标题(非数据库字段) + */ + @TableField(exist = false) + private String mainTaskTitle; + + /** + * 流水线模式(非数据库字段) + */ + @TableField(exist = false) + private String pipelineMode; + + /** + * 总子任务数(非数据库字段) + */ + @TableField(exist = false) + private Integer totalItems; + + /** + * 品牌名称(非数据库字段) + */ + @TableField(exist = false) + private String brandName; + + /** + * 类目名称(非数据库字段) + */ + @TableField(exist = false) + private String categoryName; + + /** + * 平台名称(非数据库字段) + */ + @TableField(exist = false) + private String platformName; + + /** + * 发布人姓名(非数据库字段) + */ + @TableField(exist = false) + private String publisherName; + + /** + * SPU信息(非数据库字段) + */ + @TableField(exist = false) + private String spuInfo; + + /** + * 任务子类型(非数据库字段) + */ + @TableField(exist = false) + private String taskSubType; + + /** + * 附件URL(非数据库字段) + */ + @TableField(exist = false) + private String attachmentUrl; +} diff --git a/backend/src/main/java/com/designhub/entity/TaskProgressUpdateLog.java b/backend/src/main/java/com/designhub/entity/TaskProgressUpdateLog.java new file mode 100644 index 0000000..9464ef9 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/TaskProgressUpdateLog.java @@ -0,0 +1,99 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * 任务进度更新记录实体类 + */ +@Data +@TableName("task_progress_update_log") +public class TaskProgressUpdateLog implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 记录ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 任务ID + */ + private Long taskId; + + /** + * 任务编号 + */ + private String taskNo; + + /** + * 任务标题 + */ + private String taskTitle; + + /** + * 接单人ID + */ + private Long receiverId; + + /** + * 接单人姓名 + */ + private String receiverName; + + /** + * 检查日期 + */ + private LocalDate checkDate; + + /** + * 当天是否更新过进度:0-未更新 1-已更新 + */ + private Integer isUpdated; + + /** + * 最后更新时间 + */ + private LocalDateTime lastUpdateTime; + + /** + * 检查前进度 + */ + private Integer progressBefore; + + /** + * 检查后进度(如果更新了) + */ + private Integer progressAfter; + + /** + * 是否已发送通知:0-未发送 1-已发送 + */ + private Integer notificationSent; + + /** + * 通知发送时间 + */ + private LocalDateTime notificationTime; + + /** + * 任务状态 + */ + private String taskStatus; + + /** + * 备注 + */ + private String remark; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; +} diff --git a/backend/src/main/java/com/designhub/entity/TaskReview.java b/backend/src/main/java/com/designhub/entity/TaskReview.java new file mode 100644 index 0000000..70c7167 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/TaskReview.java @@ -0,0 +1,65 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 任务评价实体类 + */ +@Data +@TableName("task_review") +public class TaskReview implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 任务ID + */ + private Long taskId; + + /** + * 评价人ID(发布者) + */ + private Long reviewerId; + + /** + * 被评价人ID(接收者) + */ + private Long receiverId; + + /** + * 评分(1-5星) + */ + private Integer rating; + + /** + * 评价内容 + */ + private String comment; + + /** + * 评价标签(JSON数组) + */ + private String tags; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} + diff --git a/backend/src/main/java/com/designhub/entity/TaskSubType.java b/backend/src/main/java/com/designhub/entity/TaskSubType.java new file mode 100644 index 0000000..c90ecd9 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/TaskSubType.java @@ -0,0 +1,124 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 任务子类型实体 + */ +@Data +@TableName("task_sub_type") +public class TaskSubType implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 子类型名称 + */ + private String name; + + /** + * 子类型编码 + */ + private String code; + + /** + * 描述 + */ + private String description; + + /** + * 排序 + */ + private Integer sortOrder; + + /** + * 是否启用:0-否,1-是 + */ + private Integer isEnabled; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getSortOrder() { + return sortOrder; + } + + public void setSortOrder(Integer sortOrder) { + this.sortOrder = sortOrder; + } + + public Integer getIsEnabled() { + return isEnabled; + } + + public void setIsEnabled(Integer isEnabled) { + this.isEnabled = isEnabled; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } + + public LocalDateTime getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(LocalDateTime updateTime) { + this.updateTime = updateTime; + } +} diff --git a/backend/src/main/java/com/designhub/entity/TeamPK.java b/backend/src/main/java/com/designhub/entity/TeamPK.java new file mode 100644 index 0000000..8d97374 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/TeamPK.java @@ -0,0 +1,359 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 团队PK实体 + */ +@Data +@TableName("team_pk") +public class TeamPK implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.AUTO) + private Long id; + + /** + * PK名称 + */ + private String name; + + /** + * PK描述 + */ + private String description; + + /** + * 甲方部门ID + */ + private Long teamADeptId; + + /** + * 甲方部门名称 + */ + private String teamAName; + + /** + * 乙方部门ID + */ + private Long teamBDeptId; + + /** + * 乙方部门名称 + */ + private String teamBName; + + /** + * 开始时间 + */ + private LocalDateTime startTime; + + /** + * 结束时间 + */ + private LocalDateTime endTime; + + /** + * PK维度配置(JSON对象) + * 例如:{"taskCount":{"weight":30},"fiveStarCount":{"weight":25},"onTimeRate":{"weight":25},"avgRating":{"weight":20}} + */ + private String dimensions; + + /** + * 甲方当前得分 + */ + private Integer teamAScore; + + /** + * 乙方当前得分 + */ + private Integer teamBScore; + + /** + * 甲方完成任务数 + */ + private Integer teamATaskCount; + + /** + * 乙方完成任务数 + */ + private Integer teamBTaskCount; + + /** + * 甲方五星好评数 + */ + private Integer teamAFiveStarCount; + + /** + * 乙方五星好评数 + */ + private Integer teamBFiveStarCount; + + /** + * 甲方准时完成率(百分比*100) + */ + private Integer teamAOnTimeRate; + + /** + * 乙方准时完成率(百分比*100) + */ + private Integer teamBOnTimeRate; + + /** + * 胜利奖励配置(JSON对象) + * 例如:{"winnerReward":200,"loserReward":100,"mvpReward":300} + */ + private String rewards; + + /** + * PK状态:draft-草稿, scheduled-待开始, active-进行中, ended-已结束, cancelled-已取消 + */ + private String status; + + /** + * 获胜方:A-甲方, B-乙方, draw-平局, null-未结束 + */ + private String winner; + + /** + * MVP用户ID + */ + private Long mvpUserId; + + /** + * 创建人ID + */ + private Long creatorId; + + /** + * 是否删除:0-否,1-是 + */ + @TableLogic + private Integer deleted; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + // Getters and Setters (手动添加以解决Lombok编译问题) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Long getTeamADeptId() { + return teamADeptId; + } + + public void setTeamADeptId(Long teamADeptId) { + this.teamADeptId = teamADeptId; + } + + public String getTeamAName() { + return teamAName; + } + + public void setTeamAName(String teamAName) { + this.teamAName = teamAName; + } + + public Long getTeamBDeptId() { + return teamBDeptId; + } + + public void setTeamBDeptId(Long teamBDeptId) { + this.teamBDeptId = teamBDeptId; + } + + public String getTeamBName() { + return teamBName; + } + + public void setTeamBName(String teamBName) { + this.teamBName = teamBName; + } + + public LocalDateTime getStartTime() { + return startTime; + } + + public void setStartTime(LocalDateTime startTime) { + this.startTime = startTime; + } + + public LocalDateTime getEndTime() { + return endTime; + } + + public void setEndTime(LocalDateTime endTime) { + this.endTime = endTime; + } + + public String getDimensions() { + return dimensions; + } + + public void setDimensions(String dimensions) { + this.dimensions = dimensions; + } + + public Integer getTeamAScore() { + return teamAScore; + } + + public void setTeamAScore(Integer teamAScore) { + this.teamAScore = teamAScore; + } + + public Integer getTeamBScore() { + return teamBScore; + } + + public void setTeamBScore(Integer teamBScore) { + this.teamBScore = teamBScore; + } + + public Integer getTeamATaskCount() { + return teamATaskCount; + } + + public void setTeamATaskCount(Integer teamATaskCount) { + this.teamATaskCount = teamATaskCount; + } + + public Integer getTeamBTaskCount() { + return teamBTaskCount; + } + + public void setTeamBTaskCount(Integer teamBTaskCount) { + this.teamBTaskCount = teamBTaskCount; + } + + public Integer getTeamAFiveStarCount() { + return teamAFiveStarCount; + } + + public void setTeamAFiveStarCount(Integer teamAFiveStarCount) { + this.teamAFiveStarCount = teamAFiveStarCount; + } + + public Integer getTeamBFiveStarCount() { + return teamBFiveStarCount; + } + + public void setTeamBFiveStarCount(Integer teamBFiveStarCount) { + this.teamBFiveStarCount = teamBFiveStarCount; + } + + public Integer getTeamAOnTimeRate() { + return teamAOnTimeRate; + } + + public void setTeamAOnTimeRate(Integer teamAOnTimeRate) { + this.teamAOnTimeRate = teamAOnTimeRate; + } + + public Integer getTeamBOnTimeRate() { + return teamBOnTimeRate; + } + + public void setTeamBOnTimeRate(Integer teamBOnTimeRate) { + this.teamBOnTimeRate = teamBOnTimeRate; + } + + public String getRewards() { + return rewards; + } + + public void setRewards(String rewards) { + this.rewards = rewards; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getWinner() { + return winner; + } + + public void setWinner(String winner) { + this.winner = winner; + } + + public Long getMvpUserId() { + return mvpUserId; + } + + public void setMvpUserId(Long mvpUserId) { + this.mvpUserId = mvpUserId; + } + + public Long getCreatorId() { + return creatorId; + } + + public void setCreatorId(Long creatorId) { + this.creatorId = creatorId; + } + + public Integer getDeleted() { + return deleted; + } + + public void setDeleted(Integer deleted) { + this.deleted = deleted; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } + + public LocalDateTime getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(LocalDateTime updateTime) { + this.updateTime = updateTime; + } +} diff --git a/backend/src/main/java/com/designhub/entity/TeamScorePool.java b/backend/src/main/java/com/designhub/entity/TeamScorePool.java new file mode 100644 index 0000000..57e60d8 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/TeamScorePool.java @@ -0,0 +1,135 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 团队积分池实体类 + */ +@Data +@TableName("team_score_pool") +public class TeamScorePool implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 积分余额 + */ + private Integer balance; + + /** + * 累计充值积分 + */ + private Integer totalRecharge; + + /** + * 累计消耗积分 + */ + private Integer totalConsume; + + /** + * 累计获得积分(接单部门) + */ + private Integer totalEarn; + + /** + * 总积分额度 + */ + private Integer totalScore; + + /** + * 剩余积分 + */ + private Integer remainingScore; + + /** + * 是否启用消耗限制:0-否,1-是 + */ + private Integer limitEnabled; + + /** + * 限制周期:DAILY-每天,WEEKLY-每周,MONTHLY-每月 + */ + private String limitPeriod; + + /** + * 周期限制额度 + */ + private Integer periodLimit; + + /** + * 当前周期已消耗 + */ + private Integer periodUsed; + + /** + * 当前周期开始时间 + */ + private LocalDateTime periodStartTime; + + /** + * 每日限额 + */ + private Integer dailyLimit; + + /** + * 每周限额 + */ + private Integer weeklyLimit; + + /** + * 每月限额 + */ + private Integer monthlyLimit; + + /** + * 每日已使用 + */ + private Integer dailyUsed; + + /** + * 每周已使用 + */ + private Integer weeklyUsed; + + /** + * 每月已使用 + */ + private Integer monthlyUsed; + + /** + * 预警阈值(百分比,如80表示80%) + */ + private Integer alertThreshold; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} + diff --git a/backend/src/main/java/com/designhub/entity/ThirdPartyCallback.java b/backend/src/main/java/com/designhub/entity/ThirdPartyCallback.java new file mode 100644 index 0000000..41f28c4 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/ThirdPartyCallback.java @@ -0,0 +1,76 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 第三方回调记录实体类 + */ +@Data +@TableName("third_party_callback") +public class ThirdPartyCallback { + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 关联的任务ID + */ + private Long taskId; + + /** + * 第三方平台任务编号 + */ + private String thirdPartyTaskNo; + + /** + * 回调类型:complete-任务完成, status_update-状态更新 + */ + private String callbackType; + + /** + * 回调处理状态:pending-待处理, success-成功, failed-失败 + */ + private String callbackStatus; + + /** + * 回调请求数据(JSON格式) + */ + private String requestData; + + /** + * 响应数据(JSON格式) + */ + private String responseData; + + /** + * 错误信息 + */ + private String errorMessage; + + /** + * 重试次数 + */ + private Integer retryCount; + + /** + * 回调接收时间 + */ + private LocalDateTime callbackTime; + + /** + * 处理完成时间 + */ + private LocalDateTime processedTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; +} diff --git a/backend/src/main/java/com/designhub/entity/User.java b/backend/src/main/java/com/designhub/entity/User.java new file mode 100644 index 0000000..8bc35e6 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/User.java @@ -0,0 +1,101 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 用户实体类 + */ +@Data +@TableName("sys_user") +public class User implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 密码 + */ + private String password; + + /** + * 真实姓名 + */ + private String realName; + + /** + * 邮箱 + */ + private String email; + + /** + * 手机号 + */ + private String phone; + + /** + * 头像URL + */ + private String avatar; + + /** + * 钉钉用户ID + */ + private String dingUserId; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 角色ID列表(逗号分隔) + */ + private String roleIds; + + /** + * 状态:0-禁用 1-启用 + */ + private Integer status; + + /** + * 用户积分 + */ + private Integer score; + + /** + * 逻辑删除:0-否 1-是 + */ + @TableLogic + private Integer deleted; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} + diff --git a/backend/src/main/java/com/designhub/entity/UserAchievement.java b/backend/src/main/java/com/designhub/entity/UserAchievement.java new file mode 100644 index 0000000..46ef0f6 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/UserAchievement.java @@ -0,0 +1,72 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 用户成就关联实体类 + */ +@Data +@TableName("user_achievement") +public class UserAchievement implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 用户ID + */ + private Long userId; + + /** + * 成就ID + */ + private Long achievementId; + + /** + * 达成时间 + */ + private LocalDateTime achievedTime; + + /** + * 当前进度值 + */ + private Integer progressValue; + + /** + * 是否已领取奖励 + */ + private Integer rewardClaimed; + + /** + * 领取奖励时间 + */ + private LocalDateTime claimTime; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + /** + * 删除标志:0-未删除,1-已删除 + */ + @TableLogic + private Integer deleted; +} + diff --git a/backend/src/main/java/com/designhub/entity/UserGoal.java b/backend/src/main/java/com/designhub/entity/UserGoal.java new file mode 100644 index 0000000..2ffe4ca --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/UserGoal.java @@ -0,0 +1,108 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 用户目标实体 + */ +@Data +@TableName("user_goal") +public class UserGoal implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 用户ID + */ + private Long userId; + + /** + * 目标类型:task_count-任务数量, score-积分, rank-排名, rating-评分 + */ + private String goalType; + + /** + * 目标名称 + */ + private String goalName; + + /** + * 目标描述 + */ + private String description; + + /** + * 目标值 + */ + private Integer targetValue; + + /** + * 当前进度值 + */ + private Integer currentValue; + + /** + * 目标周期:daily-每日, weekly-每周, monthly-每月, quarterly-每季度, yearly-每年, custom-自定义 + */ + private String period; + + /** + * 周期开始时间 + */ + private LocalDateTime periodStart; + + /** + * 周期结束时间 + */ + private LocalDateTime periodEnd; + + /** + * 目标状态:active-进行中, completed-已完成, failed-未完成, cancelled-已取消 + */ + private String status; + + /** + * 完成时间 + */ + private LocalDateTime completedTime; + + /** + * 奖励积分(达成目标后) + */ + private Integer rewardScore; + + /** + * 是否已领取奖励:0-否,1-是 + */ + private Integer rewardClaimed; + + /** + * 提醒设置:none-不提醒, daily-每天, before_deadline-截止前 + */ + private String reminderType; + + /** + * 是否删除:0-否,1-是 + */ + @TableLogic + private Integer deleted; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} diff --git a/backend/src/main/java/com/designhub/entity/UserRole.java b/backend/src/main/java/com/designhub/entity/UserRole.java new file mode 100644 index 0000000..e5c01e4 --- /dev/null +++ b/backend/src/main/java/com/designhub/entity/UserRole.java @@ -0,0 +1,39 @@ +package com.designhub.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 用户角色关联实体类 + */ +@Data +@TableName("sys_user_role") +public class UserRole implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 用户ID + */ + private Long userId; + + /** + * 角色ID + */ + private Long roleId; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; +} + diff --git a/backend/src/main/java/com/designhub/exception/BusinessException.java b/backend/src/main/java/com/designhub/exception/BusinessException.java new file mode 100644 index 0000000..e6af95a --- /dev/null +++ b/backend/src/main/java/com/designhub/exception/BusinessException.java @@ -0,0 +1,50 @@ +package com.designhub.exception; + +import com.designhub.common.ResultCode; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 业务异常类 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class BusinessException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * 错误码 + */ + private Integer code; + + /** + * 错误信息 + */ + private String message; + + public BusinessException(String message) { + super(message); + this.code = 500; + this.message = message; + } + + public BusinessException(Integer code, String message) { + super(message); + this.code = code; + this.message = message; + } + + public BusinessException(ResultCode resultCode) { + super(resultCode.getMessage()); + this.code = resultCode.getCode(); + this.message = resultCode.getMessage(); + } + + public BusinessException(ResultCode resultCode, String message) { + super(message); + this.code = resultCode.getCode(); + this.message = message; + } +} + diff --git a/backend/src/main/java/com/designhub/exception/GlobalExceptionHandler.java b/backend/src/main/java/com/designhub/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..b61b01d --- /dev/null +++ b/backend/src/main/java/com/designhub/exception/GlobalExceptionHandler.java @@ -0,0 +1,84 @@ +package com.designhub.exception; + +import com.designhub.common.Result; +import com.designhub.common.ResultCode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import java.util.stream.Collectors; + +/** + * 全局异常处理器 + */ +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + /** + * 处理业务异常 + */ + @ExceptionHandler(BusinessException.class) + public Result handleBusinessException(BusinessException e) { + log.error("业务异常:{}", e.getMessage()); + return Result.error(e.getCode(), e.getMessage()); + } + + /** + * 处理参数校验异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public Result handleValidException(MethodArgumentNotValidException e) { + String message = e.getBindingResult().getFieldErrors() + .stream() + .map(FieldError::getDefaultMessage) + .collect(Collectors.joining(", ")); + log.error("参数校验失败:{}", message); + return Result.error(ResultCode.BAD_REQUEST.getCode(), message); + } + + /** + * 处理绑定异常 + */ + @ExceptionHandler(BindException.class) + public Result handleBindException(BindException e) { + String message = e.getFieldErrors() + .stream() + .map(FieldError::getDefaultMessage) + .collect(Collectors.joining(", ")); + log.error("参数绑定失败:{}", message); + return Result.error(ResultCode.BAD_REQUEST.getCode(), message); + } + + /** + * 处理参数类型不匹配异常 + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public Result handleTypeMismatchException(MethodArgumentTypeMismatchException e) { + log.error("参数类型不匹配:{}", e.getMessage()); + return Result.error(ResultCode.BAD_REQUEST.getCode(), "参数类型错误"); + } + + /** + * 处理IllegalArgumentException + */ + @ExceptionHandler(IllegalArgumentException.class) + public Result handleIllegalArgumentException(IllegalArgumentException e) { + log.error("非法参数异常:{}", e.getMessage()); + return Result.error(ResultCode.BAD_REQUEST.getCode(), e.getMessage()); + } + + /** + * 处理其他所有异常 + */ + @ExceptionHandler(Exception.class) + public Result handleException(Exception e) { + log.error("系统异常:", e); + return Result.error(ResultCode.ERROR.getCode(), "系统异常,请联系管理员"); + } +} + diff --git a/backend/src/main/java/com/designhub/interceptor/PermissionInterceptor.java b/backend/src/main/java/com/designhub/interceptor/PermissionInterceptor.java new file mode 100644 index 0000000..83de279 --- /dev/null +++ b/backend/src/main/java/com/designhub/interceptor/PermissionInterceptor.java @@ -0,0 +1,166 @@ +package com.designhub.interceptor; + +import com.designhub.annotation.RequirePermission; +import com.designhub.annotation.RequireRole; +import com.designhub.entity.Role; +import com.designhub.exception.BusinessException; +import com.designhub.service.PermissionService; +import com.designhub.service.RoleService; +import com.designhub.util.JwtUtil; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.util.Arrays; +import java.util.List; + +/** + * 权限拦截器 + * 拦截带有@RequirePermission或@RequireRole注解的接口 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class PermissionInterceptor implements HandlerInterceptor { + + private final PermissionService permissionService; + private final RoleService roleService; + private final JwtUtil jwtUtil; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if (!(handler instanceof HandlerMethod)) { + return true; + } + + HandlerMethod handlerMethod = (HandlerMethod) handler; + + // 检查权限注解 + RequirePermission requirePermission = handlerMethod.getMethodAnnotation(RequirePermission.class); + if (requirePermission == null) { + requirePermission = handlerMethod.getBeanType().getAnnotation(RequirePermission.class); + } + + // 检查角色注解 + RequireRole requireRole = handlerMethod.getMethodAnnotation(RequireRole.class); + if (requireRole == null) { + requireRole = handlerMethod.getBeanType().getAnnotation(RequireRole.class); + } + + // 获取当前登录用户ID(从JWT Token或Session中获取) + Long userId = getCurrentUserId(request); + + // 如果方法或类上有权限或角色注解,则需要验证用户身份和权限 + if (requirePermission != null || requireRole != null) { + if (userId == null) { + throw new BusinessException("未登录或登录已过期"); + } + + // 检查权限 + if (requirePermission != null) { + checkPermissions(userId, requirePermission); + } + + // 检查角色 + if (requireRole != null) { + checkRoles(userId, requireRole); + } + } + // 如果没有权限或角色注解,允许访问(公开接口) + // 具体的权限控制由各个Controller方法自己决定 + + return true; + } + + /** + * 检查权限 + */ + private void checkPermissions(Long userId, RequirePermission requirePermission) { + String[] requiredPermissions = requirePermission.value(); + if (requiredPermissions.length == 0) { + return; + } + + // 获取用户的所有权限代码 + List userPermissions = permissionService.getPermissionCodesByUserId(userId); + + boolean hasPermission; + if (requirePermission.mode() == RequirePermission.LogicalMode.AND) { + // AND模式:需要拥有所有权限 + hasPermission = userPermissions.containsAll(Arrays.asList(requiredPermissions)); + } else { + // OR模式:需要拥有任一权限 + hasPermission = Arrays.stream(requiredPermissions) + .anyMatch(userPermissions::contains); + } + + if (!hasPermission) { + throw new BusinessException("权限不足"); + } + } + + /** + * 检查角色 + */ + private void checkRoles(Long userId, RequireRole requireRole) { + String[] requiredRoles = requireRole.value(); + if (requiredRoles.length == 0) { + return; + } + + // 获取用户的所有角色代码 + List userRoles = roleService.getRolesByUserId(userId); + List userRoleCodes = userRoles.stream() + .map(Role::getRoleCode) + .toList(); + + boolean hasRole; + if (requireRole.mode() == RequireRole.LogicalMode.AND) { + // AND模式:需要拥有所有角色 + hasRole = userRoleCodes.containsAll(Arrays.asList(requiredRoles)); + } else { + // OR模式:需要拥有任一角色 + hasRole = Arrays.stream(requiredRoles) + .anyMatch(userRoleCodes::contains); + } + + if (!hasRole) { + throw new BusinessException("角色权限不足"); + } + } + + /** + * 获取当前登录用户ID + * 从JWT Token中解析用户ID + */ + private Long getCurrentUserId(HttpServletRequest request) { + // 从Authorization请求头中获取Token + String authHeader = request.getHeader("Authorization"); + if (authHeader != null && authHeader.startsWith("Bearer ")) { + String token = authHeader.substring(7); + if (jwtUtil.validateToken(token)) { + Long userId = jwtUtil.getUserIdFromToken(token); + if (userId != null) { + return userId; + } + } + } + + // 向后兼容:从请求头中获取用户ID(临时实现) + String userIdStr = request.getHeader("X-User-Id"); + if (userIdStr != null) { + try { + return Long.parseLong(userIdStr); + } catch (NumberFormatException e) { + log.warn("Invalid user ID: {}", userIdStr); + } + } + + return null; + } +} + diff --git a/backend/src/main/java/com/designhub/mapper/AchievementMapper.java b/backend/src/main/java/com/designhub/mapper/AchievementMapper.java new file mode 100644 index 0000000..b213693 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/AchievementMapper.java @@ -0,0 +1,13 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.Achievement; +import org.apache.ibatis.annotations.Mapper; + +/** + * 成就徽章Mapper接口 + */ +@Mapper +public interface AchievementMapper extends BaseMapper { +} + diff --git a/backend/src/main/java/com/designhub/mapper/BrandMapper.java b/backend/src/main/java/com/designhub/mapper/BrandMapper.java new file mode 100644 index 0000000..6c65281 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/BrandMapper.java @@ -0,0 +1,13 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.Brand; +import org.apache.ibatis.annotations.Mapper; + +/** + * 品牌Mapper接口 + */ +@Mapper +public interface BrandMapper extends BaseMapper { +} + diff --git a/backend/src/main/java/com/designhub/mapper/CategoryMapper.java b/backend/src/main/java/com/designhub/mapper/CategoryMapper.java new file mode 100644 index 0000000..2676d47 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/CategoryMapper.java @@ -0,0 +1,13 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.Category; +import org.apache.ibatis.annotations.Mapper; + +/** + * 类目Mapper接口 + */ +@Mapper +public interface CategoryMapper extends BaseMapper { +} + diff --git a/backend/src/main/java/com/designhub/mapper/CheckInMapper.java b/backend/src/main/java/com/designhub/mapper/CheckInMapper.java new file mode 100644 index 0000000..2f0c0a4 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/CheckInMapper.java @@ -0,0 +1,29 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.CheckIn; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.time.LocalDate; + +/** + * 签到记录Mapper接口 + */ +@Mapper +public interface CheckInMapper extends BaseMapper { + + /** + * 查询用户今天是否已签到 + */ + @Select("SELECT COUNT(*) FROM user_check_in WHERE user_id = #{userId} AND check_in_date = #{date} AND deleted = 0") + int countTodayCheckIn(@Param("userId") Long userId, @Param("date") LocalDate date); + + /** + * 获取用户最近一次签到记录 + */ + @Select("SELECT * FROM user_check_in WHERE user_id = #{userId} AND deleted = 0 ORDER BY check_in_date DESC LIMIT 1") + CheckIn getLatestCheckIn(@Param("userId") Long userId); +} + diff --git a/backend/src/main/java/com/designhub/mapper/ContestMapper.java b/backend/src/main/java/com/designhub/mapper/ContestMapper.java new file mode 100644 index 0000000..a0f230a --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/ContestMapper.java @@ -0,0 +1,12 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.Contest; +import org.apache.ibatis.annotations.Mapper; + +/** + * 竞赛活动Mapper + */ +@Mapper +public interface ContestMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/designhub/mapper/ContestParticipantMapper.java b/backend/src/main/java/com/designhub/mapper/ContestParticipantMapper.java new file mode 100644 index 0000000..56e4b56 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/ContestParticipantMapper.java @@ -0,0 +1,28 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.ContestParticipant; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + * 竞赛参与者Mapper + */ +@Mapper +public interface ContestParticipantMapper extends BaseMapper { + + /** + * 获取竞赛排行榜 + */ + @Select("SELECT * FROM contest_participant WHERE contest_id = #{contestId} ORDER BY total_score DESC, task_completed DESC") + List getLeaderboard(@Param("contestId") Long contestId); + + /** + * 获取用户在竞赛中的排名 + */ + @Select("SELECT COUNT(*) + 1 FROM contest_participant WHERE contest_id = #{contestId} AND total_score > (SELECT total_score FROM contest_participant WHERE contest_id = #{contestId} AND user_id = #{userId})") + Integer getUserRank(@Param("contestId") Long contestId, @Param("userId") Long userId); +} diff --git a/backend/src/main/java/com/designhub/mapper/DelayPenaltyRecordMapper.java b/backend/src/main/java/com/designhub/mapper/DelayPenaltyRecordMapper.java new file mode 100644 index 0000000..137189b --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/DelayPenaltyRecordMapper.java @@ -0,0 +1,35 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.DelayPenaltyRecord; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + * 延误扣分记录Mapper接口 + */ +@Mapper +public interface DelayPenaltyRecordMapper extends BaseMapper { + + /** + * 根据任务ID查询延误记录 + */ + @Select("SELECT * FROM delay_penalty_record WHERE task_id = #{taskId} ORDER BY create_time DESC") + List findByTaskId(@Param("taskId") Long taskId); + + /** + * 根据设计师ID查询延误记录 + */ + @Select("SELECT * FROM delay_penalty_record WHERE designer_id = #{designerId} ORDER BY create_time DESC") + List findByDesignerId(@Param("designerId") Long designerId); + + /** + * 统计设计师的延误扣分总数 + */ + @Select("SELECT COALESCE(SUM(penalty_score), 0) FROM delay_penalty_record WHERE designer_id = #{designerId} AND status = 'confirmed'") + Integer sumPenaltyScoreByDesignerId(@Param("designerId") Long designerId); +} + diff --git a/backend/src/main/java/com/designhub/mapper/DelayPenaltyRuleMapper.java b/backend/src/main/java/com/designhub/mapper/DelayPenaltyRuleMapper.java new file mode 100644 index 0000000..b127994 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/DelayPenaltyRuleMapper.java @@ -0,0 +1,22 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.DelayPenaltyRule; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + * 延误扣分规则Mapper接口 + */ +@Mapper +public interface DelayPenaltyRuleMapper extends BaseMapper { + + /** + * 查询所有启用的规则,按最小小时数排序 + */ + @Select("SELECT * FROM delay_penalty_rule WHERE enabled = 1 ORDER BY min_hours ASC") + List findAllEnabled(); +} + diff --git a/backend/src/main/java/com/designhub/mapper/DemandTypeDepartmentMapper.java b/backend/src/main/java/com/designhub/mapper/DemandTypeDepartmentMapper.java new file mode 100644 index 0000000..98f4346 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/DemandTypeDepartmentMapper.java @@ -0,0 +1,28 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.DemandTypeDepartment; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + * 需求类型-接单部门关联Mapper + */ +@Mapper +public interface DemandTypeDepartmentMapper extends BaseMapper { + + /** + * 根据需求类型ID获取可接单的部门ID列表 + */ + @Select("SELECT department_id FROM demand_type_department WHERE demand_type_id = #{demandTypeId}") + List selectDepartmentIdsByDemandTypeId(@Param("demandTypeId") Long demandTypeId); + + /** + * 根据部门ID获取可接的需求类型ID列表 + */ + @Select("SELECT demand_type_id FROM demand_type_department WHERE department_id = #{departmentId}") + List selectDemandTypeIdsByDepartmentId(@Param("departmentId") Long departmentId); +} diff --git a/backend/src/main/java/com/designhub/mapper/DemandTypeMapper.java b/backend/src/main/java/com/designhub/mapper/DemandTypeMapper.java new file mode 100644 index 0000000..e04d4e7 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/DemandTypeMapper.java @@ -0,0 +1,13 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.DemandType; +import org.apache.ibatis.annotations.Mapper; + +/** + * 需求类型Mapper接口 + */ +@Mapper +public interface DemandTypeMapper extends BaseMapper { +} + diff --git a/backend/src/main/java/com/designhub/mapper/DepartmentMapper.java b/backend/src/main/java/com/designhub/mapper/DepartmentMapper.java new file mode 100644 index 0000000..1fae49b --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/DepartmentMapper.java @@ -0,0 +1,22 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.Department; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + * 部门Mapper接口 + */ +@Mapper +public interface DepartmentMapper extends BaseMapper { + + /** + * 查询部门树形结构 + */ + @Select("SELECT * FROM sys_dept WHERE deleted = 0 ORDER BY sort_order ASC") + List selectDeptTree(); +} + diff --git a/backend/src/main/java/com/designhub/mapper/DingTalkConfigMapper.java b/backend/src/main/java/com/designhub/mapper/DingTalkConfigMapper.java new file mode 100644 index 0000000..bd56c36 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/DingTalkConfigMapper.java @@ -0,0 +1,12 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.DingTalkConfig; +import org.apache.ibatis.annotations.Mapper; + +/** + * 钉钉配置 Mapper + */ +@Mapper +public interface DingTalkConfigMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/designhub/mapper/FileUploadMapper.java b/backend/src/main/java/com/designhub/mapper/FileUploadMapper.java new file mode 100644 index 0000000..779d54f --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/FileUploadMapper.java @@ -0,0 +1,12 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.FileUpload; +import org.apache.ibatis.annotations.Mapper; + +/** + * 文件上传记录Mapper + */ +@Mapper +public interface FileUploadMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/designhub/mapper/NotificationMapper.java b/backend/src/main/java/com/designhub/mapper/NotificationMapper.java new file mode 100644 index 0000000..a3d2168 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/NotificationMapper.java @@ -0,0 +1,26 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.Notification; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Update; + +/** + * 通知消息Mapper接口 + */ +@Mapper +public interface NotificationMapper extends BaseMapper { + + /** + * 批量标记为已读 + */ + @Update("UPDATE notification SET is_read = 1, read_time = NOW() WHERE receiver_id = #{receiverId} AND is_read = 0 AND deleted = 0") + int markAllAsRead(@Param("receiverId") Long receiverId); + + /** + * 获取未读数量 + */ + long countUnread(@Param("receiverId") Long receiverId); +} + diff --git a/backend/src/main/java/com/designhub/mapper/PermissionMapper.java b/backend/src/main/java/com/designhub/mapper/PermissionMapper.java new file mode 100644 index 0000000..72b14a3 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/PermissionMapper.java @@ -0,0 +1,33 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.Permission; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + * 权限Mapper接口 + */ +@Mapper +public interface PermissionMapper extends BaseMapper { + + /** + * 根据角色ID查询权限列表 + */ + @Select("SELECT p.* FROM sys_permission p " + + "INNER JOIN sys_role_permission rp ON p.id = rp.permission_id " + + "WHERE rp.role_id = #{roleId} AND p.deleted = 0 AND p.status = 1") + List selectPermissionsByRoleId(Long roleId); + + /** + * 根据用户ID查询权限列表 + */ + @Select("SELECT DISTINCT p.* FROM sys_permission p " + + "INNER JOIN sys_role_permission rp ON p.id = rp.permission_id " + + "INNER JOIN sys_user_role ur ON rp.role_id = ur.role_id " + + "WHERE ur.user_id = #{userId} AND p.deleted = 0 AND p.status = 1") + List selectPermissionsByUserId(Long userId); +} + diff --git a/backend/src/main/java/com/designhub/mapper/PlatformMapper.java b/backend/src/main/java/com/designhub/mapper/PlatformMapper.java new file mode 100644 index 0000000..54d0c72 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/PlatformMapper.java @@ -0,0 +1,13 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.Platform; +import org.apache.ibatis.annotations.Mapper; + +/** + * 平台Mapper接口 + */ +@Mapper +public interface PlatformMapper extends BaseMapper { +} + diff --git a/backend/src/main/java/com/designhub/mapper/RewardRuleMapper.java b/backend/src/main/java/com/designhub/mapper/RewardRuleMapper.java new file mode 100644 index 0000000..5546fda --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/RewardRuleMapper.java @@ -0,0 +1,12 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.RewardRule; +import org.apache.ibatis.annotations.Mapper; + +/** + * 自动奖励规则 Mapper + */ +@Mapper +public interface RewardRuleMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/designhub/mapper/RoleMapper.java b/backend/src/main/java/com/designhub/mapper/RoleMapper.java new file mode 100644 index 0000000..4473601 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/RoleMapper.java @@ -0,0 +1,25 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.Role; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + * 角色Mapper接口 + */ +@Mapper +public interface RoleMapper extends BaseMapper { + + /** + * 根据用户ID查询角色列表 + * 从sys_user表的role_ids字段解析角色ID,然后查询角色信息 + */ + @Select("SELECT r.* FROM sys_role r " + + "WHERE FIND_IN_SET(r.id, (SELECT role_ids FROM sys_user WHERE id = #{userId})) > 0 " + + "AND r.deleted = 0 AND r.status = 1") + List selectRolesByUserId(Long userId); +} + diff --git a/backend/src/main/java/com/designhub/mapper/RolePermissionMapper.java b/backend/src/main/java/com/designhub/mapper/RolePermissionMapper.java new file mode 100644 index 0000000..dc489db --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/RolePermissionMapper.java @@ -0,0 +1,13 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.RolePermission; +import org.apache.ibatis.annotations.Mapper; + +/** + * 角色权限关联Mapper接口 + */ +@Mapper +public interface RolePermissionMapper extends BaseMapper { +} + diff --git a/backend/src/main/java/com/designhub/mapper/S3ConfigMapper.java b/backend/src/main/java/com/designhub/mapper/S3ConfigMapper.java new file mode 100644 index 0000000..6e0001f --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/S3ConfigMapper.java @@ -0,0 +1,19 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.S3Config; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +/** + * S3配置Mapper + */ +@Mapper +public interface S3ConfigMapper extends BaseMapper { + + /** + * 获取激活的S3配置 + */ + @Select("SELECT * FROM sys_s3_config WHERE is_active = 1 AND deleted = 0 LIMIT 1") + S3Config getActiveConfig(); +} diff --git a/backend/src/main/java/com/designhub/mapper/ScorePoolMapper.java b/backend/src/main/java/com/designhub/mapper/ScorePoolMapper.java new file mode 100644 index 0000000..b3d18d9 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/ScorePoolMapper.java @@ -0,0 +1,12 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.ScorePool; +import org.apache.ibatis.annotations.Mapper; + +/** + * 激励积分池 Mapper + */ +@Mapper +public interface ScorePoolMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/designhub/mapper/ScorePoolRecordMapper.java b/backend/src/main/java/com/designhub/mapper/ScorePoolRecordMapper.java new file mode 100644 index 0000000..109e472 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/ScorePoolRecordMapper.java @@ -0,0 +1,12 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.ScorePoolRecord; +import org.apache.ibatis.annotations.Mapper; + +/** + * 积分池使用记录 Mapper + */ +@Mapper +public interface ScorePoolRecordMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/designhub/mapper/SystemConfigMapper.java b/backend/src/main/java/com/designhub/mapper/SystemConfigMapper.java new file mode 100644 index 0000000..b81ffea --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/SystemConfigMapper.java @@ -0,0 +1,13 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.SystemConfig; +import org.apache.ibatis.annotations.Mapper; + +/** + * 系统配置Mapper接口 + */ +@Mapper +public interface SystemConfigMapper extends BaseMapper { +} + diff --git a/backend/src/main/java/com/designhub/mapper/TaskApprovalLogMapper.java b/backend/src/main/java/com/designhub/mapper/TaskApprovalLogMapper.java new file mode 100644 index 0000000..85910c7 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/TaskApprovalLogMapper.java @@ -0,0 +1,12 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.TaskApprovalLog; +import org.apache.ibatis.annotations.Mapper; + +/** + * 任务审核记录 Mapper + */ +@Mapper +public interface TaskApprovalLogMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/designhub/mapper/TaskAssignLogMapper.java b/backend/src/main/java/com/designhub/mapper/TaskAssignLogMapper.java new file mode 100644 index 0000000..72582e9 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/TaskAssignLogMapper.java @@ -0,0 +1,12 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.TaskAssignLog; +import org.apache.ibatis.annotations.Mapper; + +/** + * 任务指派记录Mapper + */ +@Mapper +public interface TaskAssignLogMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/designhub/mapper/TaskAuditLogMapper.java b/backend/src/main/java/com/designhub/mapper/TaskAuditLogMapper.java new file mode 100644 index 0000000..4e68e12 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/TaskAuditLogMapper.java @@ -0,0 +1,12 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.TaskAuditLog; +import org.apache.ibatis.annotations.Mapper; + +/** + * 任务审核日志Mapper + */ +@Mapper +public interface TaskAuditLogMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/designhub/mapper/TaskItemMapper.java b/backend/src/main/java/com/designhub/mapper/TaskItemMapper.java new file mode 100644 index 0000000..034044e --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/TaskItemMapper.java @@ -0,0 +1,177 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.designhub.entity.TaskItem; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 任务子项 Mapper 接口 + */ +@Mapper +public interface TaskItemMapper extends BaseMapper { + + /** + * 查询可接单的子任务列表(带主任务信息) + */ + @Select("") + IPage selectAvailableItems(Page page, + @Param("taskTypeId") Long taskTypeId, + @Param("keyword") String keyword); + + /** + * 查询进行中的子任务列表(按接单人) + */ + @Select("") + IPage selectInProgressItems(Page page, + @Param("receiverId") Long receiverId); + + /** + * 查询已完成的子任务列表(按接单人) + */ + @Select("") + IPage selectCompletedItems(Page page, + @Param("receiverId") Long receiverId); + + /** + * 根据任务ID查询所有子任务 + */ + @Select("SELECT ti.*, " + + " dt.type_name AS taskTypeName " + + "FROM task_item ti " + + "LEFT JOIN demand_type dt ON ti.task_type_id = dt.id " + + "WHERE ti.task_id = #{taskId} " + + "ORDER BY ti.sort_order ASC") + List selectByTaskId(@Param("taskId") Long taskId); + + /** + * 查询下一个待解锁的子任务(顺序流水线) + */ + @Select("SELECT * FROM task_item " + + "WHERE task_id = #{taskId} " + + " AND prev_item_id = #{prevItemId} " + + " AND status = 'locked' " + + " AND is_locked = 1 " + + "LIMIT 1") + TaskItem selectNextLockedItem(@Param("taskId") Long taskId, + @Param("prevItemId") Long prevItemId); + + /** + * 抢单子任务(乐观锁) + */ + @Update("UPDATE task_item SET " + + "receiver_id = #{receiverId}, " + + "receiver_name = #{receiverName}, " + + "status = 'in_progress', " + + "update_time = NOW() " + + "WHERE id = #{itemId} " + + "AND status = 'pending' " + + "AND is_locked = 0 " + + "AND receiver_id IS NULL") + int grabItem(@Param("itemId") Long itemId, + @Param("receiverId") Long receiverId, + @Param("receiverName") String receiverName); + + /** + * 解锁子任务 + */ + @Update("UPDATE task_item SET " + + "status = 'pending', " + + "is_locked = 0, " + + "update_time = NOW() " + + "WHERE id = #{itemId}") + int unlockItem(@Param("itemId") Long itemId); + + /** + * 统计任务的完成子项数量 + */ + @Select("SELECT COUNT(*) FROM task_item " + + "WHERE task_id = #{taskId} AND status = 'completed'") + int countCompletedItems(@Param("taskId") Long taskId); + + /** + * 统计任务的总子项数量 + */ + @Select("SELECT COUNT(*) FROM task_item WHERE task_id = #{taskId}") + int countTotalItems(@Param("taskId") Long taskId); +} diff --git a/backend/src/main/java/com/designhub/mapper/TaskMapper.java b/backend/src/main/java/com/designhub/mapper/TaskMapper.java new file mode 100644 index 0000000..de44a7e --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/TaskMapper.java @@ -0,0 +1,206 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.designhub.entity.Task; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 任务 Mapper 接口 + */ +@Mapper +public interface TaskMapper extends BaseMapper { + + /** + * 查询任务大厅(待接单的任务) + */ + @Select("") + IPage selectTaskHall(Page page, + @Param("taskTypeId") Long taskTypeId, + @Param("demandTypeId") Long demandTypeId, + @Param("brandId") Long brandId, + @Param("categoryId") Long categoryId, + @Param("platformId") Long platformId, + @Param("taskSubType") String taskSubType, + @Param("minScore") Integer minScore, + @Param("maxScore") Integer maxScore, + @Param("keyword") String keyword, + @Param("publisherName") String publisherName, + @Param("timeRange") String timeRange, + @Param("sortBy") String sortBy); + + /** + * 查询我发布的任务 + */ + @Select("") + IPage selectMyPublishedTasks(Page page, + @Param("userId") Long userId, + @Param("status") String status); + + /** + * 查询我接收的任务 + */ + @Select("") + IPage selectMyReceivedTasks(Page page, + @Param("userId") Long userId, + @Param("status") String status); + + /** + * 根据任务编号查询 + */ + @Select("SELECT * FROM task WHERE task_no = #{taskNo} AND deleted = 0") + Task selectByTaskNo(@Param("taskNo") String taskNo); + + /** + * 查询设计师当前进行中的任务数量 + */ + @Select("SELECT COUNT(*) FROM task WHERE receiver_id = #{designerId} " + + "AND status IN ('in_progress', 'review') AND deleted = 0") + int countInProgressTasks(@Param("designerId") Long designerId); + + /** + * 抢单时基于条件更新任务,避免并发 + */ + @Update("UPDATE task SET " + + "receiver_id = #{receiverId}, " + + "receiver_dept_id = #{receiverDeptId}, " + + "status = 'in_progress', " + + "start_time = #{startTime}, " + + "actual_start_time = #{startTime}, " + + "deadline = CASE WHEN #{deadline} IS NOT NULL THEN #{deadline} ELSE deadline END, " + + "update_time = NOW() " + + "WHERE id = #{taskId} " + + "AND deleted = 0 " + + "AND status = 'pending' " + + "AND release_type = 'recruitment' " + + "AND receiver_id IS NULL") + int grabTask(@Param("taskId") Long taskId, + @Param("receiverId") Long receiverId, + @Param("receiverDeptId") Long receiverDeptId, + @Param("startTime") LocalDateTime startTime, + @Param("deadline") LocalDateTime deadline); + + /** + * 查询需求部门负责人的任务列表(部门内所有成员发起的任务) + */ + @Select("") + IPage selectLeaderTaskPage(Page page, + @Param("deptId") Long deptId, + @Param("auditStatus") String auditStatus); + + /** + * 查询接单部门负责人视图(部门成员接单的所有任务) + */ + @Select("") + IPage selectDeptReceivingTaskPage(Page page, + @Param("deptId") Long deptId, + @Param("status") String status); +} + diff --git a/backend/src/main/java/com/designhub/mapper/TaskProgressUpdateLogMapper.java b/backend/src/main/java/com/designhub/mapper/TaskProgressUpdateLogMapper.java new file mode 100644 index 0000000..c443d3c --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/TaskProgressUpdateLogMapper.java @@ -0,0 +1,12 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.TaskProgressUpdateLog; +import org.apache.ibatis.annotations.Mapper; + +/** + * 任务进度更新记录Mapper + */ +@Mapper +public interface TaskProgressUpdateLogMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/designhub/mapper/TaskReviewMapper.java b/backend/src/main/java/com/designhub/mapper/TaskReviewMapper.java new file mode 100644 index 0000000..7da0716 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/TaskReviewMapper.java @@ -0,0 +1,13 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.TaskReview; +import org.apache.ibatis.annotations.Mapper; + +/** + * 任务评价Mapper接口 + */ +@Mapper +public interface TaskReviewMapper extends BaseMapper { +} + diff --git a/backend/src/main/java/com/designhub/mapper/TaskSubTypeMapper.java b/backend/src/main/java/com/designhub/mapper/TaskSubTypeMapper.java new file mode 100644 index 0000000..ea7f51d --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/TaskSubTypeMapper.java @@ -0,0 +1,12 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.TaskSubType; +import org.apache.ibatis.annotations.Mapper; + +/** + * 任务子类型 Mapper + */ +@Mapper +public interface TaskSubTypeMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/designhub/mapper/TeamPKMapper.java b/backend/src/main/java/com/designhub/mapper/TeamPKMapper.java new file mode 100644 index 0000000..2f6ca1f --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/TeamPKMapper.java @@ -0,0 +1,12 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.TeamPK; +import org.apache.ibatis.annotations.Mapper; + +/** + * 团队PK Mapper + */ +@Mapper +public interface TeamPKMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/designhub/mapper/TeamScorePoolMapper.java b/backend/src/main/java/com/designhub/mapper/TeamScorePoolMapper.java new file mode 100644 index 0000000..ffd9c43 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/TeamScorePoolMapper.java @@ -0,0 +1,13 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.TeamScorePool; +import org.apache.ibatis.annotations.Mapper; + +/** + * 团队积分池Mapper接口 + */ +@Mapper +public interface TeamScorePoolMapper extends BaseMapper { +} + diff --git a/backend/src/main/java/com/designhub/mapper/ThirdPartyCallbackMapper.java b/backend/src/main/java/com/designhub/mapper/ThirdPartyCallbackMapper.java new file mode 100644 index 0000000..0bf372c --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/ThirdPartyCallbackMapper.java @@ -0,0 +1,12 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.ThirdPartyCallback; +import org.apache.ibatis.annotations.Mapper; + +/** + * 第三方回调记录Mapper + */ +@Mapper +public interface ThirdPartyCallbackMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/designhub/mapper/UserAchievementMapper.java b/backend/src/main/java/com/designhub/mapper/UserAchievementMapper.java new file mode 100644 index 0000000..4ac466e --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/UserAchievementMapper.java @@ -0,0 +1,13 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.UserAchievement; +import org.apache.ibatis.annotations.Mapper; + +/** + * 用户成就关联Mapper接口 + */ +@Mapper +public interface UserAchievementMapper extends BaseMapper { +} + diff --git a/backend/src/main/java/com/designhub/mapper/UserGoalMapper.java b/backend/src/main/java/com/designhub/mapper/UserGoalMapper.java new file mode 100644 index 0000000..4c9c7ac --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/UserGoalMapper.java @@ -0,0 +1,12 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.UserGoal; +import org.apache.ibatis.annotations.Mapper; + +/** + * 用户目标Mapper + */ +@Mapper +public interface UserGoalMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/designhub/mapper/UserMapper.java b/backend/src/main/java/com/designhub/mapper/UserMapper.java new file mode 100644 index 0000000..e11e541 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/UserMapper.java @@ -0,0 +1,12 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.User; +import org.apache.ibatis.annotations.Mapper; + +/** + * 用户Mapper接口 + */ +@Mapper +public interface UserMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/designhub/mapper/UserRoleMapper.java b/backend/src/main/java/com/designhub/mapper/UserRoleMapper.java new file mode 100644 index 0000000..942cf33 --- /dev/null +++ b/backend/src/main/java/com/designhub/mapper/UserRoleMapper.java @@ -0,0 +1,13 @@ +package com.designhub.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.designhub.entity.UserRole; +import org.apache.ibatis.annotations.Mapper; + +/** + * 用户角色关联Mapper接口 + */ +@Mapper +public interface UserRoleMapper extends BaseMapper { +} + diff --git a/backend/src/main/java/com/designhub/service/AchievementService.java b/backend/src/main/java/com/designhub/service/AchievementService.java new file mode 100644 index 0000000..4cadbc2 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/AchievementService.java @@ -0,0 +1,208 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.Achievement; +import com.designhub.entity.UserAchievement; +import com.designhub.mapper.AchievementMapper; +import com.designhub.mapper.UserAchievementMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 成就系统Service + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class AchievementService { + + private final AchievementMapper achievementMapper; + private final UserAchievementMapper userAchievementMapper; + private final NotificationService notificationService; + + /** + * 获取所有成就列表 + */ + public List getAllAchievements() { + return achievementMapper.selectList( + new LambdaQueryWrapper() + .eq(Achievement::getStatus, 1) + .orderByAsc(Achievement::getSortOrder) + ); + } + + /** + * 获取用户已达成的成就列表 + */ + public List getUserAchievements(Long userId) { + return userAchievementMapper.selectList( + new LambdaQueryWrapper() + .eq(UserAchievement::getUserId, userId) + .orderByDesc(UserAchievement::getAchievedTime) + ); + } + + /** + * 检查并解锁成就 + */ + @Transactional(rollbackFor = Exception.class) + public List checkAndUnlockAchievements(Long userId, String achievementType, Integer currentValue) { + List unlockedAchievements = new ArrayList<>(); + + // 获取该类型的所有成就 + List achievements = achievementMapper.selectList( + new LambdaQueryWrapper() + .eq(Achievement::getAchievementType, achievementType) + .eq(Achievement::getStatus, 1) + ); + + for (Achievement achievement : achievements) { + // 检查用户是否已获得该成就 + long count = userAchievementMapper.selectCount( + new LambdaQueryWrapper() + .eq(UserAchievement::getUserId, userId) + .eq(UserAchievement::getAchievementId, achievement.getId()) + ); + + if (count == 0) { + // 简单的条件判断(实际应该解析JSON条件) + // 这里假设condition格式为:{"type":"task_count","value":100} + if (checkAchievementCondition(achievement, currentValue)) { + // 解锁成就 + UserAchievement userAchievement = new UserAchievement(); + userAchievement.setUserId(userId); + userAchievement.setAchievementId(achievement.getId()); + userAchievement.setAchievedTime(LocalDateTime.now()); + userAchievement.setProgressValue(currentValue); + userAchievement.setRewardClaimed(0); + userAchievementMapper.insert(userAchievement); + + unlockedAchievements.add(achievement); + + // 发送通知 + notificationService.sendSystemNotification( + userId, + "🎉 恭喜解锁新成就", + "您已解锁成就:" + achievement.getAchievementName() + + ",奖励积分:" + achievement.getRewardScore(), + "high" + ); + + log.info("用户解锁成就: userId={}, achievement={}", userId, achievement.getAchievementName()); + } + } + } + + return unlockedAchievements; + } + + /** + * 领取成就奖励 + */ + @Transactional(rollbackFor = Exception.class) + public boolean claimAchievementReward(Long userId, Long achievementId) { + // 查询用户成就记录 + UserAchievement userAchievement = userAchievementMapper.selectOne( + new LambdaQueryWrapper() + .eq(UserAchievement::getUserId, userId) + .eq(UserAchievement::getAchievementId, achievementId) + ); + + if (userAchievement != null && userAchievement.getRewardClaimed() == 0) { + // 标记为已领取 + userAchievement.setRewardClaimed(1); + userAchievement.setClaimTime(LocalDateTime.now()); + userAchievementMapper.updateById(userAchievement); + + // TODO: 实际发放积分奖励 + Achievement achievement = achievementMapper.selectById(achievementId); + if (achievement != null && achievement.getRewardScore() != null && achievement.getRewardScore() > 0) { + // 这里应该调用积分服务发放奖励 + log.info("发放成就奖励: userId={}, score={}", userId, achievement.getRewardScore()); + } + + return true; + } + + return false; + } + + /** + * 初始化默认成就 + */ + @Transactional(rollbackFor = Exception.class) + public void initDefaultAchievements() { + // 任务类成就 + createAchievementIfNotExists("TASK_10", "初出茅庐", "完成10个任务", "task", 10, 50, "bronze"); + createAchievementIfNotExists("TASK_50", "驾轻就熟", "完成50个任务", "task", 50, 200, "silver"); + createAchievementIfNotExists("TASK_100", "百里挑一", "完成100个任务", "task", 100, 500, "gold"); + createAchievementIfNotExists("TASK_500", "业界大拿", "完成500个任务", "task", 500, 2000, "diamond"); + + // 积分类成就 + createAchievementIfNotExists("SCORE_1000", "积分新秀", "累计获得1000积分", "score", 1000, 100, "bronze"); + createAchievementIfNotExists("SCORE_5000", "积分高手", "累计获得5000积分", "score", 5000, 500, "silver"); + createAchievementIfNotExists("SCORE_10000", "积分大师", "累计获得10000积分", "score", 10000, 1000, "gold"); + + // 质量类成就 + createAchievementIfNotExists("QUALITY_10", "好评如潮", "获得10个五星好评", "quality", 10, 100, "silver"); + createAchievementIfNotExists("QUALITY_50", "完美主义", "获得50个五星好评", "quality", 50, 500, "gold"); + + // 速度类成就 + createAchievementIfNotExists("SPEED_10", "闪电侠", "提前完成任务10次", "speed", 10, 100, "silver"); + + log.info("默认成就初始化完成"); + } + + /** + * 如果不存在则创建成就 + */ + private void createAchievementIfNotExists(String code, String name, String description, + String type, Integer conditionValue, + Integer rewardScore, String level) { + long count = achievementMapper.selectCount( + new LambdaQueryWrapper() + .eq(Achievement::getAchievementCode, code) + ); + + if (count == 0) { + Achievement achievement = new Achievement(); + achievement.setAchievementCode(code); + achievement.setAchievementName(name); + achievement.setDescription(description); + achievement.setAchievementType(type); + achievement.setCondition("{\"type\":\"" + type + "_count\",\"value\":" + conditionValue + "}"); + achievement.setRewardScore(rewardScore); + achievement.setLevel(level); + achievement.setSortOrder(0); + achievement.setStatus(1); + achievementMapper.insert(achievement); + } + } + + /** + * 检查成就条件是否满足 + */ + private boolean checkAchievementCondition(Achievement achievement, Integer currentValue) { + // 简化版本:从condition中提取value进行比较 + // 实际应该解析JSON + String condition = achievement.getCondition(); + if (condition != null && condition.contains("\"value\":")) { + try { + String valueStr = condition.substring(condition.indexOf("\"value\":") + 8); + valueStr = valueStr.substring(0, valueStr.indexOf("}")); + int requiredValue = Integer.parseInt(valueStr.trim()); + return currentValue >= requiredValue; + } catch (Exception e) { + log.error("解析成就条件失败: {}", condition, e); + } + } + return false; + } +} + diff --git a/backend/src/main/java/com/designhub/service/AdminTaskService.java b/backend/src/main/java/com/designhub/service/AdminTaskService.java new file mode 100644 index 0000000..d96ac1a --- /dev/null +++ b/backend/src/main/java/com/designhub/service/AdminTaskService.java @@ -0,0 +1,425 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.common.ResultCode; +import com.designhub.entity.Department; +import com.designhub.entity.Task; +import com.designhub.entity.User; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.TaskMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; + +/** + * 管理员任务调配Service + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class AdminTaskService { + + private final TaskMapper taskMapper; + private final NotificationService notificationService; + private final UserService userService; + private final DepartmentService departmentService; + private final RoleService roleService; + + /** + * 管理员分配任务 + */ + @Transactional(rollbackFor = Exception.class) + public Task assignTask(Long taskId, Long designerId, Long adminId) { + // 验证管理员权限 + if (!isAdminUser(adminId)) { + throw new BusinessException(ResultCode.FORBIDDEN, "只有管理员可以分配任务"); + } + + // 验证设计师 + User designer = userService.getUserById(designerId); + if (designer == null) { + throw new BusinessException(ResultCode.PARAM_ERROR, "设计师不存在"); + } + + // 验证设计师部门类型(必须是接单部门) + if (designer.getDeptId() != null) { + Department dept = departmentService.getDepartmentById(designer.getDeptId()); + if (dept == null || !"receiving".equals(dept.getDeptType())) { + throw new BusinessException(ResultCode.FORBIDDEN, "只能分配给接单部门的设计师"); + } + } + + Task task = taskMapper.selectById(taskId); + if (task == null) { + throw new BusinessException(ResultCode.TASK_NOT_EXIST); + } + + if (!"pending".equals(task.getStatus()) && !"recruitment".equals(task.getReleaseType())) { + throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "只能分配待接单的任务"); + } + + // 验证任务需求类型是否允许该部门接单 + if (task.getDemandTypeId() != null && designer.getDeptId() != null) { + Department dept = departmentService.getDepartmentById(designer.getDeptId()); + if (dept != null && dept.getAllowedDemandTypes() != null && !dept.getAllowedDemandTypes().isEmpty()) { + String[] allowedTypes = dept.getAllowedDemandTypes().split(","); + boolean allowed = false; + for (String typeId : allowedTypes) { + if (task.getDemandTypeId().toString().equals(typeId.trim())) { + allowed = true; + break; + } + } + if (!allowed) { + throw new BusinessException(ResultCode.FORBIDDEN, "该设计师所在部门不支持此需求类型"); + } + } + } + + // 记录原接收人(如果有) + Long originalReceiverId = task.getReceiverId(); + + // 分配任务 + task.setReceiverId(designerId); + task.setReceiverDeptId(designer.getDeptId()); + task.setStatus("in_progress"); + task.setStartTime(LocalDateTime.now()); + task.setActualStartTime(LocalDateTime.now()); + + // 重新计算截止时间 + if (task.getEstimatedHours() != null) { + int hours = task.getEstimatedHours().intValue(); + if (hours > 0) { + task.setDeadline(LocalDateTime.now().plusHours(hours)); + } + } else if (task.getEstimatedDuration() != null) { + task.setDeadline(LocalDateTime.now().plusHours(task.getEstimatedDuration())); + } + + task.setUpdateTime(LocalDateTime.now()); + taskMapper.updateById(task); + + // 记录操作日志 + log.info("管理员分配任务: taskId={}, designerId={}, adminId={}", taskId, designerId, adminId); + + // 发送通知给设计师 + notificationService.sendTaskNotification( + designerId, + taskId, + "📋 新任务分配", + "管理员为您分配了新任务:" + task.getTitle() + ); + + // 如果有原接收人,通知他任务已被重新分配 + if (originalReceiverId != null && !originalReceiverId.equals(designerId)) { + notificationService.sendTaskNotification( + originalReceiverId, + taskId, + "⚠️ 任务重新分配", + "您的任务已被管理员重新分配" + ); + } + + return task; + } + + /** + * 管理员转移任务 + */ + @Transactional(rollbackFor = Exception.class) + public Task transferTask(Long taskId, Long newDesignerId, Long adminId, String reason) { + // 验证管理员权限 + if (!isAdminUser(adminId)) { + throw new BusinessException(ResultCode.FORBIDDEN, "只有管理员可以转移任务"); + } + + // 验证新设计师 + User newDesigner = userService.getUserById(newDesignerId); + if (newDesigner == null) { + throw new BusinessException(ResultCode.PARAM_ERROR, "新设计师不存在"); + } + + // 验证新设计师部门类型 + if (newDesigner.getDeptId() != null) { + Department dept = departmentService.getDepartmentById(newDesigner.getDeptId()); + if (dept == null || !"receiving".equals(dept.getDeptType())) { + throw new BusinessException(ResultCode.FORBIDDEN, "只能转移给接单部门的设计师"); + } + } + + Task task = taskMapper.selectById(taskId); + if (task == null) { + throw new BusinessException(ResultCode.TASK_NOT_EXIST); + } + + if (!"in_progress".equals(task.getStatus()) && !"review".equals(task.getStatus())) { + throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "只能转移进行中或待审核的任务"); + } + + Long originalDesignerId = task.getReceiverId(); + if (originalDesignerId == null) { + throw new BusinessException("任务没有接收人"); + } + + if (originalDesignerId.equals(newDesignerId)) { + throw new BusinessException("新接收人与原接收人相同"); + } + + // 计算已用时长 + LocalDateTime startTime = task.getActualStartTime(); + if (startTime != null) { + long hoursUsed = ChronoUnit.HOURS.between(startTime, LocalDateTime.now()); + + // 检查是否已延误 + LocalDateTime originalDeadline = task.getDeadline(); + if (originalDeadline != null && LocalDateTime.now().isAfter(originalDeadline)) { + // 原设计师已延误,记录延误信息 + long delayHours = ChronoUnit.HOURS.between(originalDeadline, LocalDateTime.now()); + log.warn("任务转移时发现延误: taskId={}, originalDesignerId={}, delayHours={}", + taskId, originalDesignerId, delayHours); + + // TODO: 记录延误扣分信息 + // TODO: 更新原设计师的延误统计 + } + } + + // 转移任务 + task.setReceiverId(newDesignerId); + task.setReceiverDeptId(newDesigner.getDeptId()); + task.setActualStartTime(LocalDateTime.now()); // 重新开始计时 + + // 如果任务状态是 review,转回 in_progress + if ("review".equals(task.getStatus())) { + task.setStatus("in_progress"); + } + + // 重新计算截止时间 + if (task.getEstimatedHours() != null) { + int hours = task.getEstimatedHours().intValue(); + if (hours > 0) { + task.setDeadline(LocalDateTime.now().plusHours(hours)); + } + } else if (task.getEstimatedDuration() != null) { + task.setDeadline(LocalDateTime.now().plusHours(task.getEstimatedDuration())); + } + + task.setUpdateTime(LocalDateTime.now()); + + // 在备注中记录转移信息 + String transferNote = String.format("[%s] 管理员转移任务:从设计师%d转移到设计师%d,原因:%s", + LocalDateTime.now().toString(), originalDesignerId, newDesignerId, reason); + String existingRemark = task.getRemark(); + task.setRemark(existingRemark != null ? existingRemark + "\n" + transferNote : transferNote); + + taskMapper.updateById(task); + + log.info("管理员转移任务: taskId={}, from={}, to={}, adminId={}, reason={}", + taskId, originalDesignerId, newDesignerId, adminId, reason); + + // 通知新设计师 + notificationService.sendTaskNotification( + newDesignerId, + taskId, + "📋 新任务转移", + "管理员为您转移了任务:" + task.getTitle() + ",原因:" + reason + ); + + // 通知原设计师 + notificationService.sendTaskNotification( + originalDesignerId, + taskId, + "⚠️ 任务已转移", + "您的任务已被管理员转移,原因:" + reason + ); + + return task; + } + + /** + * 批量分配任务 + */ + @Transactional(rollbackFor = Exception.class) + public int batchAssignTasks(List taskIds, Long designerId, Long adminId) { + int successCount = 0; + for (Long taskId : taskIds) { + try { + assignTask(taskId, designerId, adminId); + successCount++; + } catch (Exception e) { + log.error("批量分配任务失败: taskId={}, error={}", taskId, e.getMessage()); + } + } + log.info("批量分配任务完成: total={}, success={}, adminId={}", taskIds.size(), successCount, adminId); + return successCount; + } + + /** + * 调整任务优先级 + */ + @Transactional(rollbackFor = Exception.class) + public Task adjustTaskPriority(Long taskId, Integer newPriority, Long adminId) { + Task task = taskMapper.selectById(taskId); + if (task == null) { + throw new BusinessException("任务不存在"); + } + + Integer oldPriority = task.getPriority(); + task.setPriority(newPriority); + task.setUpdateTime(LocalDateTime.now()); + taskMapper.updateById(task); + + log.info("管理员调整任务优先级: taskId={}, old={}, new={}, adminId={}", + taskId, oldPriority, newPriority, adminId); + + // 如果任务有接收人,发送通知 + if (task.getReceiverId() != null) { + String priorityText = getPriorityText(newPriority); + notificationService.sendTaskNotification( + task.getReceiverId(), + taskId, + "⚡ 任务优先级调整", + "任务优先级已调整为:" + priorityText + ); + } + + return task; + } + + /** + * 获取任务池列表(待分配的任务) + */ + public List getTaskPool() { + return taskMapper.selectList( + new LambdaQueryWrapper() + .in(Task::getStatus, "pending", "pool") + .orderByDesc(Task::getCreateTime) + ); + } + + /** + * 获取所有进行中的任务 + */ + public List getInProgressTasks() { + return taskMapper.selectList( + new LambdaQueryWrapper() + .eq(Task::getStatus, "in_progress") + .orderByAsc(Task::getDeadline) + ); + } + + /** + * 强制完成任务(管理员操作) + */ + @Transactional(rollbackFor = Exception.class) + public Task forceCompleteTask(Long taskId, Long adminId, String reason) { + Task task = taskMapper.selectById(taskId); + if (task == null) { + throw new BusinessException("任务不存在"); + } + + task.setStatus("completed"); + task.setActualEndTime(LocalDateTime.now()); + task.setUpdateTime(LocalDateTime.now()); + + String completeNote = String.format("[%s] 管理员强制完成,原因:%s", + LocalDateTime.now().toString(), reason); + String existingRemark = task.getRemark(); + task.setRemark(existingRemark != null ? existingRemark + "\n" + completeNote : completeNote); + + taskMapper.updateById(task); + + log.info("管理员强制完成任务: taskId={}, adminId={}, reason={}", taskId, adminId, reason); + + // 通知相关人员 + if (task.getReceiverId() != null) { + notificationService.sendTaskNotification( + task.getReceiverId(), + taskId, + "✅ 任务已完成", + "管理员已将您的任务标记为完成,原因:" + reason + ); + } + + return task; + } + + /** + * 取消任务(管理员操作) + */ + @Transactional(rollbackFor = Exception.class) + public Task cancelTask(Long taskId, Long adminId, String reason) { + Task task = taskMapper.selectById(taskId); + if (task == null) { + throw new BusinessException("任务不存在"); + } + + task.setStatus("cancelled"); + task.setUpdateTime(LocalDateTime.now()); + + String cancelNote = String.format("[%s] 管理员取消任务,原因:%s", + LocalDateTime.now().toString(), reason); + String existingRemark = task.getRemark(); + task.setRemark(existingRemark != null ? existingRemark + "\n" + cancelNote : cancelNote); + + taskMapper.updateById(task); + + log.info("管理员取消任务: taskId={}, adminId={}, reason={}", taskId, adminId, reason); + + // 通知相关人员 + if (task.getReceiverId() != null) { + notificationService.sendTaskNotification( + task.getReceiverId(), + taskId, + "❌ 任务已取消", + "管理员已取消该任务,原因:" + reason + ); + } + + if (task.getPublisherId() != null) { + notificationService.sendTaskNotification( + task.getPublisherId(), + taskId, + "❌ 任务已取消", + "您发布的任务已被取消,原因:" + reason + ); + } + + return task; + } + + /** + * 获取优先级文本 + */ + private String getPriorityText(Integer priority) { + if (priority == null) return "未设置"; + return switch (priority) { + case 0 -> "低"; + case 1 -> "普通"; + case 2 -> "高"; + case 3 -> "紧急"; + default -> String.valueOf(priority); + }; + } + + /** + * 检查用户是否是管理员(ADMIN或SUPER_ADMIN) + */ + private boolean isAdminUser(Long userId) { + try { + var roles = roleService.getRolesByUserId(userId); + return roles.stream().anyMatch(role -> + "ADMIN".equals(role.getRoleCode()) || + "SUPER_ADMIN".equals(role.getRoleCode()) + ); + } catch (Exception e) { + log.warn("检查用户角色失败: userId={}, error={}", userId, e.getMessage()); + return false; + } + } +} + diff --git a/backend/src/main/java/com/designhub/service/AnalyticsService.java b/backend/src/main/java/com/designhub/service/AnalyticsService.java new file mode 100644 index 0000000..e8f29ed --- /dev/null +++ b/backend/src/main/java/com/designhub/service/AnalyticsService.java @@ -0,0 +1,514 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.*; +import com.designhub.mapper.*; +import com.designhub.vo.DemandStatisticsVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 数据分析Service + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class AnalyticsService { + + private final TaskMapper taskMapper; + private final UserMapper userMapper; + private final DepartmentMapper departmentMapper; + private final TeamScorePoolMapper teamScorePoolMapper; + private final TaskReviewMapper taskReviewMapper; + private final RoleService roleService; + + /** + * 获取需求统计数据(根据角色返回不同维度的数据) + * @param currentUserId 当前登录用户ID + * @param dateRange 时间范围 + * @param deptId 部门ID(可选,用于管理员查看特定部门) + * @param userId 用户ID(可选,用于管理员查看特定用户) + */ + public DemandStatisticsVO getDemandStatistics(Long currentUserId, String dateRange, Long deptId, Long userId) { + // 获取当前用户角色和部门信息 + List roles = roleService.getRolesByUserId(currentUserId); + User currentUser = userMapper.selectById(currentUserId); + + boolean isAdmin = roles.stream() + .anyMatch(role -> "ADMIN".equals(role.getRoleCode()) || "SUPER_ADMIN".equals(role.getRoleCode())); + + boolean isDeptHead = roles.stream() + .anyMatch(role -> "DEMAND_DEPT_HEAD".equals(role.getRoleCode()) || + "RECEIVING_DEPT_SUPERVISOR".equals(role.getRoleCode())); + + log.info("获取需求统计数据: currentUserId={}, isAdmin={}, isDeptHead={}, userDeptId={}, dateRange={}, deptId={}, userId={}", + currentUserId, isAdmin, isDeptHead, currentUser != null ? currentUser.getDeptId() : null, dateRange, deptId, userId); + + // 计算时间范围 + LocalDateTime startTime = calculateStartTime(dateRange); + LocalDateTime endTime = LocalDateTime.now(); + + // 构建查询条件 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() + .ge(startTime != null, Task::getCreateTime, startTime) + .le(Task::getCreateTime, endTime); + + // 根据角色和参数决定查询范围 + if (isAdmin) { + // 管理员:可以通过参数查看特定部门或用户的数据,否则查看所有数据 + if (deptId != null) { + queryWrapper.eq(Task::getPublisherDeptId, deptId); + log.info("管理员查看部门数据: deptId={}", deptId); + } else if (userId != null) { + queryWrapper.eq(Task::getPublisherId, userId); + log.info("管理员查看用户数据: userId={}", userId); + } else { + log.info("管理员查看全部数据"); + } + } else if (isDeptHead && currentUser != null && currentUser.getDeptId() != null) { + // 部门负责人:查看本部门的所有任务 + queryWrapper.eq(Task::getPublisherDeptId, currentUser.getDeptId()); + log.info("部门负责人查看部门数据: deptId={}", currentUser.getDeptId()); + } else { + // 普通用户:只能查看自己发布的任务 + queryWrapper.eq(Task::getPublisherId, currentUserId); + log.info("普通用户查看个人数据: userId={}", currentUserId); + } + + List tasks = taskMapper.selectList(queryWrapper); + log.info("查询到任务数量: {}", tasks.size()); + + return DemandStatisticsVO.builder() + .userStats(buildUserStats(tasks)) + .deptStats(buildDeptStats(tasks)) + .trendData(buildTrendData(tasks, dateRange)) + .typeDistribution(buildTypeDistribution(tasks)) + .overview(buildOverview(tasks)) + .build(); + } + + /** + * 构建用户维度统计 + */ + private List buildUserStats(List tasks) { + Map> tasksByUser = tasks.stream() + .collect(Collectors.groupingBy(Task::getPublisherId)); + + List stats = new ArrayList<>(); + + for (Map.Entry> entry : tasksByUser.entrySet()) { + Long userId = entry.getKey(); + List userTasks = entry.getValue(); + + User user = userMapper.selectById(userId); + if (user == null) continue; + + int totalDemands = userTasks.size(); + int completedDemands = (int) userTasks.stream() + .filter(t -> "completed".equals(t.getStatus())) + .count(); + int pendingDemands = (int) userTasks.stream() + .filter(t -> Arrays.asList("pending", "in_progress", "submitted").contains(t.getStatus())) + .count(); + + BigDecimal totalScore = userTasks.stream() + .map(Task::getScoreReward) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + BigDecimal avgScore = totalDemands > 0 + ? totalScore.divide(BigDecimal.valueOf(totalDemands), 2, RoundingMode.HALF_UP) + : BigDecimal.ZERO; + + double completionRate = totalDemands > 0 + ? (double) completedDemands / totalDemands * 100 + : 0.0; + + // 计算满意度(五星好评率) + double satisfactionRate = calculateSatisfactionRate(userTasks); + + Department dept = departmentMapper.selectById(user.getDeptId()); + + stats.add(DemandStatisticsVO.UserDemandStat.builder() + .userId(userId) + .userName(user.getUsername()) + .deptName(dept != null ? dept.getDeptName() : "") + .totalDemands(totalDemands) + .completedDemands(completedDemands) + .pendingDemands(pendingDemands) + .totalScore(totalScore) + .avgScore(avgScore) + .completionRate(completionRate) + .satisfactionRate(satisfactionRate) + .build()); + } + + // 按总需求数降序排序 + stats.sort((a, b) -> b.getTotalDemands().compareTo(a.getTotalDemands())); + + return stats; + } + + /** + * 构建部门维度统计 + */ + private List buildDeptStats(List tasks) { + Map> tasksByDept = tasks.stream() + .filter(t -> t.getPublisherDeptId() != null) + .collect(Collectors.groupingBy(Task::getPublisherDeptId)); + + List stats = new ArrayList<>(); + + for (Map.Entry> entry : tasksByDept.entrySet()) { + Long deptId = entry.getKey(); + List deptTasks = entry.getValue(); + + Department dept = departmentMapper.selectById(deptId); + if (dept == null) continue; + + TeamScorePool scorePool = teamScorePoolMapper.selectOne( + new LambdaQueryWrapper().eq(TeamScorePool::getDeptId, deptId) + ); + + int totalDemands = deptTasks.size(); + int completedDemands = (int) deptTasks.stream() + .filter(t -> "completed".equals(t.getStatus())) + .count(); + int pendingDemands = (int) deptTasks.stream() + .filter(t -> Arrays.asList("pending", "in_progress", "submitted").contains(t.getStatus())) + .count(); + + BigDecimal totalScore = deptTasks.stream() + .map(Task::getScoreReward) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + // 计算使用率和预警级别 + double utilizationRate = 0.0; + String alertLevel = "safe"; + + if (scorePool != null) { + BigDecimal totalAvailable = BigDecimal.valueOf(scorePool.getTotalScore() != null ? scorePool.getTotalScore() : 0); + if (totalAvailable.compareTo(BigDecimal.ZERO) > 0) { + utilizationRate = totalScore.divide(totalAvailable, 4, RoundingMode.HALF_UP) + .multiply(BigDecimal.valueOf(100)) + .doubleValue(); + } + + // 计算预警级别 + BigDecimal remainingScore = BigDecimal.valueOf(scorePool.getRemainingScore() != null ? scorePool.getRemainingScore() : 0); + if (totalAvailable.compareTo(BigDecimal.ZERO) > 0) { + double remainingRate = remainingScore.divide(totalAvailable, 4, RoundingMode.HALF_UP) + .multiply(BigDecimal.valueOf(100)) + .doubleValue(); + + if (remainingRate < 10) { + alertLevel = "danger"; + } else if (remainingRate < 30) { + alertLevel = "warning"; + } + } + + stats.add(DemandStatisticsVO.DeptDemandStat.builder() + .deptId(deptId) + .deptName(dept.getDeptName()) + .totalDemands(totalDemands) + .completedDemands(completedDemands) + .pendingDemands(pendingDemands) + .totalScore(totalScore) + .remainingScore(BigDecimal.valueOf(scorePool.getRemainingScore() != null ? scorePool.getRemainingScore() : 0)) + .dailyLimit(BigDecimal.valueOf(scorePool.getDailyLimit() != null ? scorePool.getDailyLimit() : 0)) + .weeklyLimit(BigDecimal.valueOf(scorePool.getWeeklyLimit() != null ? scorePool.getWeeklyLimit() : 0)) + .monthlyLimit(BigDecimal.valueOf(scorePool.getMonthlyLimit() != null ? scorePool.getMonthlyLimit() : 0)) + .dailyUsed(BigDecimal.valueOf(scorePool.getDailyUsed() != null ? scorePool.getDailyUsed() : 0)) + .weeklyUsed(BigDecimal.valueOf(scorePool.getWeeklyUsed() != null ? scorePool.getWeeklyUsed() : 0)) + .monthlyUsed(BigDecimal.valueOf(scorePool.getMonthlyUsed() != null ? scorePool.getMonthlyUsed() : 0)) + .utilizationRate(utilizationRate) + .alertLevel(alertLevel) + .build()); + } + } + + // 按总需求数降序排序 + stats.sort((a, b) -> b.getTotalDemands().compareTo(a.getTotalDemands())); + + return stats; + } + + /** + * 构建趋势数据 + */ + private List buildTrendData(List tasks, String dateRange) { + Map> tasksByDate = new LinkedHashMap<>(); + + // 根据dateRange生成日期范围 + int days = "week".equals(dateRange) ? 7 : "month".equals(dateRange) ? 30 : 90; + LocalDate endDate = LocalDate.now(); + + for (int i = days - 1; i >= 0; i--) { + LocalDate date = endDate.minusDays(i); + tasksByDate.put(date.format(DateTimeFormatter.ISO_LOCAL_DATE), new ArrayList<>()); + } + + // 将任务按日期分组 + for (Task task : tasks) { + String dateKey = task.getCreateTime().toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE); + if (tasksByDate.containsKey(dateKey)) { + tasksByDate.get(dateKey).add(task); + } + } + + List trendData = new ArrayList<>(); + for (Map.Entry> entry : tasksByDate.entrySet()) { + List dateTasks = entry.getValue(); + + int demandCount = dateTasks.size(); + int completedCount = (int) dateTasks.stream() + .filter(t -> "completed".equals(t.getStatus())) + .count(); + + BigDecimal scoreUsed = dateTasks.stream() + .map(Task::getScoreReward) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + trendData.add(DemandStatisticsVO.TrendData.builder() + .date(entry.getKey()) + .demandCount(demandCount) + .completedCount(completedCount) + .scoreUsed(scoreUsed) + .build()); + } + + return trendData; + } + + /** + * 构建类型分布 + */ + private List buildTypeDistribution(List tasks) { + Map> tasksByType = tasks.stream() + .filter(t -> t.getDemandTypeId() != null) + .collect(Collectors.groupingBy(t -> String.valueOf(t.getDemandTypeId()))); + + int totalTasks = tasks.size(); + List distribution = new ArrayList<>(); + + for (Map.Entry> entry : tasksByType.entrySet()) { + List typeTasks = entry.getValue(); + int count = typeTasks.size(); + double percentage = totalTasks > 0 ? (double) count / totalTasks * 100 : 0.0; + + BigDecimal totalScore = typeTasks.stream() + .map(Task::getScoreReward) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + distribution.add(DemandStatisticsVO.TypeDistribution.builder() + .typeName("类型" + entry.getKey()) + .count(count) + .percentage(percentage) + .totalScore(totalScore) + .build()); + } + + // 按数量降序排序 + distribution.sort((a, b) -> b.getCount().compareTo(a.getCount())); + + return distribution; + } + + /** + * 构建总览数据 + */ + private DemandStatisticsVO.OverviewData buildOverview(List tasks) { + LocalDateTime now = LocalDateTime.now(); + LocalDate today = now.toLocalDate(); + LocalDate weekStart = today.minusDays(today.getDayOfWeek().getValue() - 1); + LocalDate monthStart = today.withDayOfMonth(1); + + int totalDemands = tasks.size(); + + int todayDemands = (int) tasks.stream() + .filter(t -> t.getCreateTime().toLocalDate().equals(today)) + .count(); + + int weekDemands = (int) tasks.stream() + .filter(t -> !t.getCreateTime().toLocalDate().isBefore(weekStart)) + .count(); + + int monthDemands = (int) tasks.stream() + .filter(t -> !t.getCreateTime().toLocalDate().isBefore(monthStart)) + .count(); + + int completedDemands = (int) tasks.stream() + .filter(t -> "completed".equals(t.getStatus())) + .count(); + + int pendingDemands = (int) tasks.stream() + .filter(t -> Arrays.asList("pending", "in_progress", "submitted").contains(t.getStatus())) + .count(); + + int canceledDemands = (int) tasks.stream() + .filter(t -> "cancelled".equals(t.getStatus())) + .count(); + + BigDecimal totalScoreUsed = tasks.stream() + .map(Task::getScoreReward) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + BigDecimal todayScoreUsed = tasks.stream() + .filter(t -> t.getCreateTime().toLocalDate().equals(today)) + .map(Task::getScoreReward) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + BigDecimal weekScoreUsed = tasks.stream() + .filter(t -> !t.getCreateTime().toLocalDate().isBefore(weekStart)) + .map(Task::getScoreReward) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + BigDecimal monthScoreUsed = tasks.stream() + .filter(t -> !t.getCreateTime().toLocalDate().isBefore(monthStart)) + .map(Task::getScoreReward) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + // 计算平均完成时长 + double avgCompletionTime = calculateAvgCompletionTime(tasks); + + // 计算平均满意度 + double avgSatisfaction = calculateAvgSatisfaction(tasks); + + return DemandStatisticsVO.OverviewData.builder() + .totalDemands(totalDemands) + .todayDemands(todayDemands) + .weekDemands(weekDemands) + .monthDemands(monthDemands) + .completedDemands(completedDemands) + .pendingDemands(pendingDemands) + .canceledDemands(canceledDemands) + .totalScoreUsed(totalScoreUsed) + .todayScoreUsed(todayScoreUsed) + .weekScoreUsed(weekScoreUsed) + .monthScoreUsed(monthScoreUsed) + .avgCompletionTime(avgCompletionTime) + .avgSatisfaction(avgSatisfaction) + .build(); + } + + /** + * 计算时间范围起始时间 + */ + private LocalDateTime calculateStartTime(String dateRange) { + LocalDateTime now = LocalDateTime.now(); + switch (dateRange) { + case "week": + return now.minusWeeks(1); + case "month": + return now.minusMonths(1); + case "quarter": + return now.minusMonths(3); + case "year": + return now.minusYears(1); + default: + return null; // 全部 + } + } + + /** + * 计算满意度(五星好评率) + */ + private double calculateSatisfactionRate(List tasks) { + try { + List completedTaskIds = tasks.stream() + .filter(t -> "completed".equals(t.getStatus())) + .map(Task::getId) + .collect(Collectors.toList()); + + if (completedTaskIds.isEmpty()) { + return 0.0; + } + + long fiveStarCount = taskReviewMapper.selectCount( + new LambdaQueryWrapper() + .in(TaskReview::getTaskId, completedTaskIds) + .eq(TaskReview::getRating, 5) + ); + + return (double) fiveStarCount / completedTaskIds.size() * 100; + } catch (Exception e) { + log.warn("计算满意度失败(可能task_review表不存在): {}", e.getMessage()); + return 0.0; + } + } + + /** + * 计算平均完成时长(小时) + */ + private double calculateAvgCompletionTime(List tasks) { + List completedTasks = tasks.stream() + .filter(t -> "completed".equals(t.getStatus()) && t.getActualEndTime() != null) + .collect(Collectors.toList()); + + if (completedTasks.isEmpty()) { + return 0.0; + } + + long totalHours = completedTasks.stream() + .mapToLong(t -> { + LocalDateTime start = t.getActualStartTime() != null ? t.getActualStartTime() : t.getCreateTime(); + LocalDateTime end = t.getActualEndTime(); + return java.time.Duration.between(start, end).toHours(); + }) + .sum(); + + return (double) totalHours / completedTasks.size(); + } + + /** + * 计算平均满意度 + */ + private double calculateAvgSatisfaction(List tasks) { + try { + List completedTaskIds = tasks.stream() + .filter(t -> "completed".equals(t.getStatus())) + .map(Task::getId) + .collect(Collectors.toList()); + + if (completedTaskIds.isEmpty()) { + return 0.0; + } + + List reviews = taskReviewMapper.selectList( + new LambdaQueryWrapper() + .in(TaskReview::getTaskId, completedTaskIds) + ); + + if (reviews.isEmpty()) { + return 0.0; + } + + double totalRating = reviews.stream() + .mapToInt(TaskReview::getRating) + .sum(); + + return totalRating / reviews.size(); + } catch (Exception e) { + log.warn("计算平均满意度失败(可能task_review表不存在): {}", e.getMessage()); + return 0.0; + } + } +} + diff --git a/backend/src/main/java/com/designhub/service/CheckInService.java b/backend/src/main/java/com/designhub/service/CheckInService.java new file mode 100644 index 0000000..7403e1e --- /dev/null +++ b/backend/src/main/java/com/designhub/service/CheckInService.java @@ -0,0 +1,164 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.CheckIn; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.CheckInMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.List; + +/** + * 签到Service + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class CheckInService { + + private final CheckInMapper checkInMapper; + private final NotificationService notificationService; + private final SystemConfigService systemConfigService; + + /** + * 用户签到 + */ + @Transactional(rollbackFor = Exception.class) + public CheckIn checkIn(Long userId) { + LocalDate today = LocalDate.now(); + + // 检查今天是否已签到 + int count = checkInMapper.countTodayCheckIn(userId, today); + if (count > 0) { + throw new BusinessException("今天已经签到过了"); + } + + // 获取最近一次签到记录 + CheckIn lastCheckIn = checkInMapper.getLatestCheckIn(userId); + + int continuousDays = 1; + if (lastCheckIn != null) { + LocalDate lastDate = lastCheckIn.getCheckInDate(); + long daysBetween = ChronoUnit.DAYS.between(lastDate, today); + + if (daysBetween == 1) { + // 连续签到 + continuousDays = lastCheckIn.getContinuousDays() + 1; + } else if (daysBetween > 1) { + // 中断了,重新开始 + continuousDays = 1; + } + } + + // 计算奖励积分(连续签到越多,奖励越高) + int baseScore = systemConfigService.getIntValue("incentive.daily_checkin_score", 5); + int rewardScore = calculateRewardScore(baseScore, continuousDays); + + // 创建签到记录 + CheckIn checkIn = new CheckIn(); + checkIn.setUserId(userId); + checkIn.setCheckInDate(today); + checkIn.setContinuousDays(continuousDays); + checkIn.setRewardScore(rewardScore); + checkIn.setRemark("连续签到" + continuousDays + "天"); + checkInMapper.insert(checkIn); + + // TODO: 实际发放积分奖励 + log.info("用户签到成功: userId={}, continuousDays={}, rewardScore={}", userId, continuousDays, rewardScore); + + // 发送通知 + String message = String.format("签到成功!连续签到%d天,获得%d积分", continuousDays, rewardScore); + if (continuousDays >= 7) { + message += "。坚持得真棒!🎉"; + } + notificationService.sendSystemNotification(userId, "每日签到", message, "normal"); + + // 检查签到成就 + if (continuousDays == 7 || continuousDays == 30 || continuousDays == 100) { + notificationService.sendSystemNotification( + userId, + "🏆 签到成就达成", + String.format("恭喜您达成连续签到%d天的成就!", continuousDays), + "high" + ); + } + + return checkIn; + } + + /** + * 检查用户今天是否已签到 + */ + public boolean hasCheckedInToday(Long userId) { + LocalDate today = LocalDate.now(); + return checkInMapper.countTodayCheckIn(userId, today) > 0; + } + + /** + * 获取用户签到记录 + */ + public List getUserCheckInHistory(Long userId, Integer limit) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(CheckIn::getUserId, userId); + queryWrapper.orderByDesc(CheckIn::getCheckInDate); + if (limit != null && limit > 0) { + queryWrapper.last("LIMIT " + limit); + } + return checkInMapper.selectList(queryWrapper); + } + + /** + * 获取用户连续签到天数 + */ + public int getContinuousDays(Long userId) { + CheckIn lastCheckIn = checkInMapper.getLatestCheckIn(userId); + if (lastCheckIn != null) { + LocalDate today = LocalDate.now(); + LocalDate lastDate = lastCheckIn.getCheckInDate(); + long daysBetween = ChronoUnit.DAYS.between(lastDate, today); + + if (daysBetween == 0) { + // 今天已签到 + return lastCheckIn.getContinuousDays(); + } else if (daysBetween == 1) { + // 今天还没签到,但昨天签了 + return lastCheckIn.getContinuousDays(); + } + } + return 0; + } + + /** + * 获取用户总签到天数 + */ + public long getTotalCheckInDays(Long userId) { + return checkInMapper.selectCount( + new LambdaQueryWrapper() + .eq(CheckIn::getUserId, userId) + ); + } + + /** + * 计算奖励积分 + * 基础分值 + 连续签到加成 + */ + private int calculateRewardScore(int baseScore, int continuousDays) { + if (continuousDays >= 30) { + return baseScore * 3; // 30天以上,3倍奖励 + } else if (continuousDays >= 14) { + return (int) (baseScore * 2.5); // 14天以上,2.5倍奖励 + } else if (continuousDays >= 7) { + return baseScore * 2; // 7天以上,2倍奖励 + } else if (continuousDays >= 3) { + return (int) (baseScore * 1.5); // 3天以上,1.5倍奖励 + } else { + return baseScore; // 基础奖励 + } + } +} + diff --git a/backend/src/main/java/com/designhub/service/ConfigService.java b/backend/src/main/java/com/designhub/service/ConfigService.java new file mode 100644 index 0000000..a3ee383 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/ConfigService.java @@ -0,0 +1,266 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.*; +import com.designhub.mapper.*; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 配置管理Service + */ +@Service +@RequiredArgsConstructor +public class ConfigService { + + private final DemandTypeMapper demandTypeMapper; + private final BrandMapper brandMapper; + private final PlatformMapper platformMapper; + private final CategoryMapper categoryMapper; + + // ============ 需求类型管理 ============ + + /** + * 获取所有需求类型(包括禁用的,用于管理页面) + */ + public List getAllDemandTypes() { + return demandTypeMapper.selectList( + new LambdaQueryWrapper() + .orderByAsc(DemandType::getSortOrder) + ); + } + + /** + * 获取所有启用的需求类型(用于下拉选择) + */ + public List getEnabledDemandTypes() { + return demandTypeMapper.selectList( + new LambdaQueryWrapper() + .eq(DemandType::getIsEnabled, 1) + .orderByAsc(DemandType::getSortOrder) + ); + } + + /** + * 根据ID获取需求类型 + */ + public DemandType getDemandTypeById(Long id) { + return demandTypeMapper.selectById(id); + } + + /** + * 创建需求类型 + */ + public DemandType createDemandType(DemandType demandType) { + demandTypeMapper.insert(demandType); + return demandType; + } + + /** + * 更新需求类型 + */ + public boolean updateDemandType(DemandType demandType) { + return demandTypeMapper.updateById(demandType) > 0; + } + + /** + * 删除需求类型 + */ + public boolean deleteDemandType(Long id) { + return demandTypeMapper.deleteById(id) > 0; + } + + // ============ 品牌管理 ============ + + /** + * 获取所有品牌(包括禁用的,用于管理页面) + */ + public List getAllBrands() { + return brandMapper.selectList( + new LambdaQueryWrapper() + .orderByAsc(Brand::getSortOrder) + ); + } + + /** + * 获取所有启用的品牌(用于下拉选择) + */ + public List getEnabledBrands() { + return brandMapper.selectList( + new LambdaQueryWrapper() + .eq(Brand::getIsEnabled, 1) + .orderByAsc(Brand::getSortOrder) + ); + } + + /** + * 根据ID获取品牌 + */ + public Brand getBrandById(Long id) { + return brandMapper.selectById(id); + } + + /** + * 创建品牌 + */ + public Brand createBrand(Brand brand) { + brandMapper.insert(brand); + return brand; + } + + /** + * 更新品牌 + */ + public boolean updateBrand(Brand brand) { + return brandMapper.updateById(brand) > 0; + } + + /** + * 删除品牌 + */ + public boolean deleteBrand(Long id) { + return brandMapper.deleteById(id) > 0; + } + + // ============ 平台管理 ============ + + /** + * 获取所有平台(包括禁用的,用于管理页面) + */ + public List getAllPlatforms() { + return platformMapper.selectList( + new LambdaQueryWrapper() + .orderByAsc(Platform::getSortOrder) + ); + } + + /** + * 获取所有启用的平台(用于下拉选择) + */ + public List getEnabledPlatforms() { + return platformMapper.selectList( + new LambdaQueryWrapper() + .eq(Platform::getIsEnabled, 1) + .orderByAsc(Platform::getSortOrder) + ); + } + + /** + * 根据ID获取平台 + */ + public Platform getPlatformById(Long id) { + return platformMapper.selectById(id); + } + + /** + * 创建平台 + */ + public Platform createPlatform(Platform platform) { + platformMapper.insert(platform); + return platform; + } + + /** + * 更新平台 + */ + public boolean updatePlatform(Platform platform) { + return platformMapper.updateById(platform) > 0; + } + + /** + * 删除平台 + */ + public boolean deletePlatform(Long id) { + return platformMapper.deleteById(id) > 0; + } + + // ============ 类目管理 ============ + + /** + * 获取所有叶子类目(包括禁用的,用于管理页面) + */ + public List getAllLeafCategories() { + return categoryMapper.selectList( + new LambdaQueryWrapper() + .eq(Category::getIsLeaf, 1) + .orderByAsc(Category::getSortOrder) + ); + } + + /** + * 获取所有类目(包括禁用的,用于管理页面) + */ + public List getAllCategories() { + return categoryMapper.selectList( + new LambdaQueryWrapper() + .orderByAsc(Category::getSortOrder) + ); + } + + /** + * 获取所有启用的类目(树形结构) + */ + public List getEnabledCategories() { + return categoryMapper.selectList( + new LambdaQueryWrapper() + .eq(Category::getIsEnabled, 1) + .orderByAsc(Category::getSortOrder) + ); + } + + /** + * 获取所有启用的叶子类目(用于下拉选择) + */ + public List getEnabledLeafCategories() { + return categoryMapper.selectList( + new LambdaQueryWrapper() + .eq(Category::getIsEnabled, 1) + .eq(Category::getIsLeaf, 1) + .orderByAsc(Category::getSortOrder) + ); + } + + /** + * 根据父ID获取子类目 + */ + public List getCategoriesByParentId(Long parentId) { + return categoryMapper.selectList( + new LambdaQueryWrapper() + .eq(Category::getParentId, parentId) + .eq(Category::getIsEnabled, 1) + .orderByAsc(Category::getSortOrder) + ); + } + + /** + * 根据ID获取类目 + */ + public Category getCategoryById(Long id) { + return categoryMapper.selectById(id); + } + + /** + * 创建类目 + */ + public Category createCategory(Category category) { + categoryMapper.insert(category); + return category; + } + + /** + * 更新类目 + */ + public boolean updateCategory(Category category) { + return categoryMapper.updateById(category) > 0; + } + + /** + * 删除类目 + */ + public boolean deleteCategory(Long id) { + return categoryMapper.deleteById(id) > 0; + } +} + diff --git a/backend/src/main/java/com/designhub/service/ContestService.java b/backend/src/main/java/com/designhub/service/ContestService.java new file mode 100644 index 0000000..3d39983 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/ContestService.java @@ -0,0 +1,483 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.designhub.common.ResultCode; +import com.designhub.entity.Contest; +import com.designhub.entity.ContestParticipant; +import com.designhub.entity.User; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.ContestMapper; +import com.designhub.mapper.ContestParticipantMapper; +import com.designhub.mapper.UserMapper; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 竞赛活动Service + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ContestService { + + private final ContestMapper contestMapper; + private final ContestParticipantMapper participantMapper; + private final UserMapper userMapper; + private final NotificationService notificationService; + private final ObjectMapper objectMapper; + + /** + * 创建竞赛 + */ + @Transactional + public Contest createContest(Contest contest) { + // 验证时间 + if (contest.getStartTime().isAfter(contest.getEndTime())) { + throw new BusinessException(ResultCode.PARAM_ERROR, "开始时间不能晚于结束时间"); + } + + contest.setStatus("draft"); + contest.setParticipantCount(0); + contest.setDeleted(0); + contestMapper.insert(contest); + + log.info("创建竞赛成功: id={}, name={}", contest.getId(), contest.getName()); + return contest; + } + + /** + * 更新竞赛 + */ + @Transactional + public Contest updateContest(Long id, Contest contest) { + Contest existing = contestMapper.selectById(id); + if (existing == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "竞赛不存在"); + } + + // 进行中的竞赛不能修改规则 + if ("active".equals(existing.getStatus()) && contest.getRules() != null) { + throw new BusinessException(ResultCode.PARAM_ERROR, "进行中的竞赛不能修改规则"); + } + + contest.setId(id); + contestMapper.updateById(contest); + + return contestMapper.selectById(id); + } + + /** + * 发布竞赛(从草稿变为待开始或进行中) + */ + @Transactional + public Contest publishContest(Long id) { + Contest contest = contestMapper.selectById(id); + if (contest == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "竞赛不存在"); + } + + if (!"draft".equals(contest.getStatus())) { + throw new BusinessException(ResultCode.PARAM_ERROR, "只有草稿状态的竞赛可以发布"); + } + + LocalDateTime now = LocalDateTime.now(); + if (contest.getStartTime().isBefore(now)) { + contest.setStatus("active"); + } else { + contest.setStatus("scheduled"); + } + + contestMapper.updateById(contest); + + // 初始化参与者 + initParticipants(contest); + + // 发送通知 + notifyContestStart(contest); + + return contest; + } + + /** + * 初始化竞赛参与者 + */ + private void initParticipants(Contest contest) { + List users; + + if ("all".equals(contest.getParticipantScope())) { + // 全体设计师 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(User::getDeleted, 0); + users = userMapper.selectList(wrapper); + } else if ("department".equals(contest.getParticipantScope())) { + // 指定部门 + try { + List deptIds = objectMapper.readValue(contest.getDepartmentIds(), + new TypeReference>() {}); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.in(User::getDeptId, deptIds).eq(User::getDeleted, 0); + users = userMapper.selectList(wrapper); + } catch (JsonProcessingException e) { + throw new BusinessException(ResultCode.PARAM_ERROR, "部门ID格式错误"); + } + } else { + // 指定用户 + try { + List userIds = objectMapper.readValue(contest.getUserIds(), + new TypeReference>() {}); + users = userMapper.selectBatchIds(userIds); + } catch (JsonProcessingException e) { + throw new BusinessException(ResultCode.PARAM_ERROR, "用户ID格式错误"); + } + } + + for (User user : users) { + ContestParticipant participant = new ContestParticipant(); + participant.setContestId(contest.getId()); + participant.setUserId(user.getId()); + participant.setUserName(user.getRealName()); + participant.setDeptId(user.getDeptId()); + participant.setDeptName(user.getDeptName()); + participant.setTaskCompleted(0); + participant.setFiveStarCount(0); + participant.setEarlyCompletionCount(0); + participant.setPraiseCount(0); + participant.setNoDelayDays(0); + participant.setTotalScore(0); + participant.setCurrentRank(0); + participant.setRewardClaimed(0); + participant.setRewardScore(0); + participantMapper.insert(participant); + } + + // 更新参与人数 + contest.setParticipantCount(users.size()); + contestMapper.updateById(contest); + } + + /** + * 发送竞赛开始通知 + */ + private void notifyContestStart(Contest contest) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ContestParticipant::getContestId, contest.getId()); + List participants = participantMapper.selectList(wrapper); + + for (ContestParticipant p : participants) { + notificationService.sendNotification( + p.getUserId(), + "竞赛活动开始", + String.format("竞赛「%s」已开始!快来参与吧!", contest.getName()), + "contest", + contest.getId() + ); + } + } + + /** + * 获取竞赛列表 + */ + public IPage getContestList(Integer page, Integer pageSize, String status) { + Page pageParam = new Page<>(page, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + if (status != null && !status.isEmpty()) { + wrapper.eq(Contest::getStatus, status); + } + + wrapper.orderByDesc(Contest::getCreateTime); + return contestMapper.selectPage(pageParam, wrapper); + } + + /** + * 获取竞赛详情 + */ + public Contest getContestById(Long id) { + Contest contest = contestMapper.selectById(id); + if (contest == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "竞赛不存在"); + } + return contest; + } + + /** + * 获取竞赛排行榜 + */ + public List getLeaderboard(Long contestId, Integer limit) { + List list = participantMapper.getLeaderboard(contestId); + if (limit != null && list.size() > limit) { + return list.subList(0, limit); + } + return list; + } + + /** + * 获取用户在竞赛中的排名和数据 + */ + public ContestParticipant getUserContestData(Long contestId, Long userId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ContestParticipant::getContestId, contestId) + .eq(ContestParticipant::getUserId, userId); + ContestParticipant participant = participantMapper.selectOne(wrapper); + + if (participant != null) { + // 计算排名 + Integer rank = participantMapper.getUserRank(contestId, userId); + participant.setCurrentRank(rank); + } + + return participant; + } + + /** + * 更新参与者数据(当任务完成时调用) + */ + @Transactional + public void updateParticipantStats(Long userId, boolean fiveStar, boolean earlyCompletion) { + // 获取用户参与的所有进行中的竞赛 + LocalDateTime now = LocalDateTime.now(); + LambdaQueryWrapper contestWrapper = new LambdaQueryWrapper<>(); + contestWrapper.eq(Contest::getStatus, "active"); + List activeContests = contestMapper.selectList(contestWrapper); + + for (Contest contest : activeContests) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ContestParticipant::getContestId, contest.getId()) + .eq(ContestParticipant::getUserId, userId); + ContestParticipant participant = participantMapper.selectOne(wrapper); + + if (participant != null) { + participant.setTaskCompleted(participant.getTaskCompleted() + 1); + if (fiveStar) { + participant.setFiveStarCount(participant.getFiveStarCount() + 1); + } + if (earlyCompletion) { + participant.setEarlyCompletionCount(participant.getEarlyCompletionCount() + 1); + } + + // 重新计算总分 + int totalScore = calculateScore(contest, participant); + participant.setTotalScore(totalScore); + + participantMapper.updateById(participant); + } + } + } + + /** + * 计算参与者得分 + */ + private int calculateScore(Contest contest, ContestParticipant participant) { + try { + Map> rules = objectMapper.readValue( + contest.getRules(), new TypeReference>>() {}); + + int score = 0; + + if (rules.containsKey("taskCompleted")) { + score += participant.getTaskCompleted() * rules.get("taskCompleted").getOrDefault("score", 2); + } + if (rules.containsKey("fiveStarReview")) { + score += participant.getFiveStarCount() * rules.get("fiveStarReview").getOrDefault("score", 10); + } + if (rules.containsKey("earlyCompletion")) { + score += participant.getEarlyCompletionCount() * rules.get("earlyCompletion").getOrDefault("score", 5); + } + if (rules.containsKey("praise")) { + score += participant.getPraiseCount() * rules.get("praise").getOrDefault("score", 15); + } + + return score; + } catch (JsonProcessingException e) { + log.error("解析竞赛规则失败", e); + return 0; + } + } + + /** + * 结束竞赛并发放奖励 + */ + @Transactional + public void endContest(Long id) { + Contest contest = contestMapper.selectById(id); + if (contest == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "竞赛不存在"); + } + + if (!"active".equals(contest.getStatus())) { + throw new BusinessException(ResultCode.PARAM_ERROR, "只有进行中的竞赛可以结束"); + } + + contest.setStatus("ended"); + contestMapper.updateById(contest); + + // 发放奖励 + distributeRewards(contest); + + log.info("竞赛结束: id={}, name={}", contest.getId(), contest.getName()); + } + + /** + * 发放竞赛奖励 + */ + private void distributeRewards(Contest contest) { + try { + List> rewards = objectMapper.readValue( + contest.getRewards(), new TypeReference>>() {}); + + List leaderboard = participantMapper.getLeaderboard(contest.getId()); + + for (int i = 0; i < leaderboard.size(); i++) { + ContestParticipant participant = leaderboard.get(i); + int rank = i + 1; + participant.setCurrentRank(rank); + + // 查找对应排名的奖励 + for (Map reward : rewards) { + int rewardRank = (Integer) reward.get("rank"); + if (rank == rewardRank) { + int rewardScore = (Integer) reward.get("score"); + participant.setRewardScore(rewardScore); + + // 发送通知 + notificationService.sendNotification( + participant.getUserId(), + "竞赛获奖通知", + String.format("恭喜!您在竞赛「%s」中获得第%d名,奖励%d积分!", + contest.getName(), rank, rewardScore), + "contest", + contest.getId() + ); + break; + } + } + + participantMapper.updateById(participant); + } + } catch (JsonProcessingException e) { + log.error("解析竞赛奖励配置失败", e); + } + } + + /** + * 领取竞赛奖励 + */ + @Transactional + public boolean claimReward(Long contestId, Long userId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ContestParticipant::getContestId, contestId) + .eq(ContestParticipant::getUserId, userId); + ContestParticipant participant = participantMapper.selectOne(wrapper); + + if (participant == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "您未参与此竞赛"); + } + + if (participant.getRewardClaimed() == 1) { + throw new BusinessException(ResultCode.PARAM_ERROR, "奖励已领取"); + } + + if (participant.getRewardScore() == null || participant.getRewardScore() <= 0) { + throw new BusinessException(ResultCode.PARAM_ERROR, "暂无奖励可领取"); + } + + // 更新用户积分 + User user = userMapper.selectById(userId); + int currentScore = user.getScore() != null ? user.getScore() : 0; + user.setScore(currentScore + participant.getRewardScore()); + userMapper.updateById(user); + + // 标记已领取 + participant.setRewardClaimed(1); + participant.setRewardClaimTime(LocalDateTime.now()); + participantMapper.updateById(participant); + + return true; + } + + /** + * 定时任务:自动开始和结束竞赛 + */ + @Scheduled(fixedRate = 60000) // 每分钟检查一次 + @Transactional + public void checkContestStatus() { + LocalDateTime now = LocalDateTime.now(); + + // 自动开始待开始的竞赛 + LambdaQueryWrapper startWrapper = new LambdaQueryWrapper<>(); + startWrapper.eq(Contest::getStatus, "scheduled") + .le(Contest::getStartTime, now); + List toStart = contestMapper.selectList(startWrapper); + + for (Contest contest : toStart) { + contest.setStatus("active"); + contestMapper.updateById(contest); + notifyContestStart(contest); + log.info("竞赛自动开始: id={}, name={}", contest.getId(), contest.getName()); + } + + // 自动结束已到期的竞赛 + LambdaQueryWrapper endWrapper = new LambdaQueryWrapper<>(); + endWrapper.eq(Contest::getStatus, "active") + .le(Contest::getEndTime, now); + List toEnd = contestMapper.selectList(endWrapper); + + for (Contest contest : toEnd) { + endContest(contest.getId()); + } + } + + /** + * 取消竞赛 + */ + @Transactional + public void cancelContest(Long id) { + Contest contest = contestMapper.selectById(id); + if (contest == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "竞赛不存在"); + } + + if ("ended".equals(contest.getStatus())) { + throw new BusinessException(ResultCode.PARAM_ERROR, "已结束的竞赛不能取消"); + } + + contest.setStatus("cancelled"); + contestMapper.updateById(contest); + + log.info("竞赛已取消: id={}, name={}", contest.getId(), contest.getName()); + } + + /** + * 删除竞赛 + */ + @Transactional + public void deleteContest(Long id) { + Contest contest = contestMapper.selectById(id); + if (contest == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "竞赛不存在"); + } + + if ("active".equals(contest.getStatus())) { + throw new BusinessException(ResultCode.PARAM_ERROR, "进行中的竞赛不能删除"); + } + + contestMapper.deleteById(id); + + // 删除参与者数据 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ContestParticipant::getContestId, id); + participantMapper.delete(wrapper); + } +} diff --git a/backend/src/main/java/com/designhub/service/DelayPenaltyService.java b/backend/src/main/java/com/designhub/service/DelayPenaltyService.java new file mode 100644 index 0000000..18808f7 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/DelayPenaltyService.java @@ -0,0 +1,183 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.DelayPenaltyRecord; +import com.designhub.entity.DelayPenaltyRule; +import com.designhub.mapper.DelayPenaltyRecordMapper; +import com.designhub.mapper.DelayPenaltyRuleMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; + +/** + * 延误扣分Service + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DelayPenaltyService { + + private final DelayPenaltyRecordMapper delayPenaltyRecordMapper; + private final DelayPenaltyRuleMapper delayPenaltyRuleMapper; + + /** + * 计算延误扣分 + * 根据需求文档的规则: + * - 轻微延误(< 2小时):每小时扣2分 + * - 一般延误(2-8小时):每小时扣3分 + * - 严重延误(8-24小时):每小时扣5分 + * - 重大延误(> 24小时):基础50分+每天10分 + */ + public DelayPenaltyRecord calculateDelayPenalty(Long taskId, Long designerId, + LocalDateTime deadline, + LocalDateTime actualTime) { + if (deadline == null || actualTime == null) { + return null; + } + + // 如果实际时间早于截止时间,未延误 + if (actualTime.isBefore(deadline) || actualTime.isEqual(deadline)) { + return null; + } + + // 计算延误小时数 + long delayHours = ChronoUnit.HOURS.between(deadline, actualTime); + long delayDays = ChronoUnit.DAYS.between(deadline, actualTime); + + // 确定延误等级和扣分 + String delayLevel; + Integer penaltyScore; + + if (delayHours < 2) { + // 轻微延误:每小时扣2分 + delayLevel = "light"; + penaltyScore = (int) Math.ceil(delayHours * 2.0); + } else if (delayHours < 8) { + // 一般延误:每小时扣3分 + delayLevel = "general"; + penaltyScore = (int) Math.ceil(delayHours * 3.0); + } else if (delayHours < 24) { + // 严重延误:每小时扣5分 + delayLevel = "severe"; + penaltyScore = (int) Math.ceil(delayHours * 5.0); + } else { + // 重大延误:基础50分+每天10分 + delayLevel = "major"; + penaltyScore = 50 + (int) delayDays * 10; + } + + // 创建延误扣分记录 + DelayPenaltyRecord record = new DelayPenaltyRecord(); + record.setTaskId(taskId); + record.setDesignerId(designerId); + record.setDelayDays((int) delayDays); + record.setDelayHours(delayHours); + record.setDelayLevel(delayLevel); + record.setPenaltyScore(penaltyScore); + record.setOriginalDeadline(deadline); + record.setActualTime(actualTime); + record.setStatus("confirmed"); + + log.info("计算延误扣分: taskId={}, designerId={}, delayHours={}, delayLevel={}, penaltyScore={}", + taskId, designerId, delayHours, delayLevel, penaltyScore); + + return record; + } + + /** + * 保存延误扣分记录 + */ + @Transactional(rollbackFor = Exception.class) + public DelayPenaltyRecord saveDelayPenaltyRecord(DelayPenaltyRecord record) { + if (record == null) { + return null; + } + delayPenaltyRecordMapper.insert(record); + log.info("保存延误扣分记录: recordId={}, taskId={}, penaltyScore={}", + record.getId(), record.getTaskId(), record.getPenaltyScore()); + return record; + } + + /** + * 根据任务ID查询延误记录 + */ + public List getDelayRecordsByTaskId(Long taskId) { + return delayPenaltyRecordMapper.findByTaskId(taskId); + } + + /** + * 根据设计师ID查询延误记录 + */ + public List getDelayRecordsByDesignerId(Long designerId) { + return delayPenaltyRecordMapper.findByDesignerId(designerId); + } + + /** + * 统计设计师的延误扣分总数 + */ + public Integer getTotalPenaltyScoreByDesignerId(Long designerId) { + Integer total = delayPenaltyRecordMapper.sumPenaltyScoreByDesignerId(designerId); + return total != null ? total : 0; + } + + /** + * 申诉延误扣分 + */ + @Transactional(rollbackFor = Exception.class) + public void appealDelayPenalty(Long recordId, Long designerId, String appealReason) { + DelayPenaltyRecord record = delayPenaltyRecordMapper.selectById(recordId); + if (record == null) { + throw new RuntimeException("延误扣分记录不存在"); + } + + if (!record.getDesignerId().equals(designerId)) { + throw new RuntimeException("无权申诉此记录"); + } + + if (!"confirmed".equals(record.getStatus())) { + throw new RuntimeException("该记录已申诉或已处理"); + } + + record.setStatus("appealed"); + record.setAppealReason(appealReason); + record.setAppealTime(LocalDateTime.now()); + delayPenaltyRecordMapper.updateById(record); + + log.info("设计师申诉延误扣分: recordId={}, designerId={}, reason={}", + recordId, designerId, appealReason); + } + + /** + * 管理员审批申诉 + */ + @Transactional(rollbackFor = Exception.class) + public void approveAppeal(Long recordId, Long approverId, Boolean approved, String appealResult) { + DelayPenaltyRecord record = delayPenaltyRecordMapper.selectById(recordId); + if (record == null) { + throw new RuntimeException("延误扣分记录不存在"); + } + + if (!"appealed".equals(record.getStatus())) { + throw new RuntimeException("该记录不是申诉状态"); + } + + if (approved) { + record.setStatus("approved"); + } else { + record.setStatus("rejected"); + } + record.setAppealResult(appealResult); + record.setApproverId(approverId); + record.setApproveTime(LocalDateTime.now()); + delayPenaltyRecordMapper.updateById(record); + + log.info("管理员审批申诉: recordId={}, approverId={}, approved={}", + recordId, approverId, approved); + } +} + diff --git a/backend/src/main/java/com/designhub/service/DemandTypeDepartmentService.java b/backend/src/main/java/com/designhub/service/DemandTypeDepartmentService.java new file mode 100644 index 0000000..8eaeffb --- /dev/null +++ b/backend/src/main/java/com/designhub/service/DemandTypeDepartmentService.java @@ -0,0 +1,74 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.DemandTypeDepartment; +import com.designhub.mapper.DemandTypeDepartmentMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 需求类型-接单部门关联服务 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DemandTypeDepartmentService { + + private final DemandTypeDepartmentMapper demandTypeDepartmentMapper; + + /** + * 设置需求类型的可接单部门列表 + */ + @Transactional(rollbackFor = Exception.class) + public boolean setDemandTypeDepartments(Long demandTypeId, List departmentIds) { + log.info("设置需求类型可接单部门: demandTypeId={}, departmentIds={}", demandTypeId, departmentIds); + + // 删除旧的关联 + demandTypeDepartmentMapper.delete( + new LambdaQueryWrapper() + .eq(DemandTypeDepartment::getDemandTypeId, demandTypeId) + ); + + // 添加新的关联 + if (departmentIds != null && !departmentIds.isEmpty()) { + for (Long deptId : departmentIds) { + DemandTypeDepartment relation = new DemandTypeDepartment(); + relation.setDemandTypeId(demandTypeId); + relation.setDepartmentId(deptId); + demandTypeDepartmentMapper.insert(relation); + } + } + + return true; + } + + /** + * 获取需求类型的可接单部门ID列表 + */ + public List getDepartmentIdsByDemandType(Long demandTypeId) { + return demandTypeDepartmentMapper.selectDepartmentIdsByDemandTypeId(demandTypeId); + } + + /** + * 获取部门可接的需求类型ID列表 + */ + public List getDemandTypeIdsByDepartment(Long departmentId) { + return demandTypeDepartmentMapper.selectDemandTypeIdsByDepartmentId(departmentId); + } + + /** + * 检查部门是否可以接指定类型的任务 + */ + public boolean canDepartmentReceiveDemandType(Long departmentId, Long demandTypeId) { + List allowedDemandTypes = getDemandTypeIdsByDepartment(departmentId); + // 如果没有配置,表示可以接所有类型 + if (allowedDemandTypes == null || allowedDemandTypes.isEmpty()) { + return true; + } + return allowedDemandTypes.contains(demandTypeId); + } +} diff --git a/backend/src/main/java/com/designhub/service/DepartmentService.java b/backend/src/main/java/com/designhub/service/DepartmentService.java new file mode 100644 index 0000000..d25546a --- /dev/null +++ b/backend/src/main/java/com/designhub/service/DepartmentService.java @@ -0,0 +1,459 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.dto.DepartmentCreateRequest; +import com.designhub.dto.DepartmentLimitSettingsRequest; +import com.designhub.dto.DepartmentScoreAdjustRequest; +import com.designhub.dto.DepartmentUpdateRequest; +import com.designhub.entity.Department; +import com.designhub.entity.User; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.DepartmentMapper; +import com.designhub.mapper.UserMapper; +import com.designhub.vo.DepartmentOverviewVO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 部门管理Service + */ +@Service +@RequiredArgsConstructor +public class DepartmentService { + + private final DepartmentMapper departmentMapper; + private final TeamScorePoolService teamScorePoolService; + private final UserMapper userMapper; + + /** + * 获取所有部门列表 + */ + public List getAllDepartments() { + return departmentMapper.selectList( + new LambdaQueryWrapper() + .eq(Department::getStatus, 1) + .orderByAsc(Department::getSortOrder) + ); + } + + /** + * 获取部门树形结构 + */ + public List getDepartmentTree() { + List allDepts = departmentMapper.selectDeptTree(); + return buildTree(allDepts, 0L); + } + + /** + * 根据类型获取部门列表 + */ + public List getDepartmentsByType(String deptType) { + return departmentMapper.selectList( + new LambdaQueryWrapper() + .eq(Department::getDeptType, deptType) + .eq(Department::getStatus, 1) + .orderByAsc(Department::getSortOrder) + ); + } + + /** + * 根据ID获取部门 + */ + public Department getDepartmentById(Long id) { + return departmentMapper.selectById(id); + } + + /** + * 部门概览列表 + */ + public List getDepartmentOverviewList() { + List departments = getAllDepartments(); + Map memberCountMap = loadMemberCountMap(); + Map leaderNameMap = loadLeaderNames(departments); + return departments.stream() + .map(dept -> buildOverviewVO(dept, memberCountMap, leaderNameMap)) + .collect(Collectors.toList()); + } + + /** + * 获取单个部门概览 + */ + public DepartmentOverviewVO getDepartmentOverview(Long deptId) { + Department department = getDepartmentById(deptId); + if (department == null) { + throw new BusinessException("部门不存在"); + } + Map memberCountMap = loadMemberCountMap(); + Map leaderNameMap = loadLeaderNames(Collections.singletonList(department)); + return buildOverviewVO(department, memberCountMap, leaderNameMap); + } + + /** + * 创建部门 + */ + @Transactional(rollbackFor = Exception.class) + public DepartmentOverviewVO createDepartment(DepartmentCreateRequest request) { + validateDeptType(request.getDeptType()); + + Department department = new Department(); + department.setDeptName(request.getDeptName()); + department.setDeptCode(request.getDeptCode()); + department.setDeptType(request.getDeptType()); + department.setParentId(request.getParentId() != null ? request.getParentId() : 0L); + department.setLeaderId(request.getLeaderId()); + department.setSortOrder(request.getSortOrder()); + department.setStatus(request.getStatus()); + department.setAllowedDemandTypes(joinAllowedTypes(request.getAllowedDemandTypes())); + + departmentMapper.insert(department); + + // 初始化积分池 + teamScorePoolService.upsertPool(department.getId(), department.getDeptName()); + if ("demand".equals(request.getDeptType())) { + if (request.getInitialScoreBalance() != null) { + teamScorePoolService.setBalance(department.getId(), request.getInitialScoreBalance()); + department.setScoreBalance(request.getInitialScoreBalance()); + } + teamScorePoolService.updateLimitConfig( + department.getId(), + defaultLimitEnabled(request.getLimitEnabled()), + defaultLimitPeriod(request.getLimitPeriod()), + request.getPeriodLimit(), + request.getAlertThreshold() + ); + department.setLimitEnabled(defaultLimitEnabled(request.getLimitEnabled())); + department.setLimitPeriod(defaultLimitPeriod(request.getLimitPeriod())); + department.setPeriodLimit(request.getPeriodLimit()); + department.setAlertThreshold(request.getAlertThreshold()); + department.setOverLimitAction( + StringUtils.hasText(request.getOverLimitAction()) ? request.getOverLimitAction() : "alert" + ); + } + + return getDepartmentOverview(department.getId()); + } + + /** + * 更新部门 + */ + @Transactional(rollbackFor = Exception.class) + public boolean updateDepartment(Long id, DepartmentUpdateRequest request) { + Department existDept = getDepartmentById(id); + if (existDept == null) { + throw new BusinessException("部门不存在"); + } + + if (StringUtils.hasText(request.getDeptName())) { + existDept.setDeptName(request.getDeptName()); + } + if (StringUtils.hasText(request.getDeptCode())) { + existDept.setDeptCode(request.getDeptCode()); + } + if (StringUtils.hasText(request.getDeptType())) { + validateDeptType(request.getDeptType()); + existDept.setDeptType(request.getDeptType()); + } + if (request.getParentId() != null) { + existDept.setParentId(request.getParentId()); + } + if (request.getLeaderId() != null) { + existDept.setLeaderId(request.getLeaderId()); + } + if (request.getSortOrder() != null) { + existDept.setSortOrder(request.getSortOrder()); + } + if (request.getStatus() != null) { + existDept.setStatus(request.getStatus()); + } + if (request.getAllowedDemandTypes() != null) { + existDept.setAllowedDemandTypes(joinAllowedTypes(request.getAllowedDemandTypes())); + } + if (request.getLimitEnabled() != null) { + existDept.setLimitEnabled(request.getLimitEnabled()); + } + if (request.getLimitPeriod() != null) { + existDept.setLimitPeriod(request.getLimitPeriod()); + } + if (request.getPeriodLimit() != null) { + existDept.setPeriodLimit(request.getPeriodLimit()); + } + if (request.getAlertThreshold() != null) { + existDept.setAlertThreshold(request.getAlertThreshold()); + } + if (request.getOverLimitAction() != null) { + existDept.setOverLimitAction(request.getOverLimitAction()); + } + + departmentMapper.updateById(existDept); + + // 同步积分池 + teamScorePoolService.upsertPool(existDept.getId(), existDept.getDeptName()); + if ("demand".equals(existDept.getDeptType()) && + (request.getLimitEnabled() != null || request.getLimitPeriod() != null || + request.getPeriodLimit() != null || request.getAlertThreshold() != null)) { + teamScorePoolService.updateLimitConfig( + existDept.getId(), + request.getLimitEnabled(), + request.getLimitPeriod(), + request.getPeriodLimit(), + request.getAlertThreshold() + ); + } + + return true; + } + + /** + * 更新部门限制配置 + */ + @Transactional(rollbackFor = Exception.class) + public boolean updateLimitSettings(Long deptId, DepartmentLimitSettingsRequest request) { + Department department = getDepartmentById(deptId); + if (department == null) { + throw new BusinessException("部门不存在"); + } + if (!"demand".equals(department.getDeptType())) { + throw new BusinessException("只有需求部门需要配置积分限制"); + } + + department.setLimitEnabled(request.getLimitEnabled()); + department.setLimitPeriod(request.getLimitPeriod()); + department.setPeriodLimit(request.getPeriodLimit()); + department.setAlertThreshold(request.getAlertThreshold()); + department.setOverLimitAction(request.getOverLimitAction()); + departmentMapper.updateById(department); + + teamScorePoolService.updateLimitConfig( + deptId, + request.getLimitEnabled(), + request.getLimitPeriod(), + request.getPeriodLimit(), + request.getAlertThreshold() + ); + return true; + } + + /** + * 删除部门(逻辑删除) + */ + @Transactional(rollbackFor = Exception.class) + public boolean deleteDepartment(Long id) { + long childCount = departmentMapper.selectCount( + new LambdaQueryWrapper() + .eq(Department::getParentId, id) + ); + if (childCount > 0) { + throw new BusinessException("该部门下还有子部门,无法删除"); + } + + return departmentMapper.deleteById(id) > 0; + } + + /** + * 设置部门负责人/主管 + */ + @Transactional(rollbackFor = Exception.class) + public boolean setDepartmentHead(Long deptId, Long userId, String type) { + Department department = getDepartmentById(deptId); + if (department == null) { + throw new BusinessException("部门不存在"); + } + + if ("head".equals(type)) { + if (!"demand".equals(department.getDeptType())) { + throw new BusinessException("只有需求部门可以设置负责人"); + } + department.setLeaderId(userId); + } else if ("supervisor".equals(type)) { + if (!"receiving".equals(department.getDeptType())) { + throw new BusinessException("只有接单部门可以设置主管"); + } + department.setSupervisorId(userId); + } else { + throw new BusinessException("类型参数错误"); + } + + return departmentMapper.updateById(department) > 0; + } + + /** + * 调整部门积分 + */ + @Transactional(rollbackFor = Exception.class) + public void adjustScore(Long deptId, DepartmentScoreAdjustRequest request) { + Department department = getDepartmentById(deptId); + if (department == null) { + throw new BusinessException("部门不存在"); + } + if (!"demand".equals(department.getDeptType())) { + throw new BusinessException("只有需求部门可以调整积分"); + } + if (request.getAmount() == null || request.getAmount() <= 0) { + throw new BusinessException("调整积分必须大于0"); + } + + String action = request.getAction().toLowerCase(); + switch (action) { + case "recharge" -> teamScorePoolService.rechargeScore(deptId, request.getAmount()); + case "deduct" -> teamScorePoolService.deductScore(deptId, request.getAmount()); + default -> throw new BusinessException("不支持的积分操作类型"); + } + } + + private void validateDeptType(String deptType) { + if (!"demand".equals(deptType) && !"receiving".equals(deptType)) { + throw new BusinessException("部门类型必须是 demand 或 receiving"); + } + } + + private Integer defaultLimitEnabled(Integer limitEnabled) { + return limitEnabled != null ? limitEnabled : 0; + } + + private String defaultLimitPeriod(String limitPeriod) { + return StringUtils.hasText(limitPeriod) ? limitPeriod : "MONTHLY"; + } + + private String joinAllowedTypes(List allowedTypes) { + if (CollectionUtils.isEmpty(allowedTypes)) { + return null; + } + return allowedTypes.stream() + .filter(StringUtils::hasText) + .collect(Collectors.joining(",")); + } + + private List splitAllowedTypes(String allowedTypes) { + if (allowedTypes == null || allowedTypes.trim().isEmpty()) { + return Collections.emptyList(); + } + return Arrays.stream(allowedTypes.split(",")) + .map(String::trim) + .filter(StringUtils::hasText) + .collect(Collectors.toList()); + } + + private Map loadMemberCountMap() { + return userMapper.selectList(null).stream() + .filter(user -> user.getDeptId() != null) + .collect(Collectors.groupingBy( + User::getDeptId, + Collectors.counting() + )); + } + + private Map loadLeaderNames(List departments) { + List leaderIds = departments.stream() + .map(Department::getLeaderId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + if (leaderIds.isEmpty()) { + return Collections.emptyMap(); + } + return userMapper.selectBatchIds(leaderIds).stream() + .collect(Collectors.toMap( + User::getId, + user -> StringUtils.hasText(user.getRealName()) ? user.getRealName() : user.getUsername() + )); + } + + private DepartmentOverviewVO buildOverviewVO(Department department, + Map memberCountMap, + Map leaderNameMap) { + DepartmentOverviewVO vo = new DepartmentOverviewVO(); + vo.setId(department.getId()); + vo.setDeptName(department.getDeptName()); + vo.setDeptCode(department.getDeptCode()); + vo.setDeptType(department.getDeptType()); + vo.setParentId(department.getParentId()); + vo.setLeaderId(department.getLeaderId()); + if (department.getLeaderId() != null && leaderNameMap != null) { + vo.setLeaderName(leaderNameMap.get(department.getLeaderId())); + } + vo.setDescription(department.getDescription()); + vo.setSortOrder(department.getSortOrder()); + vo.setStatus(department.getStatus()); + vo.setAllowedDemandTypes(splitAllowedTypes(department.getAllowedDemandTypes())); + + var pool = teamScorePoolService.getByDeptId(department.getId()); + if (pool != null) { + vo.setScoreBalance(pool.getBalance()); + vo.setTotalRecharge(pool.getTotalRecharge()); + vo.setTotalConsume(pool.getTotalConsume()); + vo.setTotalEarn(pool.getTotalEarn()); + vo.setLimitEnabled(pool.getLimitEnabled()); + vo.setLimitPeriod(pool.getLimitPeriod()); + vo.setPeriodLimit(pool.getPeriodLimit()); + vo.setPeriodUsed(pool.getPeriodUsed()); + vo.setAlertThreshold(pool.getAlertThreshold()); + vo.setLimitAlert(teamScorePoolService.isAlert(department.getId())); + } else { + vo.setScoreBalance(department.getScoreBalance()); + vo.setLimitEnabled(department.getLimitEnabled()); + vo.setLimitPeriod(department.getLimitPeriod()); + vo.setPeriodLimit(department.getPeriodLimit()); + vo.setAlertThreshold(department.getAlertThreshold()); + vo.setLimitAlert(false); + } + vo.setOverLimitAction(department.getOverLimitAction()); + + if (memberCountMap != null && memberCountMap.containsKey(department.getId())) { + vo.setMemberCount(memberCountMap.get(department.getId()).intValue()); + } else { + vo.setMemberCount(0); + } + return vo; + } + + /** + * 构建树形结构 + */ + private List buildTree(List allDepts, Long parentId) { + List children = allDepts.stream() + .filter(dept -> { + Long deptParentId = dept.getParentId(); + if (deptParentId == null) { + return parentId == null || parentId == 0L; + } + return deptParentId.equals(parentId); + }) + .collect(Collectors.toList()); + + for (Department child : children) { + buildTree(allDepts, child.getId()); + } + + return children; + } + + /** + * 检查用户是否为任何部门的负责人 + */ + public boolean isUserDepartmentLeader(Long userId) { + if (userId == null) { + return false; + } + + // 查询是否有部门的 leader_id 等于该用户ID + Long count = departmentMapper.selectCount( + new LambdaQueryWrapper() + .eq(Department::getLeaderId, userId) + .eq(Department::getStatus, 1) + ); + + return count != null && count > 0; + } +} + diff --git a/backend/src/main/java/com/designhub/service/DesignerService.java b/backend/src/main/java/com/designhub/service/DesignerService.java new file mode 100644 index 0000000..0e2a9e2 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/DesignerService.java @@ -0,0 +1,417 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.Department; +import com.designhub.entity.Task; +import com.designhub.entity.User; +import com.designhub.mapper.DepartmentMapper; +import com.designhub.mapper.TaskMapper; +import com.designhub.mapper.UserMapper; +import com.designhub.vo.DesignerStatsVO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 设计师统计Service + */ +@Service +@RequiredArgsConstructor +public class DesignerService { + + private final UserMapper userMapper; + private final TaskMapper taskMapper; + private final DepartmentMapper departmentMapper; + + /** + * 获取所有设计师的统计信息 + */ + public List getAllDesignerStats() { + // 仅获取接单部门(designer/receiving)的用户 + List receivingDeptIds = departmentMapper.selectList( + new LambdaQueryWrapper() + .eq(Department::getDeptType, "receiving") + .eq(Department::getStatus, 1) + ).stream() + .map(Department::getId) + .collect(Collectors.toList()); + + if (receivingDeptIds.isEmpty()) { + return new ArrayList<>(); + } + + List designers = userMapper.selectList( + new LambdaQueryWrapper() + .eq(User::getStatus, 1) + .in(User::getDeptId, receivingDeptIds) + ); + + return designers.stream() + .map(this::buildDesignerStats) + .collect(Collectors.toList()); + } + + /** + * 获取单个设计师的统计信息 + */ + public DesignerStatsVO getDesignerStats(Long userId) { + User user = userMapper.selectById(userId); + if (user == null) { + return null; + } + return buildDesignerStats(user); + } + + /** + * 获取排行榜数据(支持按月查询) + */ + public List getLeaderboard(String type, Integer limit, String month) { + List stats; + + // 如果指定了月份,则查询该月份的数据 + if (month != null && !month.isEmpty()) { + stats = getDesignerStatsByMonth(month); + } else { + // 否则查询全部数据 + stats = getAllDesignerStats(); + } + + // 根据类型排序 + switch (type) { + case "score": + // 积分榜 + stats.sort((a, b) -> Integer.compare( + b.getTotalScore() != null ? b.getTotalScore() : 0, + a.getTotalScore() != null ? a.getTotalScore() : 0 + )); + break; + case "efficiency": + // 效率榜(完成率) + stats.sort((a, b) -> Double.compare( + b.getCompletionRate() != null ? b.getCompletionRate() : 0.0, + a.getCompletionRate() != null ? a.getCompletionRate() : 0.0 + )); + break; + case "quality": + // 好评榜 + stats.sort((a, b) -> { + int countCompare = Integer.compare( + b.getGoodReviewCount() != null ? b.getGoodReviewCount() : 0, + a.getGoodReviewCount() != null ? a.getGoodReviewCount() : 0 + ); + if (countCompare == 0) { + // 好评数相同时,比较平均评分 + return Double.compare( + b.getAvgRating() != null ? b.getAvgRating() : 0.0, + a.getAvgRating() != null ? a.getAvgRating() : 0.0 + ); + } + return countCompare; + }); + break; + case "diligence": + // 勤勉榜(总任务数) + stats.sort((a, b) -> Integer.compare( + b.getTotalTaskCount() != null ? b.getTotalTaskCount() : 0, + a.getTotalTaskCount() != null ? a.getTotalTaskCount() : 0 + )); + break; + default: + // 默认按积分排序 + stats.sort((a, b) -> Integer.compare( + b.getTotalScore() != null ? b.getTotalScore() : 0, + a.getTotalScore() != null ? a.getTotalScore() : 0 + )); + } + + // 限制返回数量 + if (limit != null && limit > 0 && stats.size() > limit) { + return stats.subList(0, limit); + } + + return stats; + } + + /** + * 构建设计师统计信息 + */ + private DesignerStatsVO buildDesignerStats(User user) { + DesignerStatsVO stats = new DesignerStatsVO(); + + // 基本信息 + stats.setUserId(user.getId()); + stats.setUserName(user.getRealName()); + stats.setAvatar(user.getAvatar()); + stats.setDeptName(user.getDeptName()); + + // 查询任务统计 + Long userId = user.getId(); + + // 总接收任务数 + Long totalCountLong = taskMapper.selectCount( + new LambdaQueryWrapper() + .eq(Task::getReceiverId, userId) + ); + Integer totalCount = totalCountLong.intValue(); + stats.setTotalTaskCount(totalCount); + + // 进行中任务数 + Long inProgressCountLong = taskMapper.selectCount( + new LambdaQueryWrapper() + .eq(Task::getReceiverId, userId) + .eq(Task::getStatus, "in_progress") + ); + Integer inProgressCount = inProgressCountLong.intValue(); + stats.setInProgressCount(inProgressCount); + + // 待接单任务数(队列)- 这里应该是用户已接收但还未开始的任务,实际上应该是0 + // 因为一旦接收任务,状态就会变成 in_progress + // 队列数可以理解为用户当前可以接的任务数(这个需要从任务大厅查询,这里暂时设为0) + stats.setQueueCount(0); + + // 已完成任务数 + Long completedCountLong = taskMapper.selectCount( + new LambdaQueryWrapper() + .eq(Task::getReceiverId, userId) + .eq(Task::getStatus, "completed") + ); + Integer completedCount = completedCountLong.intValue(); + stats.setCompletedCount(completedCount); + + // 延误任务数 + Long delayedCountLong = taskMapper.selectCount( + new LambdaQueryWrapper() + .eq(Task::getReceiverId, userId) + .eq(Task::getIsDelayed, 1) + ); + Integer delayedCount = delayedCountLong.intValue(); + stats.setDelayedCount(delayedCount); + + // 计算完成率 + if (totalCount > 0) { + stats.setCompletionRate(completedCount * 100.0 / totalCount); + } else { + stats.setCompletionRate(0.0); + } + + // 计算准时率 + if (completedCount > 0) { + Integer ontimeCount = completedCount - delayedCount; + stats.setOntimeRate(ontimeCount * 100.0 / completedCount); + } else { + stats.setOntimeRate(0.0); + } + + // 查询已完成任务的详细信息(用于计算积分和评分) + List completedTasks = taskMapper.selectList( + new LambdaQueryWrapper() + .eq(Task::getReceiverId, userId) + .eq(Task::getStatus, "completed") + ); + + // 累计获得积分 + Integer totalScore = completedTasks.stream() + .mapToInt(task -> task.getScore() != null ? task.getScore() : 0) + .sum(); + stats.setTotalScore(totalScore); + + // 平均评分 + List ratings = completedTasks.stream() + .map(Task::getRating) + .filter(rating -> rating != null && rating > 0) + .collect(Collectors.toList()); + + if (!ratings.isEmpty()) { + double avgRating = ratings.stream() + .mapToInt(Integer::intValue) + .average() + .orElse(0.0); + stats.setAvgRating(avgRating); + + // 好评数(5星) + long goodReviewCount = ratings.stream() + .filter(rating -> rating == 5) + .count(); + stats.setGoodReviewCount((int) goodReviewCount); + } else { + stats.setAvgRating(0.0); + stats.setGoodReviewCount(0); + } + + // 在线状态(简化处理,实际应该通过WebSocket或心跳机制维护) + stats.setOnlineStatus(1); + stats.setLastActiveTime("刚刚"); + + return stats; + } + + /** + * 获取指定月份的设计师统计信息 + */ + private List getDesignerStatsByMonth(String month) { + // 解析月份 (YYYY-MM) + String[] parts = month.split("-"); + if (parts.length != 2) { + return getAllDesignerStats(); // 格式错误时返回全部数据 + } + + int year = Integer.parseInt(parts[0]); + int monthNum = Integer.parseInt(parts[1]); + + // 计算月份的起始和结束时间 + String startDate = String.format("%d-%02d-01 00:00:00", year, monthNum); + String endDate; + if (monthNum == 12) { + endDate = String.format("%d-01-01 00:00:00", year + 1); + } else { + endDate = String.format("%d-%02d-01 00:00:00", year, monthNum + 1); + } + + // 获取接单部门的设计师列表 + List receivingDeptIds = departmentMapper.selectList( + new LambdaQueryWrapper() + .eq(Department::getDeptType, "receiving") + .eq(Department::getStatus, 1) + ).stream() + .map(Department::getId) + .collect(Collectors.toList()); + + if (receivingDeptIds.isEmpty()) { + return new ArrayList<>(); + } + + List designers = userMapper.selectList( + new LambdaQueryWrapper() + .eq(User::getStatus, 1) + .in(User::getDeptId, receivingDeptIds) + ); + + // 构建每个设计师的月度统计 + return designers.stream() + .map(user -> buildDesignerMonthStats(user, startDate, endDate)) + .collect(Collectors.toList()); + } + + /** + * 构建设计师指定月份的统计信息 + */ + private DesignerStatsVO buildDesignerMonthStats(User user, String startDate, String endDate) { + DesignerStatsVO stats = new DesignerStatsVO(); + + // 基本信息 + stats.setUserId(user.getId()); + stats.setUserName(user.getRealName()); + stats.setAvatar(user.getAvatar()); + stats.setDeptName(user.getDeptName()); + + Long userId = user.getId(); + + // 当月总接收任务数 + Long totalCountLong = taskMapper.selectCount( + new LambdaQueryWrapper() + .eq(Task::getReceiverId, userId) + .ge(Task::getCreateTime, startDate) + .lt(Task::getCreateTime, endDate) + ); + stats.setTotalTaskCount(totalCountLong.intValue()); + + // 当月进行中任务数(实际在月份查询时应该是0,因为查历史月份) + Long inProgressCountLong = taskMapper.selectCount( + new LambdaQueryWrapper() + .eq(Task::getReceiverId, userId) + .eq(Task::getStatus, "in_progress") + .ge(Task::getCreateTime, startDate) + .lt(Task::getCreateTime, endDate) + ); + stats.setInProgressCount(inProgressCountLong.intValue()); + + stats.setQueueCount(0); + + // 当月已完成任务数 + Long completedCountLong = taskMapper.selectCount( + new LambdaQueryWrapper() + .eq(Task::getReceiverId, userId) + .eq(Task::getStatus, "completed") + .ge(Task::getCompleteTime, startDate) + .lt(Task::getCompleteTime, endDate) + ); + stats.setCompletedCount(completedCountLong.intValue()); + + // 当月总积分(已完成任务的积分之和) + List completedTasks = taskMapper.selectList( + new LambdaQueryWrapper() + .eq(Task::getReceiverId, userId) + .eq(Task::getStatus, "completed") + .ge(Task::getCompleteTime, startDate) + .lt(Task::getCompleteTime, endDate) + ); + + Integer totalScore = completedTasks.stream() + .mapToInt(task -> task.getScore() != null ? task.getScore() : 0) + .sum(); + stats.setTotalScore(totalScore); + + // 当月延期任务数 + Long delayedCountLong = taskMapper.selectCount( + new LambdaQueryWrapper() + .eq(Task::getReceiverId, userId) + .eq(Task::getIsDelayed, 1) + .ge(Task::getCompleteTime, startDate) + .lt(Task::getCompleteTime, endDate) + ); + stats.setDelayedCount(delayedCountLong.intValue()); + + // 计算当月完成率 + if (totalCountLong > 0) { + double completionRate = (completedCountLong.doubleValue() / totalCountLong.doubleValue()) * 100; + stats.setCompletionRate(completionRate); + } else { + stats.setCompletionRate(0.0); + } + + // 计算当月按时完成率 + if (completedCountLong > 0) { + long ontimeCount = completedCountLong - delayedCountLong; + double ontimeRate = (ontimeCount / (double)completedCountLong) * 100; + stats.setOntimeRate(ontimeRate); + } else { + stats.setOntimeRate(0.0); + } + + // 当月平均评分 + Double avgRating = completedTasks.stream() + .filter(task -> task.getRating() != null && task.getRating() > 0) + .mapToInt(Task::getRating) + .average() + .orElse(0.0); + stats.setAvgRating(avgRating); + + // 当月好评数(评分>=4) + long goodReviewCount = completedTasks.stream() + .filter(task -> task.getRating() != null && task.getRating() >= 4) + .count(); + stats.setGoodReviewCount((int)goodReviewCount); + + // 在线状态(历史月份时设为离线) + stats.setOnlineStatus(0); + stats.setLastActiveTime(null); + + return stats; + } + + /** + * 获取设计师进行中的任务列表 + */ + public List getDesignerInProgressTasks(Long userId) { + return taskMapper.selectList( + new LambdaQueryWrapper() + .eq(Task::getReceiverId, userId) + .eq(Task::getStatus, "in_progress") + .orderByDesc(Task::getCreateTime) + ); + } +} + diff --git a/backend/src/main/java/com/designhub/service/DingTalkService.java b/backend/src/main/java/com/designhub/service/DingTalkService.java new file mode 100644 index 0000000..b96ceef --- /dev/null +++ b/backend/src/main/java/com/designhub/service/DingTalkService.java @@ -0,0 +1,507 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.common.ResultCode; +import com.designhub.entity.DingTalkConfig; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.DingTalkConfigMapper; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestTemplate; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.*; + +/** + * 钉钉集成 Service + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DingTalkService { + + private final DingTalkConfigMapper configMapper; + private final ObjectMapper objectMapper; + private final RestTemplate restTemplate = new RestTemplate(); + + private static final String DINGTALK_API_BASE = "https://oapi.dingtalk.com"; + + /** + * 获取钉钉配置(优先获取启用的配置,如果没有则获取最新的配置) + */ + public DingTalkConfig getConfig() { + // 1. 优先获取启用的配置 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(DingTalkConfig::getEnabled, 1) + .orderByDesc(DingTalkConfig::getUpdateTime) + .last("LIMIT 1"); + DingTalkConfig config = configMapper.selectOne(wrapper); + + // 2. 如果没有启用的配置,获取最新的配置(用于管理界面显示) + if (config == null) { + wrapper = new LambdaQueryWrapper<>(); + wrapper.orderByDesc(DingTalkConfig::getUpdateTime) + .last("LIMIT 1"); + config = configMapper.selectOne(wrapper); + } + + return config; + } + + /** + * 检查钉钉是否已配置且启用 + */ + public boolean isConfigured() { + DingTalkConfig config = getConfig(); + return config != null + && config.getEnabled() == 1 + && config.getAppKey() != null + && !config.getAppKey().isEmpty() + && config.getAppSecret() != null + && !config.getAppSecret().isEmpty(); + } + + /** + * 保存钉钉配置 + */ + @Transactional + public DingTalkConfig saveConfig(DingTalkConfig config) { + // 如果enabledFeatures不是字符串格式,转换为JSON字符串 + if (config.getEnabledFeatures() != null && !config.getEnabledFeatures().trim().startsWith("{")) { + // 已经是JSON字符串,不需要处理 + } + + if (config.getId() != null) { + configMapper.updateById(config); + } else { + config.setEnabled(1); + configMapper.insert(config); + } + return config; + } + + /** + * 测试钉钉连接 + */ + public boolean testConnection(DingTalkConfig config) { + try { + String accessToken = getAccessToken(config); + return accessToken != null && !accessToken.isEmpty(); + } catch (Exception e) { + log.error("钉钉连接测试失败", e); + return false; + } + } + + /** + * 获取 Access Token + */ + public String getAccessToken(DingTalkConfig config) { + // 检查缓存的token是否有效 + if (config.getAccessToken() != null && config.getTokenExpireTime() != null + && config.getTokenExpireTime().isAfter(LocalDateTime.now())) { + return config.getAccessToken(); + } + + try { + String url = String.format("%s/gettoken?appkey=%s&appsecret=%s", + DINGTALK_API_BASE, config.getAppKey(), config.getAppSecret()); + + ResponseEntity response = restTemplate.getForEntity(url, Map.class); + Map body = response.getBody(); + + if (body != null && body.get("errcode").equals(0)) { + String accessToken = (String) body.get("access_token"); + int expiresIn = (Integer) body.get("expires_in"); + + // 缓存token + config.setAccessToken(accessToken); + config.setTokenExpireTime(LocalDateTime.now().plusSeconds(expiresIn - 300)); // 提前5分钟过期 + configMapper.updateById(config); + + return accessToken; + } else { + log.error("获取钉钉AccessToken失败: {}", body); + return null; + } + } catch (Exception e) { + log.error("获取钉钉AccessToken异常", e); + return null; + } + } + + /** + * 发送工作通知 + */ + public boolean sendWorkNotification(List userIds, String title, String content) { + DingTalkConfig config = getConfig(); + if (config == null) { + log.warn("钉钉未配置,跳过发送通知"); + return false; + } + + try { + String accessToken = getAccessToken(config); + if (accessToken == null) { + return false; + } + + String url = String.format("%s/topapi/message/corpconversation/asyncsend_v2?access_token=%s", + DINGTALK_API_BASE, accessToken); + + Map msg = new HashMap<>(); + msg.put("msgtype", "action_card"); + Map actionCard = new HashMap<>(); + actionCard.put("title", title); + actionCard.put("markdown", content); + actionCard.put("single_title", "查看详情"); + actionCard.put("single_url", "dingtalk://dingtalkclient/page/link?url=" + + URLEncoder.encode("https://your-domain.com", StandardCharsets.UTF_8.toString())); + msg.put("action_card", actionCard); + + Map request = new HashMap<>(); + request.put("agent_id", config.getAgentId()); + request.put("userid_list", String.join(",", userIds)); + request.put("msg", msg); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity> entity = new HttpEntity<>(request, headers); + + ResponseEntity response = restTemplate.postForEntity(url, entity, Map.class); + Map body = response.getBody(); + + if (body != null && body.get("errcode").equals(0)) { + log.info("钉钉工作通知发送成功: userIds={}", userIds); + return true; + } else { + log.error("钉钉工作通知发送失败: {}", body); + return false; + } + } catch (Exception e) { + log.error("发送钉钉工作通知异常", e); + return false; + } + } + + /** + * 发送机器人消息 + */ + public boolean sendRobotMessage(String title, String content) { + DingTalkConfig config = getConfig(); + if (config == null || config.getRobotWebhook() == null) { + log.warn("钉钉机器人未配置"); + return false; + } + + try { + String webhook = config.getRobotWebhook(); + + // 如果有密钥,需要签名 + if (config.getRobotSecret() != null && !config.getRobotSecret().isEmpty()) { + long timestamp = System.currentTimeMillis(); + String sign = generateSign(timestamp, config.getRobotSecret()); + webhook = webhook + "×tamp=" + timestamp + "&sign=" + sign; + } + + Map message = new HashMap<>(); + message.put("msgtype", "markdown"); + Map markdown = new HashMap<>(); + markdown.put("title", title); + markdown.put("text", "### " + title + "\n\n" + content); + message.put("markdown", markdown); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity> entity = new HttpEntity<>(message, headers); + + ResponseEntity response = restTemplate.postForEntity(webhook, entity, Map.class); + Map body = response.getBody(); + + if (body != null && body.get("errcode").equals(0)) { + log.info("钉钉机器人消息发送成功: title={}", title); + return true; + } else { + log.error("钉钉机器人消息发送失败: {}", body); + return false; + } + } catch (Exception e) { + log.error("发送钉钉机器人消息异常", e); + return false; + } + } + + /** + * 生成签名 + */ + private String generateSign(long timestamp, String secret) throws Exception { + String stringToSign = timestamp + "\n" + secret; + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); + byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)); + return URLEncoder.encode(Base64.getEncoder().encodeToString(signData), StandardCharsets.UTF_8.toString()); + } + + /** + * 获取部门列表 + */ + public List> getDepartmentList() { + DingTalkConfig config = getConfig(); + if (config == null) { + return Collections.emptyList(); + } + + try { + String accessToken = getAccessToken(config); + if (accessToken == null) { + return Collections.emptyList(); + } + + String url = String.format("%s/topapi/v2/department/listsub?access_token=%s", + DINGTALK_API_BASE, accessToken); + + Map request = new HashMap<>(); + request.put("dept_id", 1); // 根部门 + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity> entity = new HttpEntity<>(request, headers); + + ResponseEntity response = restTemplate.postForEntity(url, entity, Map.class); + Map body = response.getBody(); + + if (body != null && body.get("errcode").equals(0)) { + return (List>) body.get("result"); + } + return Collections.emptyList(); + } catch (Exception e) { + log.error("获取钉钉部门列表异常", e); + return Collections.emptyList(); + } + } + + /** + * 钉钉OAuth登录 - 获取用户访问令牌 + * 参考:https://open.dingtalk.com/document/orgapp/obtain-user-token + */ + public Map getUserAccessToken(String code) { + DingTalkConfig config = getConfig(); + if (!isConfigured()) { + throw new BusinessException(ResultCode.PARAM_ERROR, "钉钉登录未配置或未启用,请联系管理员在系统设置中配置"); + } + + try { + String url = "https://api.dingtalk.com/v1.0/oauth2/userAccessToken"; + + Map request = new HashMap<>(); + request.put("clientId", config.getAppKey()); + request.put("clientSecret", config.getAppSecret()); + request.put("code", code); + request.put("grantType", "authorization_code"); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity> entity = new HttpEntity<>(request, headers); + + ResponseEntity response = restTemplate.postForEntity(url, entity, Map.class); + Map body = response.getBody(); + + log.info("获取用户访问令牌响应: {}", body); + return body; + } catch (Exception e) { + log.error("获取用户访问令牌失败", e); + throw new BusinessException(ResultCode.DINGTALK_API_ERROR, "获取钉钉用户令牌失败: " + e.getMessage()); + } + } + + /** + * 获取用户unionId + * 参考:https://open.dingtalk.com/document/orgapp/obtain-the-user-information-based-on-the-sns-temporary-authorization + */ + public Map getUserInfo(String accessToken) { + try { + String url = "https://api.dingtalk.com/v1.0/contact/users/me"; + + HttpHeaders headers = new HttpHeaders(); + headers.set("x-acs-dingtalk-access-token", accessToken); + HttpEntity entity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, Map.class); + Map body = response.getBody(); + + log.info("获取用户信息响应: {}", body); + return body; + } catch (Exception e) { + log.error("获取用户信息失败", e); + throw new BusinessException(ResultCode.DINGTALK_API_ERROR, "获取钉钉用户信息失败: " + e.getMessage()); + } + } + + /** + * 根据unionId获取userId + */ + public String getUserIdByUnionId(String unionId) { + DingTalkConfig config = getConfig(); + String accessToken = getAccessToken(config); + + try { + String url = String.format("%s/topapi/user/getbyunionid?access_token=%s", + DINGTALK_API_BASE, accessToken); + + Map request = new HashMap<>(); + request.put("unionid", unionId); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity> entity = new HttpEntity<>(request, headers); + + ResponseEntity response = restTemplate.postForEntity(url, entity, Map.class); + Map body = response.getBody(); + + if (body != null && body.get("errcode").equals(0)) { + Map result = (Map) body.get("result"); + return (String) result.get("userid"); + } + return null; + } catch (Exception e) { + log.error("根据unionId获取userId失败", e); + return null; + } + } + + /** + * 根据userId获取详细用户信息(包含部门信息) + */ + public Map getDetailedUserInfo(String userId) { + DingTalkConfig config = getConfig(); + String accessToken = getAccessToken(config); + + try { + String url = String.format("%s/topapi/v2/user/get?access_token=%s", + DINGTALK_API_BASE, accessToken); + + Map request = new HashMap<>(); + request.put("userid", userId); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity> entity = new HttpEntity<>(request, headers); + + ResponseEntity response = restTemplate.postForEntity(url, entity, Map.class); + Map body = response.getBody(); + + if (body != null && body.get("errcode").equals(0)) { + Map result = (Map) body.get("result"); + + // 获取部门信息 + List deptIdList = (List) result.get("dept_id_list"); + if (deptIdList != null && !deptIdList.isEmpty()) { + List> departments = new ArrayList<>(); + for (Long deptId : deptIdList) { + Map deptInfo = getDepartmentInfo(deptId.toString()); + if (deptInfo != null) { + departments.add(deptInfo); + } + } + result.put("departments", departments); + log.info("获取到用户部门信息: userId={}, departments={}", userId, departments.size()); + } + + return result; + } + return null; + } catch (Exception e) { + log.error("获取详细用户信息失败", e); + return null; + } + } + + /** + * 获取钉钉部门信息 + */ + public Map getDepartmentInfo(String deptId) { + DingTalkConfig config = getConfig(); + String accessToken = getAccessToken(config); + + try { + String url = String.format("%s/topapi/v2/department/get?access_token=%s", + DINGTALK_API_BASE, accessToken); + + Map request = new HashMap<>(); + request.put("dept_id", Long.parseLong(deptId)); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity> entity = new HttpEntity<>(request, headers); + + ResponseEntity response = restTemplate.postForEntity(url, entity, Map.class); + Map body = response.getBody(); + + if (body != null && body.get("errcode").equals(0)) { + return (Map) body.get("result"); + } + return null; + } catch (Exception e) { + log.error("获取钉钉部门信息失败: deptId={}", deptId, e); + return null; + } + } + + /** + * 同步钉钉用户 + */ + public int syncUsers() { + DingTalkConfig config = getConfig(); + if (config == null) { + throw new BusinessException(ResultCode.PARAM_ERROR, "钉钉未配置"); + } + + // 获取用户列表并同步到系统 + // 这里需要根据实际需求实现用户同步逻辑 + log.info("开始同步钉钉用户..."); + + // TODO: 实现用户同步逻辑 + + return 0; + } + + /** + * 检查功能是否启用 + */ + public boolean isFeatureEnabled(String feature) { + DingTalkConfig config = getConfig(); + if (config == null || config.getEnabledFeatures() == null) { + return false; + } + + try { + Map features = objectMapper.readValue( + config.getEnabledFeatures(), new TypeReference>() {}); + return features.getOrDefault(feature, false); + } catch (Exception e) { + return false; + } + } + + /** + * 发送任务通知(如果启用钉钉通知) + */ + public void sendTaskNotification(String dingTalkUserId, String taskTitle, String action) { + if (!isFeatureEnabled("notification") || dingTalkUserId == null) { + return; + } + + String title = "任务通知"; + String content = String.format("您的任务「%s」%s", taskTitle, action); + sendWorkNotification(Collections.singletonList(dingTalkUserId), title, content); + } +} diff --git a/backend/src/main/java/com/designhub/service/ExportService.java b/backend/src/main/java/com/designhub/service/ExportService.java new file mode 100644 index 0000000..cdffec1 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/ExportService.java @@ -0,0 +1,156 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.*; +import com.designhub.mapper.*; +import com.designhub.util.ExcelExportUtil; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 数据导出Service + */ +@Service +@RequiredArgsConstructor +public class ExportService { + + private final TaskMapper taskMapper; + private final UserMapper userMapper; + private final DepartmentMapper departmentMapper; + private final DesignerService designerService; + + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + /** + * 导出任务列表 + */ + public void exportTasks(HttpServletResponse response, String status, Long publisherId) throws IOException { + // 查询任务数据 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (status != null && !status.isEmpty()) { + queryWrapper.eq(Task::getStatus, status); + } + if (publisherId != null) { + queryWrapper.eq(Task::getPublisherId, publisherId); + } + queryWrapper.orderByDesc(Task::getCreateTime); + + List tasks = taskMapper.selectList(queryWrapper); + + // 定义表头 + String[] headers = { + "任务ID", "任务标题", "发布人", "接收人", "任务类型", "任务状态", + "预估积分", "任务时效(h)", "开始时间", "截止时间", "创建时间" + }; + + // 定义字段名 + String[] fieldNames = { + "id", "title", "publisherId", "receiverId", "demandTypeId", "status", + "scoreReward", "estimatedDuration", "actualStartTime", "deadline", "createTime" + }; + + // 生成文件名 + String fileName = "任务列表_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + + // 导出Excel + ExcelExportUtil.exportExcel(response, fileName, "任务列表", headers, fieldNames, tasks); + } + + /** + * 导出用户列表 + */ + public void exportUsers(HttpServletResponse response, Long deptId, Integer status) throws IOException { + // 查询用户数据 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (deptId != null) { + queryWrapper.eq(User::getDeptId, deptId); + } + if (status != null) { + queryWrapper.eq(User::getStatus, status); + } + queryWrapper.orderByDesc(User::getCreateTime); + + List users = userMapper.selectList(queryWrapper); + + // 定义表头 + String[] headers = { + "用户ID", "用户名", "真实姓名", "手机号", "邮箱", "部门ID", + "状态", "创建时间" + }; + + // 定义字段名 + String[] fieldNames = { + "id", "username", "realName", "phone", "email", "deptId", + "status", "createTime" + }; + + // 生成文件名 + String fileName = "用户列表_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + + // 导出Excel + ExcelExportUtil.exportExcel(response, fileName, "用户列表", headers, fieldNames, users); + } + + /** + * 导出部门列表 + */ + public void exportDepartments(HttpServletResponse response) throws IOException { + // 查询部门数据 + List departments = departmentMapper.selectList( + new LambdaQueryWrapper() + .orderByAsc(Department::getSortOrder) + ); + + // 定义表头 + String[] headers = { + "部门ID", "部门名称", "部门编码", "部门类型", "父部门ID", + "描述", "排序", "状态", "创建时间" + }; + + // 定义字段名 + String[] fieldNames = { + "id", "deptName", "deptCode", "deptType", "parentId", + "description", "sortOrder", "status", "createTime" + }; + + // 生成文件名 + String fileName = "部门列表_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + + // 导出Excel + ExcelExportUtil.exportExcel(response, fileName, "部门列表", headers, fieldNames, departments); + } + + /** + * 导出设计师统计数据 + */ + public void exportDesignerStats(HttpServletResponse response) throws IOException { + // 获取设计师统计数据 + var stats = designerService.getAllDesignerStats(); + + // 定义表头 + String[] headers = { + "设计师ID", "设计师姓名", "当前任务数", "排队任务数", "已完成任务数", + "累计积分", "延误数量", "好评数", "好评率(%)" + }; + + // 定义字段名 + String[] fieldNames = { + "designerId", "designerName", "currentTasks", "queueTasks", "completedTasks", + "totalScore", "delayCount", "goodReviews", "goodReviewRate" + }; + + // 生成文件名 + String fileName = "设计师统计_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + + // 导出Excel + ExcelExportUtil.exportExcel(response, fileName, "设计师统计", headers, fieldNames, stats); + } +} + diff --git a/backend/src/main/java/com/designhub/service/NotificationService.java b/backend/src/main/java/com/designhub/service/NotificationService.java new file mode 100644 index 0000000..5effbd8 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/NotificationService.java @@ -0,0 +1,240 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.designhub.entity.Notification; +import com.designhub.mapper.NotificationMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 通知消息Service + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class NotificationService { + + private final NotificationMapper notificationMapper; + + /** + * 将优先级字符串转换为整数 + * low -> 0, normal -> 1, high -> 2, urgent -> 3 + */ + private Integer convertPriorityToInt(String priority) { + if (priority == null) return 1; // 默认普通 + return switch (priority.toLowerCase()) { + case "low" -> 0; + case "normal" -> 1; + case "high" -> 2; + case "urgent" -> 3; + default -> 1; // 默认普通 + }; + } + + /** + * 发送通知 + */ + @Transactional(rollbackFor = Exception.class) + public Notification sendNotification(Notification notification) { + if (notification.getIsRead() == null) { + notification.setIsRead(0); + } + if (notification.getPriority() == null) { + notification.setPriority(1); // 默认普通 + } + if (notification.getPushDingtalk() == null) { + notification.setPushDingtalk(0); + } + + notificationMapper.insert(notification); + log.info("发送通知成功: receiverId={}, title={}", notification.getReceiverId(), notification.getTitle()); + + // TODO: 如果开启了钉钉推送,这里可以异步推送 + if (notification.getPushDingtalk() == 1) { + asyncPushToDingtalk(notification); + } + + return notification; + } + + /** + * 批量发送通知 + */ + @Transactional(rollbackFor = Exception.class) + public void batchSendNotification(List receiverIds, String title, String content, + String notificationType, String relatedType, Long relatedId) { + for (Long receiverId : receiverIds) { + Notification notification = new Notification(); + notification.setReceiverId(receiverId); + notification.setTitle(title); + notification.setContent(content); + notification.setNotificationType(notificationType); + notification.setRelatedType(relatedType); + notification.setRelatedId(relatedId); + notification.setIsRead(0); + notification.setPriority(1); // 普通 + notificationMapper.insert(notification); + } + log.info("批量发送通知成功: count={}", receiverIds.size()); + } + + /** + * 获取用户通知列表(分页) + */ + public Page getUserNotifications(Long receiverId, Integer pageNum, Integer pageSize, Boolean unreadOnly) { + Page page = new Page<>(pageNum, pageSize); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(Notification::getReceiverId, receiverId); + + if (unreadOnly != null && unreadOnly) { + queryWrapper.eq(Notification::getIsRead, 0); + } + + queryWrapper.orderByDesc(Notification::getCreateTime); + return notificationMapper.selectPage(page, queryWrapper); + } + + /** + * 标记为已读 + */ + @Transactional(rollbackFor = Exception.class) + public boolean markAsRead(Long notificationId, Long receiverId) { + Notification notification = notificationMapper.selectById(notificationId); + if (notification != null && notification.getReceiverId().equals(receiverId)) { + notification.setIsRead(1); + notification.setReadTime(LocalDateTime.now()); + notificationMapper.updateById(notification); + log.info("标记通知已读: id={}", notificationId); + return true; + } + return false; + } + + /** + * 批量标记为已读 + */ + @Transactional(rollbackFor = Exception.class) + public int markAllAsRead(Long receiverId) { + int count = notificationMapper.markAllAsRead(receiverId); + log.info("批量标记通知已读: receiverId={}, count={}", receiverId, count); + return count; + } + + /** + * 获取未读数量 + */ + public long getUnreadCount(Long receiverId) { + return notificationMapper.selectCount( + new LambdaQueryWrapper() + .eq(Notification::getReceiverId, receiverId) + .eq(Notification::getIsRead, 0) + ); + } + + /** + * 删除通知 + */ + @Transactional(rollbackFor = Exception.class) + public boolean deleteNotification(Long notificationId, Long receiverId) { + Notification notification = notificationMapper.selectById(notificationId); + if (notification != null && notification.getReceiverId().equals(receiverId)) { + notificationMapper.deleteById(notificationId); + log.info("删除通知: id={}", notificationId); + return true; + } + return false; + } + + /** + * 异步推送到钉钉(预留接口) + */ + private void asyncPushToDingtalk(Notification notification) { + // TODO: 实现钉钉推送逻辑 + log.info("钉钉推送功能待实现: notificationId={}", notification.getId()); + } + + // ==================== 便捷方法 ==================== + + /** + * 发送任务通知 + */ + public void sendTaskNotification(Long receiverId, Long taskId, String title, String content) { + Notification notification = new Notification(); + notification.setReceiverId(receiverId); + notification.setNotificationType("task"); + notification.setRelatedType("task"); + notification.setRelatedId(taskId); + notification.setTitle(title); + notification.setContent(content); + notification.setLinkUrl("/tasks/" + taskId); + notification.setPushDingtalk(1); // 任务通知默认推送钉钉 + sendNotification(notification); + } + + /** + * 发送积分通知 + */ + public void sendScoreNotification(Long receiverId, String title, String content) { + Notification notification = new Notification(); + notification.setReceiverId(receiverId); + notification.setNotificationType("score"); + notification.setRelatedType("score"); + notification.setTitle(title); + notification.setContent(content); + notification.setLinkUrl("/my-score"); + sendNotification(notification); + } + + /** + * 发送评审通知 + */ + public void sendReviewNotification(Long receiverId, Long taskId, String title, String content) { + Notification notification = new Notification(); + notification.setReceiverId(receiverId); + notification.setNotificationType("review"); + notification.setRelatedType("task"); + notification.setRelatedId(taskId); + notification.setTitle(title); + notification.setContent(content); + notification.setLinkUrl("/tasks/" + taskId); + notification.setPriority(2); // 高 + notification.setPushDingtalk(1); + sendNotification(notification); + } + + /** + * 发送系统通知 + */ + public void sendSystemNotification(Long receiverId, String title, String content, String priority) { + Notification notification = new Notification(); + notification.setReceiverId(receiverId); + notification.setNotificationType("system"); + notification.setRelatedType("system"); + notification.setTitle(title); + notification.setContent(content); + notification.setPriority(priority != null ? convertPriorityToInt(priority) : 1); // 默认普通 + sendNotification(notification); + } + + /** + * 发送通用通知(便捷方法) + */ + public void sendNotification(Long receiverId, String title, String content, String relatedType, Long relatedId) { + Notification notification = new Notification(); + notification.setReceiverId(receiverId); + notification.setNotificationType(relatedType); + notification.setRelatedType(relatedType); + notification.setRelatedId(relatedId); + notification.setTitle(title); + notification.setContent(content); + notification.setPriority(1); // 普通 + sendNotification(notification); + } +} + diff --git a/backend/src/main/java/com/designhub/service/PermissionService.java b/backend/src/main/java/com/designhub/service/PermissionService.java new file mode 100644 index 0000000..cc3b827 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/PermissionService.java @@ -0,0 +1,190 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.Permission; +import com.designhub.entity.RolePermission; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.PermissionMapper; +import com.designhub.mapper.RolePermissionMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 权限管理Service + */ +@Service +@RequiredArgsConstructor +public class PermissionService { + + private final PermissionMapper permissionMapper; + private final RolePermissionMapper rolePermissionMapper; + + /** + * 获取所有权限列表 + */ + public List getAllPermissions() { + return permissionMapper.selectList( + new LambdaQueryWrapper() + .eq(Permission::getStatus, 1) + .orderByAsc(Permission::getSortOrder) + ); + } + + /** + * 获取权限树形结构 + */ + public List getPermissionTree() { + List allPermissions = permissionMapper.selectList( + new LambdaQueryWrapper() + .orderByAsc(Permission::getSortOrder) + ); + return buildTree(allPermissions, 0L); + } + + /** + * 根据ID获取权限 + */ + public Permission getPermissionById(Long id) { + return permissionMapper.selectById(id); + } + + /** + * 根据角色ID获取权限列表 + */ + public List getPermissionsByRoleId(Long roleId) { + return permissionMapper.selectPermissionsByRoleId(roleId); + } + + /** + * 根据用户ID获取权限列表 + */ + public List getPermissionsByUserId(Long userId) { + return permissionMapper.selectPermissionsByUserId(userId); + } + + /** + * 根据用户ID获取权限代码列表 + */ + public List getPermissionCodesByUserId(Long userId) { + List permissions = getPermissionsByUserId(userId); + return permissions.stream() + .map(Permission::getPermissionCode) + .collect(Collectors.toList()); + } + + /** + * 创建权限 + */ + @Transactional(rollbackFor = Exception.class) + public Permission createPermission(Permission permission) { + // 验证权限编码唯一性 + long count = permissionMapper.selectCount( + new LambdaQueryWrapper() + .eq(Permission::getPermissionCode, permission.getPermissionCode()) + ); + if (count > 0) { + throw new BusinessException("权限编码已存在"); + } + + // 设置默认值 + if (permission.getStatus() == null) { + permission.setStatus(1); + } + if (permission.getParentId() == null) { + permission.setParentId(0L); + } + + permissionMapper.insert(permission); + return permission; + } + + /** + * 更新权限 + */ + @Transactional(rollbackFor = Exception.class) + public boolean updatePermission(Permission permission) { + Permission existPermission = getPermissionById(permission.getId()); + if (existPermission == null) { + throw new BusinessException("权限不存在"); + } + + // 不允许将父ID设置为自己 + if (permission.getParentId() != null && permission.getParentId().equals(permission.getId())) { + throw new BusinessException("不能将父权限设置为自己"); + } + + return permissionMapper.updateById(permission) > 0; + } + + /** + * 删除权限 + */ + @Transactional(rollbackFor = Exception.class) + public boolean deletePermission(Long id) { + Permission permission = getPermissionById(id); + if (permission == null) { + throw new BusinessException("权限不存在"); + } + + // 检查是否有子权限 + long childCount = permissionMapper.selectCount( + new LambdaQueryWrapper() + .eq(Permission::getParentId, id) + ); + if (childCount > 0) { + throw new BusinessException("该权限下还有子权限,无法删除"); + } + + // 检查是否有角色使用此权限 + long roleCount = rolePermissionMapper.selectCount( + new LambdaQueryWrapper() + .eq(RolePermission::getPermissionId, id) + ); + if (roleCount > 0) { + throw new BusinessException("该权限已分配给角色,无法删除"); + } + + return permissionMapper.deleteById(id) > 0; + } + + /** + * 构建树形结构 + */ + private List buildTree(List allPermissions, Long parentId) { + List children = allPermissions.stream() + .filter(permission -> permission.getParentId().equals(parentId)) + .collect(Collectors.toList()); + + for (Permission child : children) { + List subChildren = buildTree(allPermissions, child.getId()); + // 可以将子权限存储到一个临时字段中,或者返回VO对象 + } + + return children; + } + + /** + * 根据用户ID检查是否有指定权限 + */ + public boolean hasPermission(Long userId, String permissionCode) { + List permissionCodes = getPermissionCodesByUserId(userId); + return permissionCodes.contains(permissionCode); + } + + /** + * 根据用户ID检查是否有访问接口的权限 + */ + public boolean hasApiPermission(Long userId, String path, String method) { + List permissions = getPermissionsByUserId(userId); + return permissions.stream() + .anyMatch(p -> "api".equals(p.getType()) && + path.equals(p.getPath()) && + (method == null || method.equals(p.getMethod()))); + } +} + diff --git a/backend/src/main/java/com/designhub/service/RewardRuleService.java b/backend/src/main/java/com/designhub/service/RewardRuleService.java new file mode 100644 index 0000000..35f67a0 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/RewardRuleService.java @@ -0,0 +1,471 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.designhub.common.ResultCode; +import com.designhub.entity.RewardRule; +import com.designhub.entity.Task; +import com.designhub.entity.User; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.RewardRuleMapper; +import com.designhub.mapper.TaskMapper; +import com.designhub.mapper.UserMapper; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.time.temporal.TemporalAdjusters; +import java.util.List; +import java.util.Map; + +/** + * 自动奖励规则 Service + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class RewardRuleService { + + private final RewardRuleMapper rewardRuleMapper; + private final UserMapper userMapper; + private final TaskMapper taskMapper; + private final NotificationService notificationService; + private final ObjectMapper objectMapper; + + /** + * 创建奖励规则 + */ + @Transactional + public RewardRule createRule(RewardRule rule) { + rule.setEnabled(1); + rule.setDeleted(0); + rewardRuleMapper.insert(rule); + log.info("创建奖励规则成功: id={}, name={}", rule.getId(), rule.getName()); + return rule; + } + + /** + * 更新奖励规则 + */ + @Transactional + public RewardRule updateRule(Long id, RewardRule rule) { + RewardRule existing = rewardRuleMapper.selectById(id); + if (existing == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "规则不存在"); + } + rule.setId(id); + rewardRuleMapper.updateById(rule); + return rewardRuleMapper.selectById(id); + } + + /** + * 启用/禁用规则 + */ + @Transactional + public void toggleRule(Long id, boolean enabled) { + RewardRule rule = rewardRuleMapper.selectById(id); + if (rule == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "规则不存在"); + } + rule.setEnabled(enabled ? 1 : 0); + rewardRuleMapper.updateById(rule); + } + + /** + * 删除规则 + */ + @Transactional + public void deleteRule(Long id) { + rewardRuleMapper.deleteById(id); + } + + /** + * 获取规则列表 + */ + public IPage getRuleList(Integer page, Integer pageSize, String ruleType) { + Page pageParam = new Page<>(page, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + if (ruleType != null && !ruleType.isEmpty()) { + wrapper.eq(RewardRule::getRuleType, ruleType); + } + + wrapper.orderByDesc(RewardRule::getPriority) + .orderByDesc(RewardRule::getCreateTime); + return rewardRuleMapper.selectPage(pageParam, wrapper); + } + + /** + * 获取规则详情 + */ + public RewardRule getRuleById(Long id) { + RewardRule rule = rewardRuleMapper.selectById(id); + if (rule == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "规则不存在"); + } + return rule; + } + + /** + * 获取所有启用的规则 + */ + public List getEnabledRules() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(RewardRule::getEnabled, 1); + return rewardRuleMapper.selectList(wrapper); + } + + /** + * 任务完成时触发检查 + */ + @Transactional + public void onTaskCompleted(Task task, User user) { + // 获取任务相关的奖励规则 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(RewardRule::getEnabled, 1) + .eq(RewardRule::getRuleType, "task"); + List rules = rewardRuleMapper.selectList(wrapper); + + for (RewardRule rule : rules) { + if (checkRuleCondition(rule, user, task)) { + applyReward(rule, user); + } + } + } + + /** + * 好评时触发检查 + */ + @Transactional + public void onTaskRated(Task task, User user, Integer rating) { + if (rating == null || rating < 5) return; + + // 获取好评相关的奖励规则 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(RewardRule::getEnabled, 1) + .eq(RewardRule::getRuleType, "rating"); + List rules = rewardRuleMapper.selectList(wrapper); + + for (RewardRule rule : rules) { + if (checkRuleCondition(rule, user, task)) { + applyReward(rule, user); + } + } + } + + /** + * 检查规则条件是否满足 + */ + private boolean checkRuleCondition(RewardRule rule, User user, Task task) { + try { + Map condition = objectMapper.readValue( + rule.getTriggerCondition(), new TypeReference>() {}); + + String type = (String) condition.get("type"); + String operator = (String) condition.get("operator"); + int targetValue = ((Number) condition.get("value")).intValue(); + + int actualValue = 0; + + switch (type) { + case "task_count": + // 统计用户完成任务数 + actualValue = countUserTasks(user.getId(), "completed"); + break; + case "rating": + // 任务评分 + actualValue = task.getRating() != null ? task.getRating() : 0; + break; + case "five_star_count": + // 统计五星好评数 + actualValue = countUserFiveStarTasks(user.getId()); + break; + default: + return false; + } + + return compareValue(actualValue, operator, targetValue); + } catch (JsonProcessingException e) { + log.error("解析规则条件失败", e); + return false; + } + } + + /** + * 值比较 + */ + private boolean compareValue(int actual, String operator, int target) { + switch (operator) { + case ">=": return actual >= target; + case ">": return actual > target; + case "<=": return actual <= target; + case "<": return actual < target; + case "==": return actual == target; + default: return false; + } + } + + /** + * 统计用户任务数 + */ + private int countUserTasks(Long userId, String status) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Task::getReceiverId, userId) + .eq(Task::getStatus, status); + Long count = taskMapper.selectCount(wrapper); + return count != null ? count.intValue() : 0; + } + + /** + * 统计用户五星好评数 + */ + private int countUserFiveStarTasks(Long userId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Task::getReceiverId, userId) + .eq(Task::getStatus, "completed") + .ge(Task::getRating, 5); + Long count = taskMapper.selectCount(wrapper); + return count != null ? count.intValue() : 0; + } + + /** + * 应用奖励 + */ + @Transactional + public void applyReward(RewardRule rule, User user) { + if (rule.getRewardScore() != null && rule.getRewardScore() > 0) { + int currentScore = user.getScore() != null ? user.getScore() : 0; + user.setScore(currentScore + rule.getRewardScore()); + userMapper.updateById(user); + + // 发送通知 + notificationService.sendNotification( + user.getId(), + "获得奖励", + String.format("恭喜!根据「%s」规则,您获得 %d 积分奖励!", rule.getName(), rule.getRewardScore()), + "reward", + rule.getId() + ); + + log.info("用户获得自动奖励: userId={}, ruleId={}, score={}", + user.getId(), rule.getId(), rule.getRewardScore()); + } + } + + /** + * 定时任务:执行每日奖励规则 + */ + @Scheduled(cron = "0 0 23 * * ?") // 每天23点执行 + @Transactional + public void executeDailyRewards() { + log.info("开始执行每日自动奖励规则"); + executePeriodicRewards("daily"); + } + + /** + * 定时任务:执行每周奖励规则 + */ + @Scheduled(cron = "0 0 22 ? * SUN") // 每周日22点执行 + @Transactional + public void executeWeeklyRewards() { + log.info("开始执行每周自动奖励规则"); + executePeriodicRewards("weekly"); + } + + /** + * 定时任务:执行每月奖励规则 + */ + @Scheduled(cron = "0 0 21 L * ?") // 每月最后一天21点执行 + @Transactional + public void executeMonthlyRewards() { + log.info("开始执行每月自动奖励规则"); + executePeriodicRewards("monthly"); + } + + /** + * 执行周期性奖励 + */ + private void executePeriodicRewards(String ruleType) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(RewardRule::getEnabled, 1) + .eq(RewardRule::getRuleType, ruleType); + List rules = rewardRuleMapper.selectList(wrapper); + + for (RewardRule rule : rules) { + try { + executePeriodicRule(rule); + rule.setLastExecuteTime(LocalDateTime.now()); + rewardRuleMapper.updateById(rule); + } catch (Exception e) { + log.error("执行奖励规则失败: ruleId={}", rule.getId(), e); + } + } + } + + /** + * 执行单个周期性规则 + */ + private void executePeriodicRule(RewardRule rule) { + // 获取符合范围的用户 + List users = getUsersByScope(rule); + + LocalDateTime startTime = getStartTimeByRuleType(rule.getRuleType()); + LocalDateTime endTime = LocalDateTime.now(); + + for (User user : users) { + // 检查用户在该周期内是否满足条件 + if (checkPeriodicCondition(rule, user, startTime, endTime)) { + applyReward(rule, user); + } + } + } + + /** + * 根据范围获取用户列表 + */ + private List getUsersByScope(RewardRule rule) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(User::getDeleted, 0); + + if ("designer".equals(rule.getScope())) { + // 获取设计师角色的用户 + // 简化处理,获取所有用户 + } else if ("department".equals(rule.getScope()) && rule.getScopeDeptIds() != null) { + try { + List deptIds = objectMapper.readValue( + rule.getScopeDeptIds(), new TypeReference>() {}); + wrapper.in(User::getDeptId, deptIds); + } catch (JsonProcessingException e) { + log.error("解析部门ID失败", e); + } + } + + return userMapper.selectList(wrapper); + } + + /** + * 获取周期开始时间 + */ + private LocalDateTime getStartTimeByRuleType(String ruleType) { + LocalDateTime now = LocalDateTime.now(); + switch (ruleType) { + case "daily": + return now.toLocalDate().atStartOfDay(); + case "weekly": + return now.with(TemporalAdjusters.previousOrSame(java.time.DayOfWeek.MONDAY)) + .toLocalDate().atStartOfDay(); + case "monthly": + return now.with(TemporalAdjusters.firstDayOfMonth()) + .toLocalDate().atStartOfDay(); + default: + return now.minusDays(1); + } + } + + /** + * 检查周期性条件 + */ + private boolean checkPeriodicCondition(RewardRule rule, User user, + LocalDateTime startTime, LocalDateTime endTime) { + try { + Map condition = objectMapper.readValue( + rule.getTriggerCondition(), new TypeReference>() {}); + + String type = (String) condition.get("type"); + String operator = (String) condition.get("operator"); + int targetValue = ((Number) condition.get("value")).intValue(); + + int actualValue = 0; + + switch (type) { + case "task_count": + // 统计周期内完成任务数 + LambdaQueryWrapper taskWrapper = new LambdaQueryWrapper<>(); + taskWrapper.eq(Task::getReceiverId, user.getId()) + .eq(Task::getStatus, "completed") + .ge(Task::getCompleteTime, startTime) + .le(Task::getCompleteTime, endTime); + Long taskCount = taskMapper.selectCount(taskWrapper); + actualValue = taskCount != null ? taskCount.intValue() : 0; + break; + case "five_star_count": + // 统计周期内五星好评数 + LambdaQueryWrapper fiveStarWrapper = new LambdaQueryWrapper<>(); + fiveStarWrapper.eq(Task::getReceiverId, user.getId()) + .eq(Task::getStatus, "completed") + .ge(Task::getRating, 5) + .ge(Task::getCompleteTime, startTime) + .le(Task::getCompleteTime, endTime); + Long fiveStarCount = taskMapper.selectCount(fiveStarWrapper); + actualValue = fiveStarCount != null ? fiveStarCount.intValue() : 0; + break; + default: + return false; + } + + return compareValue(actualValue, operator, targetValue); + } catch (JsonProcessingException e) { + log.error("解析规则条件失败", e); + return false; + } + } + + /** + * 初始化默认奖励规则 + */ + @Transactional + public void initDefaultRules() { + // 检查是否已有规则 + if (rewardRuleMapper.selectCount(null) > 0) { + return; + } + + // 每日完成5个任务奖励 + RewardRule dailyRule = new RewardRule(); + dailyRule.setName("每日任务达人"); + dailyRule.setDescription("每天完成5个任务,额外奖励20积分"); + dailyRule.setRuleType("daily"); + dailyRule.setTriggerCondition("{\"type\":\"task_count\",\"operator\":\">=\",\"value\":5}"); + dailyRule.setRewardType("score"); + dailyRule.setRewardScore(20); + dailyRule.setScope("all"); + dailyRule.setEnabled(1); + dailyRule.setPriority(100); + rewardRuleMapper.insert(dailyRule); + + // 每周完成20个任务奖励 + RewardRule weeklyRule = new RewardRule(); + weeklyRule.setName("每周效率之星"); + weeklyRule.setDescription("每周完成20个任务,额外奖励100积分"); + weeklyRule.setRuleType("weekly"); + weeklyRule.setTriggerCondition("{\"type\":\"task_count\",\"operator\":\">=\",\"value\":20}"); + weeklyRule.setRewardType("score"); + weeklyRule.setRewardScore(100); + weeklyRule.setScope("all"); + weeklyRule.setEnabled(1); + weeklyRule.setPriority(90); + rewardRuleMapper.insert(weeklyRule); + + // 五星好评奖励 + RewardRule ratingRule = new RewardRule(); + ratingRule.setName("五星好评奖励"); + ratingRule.setDescription("获得五星好评,奖励10积分"); + ratingRule.setRuleType("rating"); + ratingRule.setTriggerCondition("{\"type\":\"rating\",\"operator\":\">=\",\"value\":5}"); + ratingRule.setRewardType("score"); + ratingRule.setRewardScore(10); + ratingRule.setScope("all"); + ratingRule.setEnabled(1); + ratingRule.setPriority(80); + rewardRuleMapper.insert(ratingRule); + + log.info("初始化默认奖励规则完成"); + } +} diff --git a/backend/src/main/java/com/designhub/service/RoleService.java b/backend/src/main/java/com/designhub/service/RoleService.java new file mode 100644 index 0000000..35cba05 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/RoleService.java @@ -0,0 +1,198 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.Role; +import com.designhub.entity.RolePermission; +import com.designhub.entity.UserRole; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.RoleMapper; +import com.designhub.mapper.RolePermissionMapper; +import com.designhub.mapper.UserRoleMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 角色管理Service + */ +@Service +@RequiredArgsConstructor +public class RoleService { + + private final RoleMapper roleMapper; + private final RolePermissionMapper rolePermissionMapper; + private final UserRoleMapper userRoleMapper; + + /** + * 获取所有角色列表 + */ + public List getAllRoles() { + return roleMapper.selectList( + new LambdaQueryWrapper() + .eq(Role::getStatus, 1) + .orderByAsc(Role::getSortOrder) + ); + } + + /** + * 根据ID获取角色 + */ + public Role getRoleById(Long id) { + return roleMapper.selectById(id); + } + + /** + * 根据用户ID获取角色列表 + */ + public List getRolesByUserId(Long userId) { + return roleMapper.selectRolesByUserId(userId); + } + + /** + * 创建角色 + */ + @Transactional(rollbackFor = Exception.class) + public Role createRole(Role role) { + // 验证角色编码唯一性 + long count = roleMapper.selectCount( + new LambdaQueryWrapper() + .eq(Role::getRoleCode, role.getRoleCode()) + ); + if (count > 0) { + throw new BusinessException("角色编码已存在"); + } + + // 设置默认值 + if (role.getStatus() == null) { + role.setStatus(1); + } + if (role.getBuiltIn() == null) { + role.setBuiltIn(0); + } + if (role.getDataScope() == null) { + role.setDataScope("self"); + } + + roleMapper.insert(role); + return role; + } + + /** + * 更新角色 + */ + @Transactional(rollbackFor = Exception.class) + public boolean updateRole(Role role) { + Role existRole = getRoleById(role.getId()); + if (existRole == null) { + throw new BusinessException("角色不存在"); + } + + // 内置角色不允许修改编码和级别 + if (existRole.getBuiltIn() == 1) { + role.setRoleCode(null); + role.setLevel(null); + role.setBuiltIn(null); + } + + return roleMapper.updateById(role) > 0; + } + + /** + * 删除角色 + */ + @Transactional(rollbackFor = Exception.class) + public boolean deleteRole(Long id) { + Role role = getRoleById(id); + if (role == null) { + throw new BusinessException("角色不存在"); + } + + // 内置角色不允许删除 + if (role.getBuiltIn() == 1) { + throw new BusinessException("内置角色不允许删除"); + } + + // 检查是否有用户使用此角色 + long userCount = userRoleMapper.selectCount( + new LambdaQueryWrapper() + .eq(UserRole::getRoleId, id) + ); + if (userCount > 0) { + throw new BusinessException("该角色下还有用户,无法删除"); + } + + // 删除角色权限关联 + rolePermissionMapper.delete( + new LambdaQueryWrapper() + .eq(RolePermission::getRoleId, id) + ); + + return roleMapper.deleteById(id) > 0; + } + + /** + * 为角色分配权限 + */ + @Transactional(rollbackFor = Exception.class) + public boolean assignPermissions(Long roleId, List permissionIds) { + Role role = getRoleById(roleId); + if (role == null) { + throw new BusinessException("角色不存在"); + } + + // 删除原有权限 + rolePermissionMapper.delete( + new LambdaQueryWrapper() + .eq(RolePermission::getRoleId, roleId) + ); + + // 分配新权限 + if (permissionIds != null && !permissionIds.isEmpty()) { + for (Long permissionId : permissionIds) { + RolePermission rolePermission = new RolePermission(); + rolePermission.setRoleId(roleId); + rolePermission.setPermissionId(permissionId); + rolePermissionMapper.insert(rolePermission); + } + } + + return true; + } + + /** + * 获取角色的权限ID列表 + */ + public List getRolePermissionIds(Long roleId) { + List rolePermissions = rolePermissionMapper.selectList( + new LambdaQueryWrapper() + .eq(RolePermission::getRoleId, roleId) + ); + return rolePermissions.stream() + .map(RolePermission::getPermissionId) + .toList(); + } + + /** + * 启用/禁用角色 + */ + @Transactional(rollbackFor = Exception.class) + public boolean toggleRoleStatus(Long id, Integer status) { + Role role = getRoleById(id); + if (role == null) { + throw new BusinessException("角色不存在"); + } + + // 内置角色不允许禁用 + if (role.getBuiltIn() == 1 && status == 0) { + throw new BusinessException("内置角色不允许禁用"); + } + + Role updateRole = new Role(); + updateRole.setId(id); + updateRole.setStatus(status); + return roleMapper.updateById(updateRole) > 0; + } +} + diff --git a/backend/src/main/java/com/designhub/service/S3ConfigService.java b/backend/src/main/java/com/designhub/service/S3ConfigService.java new file mode 100644 index 0000000..f55f52b --- /dev/null +++ b/backend/src/main/java/com/designhub/service/S3ConfigService.java @@ -0,0 +1,120 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.S3Config; +import com.designhub.mapper.S3ConfigMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * S3配置服务 + */ +@Service +public class S3ConfigService { + + @Autowired + private S3ConfigMapper s3ConfigMapper; + + /** + * 获取激活的S3配置 + */ + public S3Config getActiveConfig() { + return s3ConfigMapper.getActiveConfig(); + } + + /** + * 获取所有S3配置 + */ + public List getAllConfigs() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.orderByDesc(S3Config::getIsActive) + .orderByDesc(S3Config::getCreateTime); + return s3ConfigMapper.selectList(wrapper); + } + + /** + * 根据ID获取S3配置 + */ + public S3Config getConfigById(Long id) { + return s3ConfigMapper.selectById(id); + } + + /** + * 创建S3配置 + */ + @Transactional + public S3Config createConfig(S3Config config) { + // 如果新配置设置为激活,则将其他配置设为未激活 + if (config.getIsActive() != null && config.getIsActive()) { + deactivateAllConfigs(); + } + s3ConfigMapper.insert(config); + return config; + } + + /** + * 更新S3配置 + */ + @Transactional + public S3Config updateConfig(Long id, S3Config config) { + S3Config existingConfig = s3ConfigMapper.selectById(id); + if (existingConfig == null) { + throw new RuntimeException("S3配置不存在"); + } + + // 如果新配置设置为激活,则将其他配置设为未激活 + if (config.getIsActive() != null && config.getIsActive()) { + deactivateAllConfigs(); + } + + config.setId(id); + s3ConfigMapper.updateById(config); + return s3ConfigMapper.selectById(id); + } + + /** + * 删除S3配置 + */ + public void deleteConfig(Long id) { + S3Config config = s3ConfigMapper.selectById(id); + if (config != null && config.getIsActive()) { + throw new RuntimeException("无法删除激活的配置,请先激活其他配置"); + } + s3ConfigMapper.deleteById(id); + } + + /** + * 激活指定的S3配置 + */ + @Transactional + public void activateConfig(Long id) { + S3Config config = s3ConfigMapper.selectById(id); + if (config == null) { + throw new RuntimeException("S3配置不存在"); + } + + // 将其他配置设为未激活 + deactivateAllConfigs(); + + // 激活当前配置 + config.setIsActive(true); + s3ConfigMapper.updateById(config); + } + + /** + * 将所有配置设为未激活 + */ + private void deactivateAllConfigs() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(S3Config::getIsActive, true); + List activeConfigs = s3ConfigMapper.selectList(wrapper); + + for (S3Config activeConfig : activeConfigs) { + activeConfig.setIsActive(false); + s3ConfigMapper.updateById(activeConfig); + } + } +} diff --git a/backend/src/main/java/com/designhub/service/S3Service.java b/backend/src/main/java/com/designhub/service/S3Service.java new file mode 100644 index 0000000..946f9b7 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/S3Service.java @@ -0,0 +1,146 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.FileUpload; +import com.designhub.entity.S3Config; +import com.designhub.mapper.FileUploadMapper; +import com.designhub.mapper.S3ConfigMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3Configuration; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; + +import java.io.IOException; +import java.net.URI; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; + +/** + * S3文件存储服务 + */ +@Service +public class S3Service { + + private static final Logger log = LoggerFactory.getLogger(S3Service.class); + + @Autowired + private S3ConfigMapper s3ConfigMapper; + + @Autowired + private FileUploadMapper fileUploadMapper; + + /** + * 上传文件到S3 + * + * @param file 文件 + * @param uploaderId 上传者ID + * @param uploaderName 上传者姓名 + * @param relatedType 关联类型 + * @param relatedId 关联ID + * @return 文件URL + */ + public String uploadFile(MultipartFile file, Long uploaderId, String uploaderName, + String relatedType, Long relatedId) throws IOException { + // 获取激活的S3配置 + S3Config config = s3ConfigMapper.getActiveConfig(); + if (config == null) { + throw new RuntimeException("S3配置未激活,请先配置S3存储"); + } + + // 生成唯一文件名 + String originalFilename = file.getOriginalFilename(); + String fileExtension = originalFilename.substring(originalFilename.lastIndexOf(".")); + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 8); + String storedName = timestamp + "_" + uuid + fileExtension; + + // 构建文件路径 + String filePath = config.getFilePath() + storedName; + + try { + // 创建S3客户端 + S3Client s3Client = createS3Client(config); + + // 上传文件 + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(config.getBucket()) + .key(filePath) + .contentType(file.getContentType()) + .build(); + + s3Client.putObject(putObjectRequest, RequestBody.fromInputStream(file.getInputStream(), file.getSize())); + + // 生成文件URL + String fileUrl = config.getPublicDomain() + "/" + filePath; + + // 保存文件上传记录 + FileUpload fileUpload = new FileUpload(); + fileUpload.setOriginalName(originalFilename); + fileUpload.setStoredName(storedName); + fileUpload.setFilePath(filePath); + fileUpload.setFileUrl(fileUrl); + fileUpload.setFileSize(file.getSize()); + fileUpload.setFileType(file.getContentType()); + fileUpload.setUploaderId(uploaderId); + fileUpload.setUploaderName(uploaderName); + fileUpload.setRelatedType(relatedType); + fileUpload.setRelatedId(relatedId); + fileUploadMapper.insert(fileUpload); + + log.info("文件上传成功: {} -> {}", originalFilename, fileUrl); + return fileUrl; + + } catch (Exception e) { + log.error("文件上传失败: {}", originalFilename, e); + throw new RuntimeException("文件上传失败: " + e.getMessage()); + } + } + + /** + * 创建S3客户端 + */ + private S3Client createS3Client(S3Config config) { + AwsBasicCredentials credentials = AwsBasicCredentials.create( + config.getAccessKeyId(), + config.getSecretAccessKey() + ); + + S3Configuration s3Config = S3Configuration.builder() + .pathStyleAccessEnabled(config.getEnablePathStyle()) + .build(); + + return S3Client.builder() + .region(Region.of(config.getRegion())) + .credentialsProvider(StaticCredentialsProvider.create(credentials)) + .endpointOverride(URI.create(config.getEndpoint())) + .serviceConfiguration(s3Config) + .build(); + } + + /** + * 获取文件上传记录 + */ + public FileUpload getFileUploadById(Long id) { + return fileUploadMapper.selectById(id); + } + + /** + * 根据关联信息获取文件列表 + */ + public java.util.List getFilesByRelated(String relatedType, Long relatedId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(FileUpload::getRelatedType, relatedType) + .eq(FileUpload::getRelatedId, relatedId) + .orderByDesc(FileUpload::getCreateTime); + return fileUploadMapper.selectList(wrapper); + } +} diff --git a/backend/src/main/java/com/designhub/service/ScorePoolService.java b/backend/src/main/java/com/designhub/service/ScorePoolService.java new file mode 100644 index 0000000..b34985b --- /dev/null +++ b/backend/src/main/java/com/designhub/service/ScorePoolService.java @@ -0,0 +1,419 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.designhub.common.ResultCode; +import com.designhub.entity.ScorePool; +import com.designhub.entity.ScorePoolRecord; +import com.designhub.entity.User; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.ScorePoolMapper; +import com.designhub.mapper.ScorePoolRecordMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.time.YearMonth; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 激励积分池 Service + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ScorePoolService { + + private final ScorePoolMapper scorePoolMapper; + private final ScorePoolRecordMapper recordMapper; + private final NotificationService notificationService; + + /** + * 创建积分池 + */ + @Transactional + public ScorePool createPool(ScorePool pool) { + // 检查是否已存在相同周期的积分池 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ScorePool::getPoolType, pool.getPoolType()) + .eq(ScorePool::getYear, pool.getYear()); + + if ("monthly".equals(pool.getPoolType())) { + wrapper.eq(ScorePool::getMonth, pool.getMonth()); + } else if ("quarterly".equals(pool.getPoolType())) { + wrapper.eq(ScorePool::getQuarter, pool.getQuarter()); + } + + if (scorePoolMapper.selectCount(wrapper) > 0) { + throw new BusinessException(ResultCode.PARAM_ERROR, "该周期已存在积分池"); + } + + // 计算各项预算默认分配 + if (pool.getTaskRewardBudget() == null) { + pool.setTaskRewardBudget((int)(pool.getTotalBudget() * 0.4)); // 40% + } + if (pool.getRatingRewardBudget() == null) { + pool.setRatingRewardBudget((int)(pool.getTotalBudget() * 0.2)); // 20% + } + if (pool.getContestRewardBudget() == null) { + pool.setContestRewardBudget((int)(pool.getTotalBudget() * 0.15)); // 15% + } + if (pool.getCheckinRewardBudget() == null) { + pool.setCheckinRewardBudget((int)(pool.getTotalBudget() * 0.1)); // 10% + } + if (pool.getOtherRewardBudget() == null) { + pool.setOtherRewardBudget((int)(pool.getTotalBudget() * 0.15)); // 15% + } + + pool.setUsedScore(0); + pool.setRemainingScore(pool.getTotalBudget()); + pool.setTaskRewardUsed(0); + pool.setRatingRewardUsed(0); + pool.setContestRewardUsed(0); + pool.setCheckinRewardUsed(0); + pool.setOtherRewardUsed(0); + pool.setStatus("active"); + pool.setDeleted(0); + + scorePoolMapper.insert(pool); + log.info("创建积分池成功: id={}, name={}", pool.getId(), pool.getName()); + return pool; + } + + /** + * 更新积分池 + */ + @Transactional + public ScorePool updatePool(Long id, ScorePool pool) { + ScorePool existing = scorePoolMapper.selectById(id); + if (existing == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "积分池不存在"); + } + + // 如果增加了总预算,同步增加剩余积分 + if (pool.getTotalBudget() != null && pool.getTotalBudget() > existing.getTotalBudget()) { + int increase = pool.getTotalBudget() - existing.getTotalBudget(); + pool.setRemainingScore(existing.getRemainingScore() + increase); + } + + pool.setId(id); + scorePoolMapper.updateById(pool); + return scorePoolMapper.selectById(id); + } + + /** + * 获取积分池列表 + */ + public IPage getPoolList(Integer page, Integer pageSize, String poolType, Integer year) { + Page pageParam = new Page<>(page, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + if (poolType != null && !poolType.isEmpty()) { + wrapper.eq(ScorePool::getPoolType, poolType); + } + if (year != null) { + wrapper.eq(ScorePool::getYear, year); + } + + wrapper.orderByDesc(ScorePool::getYear) + .orderByDesc(ScorePool::getMonth) + .orderByDesc(ScorePool::getCreateTime); + return scorePoolMapper.selectPage(pageParam, wrapper); + } + + /** + * 获取积分池详情 + */ + public ScorePool getPoolById(Long id) { + ScorePool pool = scorePoolMapper.selectById(id); + if (pool == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "积分池不存在"); + } + return pool; + } + + /** + * 获取当前活跃的积分池 + */ + public ScorePool getCurrentActivePool() { + LocalDateTime now = LocalDateTime.now(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ScorePool::getStatus, "active") + .eq(ScorePool::getPoolType, "monthly") + .eq(ScorePool::getYear, now.getYear()) + .eq(ScorePool::getMonth, now.getMonthValue()); + + ScorePool pool = scorePoolMapper.selectOne(wrapper); + if (pool == null) { + // 自动创建当月积分池 + pool = autoCreateMonthlyPool(now.getYear(), now.getMonthValue()); + } + return pool; + } + + /** + * 自动创建月度积分池 + */ + @Transactional + public ScorePool autoCreateMonthlyPool(int year, int month) { + ScorePool pool = new ScorePool(); + pool.setName(String.format("%d年%d月激励积分池", year, month)); + pool.setPoolType("monthly"); + pool.setYear(year); + pool.setMonth(month); + pool.setTotalBudget(100000); // 默认10万积分预算 + pool.setOverflowStrategy("alert"); + pool.setAlertThreshold(80); + + return createPool(pool); + } + + /** + * 从积分池发放积分 + */ + @Transactional + public boolean distributeScore(User user, int score, String rewardType, String bizType, Long bizId, String remark) { + ScorePool pool = getCurrentActivePool(); + + // 检查是否超支 + if (pool.getRemainingScore() < score) { + if ("stop".equals(pool.getOverflowStrategy())) { + log.warn("积分池已耗尽,停止发放: poolId={}", pool.getId()); + return false; + } else if ("alert".equals(pool.getOverflowStrategy())) { + // 发送提醒但继续发放 + notificationService.sendNotification( + pool.getCreatorId(), + "积分池预算不足提醒", + String.format("积分池「%s」剩余积分不足,当前剩余 %d,请及时补充预算!", + pool.getName(), pool.getRemainingScore()), + "score_pool", + pool.getId() + ); + } + // auto_increase 策略自动增加预算,不做限制 + } + + // 检查分项预算 + int categoryUsed = getCategoryUsed(pool, rewardType); + int categoryBudget = getCategoryBudget(pool, rewardType); + + if (categoryUsed + score > categoryBudget && "stop".equals(pool.getOverflowStrategy())) { + log.warn("分项预算已用完: poolId={}, type={}", pool.getId(), rewardType); + return false; + } + + // 记录变动前状态 + int beforeRemaining = pool.getRemainingScore(); + + // 扣减积分池 + pool.setUsedScore(pool.getUsedScore() + score); + pool.setRemainingScore(pool.getRemainingScore() - score); + updateCategoryUsed(pool, rewardType, score); + + scorePoolMapper.updateById(pool); + + // 创建记录 + ScorePoolRecord record = new ScorePoolRecord(); + record.setPoolId(pool.getId()); + record.setUserId(user.getId()); + record.setUserName(user.getRealName()); + record.setRewardType(rewardType); + record.setScoreChange(score); + record.setBeforeRemaining(beforeRemaining); + record.setAfterRemaining(pool.getRemainingScore()); + record.setBizType(bizType); + record.setBizId(bizId); + record.setRemark(remark); + recordMapper.insert(record); + + // 检查是否需要发送预警 + checkAndSendAlert(pool); + + log.info("积分池发放成功: poolId={}, userId={}, score={}, type={}", + pool.getId(), user.getId(), score, rewardType); + return true; + } + + /** + * 获取分项已使用积分 + */ + private int getCategoryUsed(ScorePool pool, String rewardType) { + switch (rewardType) { + case "task": return pool.getTaskRewardUsed(); + case "rating": return pool.getRatingRewardUsed(); + case "contest": return pool.getContestRewardUsed(); + case "checkin": return pool.getCheckinRewardUsed(); + default: return pool.getOtherRewardUsed(); + } + } + + /** + * 获取分项预算 + */ + private int getCategoryBudget(ScorePool pool, String rewardType) { + switch (rewardType) { + case "task": return pool.getTaskRewardBudget(); + case "rating": return pool.getRatingRewardBudget(); + case "contest": return pool.getContestRewardBudget(); + case "checkin": return pool.getCheckinRewardBudget(); + default: return pool.getOtherRewardBudget(); + } + } + + /** + * 更新分项已使用积分 + */ + private void updateCategoryUsed(ScorePool pool, String rewardType, int score) { + switch (rewardType) { + case "task": + pool.setTaskRewardUsed(pool.getTaskRewardUsed() + score); + break; + case "rating": + pool.setRatingRewardUsed(pool.getRatingRewardUsed() + score); + break; + case "contest": + pool.setContestRewardUsed(pool.getContestRewardUsed() + score); + break; + case "checkin": + pool.setCheckinRewardUsed(pool.getCheckinRewardUsed() + score); + break; + default: + pool.setOtherRewardUsed(pool.getOtherRewardUsed() + score); + } + } + + /** + * 检查并发送预警 + */ + private void checkAndSendAlert(ScorePool pool) { + if (pool.getAlertThreshold() == null) return; + + int usagePercent = (int)((double) pool.getUsedScore() / pool.getTotalBudget() * 100); + if (usagePercent >= pool.getAlertThreshold()) { + notificationService.sendNotification( + pool.getCreatorId(), + "积分池使用预警", + String.format("积分池「%s」使用率已达 %d%%,请关注!", pool.getName(), usagePercent), + "score_pool", + pool.getId() + ); + } + } + + /** + * 获取积分池使用记录 + */ + public IPage getPoolRecords(Long poolId, Integer page, Integer pageSize) { + Page pageParam = new Page<>(page, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ScorePoolRecord::getPoolId, poolId) + .orderByDesc(ScorePoolRecord::getCreateTime); + return recordMapper.selectPage(pageParam, wrapper); + } + + /** + * 获取积分池统计信息 + */ + public Map getPoolStatistics(Long poolId) { + ScorePool pool = getPoolById(poolId); + Map stats = new HashMap<>(); + + stats.put("totalBudget", pool.getTotalBudget()); + stats.put("usedScore", pool.getUsedScore()); + stats.put("remainingScore", pool.getRemainingScore()); + stats.put("usagePercent", (double) pool.getUsedScore() / pool.getTotalBudget() * 100); + + // 分项统计 + Map categories = new HashMap<>(); + categories.put("task", Map.of("budget", pool.getTaskRewardBudget(), "used", pool.getTaskRewardUsed())); + categories.put("rating", Map.of("budget", pool.getRatingRewardBudget(), "used", pool.getRatingRewardUsed())); + categories.put("contest", Map.of("budget", pool.getContestRewardBudget(), "used", pool.getContestRewardUsed())); + categories.put("checkin", Map.of("budget", pool.getCheckinRewardBudget(), "used", pool.getCheckinRewardUsed())); + categories.put("other", Map.of("budget", pool.getOtherRewardBudget(), "used", pool.getOtherRewardUsed())); + stats.put("categories", categories); + + return stats; + } + + /** + * 关闭积分池 + */ + @Transactional + public void closePool(Long id) { + ScorePool pool = getPoolById(id); + pool.setStatus("closed"); + scorePoolMapper.updateById(pool); + log.info("积分池已关闭: id={}", id); + } + + /** + * 结算积分池 + */ + @Transactional + public void settlePool(Long id) { + ScorePool pool = getPoolById(id); + if (!"closed".equals(pool.getStatus())) { + throw new BusinessException(ResultCode.PARAM_ERROR, "只有已关闭的积分池才能结算"); + } + pool.setStatus("settled"); + scorePoolMapper.updateById(pool); + log.info("积分池已结算: id={}, used={}, remaining={}", + id, pool.getUsedScore(), pool.getRemainingScore()); + } + + /** + * 定时任务:每月1日自动创建新的月度积分池 + */ + @Scheduled(cron = "0 0 0 1 * ?") // 每月1日0点执行 + @Transactional + public void autoCreateNewMonthPool() { + LocalDateTime now = LocalDateTime.now(); + int year = now.getYear(); + int month = now.getMonthValue(); + + // 检查是否已存在 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ScorePool::getPoolType, "monthly") + .eq(ScorePool::getYear, year) + .eq(ScorePool::getMonth, month); + + if (scorePoolMapper.selectCount(wrapper) == 0) { + autoCreateMonthlyPool(year, month); + log.info("自动创建{}年{}月积分池", year, month); + } + + // 关闭上个月的积分池 + YearMonth lastMonth = YearMonth.of(year, month).minusMonths(1); + LambdaQueryWrapper lastWrapper = new LambdaQueryWrapper<>(); + lastWrapper.eq(ScorePool::getPoolType, "monthly") + .eq(ScorePool::getYear, lastMonth.getYear()) + .eq(ScorePool::getMonth, lastMonth.getMonthValue()) + .eq(ScorePool::getStatus, "active"); + + List lastPools = scorePoolMapper.selectList(lastWrapper); + for (ScorePool lastPool : lastPools) { + lastPool.setStatus("closed"); + scorePoolMapper.updateById(lastPool); + log.info("自动关闭上月积分池: id={}", lastPool.getId()); + } + } + + /** + * 删除积分池 + */ + @Transactional + public void deletePool(Long id) { + ScorePool pool = getPoolById(id); + if ("active".equals(pool.getStatus())) { + throw new BusinessException(ResultCode.PARAM_ERROR, "进行中的积分池不能删除"); + } + scorePoolMapper.deleteById(id); + } +} diff --git a/backend/src/main/java/com/designhub/service/SystemConfigService.java b/backend/src/main/java/com/designhub/service/SystemConfigService.java new file mode 100644 index 0000000..d761bcd --- /dev/null +++ b/backend/src/main/java/com/designhub/service/SystemConfigService.java @@ -0,0 +1,257 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.SystemConfig; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.SystemConfigMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 系统配置Service + */ +@Service +@RequiredArgsConstructor +public class SystemConfigService { + + private final SystemConfigMapper systemConfigMapper; + + /** + * 获取所有配置列表 + */ + public List getAllConfigs() { + return systemConfigMapper.selectList( + new LambdaQueryWrapper() + .orderByAsc(SystemConfig::getConfigGroup, SystemConfig::getSortOrder) + ); + } + + /** + * 根据分组获取配置列表 + */ + public List getConfigsByGroup(String group) { + return systemConfigMapper.selectList( + new LambdaQueryWrapper() + .eq(SystemConfig::getConfigGroup, group) + .orderByAsc(SystemConfig::getSortOrder) + ); + } + + /** + * 根据键获取配置值 + */ + public String getConfigValue(String key) { + SystemConfig config = systemConfigMapper.selectOne( + new LambdaQueryWrapper() + .eq(SystemConfig::getConfigKey, key) + ); + return config != null ? config.getConfigValue() : null; + } + + /** + * 根据键获取配置值(带默认值) + */ + public String getConfigValue(String key, String defaultValue) { + String value = getConfigValue(key); + return value != null ? value : defaultValue; + } + + /** + * 获取整型配置值 + */ + public Integer getIntValue(String key, Integer defaultValue) { + String value = getConfigValue(key); + if (value != null) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * 获取布尔型配置值 + */ + public Boolean getBooleanValue(String key, Boolean defaultValue) { + String value = getConfigValue(key); + if (value != null) { + return Boolean.parseBoolean(value); + } + return defaultValue; + } + + /** + * 获取所有配置的Map + */ + public Map getAllConfigsMap() { + List configs = getAllConfigs(); + return configs.stream() + .collect(Collectors.toMap( + SystemConfig::getConfigKey, + SystemConfig::getConfigValue, + (v1, v2) -> v2 + )); + } + + /** + * 根据分组获取配置Map + */ + public Map getConfigsMapByGroup(String group) { + List configs = getConfigsByGroup(group); + return configs.stream() + .collect(Collectors.toMap( + SystemConfig::getConfigKey, + SystemConfig::getConfigValue, + (v1, v2) -> v2 + )); + } + + /** + * 创建配置 + */ + @Transactional(rollbackFor = Exception.class) + public SystemConfig createConfig(SystemConfig config) { + // 验证键的唯一性 + long count = systemConfigMapper.selectCount( + new LambdaQueryWrapper() + .eq(SystemConfig::getConfigKey, config.getConfigKey()) + ); + if (count > 0) { + throw new BusinessException("配置键已存在"); + } + + // 设置默认值 + if (config.getBuiltIn() == null) { + config.setBuiltIn(0); + } + if (config.getSortOrder() == null) { + config.setSortOrder(0); + } + + systemConfigMapper.insert(config); + return config; + } + + /** + * 更新配置 + */ + @Transactional(rollbackFor = Exception.class) + public boolean updateConfig(SystemConfig config) { + SystemConfig existConfig = systemConfigMapper.selectById(config.getId()); + if (existConfig == null) { + throw new BusinessException("配置不存在"); + } + + // 内置配置不允许修改键和分组 + if (existConfig.getBuiltIn() == 1) { + config.setConfigKey(null); + config.setConfigGroup(null); + config.setBuiltIn(null); + } + + return systemConfigMapper.updateById(config) > 0; + } + + /** + * 批量更新配置值 + */ + @Transactional(rollbackFor = Exception.class) + public boolean batchUpdateValues(Map configMap) { + for (Map.Entry entry : configMap.entrySet()) { + SystemConfig config = systemConfigMapper.selectOne( + new LambdaQueryWrapper() + .eq(SystemConfig::getConfigKey, entry.getKey()) + ); + if (config != null) { + config.setConfigValue(entry.getValue()); + systemConfigMapper.updateById(config); + } + } + return true; + } + + /** + * 删除配置 + */ + @Transactional(rollbackFor = Exception.class) + public boolean deleteConfig(Long id) { + SystemConfig config = systemConfigMapper.selectById(id); + if (config == null) { + throw new BusinessException("配置不存在"); + } + + // 内置配置不允许删除 + if (config.getBuiltIn() == 1) { + throw new BusinessException("内置配置不允许删除"); + } + + return systemConfigMapper.deleteById(id) > 0; + } + + /** + * 初始化默认配置 + */ + @Transactional(rollbackFor = Exception.class) + public void initDefaultConfigs() { + // 任务相关配置 + createConfigIfNotExists("task.max_concurrent", "5", "task", "最大并发任务数", "设计师同时进行的最大任务数", "number", 1); + createConfigIfNotExists("task.grab_timeout", "30", "task", "抢单超时时间(分钟)", "任务发布后多久无人抢单则自动关闭", "number", 1); + createConfigIfNotExists("task.auto_close_days", "7", "task", "自动关闭天数", "未完成的任务多少天后自动关闭", "number", 1); + + // 积分相关配置 + createConfigIfNotExists("score.low_balance_alert", "100", "score", "积分余额预警阈值", "部门积分低于此值时发出预警", "number", 1); + createConfigIfNotExists("score.delay_penalty_rate", "10", "score", "延误扣分比例(%)", "任务延误时的扣分比例", "number", 1); + + // 通知相关配置 + createConfigIfNotExists("notification.dingtalk_enabled", "false", "notification", "启用钉钉通知", "是否启用钉钉消息推送", "boolean", 1); + createConfigIfNotExists("notification.email_enabled", "false", "notification", "启用邮件通知", "是否启用邮件通知", "boolean", 1); + + // 激励系统配置 + createConfigIfNotExists("incentive.enabled", "true", "incentive", "启用激励系统", "是否启用激励功能", "boolean", 1); + createConfigIfNotExists("incentive.daily_checkin_score", "5", "incentive", "每日签到奖励积分", "设计师每日签到可获得的积分", "number", 1); + + // 导航栏配置 - 主菜单 + createConfigIfNotExists("navbar.menu.home", "true", "navbar", "显示工作台菜单", "是否在导航栏显示工作台菜单", "boolean", 1); + createConfigIfNotExists("navbar.menu.tasks", "true", "navbar", "显示任务大厅菜单", "是否在导航栏显示任务大厅菜单", "boolean", 1); + createConfigIfNotExists("navbar.menu.designer_hall", "true", "navbar", "显示设计师大厅菜单", "是否在导航栏显示设计师大厅菜单", "boolean", 1); + createConfigIfNotExists("navbar.menu.incentive", "true", "navbar", "显示激励中心菜单", "是否在导航栏显示激励中心菜单", "boolean", 1); + createConfigIfNotExists("navbar.menu.analytics", "true", "navbar", "显示数据分析菜单", "是否在导航栏显示数据分析菜单", "boolean", 1); + + // 导航栏配置 - 功能按钮 + createConfigIfNotExists("navbar.action.notification", "true", "navbar", "显示通知按钮", "是否在导航栏显示通知铃铛按钮", "boolean", 1); + createConfigIfNotExists("navbar.action.checkin", "true", "navbar", "显示签到按钮", "是否在导航栏显示每日签到按钮", "boolean", 1); + createConfigIfNotExists("navbar.action.achievement", "true", "navbar", "显示成就按钮", "是否在导航栏显示成就中心按钮", "boolean", 1); + createConfigIfNotExists("navbar.action.settings", "true", "navbar", "显示设置按钮", "是否在导航栏显示设置按钮", "boolean", 1); + } + + /** + * 如果不存在则创建配置 + */ + private void createConfigIfNotExists(String key, String value, String group, String name, String description, String valueType, Integer builtIn) { + long count = systemConfigMapper.selectCount( + new LambdaQueryWrapper() + .eq(SystemConfig::getConfigKey, key) + ); + if (count == 0) { + SystemConfig config = new SystemConfig(); + config.setConfigKey(key); + config.setConfigValue(value); + config.setConfigGroup(group); + config.setConfigName(name); + config.setDescription(description); + config.setValueType(valueType); + config.setBuiltIn(builtIn); + config.setSortOrder(0); + systemConfigMapper.insert(config); + } + } +} + diff --git a/backend/src/main/java/com/designhub/service/SystemHealthService.java b/backend/src/main/java/com/designhub/service/SystemHealthService.java new file mode 100644 index 0000000..20279a8 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/SystemHealthService.java @@ -0,0 +1,63 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.Role; +import com.designhub.mapper.DepartmentMapper; +import com.designhub.mapper.RoleMapper; +import com.designhub.mapper.TaskMapper; +import com.designhub.mapper.UserMapper; +import com.designhub.vo.SystemHealthVO; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 系统健康检查服务 + */ +@Service +@RequiredArgsConstructor +public class SystemHealthService { + + private final UserMapper userMapper; + private final TaskMapper taskMapper; + private final RoleMapper roleMapper; + private final DepartmentMapper departmentMapper; + + @Value("${spring.profiles.active:default}") + private String activeProfile; + + /** + * 执行健康检查 + */ + public SystemHealthVO check() { + SystemHealthVO health = new SystemHealthVO(); + health.setProfile(activeProfile); + + try { + health.setUserCount(userMapper.selectCount(null)); + health.setTaskCount(taskMapper.selectCount(null)); + health.setDepartmentCount(departmentMapper.selectCount(null)); + health.setRoleCount(roleMapper.selectCount(new LambdaQueryWrapper().select(Role::getId))); + health.setDatabaseUp(true); + } catch (Exception ex) { + health.setDatabaseUp(false); + health.getChecks().add(new SystemHealthVO.CheckItem("database", false, ex.getMessage())); + return health; + } + + // 校验 sys_role 表结构是否与实体一致(只查询存在的列) + try { + roleMapper.selectList(new LambdaQueryWrapper() + .select(Role::getId, Role::getRoleCode, Role::getRoleName, Role::getStatus) + .last("LIMIT 1")); + health.getChecks().add(new SystemHealthVO.CheckItem("sys_role_schema", true, "Role columns verified")); + } catch (Exception ex) { + health.getChecks().add(new SystemHealthVO.CheckItem("sys_role_schema", false, ex.getMessage())); + } + + return health; + } +} + diff --git a/backend/src/main/java/com/designhub/service/TaskApprovalService.java b/backend/src/main/java/com/designhub/service/TaskApprovalService.java new file mode 100644 index 0000000..47f287a --- /dev/null +++ b/backend/src/main/java/com/designhub/service/TaskApprovalService.java @@ -0,0 +1,518 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.dto.PipelineTaskDTO; +import com.designhub.dto.TaskApprovalDTO; +import com.designhub.dto.TaskUpdateDTO; +import com.designhub.entity.*; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.*; +import com.designhub.common.ResultCode; +import com.designhub.entity.Brand; +import com.designhub.entity.Category; +import com.designhub.entity.Platform; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 任务审核服务 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class TaskApprovalService { + + private final TaskMapper taskMapper; + private final TaskApprovalLogMapper approvalLogMapper; + private final DepartmentMapper departmentMapper; + private final UserMapper userMapper; + private final BrandMapper brandMapper; + private final CategoryMapper categoryMapper; + private final PlatformMapper platformMapper; + private final NotificationService notificationService; + private final TeamScorePoolService teamScorePoolService; + private final TaskService taskService; + private final ConfigService configService; + private final ObjectMapper objectMapper; + + /** + * 需求部门负责人审核通过 + */ + @Transactional(rollbackFor = Exception.class) + public void approveDemandLevel(Long taskId, Long approverId, TaskApprovalDTO dto) { + // 1. 验证任务和权限 + Task task = getTaskForApproval(taskId, "pending_demand_approval"); + validateApprover(task, approverId, "demand_leader"); + + // 2. 应用任务修改(如果有) + if (dto.getTaskUpdates() != null) { + applyTaskUpdates(task, dto.getTaskUpdates()); + logApproval(taskId, approverId, 1, "modify", dto.getComment()); + } + + // 3. 根据发布类型决定是否需要接单部门负责人审核 + String releaseType = task.getReleaseType(); + boolean needReceivingApproval = "default".equals(releaseType); + + if (needReceivingApproval) { + // default模式:需要接单部门负责人审核 + Long receivingLeaderId = determineReceivingLeader(task); + if (receivingLeaderId == null) { + throw new BusinessException(ResultCode.PARAM_ERROR, "无法确定接单部门负责人,请在系统设置中为接单部门配置负责人"); + } + + // 更新任务状态为待接单部门审核 + task.setApprovalStatus("pending_receiving_approval"); + task.setDemandApprovalTime(LocalDateTime.now()); + task.setCurrentApproverId(receivingLeaderId); + task.setCurrentApproverRole("receiving_leader"); + taskMapper.updateById(task); + + // 记录审核日志 + logApproval(taskId, approverId, 1, "approve", dto.getComment()); + + // 发送通知给接单部门负责人 + User receivingLeader = userMapper.selectById(receivingLeaderId); + if (receivingLeader != null) { + notificationService.sendNotification( + receivingLeaderId, + "新任务待审核", + String.format("任务【%s】需要您审核并分配设计师", task.getTitle()), + "task_approval", + taskId + ); + } + + log.info("需求部门负责人审核通过,任务ID:{},审核人:{},等待接单部门负责人审核", taskId, approverId); + } else { + // recruitment或assigned模式:不需要接单部门负责人审核,直接发布 + task.setDemandApprovalTime(LocalDateTime.now()); + + // 判断是否为流水线任务 + if (task.getIsPipeline() != null && task.getIsPipeline() == 1) { + // 流水线任务:拆分子任务 + splitIntoPipelineSubTasks(task); + // splitIntoPipelineSubTasks方法会根据releaseType设置任务状态 + } else { + // 普通任务处理 + if ("assigned".equals(releaseType) && task.getReceiverId() != null) { + // assigned模式:如果指定了接收人,直接分配给该设计师 + User receiver = userMapper.selectById(task.getReceiverId()); + if (receiver != null && receiver.getDeptId() != null) { + task.setReceiverDeptId(receiver.getDeptId()); + task.setStatus("in_progress"); + task.setStartTime(LocalDateTime.now()); + task.setActualStartTime(LocalDateTime.now()); + + // 重新计算截止时间 + if (task.getEstimatedHours() != null) { + int hours = task.getEstimatedHours().intValue(); + if (hours > 0) { + task.setDeadline(LocalDateTime.now().plusHours(hours)); + } + } + + // 发送通知给被指定的设计师 + notificationService.sendNotification( + task.getReceiverId(), + "新任务分配", + String.format("任务【%s】已分配给您", task.getTitle()), + "task_assigned", + taskId + ); + + log.info("assigned模式任务已直接分配给设计师:taskId={}, receiverId={}", taskId, task.getReceiverId()); + } else { + throw new BusinessException(ResultCode.PARAM_ERROR, "指定的接收人不存在或部门信息不完整"); + } + } else { + // recruitment模式:发布到任务大厅 + task.setStatus("pending"); + } + } + + // 更新审核状态为已通过 + task.setApprovalStatus("approved"); + task.setCurrentApproverId(null); + task.setCurrentApproverRole(null); + taskMapper.updateById(task); + + // 扣除部门积分 + try { + teamScorePoolService.deductScore(task.getPublisherDeptId(), task.getScore()); + } catch (Exception e) { + log.error("扣除部门积分失败", e); + throw new BusinessException(ResultCode.ERROR, "扣除部门积分失败:" + e.getMessage()); + } + + // 记录审核日志 + logApproval(taskId, approverId, 1, "approve", dto.getComment()); + + log.info("需求部门负责人审核通过,任务ID:{},审核人:{},发布类型:{},已直接发布", taskId, approverId, releaseType); + } + } + + /** + * 需求部门负责人驳回 + */ + @Transactional(rollbackFor = Exception.class) + public void rejectByDemandLeader(Long taskId, Long approverId, String reason) { + Task task = getTaskForApproval(taskId, "pending_demand_approval"); + validateApprover(task, approverId, "demand_leader"); + + task.setApprovalStatus("rejected_by_demand"); + task.setStatus("draft"); + task.setCurrentApproverId(null); + task.setCurrentApproverRole(null); + taskMapper.updateById(task); + + logApproval(taskId, approverId, 1, "reject", reason); + + // 通知发起人 + notificationService.sendNotification( + task.getPublisherId(), + "任务已被驳回", + String.format("您的任务【%s】被需求部门负责人驳回,原因:%s", task.getTitle(), reason), + "task_reject", + taskId + ); + + log.info("需求部门负责人驳回任务,任务ID:{},审核人:{}", taskId, approverId); + } + + /** + * 接单部门负责人审核通过 + */ + @Transactional(rollbackFor = Exception.class) + public void approveReceivingLevel(Long taskId, Long approverId, TaskApprovalDTO dto) { + Task task = getTaskForApproval(taskId, "pending_receiving_approval"); + validateApprover(task, approverId, "receiving_leader"); + + // 应用任务修改(如果有) + if (dto.getTaskUpdates() != null) { + applyTaskUpdates(task, dto.getTaskUpdates()); + logApproval(taskId, approverId, 2, "modify", dto.getComment()); + } + + // 判断是否为流水线任务 + if (task.getIsPipeline() != null && task.getIsPipeline() == 1) { + // 流水线任务:拆分子任务 + splitIntoPipelineSubTasks(task); + } else { + // 普通任务:直接发布到任务大厅 + task.setStatus("pending"); + } + + // 更新审核状态 + task.setApprovalStatus("approved"); + task.setReceivingApprovalTime(LocalDateTime.now()); + task.setCurrentApproverId(null); + task.setCurrentApproverRole(null); + taskMapper.updateById(task); + + // 扣除部门积分 + try { + teamScorePoolService.deductScore(task.getPublisherDeptId(), task.getScore()); + } catch (Exception e) { + log.error("扣除部门积分失败", e); + throw new BusinessException(ResultCode.ERROR, "扣除部门积分失败:" + e.getMessage()); + } + + // 记录审核日志 + logApproval(taskId, approverId, 2, "approve", dto.getComment()); + + // TODO: 通知任务大厅(所有接单部门成员) + // 暂时不实现群发通知功能,后续可以通过WebSocket或轮询方式实现 + log.info("任务【{}】已发布到任务大厅", task.getTitle()); + + log.info("接单部门负责人审核通过,任务ID:{},审核人:{}", taskId, approverId); + } + + /** + * 接单部门负责人驳回 + */ + @Transactional(rollbackFor = Exception.class) + public void rejectByReceivingLeader(Long taskId, Long approverId, String reason) { + Task task = getTaskForApproval(taskId, "pending_receiving_approval"); + validateApprover(task, approverId, "receiving_leader"); + + // 退回给需求部门负责人 + Department demandDept = departmentMapper.selectById(task.getPublisherDeptId()); + if (demandDept == null || demandDept.getLeaderId() == null) { + throw new BusinessException(ResultCode.PARAM_ERROR, "需求部门负责人不存在"); + } + + task.setApprovalStatus("rejected_by_receiving"); + task.setStatus("draft"); + task.setCurrentApproverId(demandDept.getLeaderId()); + task.setCurrentApproverRole("demand_leader"); + taskMapper.updateById(task); + + logApproval(taskId, approverId, 2, "reject", reason); + + // 通知需求部门负责人 + notificationService.sendNotification( + demandDept.getLeaderId(), + "任务已被驳回", + String.format("任务【%s】被接单部门负责人驳回,原因:%s", task.getTitle(), reason), + "task_reject", + taskId + ); + + log.info("接单部门负责人驳回任务,任务ID:{},审核人:{}", taskId, approverId); + } + + /** + * 获取待审核任务列表 + */ + public List getPendingApprovalTasks(Long userId) { + List tasks = taskMapper.selectList( + new LambdaQueryWrapper() + .eq(Task::getCurrentApproverId, userId) + .in(Task::getApprovalStatus, "pending_demand_approval", "pending_receiving_approval") + .orderByDesc(Task::getCreateTime) + ); + + // 填充关联信息 + for (Task task : tasks) { + // 填充发起人姓名 + if (task.getPublisherId() != null) { + User publisher = userMapper.selectById(task.getPublisherId()); + if (publisher != null) { + task.setPublisherName(publisher.getUsername()); + } + } + + // 填充发起部门名称 + if (task.getPublisherDeptId() != null) { + Department dept = departmentMapper.selectById(task.getPublisherDeptId()); + if (dept != null) { + task.setPublisherDeptName(dept.getDeptName()); + } + } + + // 填充品牌名称 + if (task.getBrandId() != null) { + Brand brand = brandMapper.selectById(task.getBrandId()); + if (brand != null) { + // Brand实体类中name字段映射到brand_name,使用getName() + task.setBrandName(brand.getName()); + } + } + + // 填充类目名称 + if (task.getCategoryId() != null) { + Category category = categoryMapper.selectById(task.getCategoryId()); + if (category != null) { + // Category实体类中name字段映射到category_name,使用getName() + task.setCategoryName(category.getName()); + } + } + + // 填充平台名称 + if (task.getPlatformId() != null) { + Platform platform = platformMapper.selectById(task.getPlatformId()); + if (platform != null) { + // Platform实体类中name字段映射到platform_name,使用getName() + task.setPlatformName(platform.getName()); + } + } + + // 填充当前审核人姓名 + if (task.getCurrentApproverId() != null) { + User approver = userMapper.selectById(task.getCurrentApproverId()); + if (approver != null) { + task.setCurrentApproverName(approver.getRealName() != null ? approver.getRealName() : approver.getUsername()); + } + } + } + + return tasks; + } + + /** + * 获取任务并验证审核状态 + */ + private Task getTaskForApproval(Long taskId, String expectedStatus) { + Task task = taskMapper.selectById(taskId); + if (task == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "任务不存在"); + } + if (!expectedStatus.equals(task.getApprovalStatus())) { + throw new BusinessException(ResultCode.TASK_STATUS_ERROR, + "任务当前状态不允许此操作,当前状态:" + task.getApprovalStatus()); + } + return task; + } + + /** + * 验证审核人权限 + */ + private void validateApprover(Task task, Long approverId, String expectedRole) { + if (!approverId.equals(task.getCurrentApproverId())) { + throw new BusinessException(ResultCode.FORBIDDEN, "您无权审核此任务"); + } + if (!expectedRole.equals(task.getCurrentApproverRole())) { + throw new BusinessException(ResultCode.FORBIDDEN, "审核角色不匹配"); + } + } + + /** + * 确定接单部门负责人 + */ + private Long determineReceivingLeader(Task task) { + // 根据任务类型的allowedDeptIds确定接单部门 + // 简化版本:取第一个任务类型的第一个允许部门 + String taskTypeIds = task.getTaskTypeIds(); + if (taskTypeIds == null || taskTypeIds.isEmpty()) { + return null; + } + + String[] typeIdArray = taskTypeIds.split(","); + for (String typeIdStr : typeIdArray) { + try { + Long typeId = Long.parseLong(typeIdStr.trim()); + DemandType demandType = configService.getDemandTypeById(typeId); + if (demandType != null && demandType.getAllowedDeptIds() != null) { + String[] deptIds = demandType.getAllowedDeptIds().split(","); + if (deptIds.length > 0) { + Long deptId = Long.parseLong(deptIds[0].trim()); + Department dept = departmentMapper.selectById(deptId); + if (dept != null && dept.getLeaderId() != null) { + return dept.getLeaderId(); + } + } + } + } catch (Exception e) { + log.error("解析任务类型或部门ID失败", e); + } + } + return null; + } + + /** + * 应用任务更新 + */ + private void applyTaskUpdates(Task task, TaskUpdateDTO updates) { + if (updates.getTitle() != null) { + task.setTitle(updates.getTitle()); + } + if (updates.getTaskSubType() != null) { + task.setTaskSubType(updates.getTaskSubType()); + } + if (updates.getBrandId() != null) { + task.setBrandId(updates.getBrandId()); + } + if (updates.getPlatformId() != null) { + task.setPlatformId(updates.getPlatformId()); + } + if (updates.getCategoryId() != null) { + task.setCategoryId(updates.getCategoryId()); + } + if (updates.getSpuInfo() != null) { + task.setSpuInfo(updates.getSpuInfo()); + } + if (updates.getRemark() != null) { + task.setRemark(updates.getRemark()); + } + if (updates.getAttachmentUrl() != null) { + task.setAttachmentUrl(updates.getAttachmentUrl()); + } + // 如果修改了任务类型和数量,需要重新计算积分 + if (updates.getTaskTypes() != null && !updates.getTaskTypes().isEmpty()) { + String taskTypeIds = updates.getTaskTypes().stream() + .map(t -> String.valueOf(t.getTaskTypeId())) + .collect(Collectors.joining(",")); + task.setTaskTypeIds(taskTypeIds); + + // 如果只有一个任务类型,更新quantity字段 + if (updates.getTaskTypes().size() == 1) { + task.setQuantity(updates.getTaskTypes().get(0).getQuantity()); + } + + // 重新计算积分 + int totalScore = taskService.calculateTotalScore(updates.getTaskTypes()); + task.setScore(totalScore); + } + } + + /** + * 拆分为流水线子任务 + */ + private void splitIntoPipelineSubTasks(Task task) { + try { + // 构建流水线任务DTO + PipelineTaskDTO dto = new PipelineTaskDTO(); + dto.setTitle(task.getTitle()); + dto.setTaskSubType(task.getTaskSubType()); + dto.setBrandId(task.getBrandId()); + dto.setPlatformId(task.getPlatformId()); + dto.setCategoryId(task.getCategoryId()); + dto.setSpuInfo(task.getSpuInfo()); + dto.setRemark(task.getRemark()); + dto.setAttachmentUrl(task.getAttachmentUrl()); + dto.setPipelineMode(task.getPipelineMode()); + dto.setReleaseType(task.getReleaseType()); // 传递发布类型 + + // 解析任务类型 + String[] taskTypeIds = task.getTaskTypeIds().split(","); + List items = new java.util.ArrayList<>(); + + // 简化版本:所有任务类型使用相同的数量(task.quantity) + int quantity = task.getQuantity() != null ? task.getQuantity() : 1; + + for (String taskTypeIdStr : taskTypeIds) { + PipelineTaskDTO.TaskItemDTO item = new PipelineTaskDTO.TaskItemDTO(); + item.setTaskTypeId(Long.parseLong(taskTypeIdStr.trim())); + item.setQuantity(quantity); + items.add(item); + } + dto.setItems(items); + + // 调用TaskService创建流水线子任务 + taskService.createPipelineTaskItems(task, dto); + + // 根据发布类型设置主任务状态 + if ("recruitment".equals(task.getReleaseType())) { + // recruitment模式:发布到任务大厅 + task.setStatus("pending"); + } else if ("assigned".equals(task.getReleaseType()) && task.getReceiverId() != null) { + // assigned模式:如果指定了接收人,状态已经是in_progress(在approveDemandLevel中已设置) + // 这里不需要再次设置 + } + + } catch (Exception e) { + log.error("拆分流水线任务失败", e); + throw new BusinessException(ResultCode.ERROR, "拆分流水线任务失败:" + e.getMessage()); + } + } + + /** + * 记录审核日志 + */ + private void logApproval(Long taskId, Long approverId, Integer level, String action, String comment) { + try { + User approver = userMapper.selectById(approverId); + + TaskApprovalLog log = new TaskApprovalLog(); + log.setTaskId(taskId); + log.setApprovalLevel(level); + log.setApproverId(approverId); + log.setApproverName(approver != null ? approver.getRealName() : null); + log.setAction(action); + log.setComment(comment); + log.setCreateTime(LocalDateTime.now()); + + approvalLogMapper.insert(log); + } catch (Exception e) { + this.log.error("记录审核日志失败", e); + } + } +} diff --git a/backend/src/main/java/com/designhub/service/TaskItemService.java b/backend/src/main/java/com/designhub/service/TaskItemService.java new file mode 100644 index 0000000..62efa35 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/TaskItemService.java @@ -0,0 +1,365 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.designhub.common.ResultCode; +import com.designhub.entity.Task; +import com.designhub.entity.TaskItem; +import com.designhub.entity.Notification; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.TaskItemMapper; +import com.designhub.mapper.TaskMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 任务子项服务类(流水线任务) + */ +@Service +@RequiredArgsConstructor +public class TaskItemService { + private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TaskItemService.class); + + private final TaskItemMapper taskItemMapper; + private final TaskMapper taskMapper; + private final UserService userService; + private final NotificationService notificationService; + + /** + * 获取可接单的子任务列表 + */ + public IPage getAvailableItems(Integer page, Integer pageSize, Long taskTypeId, String keyword) { + Page pageParam = new Page<>(page, pageSize); + return taskItemMapper.selectAvailableItems(pageParam, taskTypeId, keyword); + } + + /** + * 获取进行中的子任务列表 + */ + public IPage getInProgressItems(Integer page, Integer pageSize, Long receiverId) { + Page pageParam = new Page<>(page, pageSize); + return taskItemMapper.selectInProgressItems(pageParam, receiverId); + } + + /** + * 获取已完成的子任务列表 + */ + public IPage getCompletedItems(Integer page, Integer pageSize, Long receiverId) { + Page pageParam = new Page<>(page, pageSize); + return taskItemMapper.selectCompletedItems(pageParam, receiverId); + } + + /** + * 根据任务ID获取所有子任务 + */ + public List getItemsByTaskId(Long taskId) { + return taskItemMapper.selectByTaskId(taskId); + } + + /** + * 获取子任务详情 + */ + public TaskItem getItemById(Long itemId) { + TaskItem item = taskItemMapper.selectById(itemId); + if (item == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "子任务不存在"); + } + + // 加载主任务信息 + Task mainTask = taskMapper.selectById(item.getTaskId()); + if (mainTask != null) { + item.setMainTaskTitle(mainTask.getTitle()); + item.setPipelineMode(mainTask.getPipelineMode()); + item.setBrandName(mainTask.getBrandName()); + item.setCategoryName(mainTask.getCategoryName()); + item.setPlatformName(mainTask.getPlatformName()); + item.setPublisherName(mainTask.getPublisherName()); + item.setSpuInfo(mainTask.getSpuInfo()); + item.setTaskSubType(mainTask.getTaskSubType()); + item.setAttachmentUrl(mainTask.getAttachmentUrl()); + } + + return item; + } + + /** + * 抢单子任务 + */ + @Transactional(rollbackFor = Exception.class) + public void grabItem(Long itemId, Long userId) { + log.info("设计师 {} 抢单子任务 {}", userId, itemId); + + // 获取用户信息 + var user = userService.getUserById(userId); + if (user == null) { + throw new BusinessException(ResultCode.FORBIDDEN, "用户不存在"); + } + + // 获取子任务信息 + TaskItem item = taskItemMapper.selectById(itemId); + if (item == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "子任务不存在"); + } + + // 验证状态 + if (!"pending".equals(item.getStatus())) { + throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "该子任务不可接单"); + } + + if (item.getIsLocked() != null && item.getIsLocked() == 1) { + throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "该子任务已被锁定,请等待前序任务完成"); + } + + if (item.getReceiverId() != null) { + throw new BusinessException(ResultCode.TASK_ALREADY_TAKEN, "该子任务已被接单"); + } + + // 乐观锁抢单 + int result = taskItemMapper.grabItem(itemId, userId, user.getRealName()); + if (result == 0) { + throw new BusinessException(ResultCode.TASK_ALREADY_TAKEN, "抢单失败,该子任务可能已被他人接单"); + } + + log.info("设计师 {} 成功抢单子任务 {}", userId, itemId); + + // 发送通知给任务发布者 + Task mainTask = taskMapper.selectById(item.getTaskId()); + if (mainTask != null && mainTask.getPublisherId() != null) { + Notification notification = new Notification(); + notification.setReceiverId(mainTask.getPublisherId()); + notification.setNotificationType("task"); + notification.setTitle("子任务已被接单"); + notification.setContent(user.getRealName() + " 接单了子任务:" + item.getItemNo()); + notification.setRelatedId(itemId); + notification.setRelatedType("task_item"); + notification.setPriority(1); // 普通 + notificationService.sendNotification(notification); + } + } + + /** + * 更新子任务进度 + */ + @Transactional(rollbackFor = Exception.class) + public void updateProgress(Long itemId, Long userId, Integer progress) { + log.info("设计师 {} 更新子任务 {} 进度为 {}%", userId, itemId, progress); + + TaskItem item = taskItemMapper.selectById(itemId); + if (item == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "子任务不存在"); + } + + // 验证权限 + if (!userId.equals(item.getReceiverId())) { + throw new BusinessException(ResultCode.FORBIDDEN, "无权更新该子任务"); + } + + // 验证状态 + if (!"in_progress".equals(item.getStatus())) { + throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "只能更新进行中的子任务"); + } + + // 验证进度值 + if (progress < 0 || progress > 100) { + throw new BusinessException(ResultCode.PARAM_ERROR, "进度值必须在0-100之间"); + } + + // 更新进度 + item.setProgress(progress); + item.setUpdateTime(LocalDateTime.now()); + taskItemMapper.updateById(item); + + log.info("子任务 {} 进度已更新为 {}%", itemId, progress); + } + + /** + * 完结子任务(关键方法:包含自动解锁逻辑) + */ + @Transactional(rollbackFor = Exception.class) + public Map completeItem(Long itemId, Long userId, String deliveryPath) { + log.info("设计师 {} 完结子任务 {}", userId, itemId); + + // 1. 获取子任务信息 + TaskItem item = taskItemMapper.selectById(itemId); + if (item == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "子任务不存在"); + } + + // 2. 验证权限 + if (!userId.equals(item.getReceiverId())) { + throw new BusinessException(ResultCode.FORBIDDEN, "无权完结该子任务"); + } + + // 3. 验证状态 + if (!"in_progress".equals(item.getStatus())) { + throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "只能完结进行中的子任务"); + } + + // 4. 更新子任务状态 + item.setStatus("completed"); + item.setProgress(100); + item.setDeliveryPath(deliveryPath); + item.setCompleteTime(LocalDateTime.now()); + item.setUpdateTime(LocalDateTime.now()); + taskItemMapper.updateById(item); + + log.info("子任务 {} 已完成", itemId); + + // 5. 设计师立即获得积分(如果需要,可以调用积分服务) + // scoreService.addScore(userId, item.getTotalScore()); + + Map result = new HashMap<>(); + result.put("completedItem", item); + + // 6. 🔥 关键步骤:检查是否是顺序流水线,基于group_no解锁下一组 + Task mainTask = taskMapper.selectById(item.getTaskId()); + if (mainTask != null && "sequential".equals(mainTask.getPipelineMode())) { + // 获取当前子任务的分组号 + Integer currentGroupNo = item.getGroupNo(); + if (currentGroupNo == null) { + currentGroupNo = 1; // 兼容旧数据 + } + + // 检查当前组的所有子任务是否都已完成 + List currentGroupItems = taskItemMapper.selectList( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(TaskItem::getTaskId, item.getTaskId()) + .eq(TaskItem::getGroupNo, currentGroupNo) + ); + + boolean allGroupCompleted = currentGroupItems.stream() + .allMatch(i -> "completed".equals(i.getStatus())); + + if (allGroupCompleted) { + log.info("✅ 第 {} 组所有子任务已完成,准备解锁下一组", currentGroupNo); + + // 解锁下一组的所有子任务 + Integer nextGroupNo = currentGroupNo + 1; + List nextGroupItems = taskItemMapper.selectList( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(TaskItem::getTaskId, item.getTaskId()) + .eq(TaskItem::getGroupNo, nextGroupNo) + .eq(TaskItem::getStatus, "locked") + ); + + if (!nextGroupItems.isEmpty()) { + // 批量解锁下一组的所有子任务 + for (TaskItem nextItem : nextGroupItems) { + nextItem.setStatus("pending"); + nextItem.setIsLocked(0); + nextItem.setUpdateTime(LocalDateTime.now()); + taskItemMapper.updateById(nextItem); + + log.info("✅ 解锁子任务: {} (组号: {}, ID: {})", + nextItem.getItemNo(), nextGroupNo, nextItem.getId()); + } + + result.put("unlockedGroupNo", nextGroupNo); + result.put("unlockedCount", nextGroupItems.size()); + + // 发送通知:新的子任务组已开放 + Notification notification = new Notification(); + notification.setNotificationType("task"); + notification.setTitle("新子任务组已开放"); + notification.setContent("第 " + nextGroupNo + " 组共 " + nextGroupItems.size() + " 个子任务已解锁,可以接单了!"); + notification.setRelatedId(item.getTaskId()); + notification.setRelatedType("task"); + notification.setPriority(1); // 普通 + // 可以通知所有接单部门的成员(这里暂时省略,可以扩展) + + log.info("第 {} 组 {} 个子任务已进入可接任务池", nextGroupNo, nextGroupItems.size()); + } else { + log.info("没有更多子任务组需要解锁"); + } + } else { + log.info("当前组还有子任务未完成,暂不解锁下一组"); + } + } + + // 7. 检查主任务是否所有子任务都已完成 + int completedCount = taskItemMapper.countCompletedItems(item.getTaskId()); + int totalCount = taskItemMapper.countTotalItems(item.getTaskId()); + + if (completedCount == totalCount) { + // 所有子任务完成,更新主任务状态 + mainTask.setStatus("completed"); + mainTask.setCompleteTime(LocalDateTime.now()); + mainTask.setUpdateTime(LocalDateTime.now()); + taskMapper.updateById(mainTask); + + log.info("🎉 主任务 {} 所有子任务已完成,主任务标记为完成", mainTask.getId()); + + // 发送通知给发布者 + if (mainTask.getPublisherId() != null) { + Notification notification = new Notification(); + notification.setReceiverId(mainTask.getPublisherId()); + notification.setNotificationType("task"); + notification.setTitle("流水线任务已完成"); + notification.setContent("您的任务《" + mainTask.getTitle() + "》所有子任务已完成!"); + notification.setRelatedId(mainTask.getId()); + notification.setRelatedType("task"); + notification.setPriority(1); // 普通 + notificationService.sendNotification(notification); + } + + result.put("mainTaskCompleted", true); + } else { + result.put("mainTaskCompleted", false); + result.put("progress", completedCount + "/" + totalCount); + } + + return result; + } + + /** + * 驳回子任务 + */ + @Transactional(rollbackFor = Exception.class) + public void rejectItem(Long itemId, String reason) { + TaskItem item = taskItemMapper.selectById(itemId); + if (item == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "子任务不存在"); + } + + item.setStatus("rejected"); + item.setUpdateTime(LocalDateTime.now()); + taskItemMapper.updateById(item); + + // 发送通知给接单人 + if (item.getReceiverId() != null) { + Notification notification = new Notification(); + notification.setReceiverId(item.getReceiverId()); + notification.setNotificationType("task"); + notification.setTitle("子任务被驳回"); + notification.setContent("您的子任务 " + item.getItemNo() + " 被驳回。原因:" + reason); + notification.setRelatedId(itemId); + notification.setRelatedType("task_item"); + notification.setPriority(1); // 普通 + notificationService.sendNotification(notification); + } + + log.info("子任务 {} 已驳回", itemId); + } + + /** + * 获取主任务的进度统计 + */ + public Map getTaskProgress(Long taskId) { + int completedCount = taskItemMapper.countCompletedItems(taskId); + int totalCount = taskItemMapper.countTotalItems(taskId); + + Map progress = new HashMap<>(); + progress.put("completedCount", completedCount); + progress.put("totalCount", totalCount); + progress.put("percentage", totalCount > 0 ? (completedCount * 100 / totalCount) : 0); + + return progress; + } +} diff --git a/backend/src/main/java/com/designhub/service/TaskProgressCheckService.java b/backend/src/main/java/com/designhub/service/TaskProgressCheckService.java new file mode 100644 index 0000000..d05762b --- /dev/null +++ b/backend/src/main/java/com/designhub/service/TaskProgressCheckService.java @@ -0,0 +1,186 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.Task; +import com.designhub.entity.TaskProgressUpdateLog; +import com.designhub.mapper.TaskMapper; +import com.designhub.mapper.TaskProgressUpdateLogMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +/** + * 任务进度检查服务 + * 每日18:00检查任务进度是否更新,并发送通知 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class TaskProgressCheckService { + + private final TaskMapper taskMapper; + private final TaskProgressUpdateLogMapper progressUpdateLogMapper; + private final NotificationService notificationService; + private final UserService userService; + + /** + * 每日18:00执行进度更新检查 + * cron: 秒 分 时 日 月 周 + */ + @Scheduled(cron = "0 0 18 * * ?") + @Transactional + public void checkDailyProgressUpdate() { + log.info("=== 开始执行每日进度更新检查任务 ==="); + + LocalDate today = LocalDate.now(); + LocalDateTime todayStart = LocalDateTime.of(today, LocalTime.MIN); + LocalDateTime now = LocalDateTime.now(); + + // 查询所有进行中的任务 + List inProgressTasks = taskMapper.selectList( + new LambdaQueryWrapper() + .eq(Task::getStatus, "in_progress") + .isNotNull(Task::getReceiverId) + ); + + log.info("当前进行中的任务数量: {}", inProgressTasks.size()); + + int notUpdatedCount = 0; + int updatedCount = 0; + int notificationSentCount = 0; + + for (Task task : inProgressTasks) { + try { + // 检查今天是否已经有记录 + TaskProgressUpdateLog existingLog = progressUpdateLogMapper.selectOne( + new LambdaQueryWrapper() + .eq(TaskProgressUpdateLog::getTaskId, task.getId()) + .eq(TaskProgressUpdateLog::getCheckDate, today) + ); + + if (existingLog != null) { + log.debug("任务 {} 今天已检查过,跳过", task.getTaskNo()); + continue; + } + + // 检查任务进度是否在今天更新过 + boolean updatedToday = task.getUpdateTime() != null + && task.getUpdateTime().isAfter(todayStart); + + // 创建检查记录 + // 获取接单人姓名 + String receiverName = null; + if (task.getReceiverId() != null) { + try { + var receiver = userService.getUserById(task.getReceiverId()); + if (receiver != null) { + receiverName = receiver.getRealName() != null ? receiver.getRealName() : receiver.getUsername(); + } + } catch (Exception e) { + log.warn("获取接单人姓名失败: receiverId={}", task.getReceiverId()); + } + } + + TaskProgressUpdateLog log = new TaskProgressUpdateLog(); + log.setTaskId(task.getId()); + log.setTaskNo(task.getTaskNo()); + log.setTaskTitle(task.getTitle()); + log.setReceiverId(task.getReceiverId()); + log.setReceiverName(receiverName); + log.setCheckDate(today); + log.setIsUpdated(updatedToday ? 1 : 0); + log.setLastUpdateTime(task.getUpdateTime()); + log.setProgressBefore(task.getProgress()); + log.setTaskStatus(task.getStatus()); + log.setCreateTime(now); + + if (updatedToday) { + updatedCount++; + log.setProgressAfter(task.getProgress()); + log.setRemark("今日已更新进度"); + log.setNotificationSent(0); // 已更新,不需要发送通知 + } else { + notUpdatedCount++; + log.setRemark("今日未更新进度,已发送提醒通知"); + + // 发送通知给接单人 + if (task.getReceiverId() != null) { + try { + notificationService.sendTaskNotification( + task.getReceiverId(), + task.getId(), + String.format("任务【%s】需要更新进度", task.getTitle()), + String.format("您接收的任务【%s】(编号:%s)今天还未更新进度,请及时更新任务进展情况。", + task.getTitle(), task.getTaskNo()) + ); + log.setNotificationSent(1); + log.setNotificationTime(LocalDateTime.now()); + notificationSentCount++; + this.log.debug("已向用户 {} 发送进度更新提醒", task.getReceiverId()); + } catch (Exception e) { + this.log.error("发送进度更新通知失败: taskId={}, receiverId={}", + task.getId(), task.getReceiverId(), e); + log.setNotificationSent(0); + log.setRemark("今日未更新进度,通知发送失败: " + e.getMessage()); + } + } + } + + // 保存检查记录 + progressUpdateLogMapper.insert(log); + + } catch (Exception e) { + this.log.error("检查任务进度失败: taskId={}, taskNo={}", + task.getId(), task.getTaskNo(), e); + } + } + + log.info("=== 每日进度更新检查完成 ==="); + log.info("总任务数: {}, 已更新: {}, 未更新: {}, 通知已发送: {}", + inProgressTasks.size(), updatedCount, notUpdatedCount, notificationSentCount); + } + + /** + * 手动触发进度检查(用于测试或管理员手动触发) + */ + public void manualCheckProgressUpdate() { + log.info("手动触发进度更新检查"); + checkDailyProgressUpdate(); + } + + /** + * 获取指定日期的进度更新记录 + */ + public List getProgressUpdateLogs(LocalDate date, Integer isUpdated) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(TaskProgressUpdateLog::getCheckDate, date); + if (isUpdated != null) { + wrapper.eq(TaskProgressUpdateLog::getIsUpdated, isUpdated); + } + wrapper.orderByDesc(TaskProgressUpdateLog::getCreateTime); + return progressUpdateLogMapper.selectList(wrapper); + } + + /** + * 获取某个用户的进度更新记录 + */ + public List getUserProgressUpdateLogs(Long userId, LocalDate startDate, LocalDate endDate) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(TaskProgressUpdateLog::getReceiverId, userId); + if (startDate != null) { + wrapper.ge(TaskProgressUpdateLog::getCheckDate, startDate); + } + if (endDate != null) { + wrapper.le(TaskProgressUpdateLog::getCheckDate, endDate); + } + wrapper.orderByDesc(TaskProgressUpdateLog::getCheckDate); + return progressUpdateLogMapper.selectList(wrapper); + } +} diff --git a/backend/src/main/java/com/designhub/service/TaskService.java b/backend/src/main/java/com/designhub/service/TaskService.java new file mode 100644 index 0000000..b3bb651 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/TaskService.java @@ -0,0 +1,1723 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.designhub.common.ResultCode; +import com.designhub.dto.TaskAuditRequest; +import com.designhub.dto.TaskAssignRequest; +import com.designhub.dto.TaskUpdateDTO; +import com.designhub.entity.*; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.TaskAuditLogMapper; +import com.designhub.mapper.TaskAssignLogMapper; +import com.designhub.mapper.TaskMapper; +import com.designhub.vo.DashboardStatsVO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; +import org.springframework.util.StringUtils; + +/** + * 任务服务类 + */ +@Service +@RequiredArgsConstructor +public class TaskService { + private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TaskService.class); + + private final TaskMapper taskMapper; + private final TaskAuditLogMapper taskAuditLogMapper; + private final TaskAssignLogMapper taskAssignLogMapper; + private final TeamScorePoolService teamScorePoolService; + private final ConfigService configService; + private final DelayPenaltyService delayPenaltyService; + private final UserService userService; + private final DepartmentService departmentService; + private final RoleService roleService; + private final NotificationService notificationService; + private final com.designhub.mapper.TaskItemMapper taskItemMapper; + + /** + * 创建任务 + */ + @Transactional(rollbackFor = Exception.class) + public Long createTask(Task task) { + log.info("创建任务: {}", task.getTitle()); + + // 验证用户权限:只有需求部门可以发起任务(管理员除外) + if (task.getPublisherId() != null) { + var user = userService.getUserById(task.getPublisherId()); + if (user == null) { + throw new BusinessException(ResultCode.FORBIDDEN, "用户不存在"); + } + + // 检查是否是管理员(管理员可以同时拥有需求部门和接单部门权限) + boolean isAdmin = isAdminUser(user.getId()); + + if (!isAdmin) { + if (user.getDeptId() == null) { + throw new BusinessException(ResultCode.FORBIDDEN, "用户部门信息不完整"); + } + + var department = departmentService.getDepartmentById(user.getDeptId()); + if (department == null) { + throw new BusinessException(ResultCode.FORBIDDEN, "用户部门不存在"); + } + + if (!"demand".equals(department.getDeptType())) { + throw new BusinessException(ResultCode.FORBIDDEN, "只有需求部门可以发起任务,您所在的部门是接单部门"); + } + } + + // 设置发布人部门ID + if (task.getPublisherDeptId() == null && user.getDeptId() != null) { + task.setPublisherDeptId(user.getDeptId()); + } + } + + // 验证任务类型并计算积分(支持多类型) + if (task.getQuantity() != null) { + // 优先处理多类型 + if (task.getTaskTypeIds() != null && !task.getTaskTypeIds().isEmpty()) { + String[] typeIds = task.getTaskTypeIds().split(","); + + // 验证所有类型ID有效 + for (String typeIdStr : typeIds) { + Long typeId = Long.parseLong(typeIdStr.trim()); + var demandType = configService.getDemandTypeById(typeId); + if (demandType == null) { + throw new BusinessException(ResultCode.PARAM_ERROR, "任务类型ID无效: " + typeId); + } + } + + // 计算总积分和工时:取第一个类型的单价 + Long firstTypeId = Long.parseLong(typeIds[0].trim()); + var firstType = configService.getDemandTypeById(firstTypeId); + int totalScore = firstType.getUnitScore() * task.getQuantity(); + int totalHours = firstType.getEstimatedHours() * task.getQuantity(); + + task.setScore(totalScore); + task.setEstimatedHours(BigDecimal.valueOf(totalHours)); + + // 兼容性:同时保存第一个类型到task_type_id + task.setTaskTypeId(firstTypeId); + + log.info("创建多类型任务: typeIds={}, score={}", task.getTaskTypeIds(), totalScore); + } + // 兼容单类型模式 + else if (task.getTaskTypeId() != null) { + var demandType = configService.getDemandTypeById(task.getTaskTypeId()); + if (demandType == null) { + throw new BusinessException(ResultCode.PARAM_ERROR, "任务类型不存在"); + } + + // 计算总积分和工时 + int totalScore = demandType.getUnitScore() * task.getQuantity(); + int totalHours = demandType.getEstimatedHours() * task.getQuantity(); + + task.setScore(totalScore); + task.setEstimatedHours(BigDecimal.valueOf(totalHours)); + + // 自动填充taskTypeIds字段 + task.setTaskTypeIds(String.valueOf(task.getTaskTypeId())); + + log.info("创建单类型任务: typeId={}, score={}", task.getTaskTypeId(), totalScore); + } else { + throw new BusinessException(ResultCode.PARAM_ERROR, "请选择任务类型"); + } + } + + // 检查部门积分是否充足 + if (task.getPublisherDeptId() != null && task.getScore() != null) { + if (!teamScorePoolService.checkBalance(task.getPublisherDeptId(), task.getScore())) { + throw new BusinessException(ResultCode.INSUFFICIENT_SCORE, "部门积分余额不足"); + } + + // 检查周期消耗限制 + if (!teamScorePoolService.checkPeriodLimit(task.getPublisherDeptId(), task.getScore())) { + throw new BusinessException(ResultCode.SCORE_LIMIT_EXCEEDED, "超过本周期积分消耗限制"); + } + } + + // 生成任务编号 + task.setTaskNo(generateTaskNo()); + + // 生成第三方平台任务编号 + task.setThirdPartyTaskNo(generateThirdPartyTaskNo()); + + // 设置任务状态:默认为草稿,需要审核 + task.setStatus("draft"); + + // 设置审核状态:初始为草稿状态 + task.setAuditStatus("draft"); + + // 设置进度为0 + task.setProgress(0); + + // 设置紧急标识默认值 + if (task.getIsUrgent() == null) { + task.setIsUrgent(0); + } + + // 设置延误标识默认值 + if (task.getIsDelayed() == null) { + task.setIsDelayed(0); + } + + // 【两级审核流程】初始化审核状态 + initApprovalStatus(task); + + // 保存任务(草稿状态不扣除积分) + taskMapper.insert(task); + + // 注意:草稿状态不扣除积分,只有审核通过后才扣除 + // 积分扣除将在审核通过时进行 + + // 【两级审核流程】发送审核通知 + sendApprovalNotification(task); + + return task.getId(); + } + + /** + * 创建流水线任务 + */ + @Transactional(rollbackFor = Exception.class) + public Long createPipelineTask(com.designhub.dto.PipelineTaskDTO dto, Long publisherId) { + log.info("创建流水线任务: {}, 模式: {}", dto.getTitle(), dto.getPipelineMode()); + + // 验证用户权限 + var user = userService.getUserById(publisherId); + if (user == null) { + throw new BusinessException(ResultCode.FORBIDDEN, "用户不存在"); + } + + // 检查是否是管理员 + boolean isAdmin = isAdminUser(user.getId()); + + if (!isAdmin) { + if (user.getDeptId() == null) { + throw new BusinessException(ResultCode.FORBIDDEN, "用户部门信息不完整"); + } + + var department = departmentService.getDepartmentById(user.getDeptId()); + if (department == null) { + throw new BusinessException(ResultCode.FORBIDDEN, "用户部门不存在"); + } + + if (!"demand".equals(department.getDeptType())) { + throw new BusinessException(ResultCode.FORBIDDEN, "只有需求部门可以发起任务"); + } + } + + // 验证流水线模式 + if (!"sequential".equals(dto.getPipelineMode()) && !"parallel".equals(dto.getPipelineMode())) { + throw new BusinessException(ResultCode.PARAM_ERROR, "流水线模式必须是 sequential 或 parallel"); + } + + // 验证子任务列表 + if (dto.getItems() == null || dto.getItems().isEmpty()) { + throw new BusinessException(ResultCode.PARAM_ERROR, "流水线任务至少需要一个子任务"); + } + + // 计算总积分和工时 + int totalScore = 0; + double totalHours = 0; + for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : dto.getItems()) { + var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId()); + if (demandType == null) { + throw new BusinessException(ResultCode.PARAM_ERROR, "任务类型ID无效: " + itemDto.getTaskTypeId()); + } + + int itemScore = demandType.getUnitScore() * itemDto.getQuantity(); + double itemHours = demandType.getEstimatedHours() * itemDto.getQuantity(); + + totalScore += itemScore; + totalHours += itemHours; + } + + // 检查部门积分是否充足 + if (user.getDeptId() != null) { + if (!teamScorePoolService.checkBalance(user.getDeptId(), totalScore)) { + throw new BusinessException(ResultCode.INSUFFICIENT_SCORE, "部门积分余额不足"); + } + + if (!teamScorePoolService.checkPeriodLimit(user.getDeptId(), totalScore)) { + throw new BusinessException(ResultCode.SCORE_LIMIT_EXCEEDED, "超过本周期积分消耗限制"); + } + } + + // 创建主任务 + Task task = new Task(); + task.setTaskNo(generateTaskNo()); + task.setThirdPartyTaskNo(generateThirdPartyTaskNo()); + task.setTitle(dto.getTitle()); + task.setPublisherId(publisherId); + task.setPublisherDeptId(user.getDeptId()); + task.setIsPipeline(1); + task.setPipelineMode(dto.getPipelineMode()); + task.setScore(totalScore); + task.setEstimatedHours(BigDecimal.valueOf(totalHours)); + task.setBrandId(dto.getBrandId()); + task.setCategoryId(dto.getCategoryId()); + task.setPlatformId(dto.getPlatformId()); + task.setSpuInfo(dto.getSpuInfo()); + task.setTaskSubType(dto.getTaskSubType()); + task.setAttachmentUrl(dto.getAttachmentUrl()); + task.setRemark(dto.getRemark()); + task.setPriority(dto.getPriority() != null ? dto.getPriority() : 1); // 默认优先级:1-普通 + + // 设置时间字段 + task.setCreateTime(LocalDateTime.now()); + // 如果前端没有传递截止时间,默认为创建时间+7天 + if (dto.getDeadline() != null) { + task.setDeadline(dto.getDeadline()); + } else { + task.setDeadline(LocalDateTime.now().plusDays(7)); + } + + // 设置任务类型ID(使用第一个任务类型作为主任务类型) + if (dto.getItems() != null && !dto.getItems().isEmpty()) { + task.setTaskTypeId(dto.getItems().get(0).getTaskTypeId()); + // 设置所有任务类型ID(逗号分隔) + String taskTypeIds = dto.getItems().stream() + .map(item -> String.valueOf(item.getTaskTypeId())) + .collect(Collectors.joining(",")); + task.setTaskTypeIds(taskTypeIds); + } + + task.setStatus("draft"); + task.setAuditStatus("draft"); + task.setProgress(0); + task.setIsUrgent(0); + task.setIsDelayed(0); + // 设置发布类型(从前端传递,默认为recruitment) + task.setReleaseType(dto.getReleaseType() != null ? dto.getReleaseType() : "recruitment"); + // 如果是assigned模式,设置接收人 + if ("assigned".equals(dto.getReleaseType()) && dto.getReceiverId() != null) { + task.setReceiverId(dto.getReceiverId()); + // 获取接收人的部门ID + User receiver = userService.getUserById(dto.getReceiverId()); + if (receiver != null && receiver.getDeptId() != null) { + task.setReceiverDeptId(receiver.getDeptId()); + } + } + + // 【两级审核流程】初始化审核状态 + initApprovalStatus(task); + + taskMapper.insert(task); + log.info("主任务创建成功,ID: {}, 任务编号: {}", task.getId(), task.getTaskNo()); + + // **步骤1:按接单部门自动分组** + // 遍历任务类型列表,根据接单部门分组 + int currentGroupNo = 1; + String lastAllowedDeptIds = null; + + for (int i = 0; i < dto.getItems().size(); i++) { + com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto = dto.getItems().get(i); + var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId()); + + // 获取当前任务类型的接单部门配置 + String currentAllowedDeptIds = demandType.getAllowedDeptIds(); + + // 判断是否需要创建新分组 + boolean needNewGroup = false; + if (i > 0) { + // 如果当前或上一个的 allowed_dept_ids 为 null,则必须分组 + // 因为 null 表示所有部门可接单,无法确定具体接单部门 + if (currentAllowedDeptIds == null || lastAllowedDeptIds == null) { + needNewGroup = true; + } + // 如果两者都不为 null,则比较是否相同 + else if (!currentAllowedDeptIds.equals(lastAllowedDeptIds)) { + needNewGroup = true; + } + } + + if (needNewGroup) { + currentGroupNo++; + log.info("检测到接单部门变化,创建新分组: groupNo={}, 前一部门={}, 当前部门={}", + currentGroupNo, lastAllowedDeptIds, currentAllowedDeptIds); + } + + // 记录当前分组号(临时存储在sortOrder字段,稍后会恢复) + itemDto.setSortOrder(currentGroupNo * 1000 + i); // 高位存group_no,低位存原始顺序 + lastAllowedDeptIds = currentAllowedDeptIds; + } + + log.info("分组完成,共 {} 个子任务组", currentGroupNo); + + // **步骤2:创建子任务,按分组设置状态** + for (int i = 0; i < dto.getItems().size(); i++) { + com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto = dto.getItems().get(i); + + var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId()); + + // 解析分组号 + int itemGroupNo = itemDto.getSortOrder() / 1000; + int itemIndex = itemDto.getSortOrder() % 1000; + + com.designhub.entity.TaskItem item = new com.designhub.entity.TaskItem(); + item.setTaskId(task.getId()); + item.setItemNo(generateTaskItemNo(task.getTaskNo(), i + 1)); + item.setGroupNo(itemGroupNo); + item.setTaskTypeId(itemDto.getTaskTypeId()); + item.setQuantity(itemDto.getQuantity()); + item.setUnitScore(demandType.getUnitScore()); + item.setTotalScore(demandType.getUnitScore() * itemDto.getQuantity()); + item.setEstimatedHours(BigDecimal.valueOf(demandType.getEstimatedHours() * itemDto.getQuantity())); + item.setSortOrder(i + 1); // 恢复真实的排序号 + item.setProgress(0); + + // 关键:根据流水线模式和分组设置初始状态 + if ("sequential".equals(dto.getPipelineMode())) { + if (itemGroupNo == 1) { + // 顺序流水线:第一组的所有子任务都是 pending(可接单) + item.setStatus("pending"); + item.setIsLocked(0); + item.setPrevItemId(null); + } else { + // 其他组的子任务都是 locked(锁定) + item.setStatus("locked"); + item.setIsLocked(1); + } + } else { + // 并行流水线:所有子任务都是 pending(可接单) + item.setStatus("pending"); + item.setIsLocked(0); + } + + taskItemMapper.insert(item); + log.info("子任务创建成功,ID: {}, 编号: {}, 组号: {}, 状态: {}", + item.getId(), item.getItemNo(), item.getGroupNo(), item.getStatus()); + } + + log.info("流水线任务创建完成,主任务ID: {}, 子任务数: {}, 分组数: {}", + task.getId(), dto.getItems().size(), currentGroupNo); + + return task.getId(); + } + + /** + * 生成子任务编号 + */ + private String generateTaskItemNo(String taskNo, int itemIndex) { + return taskNo + "-" + String.format("%02d", itemIndex); + } + + /** + * 生成任务编号 + */ + private String generateTaskNo() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); + String date = LocalDateTime.now().format(formatter); + String random = String.format("%04d", new Random().nextInt(10000)); + return "TASK" + date + random; + } + + /** + * 生成第三方平台任务编号 + * 格式:THIRD-yyyyMMddHHmmss-随机数 + */ + private String generateThirdPartyTaskNo() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + String datetime = LocalDateTime.now().format(formatter); + String random = String.format("%06d", new Random().nextInt(1000000)); + return "THIRD-" + datetime + "-" + random; + } + + /** + * 获取任务列表 + */ + public IPage getTaskList(Integer page, Integer pageSize, String status, Long taskTypeId) { + Page pageParam = new Page<>(page, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + // 根据状态筛选 + if (StringUtils.hasText(status)) { + wrapper.eq(Task::getStatus, status); + } + + // 根据任务类型筛选 + if (taskTypeId != null) { + wrapper.eq(Task::getTaskTypeId, taskTypeId); + } + + // 按更新时间倒序 + wrapper.orderByDesc(Task::getUpdateTime); + + return taskMapper.selectPage(pageParam, wrapper); + } + + /** + * 获取所有待审核任务(处于审批流程中的任务) + * 包括:pending_demand_approval 和 pending_receiving_approval 状态的任务 + */ + public IPage getAllPendingApprovalTasks(Integer page, Integer pageSize, Long taskTypeId) { + Page pageParam = new Page<>(page, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + // 查询处于审批流程中的任务 + wrapper.in(Task::getApprovalStatus, "pending_demand_approval", "pending_receiving_approval"); + + // 根据任务类型筛选 + if (taskTypeId != null) { + wrapper.eq(Task::getTaskTypeId, taskTypeId); + } + + // 未删除的任务 + wrapper.eq(Task::getDeleted, 0); + + // 按创建时间倒序 + wrapper.orderByDesc(Task::getCreateTime); + + IPage result = taskMapper.selectPage(pageParam, wrapper); + + // 填充关联信息 + for (Task task : result.getRecords()) { + // 填充发布人名字 + if (task.getPublisherId() != null) { + User publisher = userService.getUserById(task.getPublisherId()); + if (publisher != null) { + task.setPublisherName(publisher.getRealName() != null ? publisher.getRealName() : publisher.getUsername()); + } + } + // 填充接收人名字 + if (task.getReceiverId() != null) { + User receiver = userService.getUserById(task.getReceiverId()); + if (receiver != null) { + task.setReceiverName(receiver.getRealName() != null ? receiver.getRealName() : receiver.getUsername()); + } + } + // 填充当前审核人姓名 + if (task.getCurrentApproverId() != null) { + User approver = userService.getUserById(task.getCurrentApproverId()); + if (approver != null) { + task.setCurrentApproverName(approver.getRealName() != null ? approver.getRealName() : approver.getUsername()); + } + } + } + + return result; + } + + /** + * 获取已完成任务列表(根据角色权限) + * - 个人用户:只看自己接的已完成任务 + * - 部门负责人:看本部门所有已完成任务 + * - 管理员:看所有已完成任务 + */ + public IPage getCompletedTasks(Integer page, Integer pageSize, + Long userId, String keyword, String sortBy) { + // 1. 获取用户信息和角色 + User user = userService.getUserById(userId); + if (user == null) { + throw new BusinessException("用户不存在"); + } + + boolean isAdmin = isAdminUser(userId); + boolean isDeptHead = isDeptHeadUser(userId); + + // 2. 构建查询条件 + Page pageParam = new Page<>(page, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + // 状态必须是已完成 + wrapper.eq(Task::getStatus, "completed"); + + // 3. 根据角色筛选数据范围 + if (isAdmin) { + // 管理员:查看所有 + log.info("管理员查看所有已完成任务"); + } else if (isDeptHead) { + // 部门负责人:查看本部门的 + wrapper.and(w -> w + .eq(Task::getPublisherDeptId, user.getDeptId()) // 本部门发起的 + .or() + .eq(Task::getReceiverDeptId, user.getDeptId()) // 或本部门接的 + ); + log.info("部门负责人查看部门已完成任务: deptId={}", user.getDeptId()); + } else { + // 普通用户:只看自己接的 + wrapper.eq(Task::getReceiverId, userId); + log.info("个人用户查看个人已完成任务: userId={}", userId); + } + + // 4. 关键词搜索 + if (org.springframework.util.StringUtils.hasText(keyword)) { + wrapper.and(w -> w + .like(Task::getTitle, keyword) + .or() + .like(Task::getTaskNo, keyword) + ); + } + + // 5. 排序 + if ("score".equals(sortBy)) { + wrapper.orderByDesc(Task::getScore); + } else { + wrapper.orderByDesc(Task::getCompleteTime); // 默认按完成时间降序 + } + + // 6. 查询并填充用户名 + IPage result = taskMapper.selectPage(pageParam, wrapper); + for (Task task : result.getRecords()) { + // 填充发布人名字 + if (task.getPublisherId() != null) { + User publisher = userService.getUserById(task.getPublisherId()); + if (publisher != null) { + task.setPublisherName(publisher.getRealName()); + } + } + // 填充接收人名字 + if (task.getReceiverId() != null) { + User receiver = userService.getUserById(task.getReceiverId()); + if (receiver != null) { + task.setReceiverName(receiver.getRealName()); + } + } + // 填充当前审核人姓名 + if (task.getCurrentApproverId() != null) { + User approver = userService.getUserById(task.getCurrentApproverId()); + if (approver != null) { + task.setCurrentApproverName(approver.getRealName() != null ? approver.getRealName() : approver.getUsername()); + } + } + } + return result; + } + + /** + * 获取任务大厅 + */ + public IPage getTaskHall(Integer page, Integer pageSize, Long taskTypeId, Long demandTypeId, + Long brandId, Long categoryId, Long platformId, String taskSubType, + Integer minScore, Integer maxScore, String keyword, String publisherName, + String timeRange, String sortBy) { + Page pageParam = new Page<>(page, pageSize); + return taskMapper.selectTaskHall(pageParam, taskTypeId, demandTypeId, brandId, categoryId, + platformId, taskSubType, minScore, maxScore, keyword, + publisherName, timeRange, sortBy); + } + + /** + * 获取我发布的任务 + */ + public IPage getMyPublishedTasks(Integer page, Integer pageSize, Long userId, String status) { + Page pageParam = new Page<>(page, pageSize); + return taskMapper.selectMyPublishedTasks(pageParam, userId, status); + } + + /** + * 获取我接收的任务 + */ + public IPage getMyReceivedTasks(Integer page, Integer pageSize, Long userId, String status) { + Page pageParam = new Page<>(page, pageSize); + return taskMapper.selectMyReceivedTasks(pageParam, userId, status); + } + + /** + * 获取任务详情 + */ + public Task getTaskById(Long id) { + Task task = taskMapper.selectById(id); + if (task == null) { + throw new BusinessException(ResultCode.TASK_NOT_EXIST); + } + return task; + } + + /** + * 更新任务 + */ + @Transactional(rollbackFor = Exception.class) + public void updateTask(Long id, Task task) { + Task existTask = getTaskById(id); + // TODO: 验证权限 + task.setId(id); + taskMapper.updateById(task); + } + + /** + * 抢单 + */ + @Transactional(rollbackFor = Exception.class) + public void grabTask(Long taskId, Long userId) { + log.info("用户 {} 抢单任务 {}", userId, taskId); + + Task task = getTaskById(taskId); + + if (!"pending".equals(task.getStatus())) { + throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "任务不是待接单状态"); + } + if (task.getReceiverId() != null) { + throw new BusinessException(ResultCode.TASK_ALREADY_TAKEN); + } + if (!"recruitment".equalsIgnoreCase(task.getReleaseType())) { + throw new BusinessException(ResultCode.FORBIDDEN, "该任务不支持抢单"); + } + + // 验证用户权限:只有接单部门可以接任务(管理员除外) + var user = userService.getUserById(userId); + if (user == null) { + throw new BusinessException(ResultCode.FORBIDDEN, "用户不存在"); + } + + // 检查是否是管理员(管理员可以同时拥有需求部门和接单部门权限) + boolean isAdmin = isAdminUser(userId); + + Long deptId = user.getDeptId(); + Department department = deptId != null ? departmentService.getDepartmentById(deptId) : null; + if (department == null) { + throw new BusinessException(ResultCode.FORBIDDEN, "未找到用户所属部门,请联系管理员"); + } + if (!"receiving".equals(department.getDeptType())) { + throw new BusinessException(ResultCode.FORBIDDEN, "只有接单部门可以接收任务"); + } + if (!isAdmin && deptId == null) { + throw new BusinessException(ResultCode.FORBIDDEN, "用户部门信息不完整"); + } + if (task.getDemandTypeId() != null) { + validateAllowedDemandTypes(department, task.getDemandTypeId()); + } + + LocalDateTime now = LocalDateTime.now(); + LocalDateTime deadline = task.getDeadline(); + if (deadline == null && task.getEstimatedHours() != null) { + int hours = task.getEstimatedHours().intValue(); + if (hours > 0) { + deadline = now.plusHours(hours); + } + } + + int updated = taskMapper.grabTask(taskId, userId, deptId, now, deadline); + if (updated == 0) { + throw new BusinessException(ResultCode.TASK_ALREADY_TAKEN, "任务已被其他设计师抢走"); + } + + // TODO: 发送通知 + } + + /** + * 提交任务 + */ + @Transactional(rollbackFor = Exception.class) + public void submitTask(Long taskId, Long userId, String deliveryPath) { + log.info("用户 {} 提交任务 {}", userId, taskId); + + Task task = getTaskById(taskId); + + // 验证权限(必须是任务接收人) + if (!userId.equals(task.getReceiverId())) { + throw new BusinessException(ResultCode.FORBIDDEN, "只有任务接收人可以提交"); + } + + // 验证任务状态:允许 in_progress 或 rejected(驳回后重新提交) + if (!"in_progress".equals(task.getStatus()) && !"rejected".equals(task.getStatus())) { + throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "任务状态不允许提交,当前状态:" + task.getStatus()); + } + + // 更新任务 + task.setDeliveryPath(deliveryPath); + task.setStatus("review"); + task.setCompleteTime(LocalDateTime.now()); + task.setProgress(100); + + taskMapper.updateById(task); + + // TODO: 发送通知给发布人 + } + + /** + * 审核任务 + */ + @Transactional(rollbackFor = Exception.class) + public void reviewTask(Long taskId, Long userId, Boolean approved, String comment, Integer rating) { + log.info("用户 {} 审核任务 {}, 结果: {}", userId, taskId, approved); + + Task task = getTaskById(taskId); + + // 验证权限(必须是任务发布人) + if (!userId.equals(task.getPublisherId())) { + throw new BusinessException(ResultCode.FORBIDDEN, "只有任务发布人可以审核"); + } + + // 验证任务状态 + if (!"review".equals(task.getStatus())) { + throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "任务不是待审核状态"); + } + + if (approved) { + // 审核通过 + task.setStatus("completed"); + task.setReviewComment(comment); + task.setRating(rating); + + // 设置完成时间(如果还没有设置) + if (task.getCompleteTime() == null) { + task.setCompleteTime(LocalDateTime.now()); + } + + // 检查是否延误并计算扣分 + if (task.getDeadline() != null && task.getCompleteTime() != null + && task.getCompleteTime().isAfter(task.getDeadline())) { + task.setIsDelayed(1); + + // 计算延误天数 + long delayDays = java.time.temporal.ChronoUnit.DAYS.between( + task.getDeadline(), task.getCompleteTime()); + task.setDelayDays((int) delayDays); + } + + // 计算延误扣分(移到外层作用域) + DelayPenaltyRecord delayRecord = null; + if (task.getCompleteTime() != null && task.getDeadline() != null + && task.getCompleteTime().isAfter(task.getDeadline()) + && task.getReceiverId() != null) { + delayRecord = delayPenaltyService.calculateDelayPenalty( + taskId, + task.getReceiverId(), + task.getDeadline(), + task.getCompleteTime() + ); + + if (delayRecord != null) { + // 保存延误扣分记录 + delayPenaltyService.saveDelayPenaltyRecord(delayRecord); + log.info("任务延误扣分: taskId={}, designerId={}, penaltyScore={}", + taskId, task.getReceiverId(), delayRecord.getPenaltyScore()); + } + } + + // 奖励设计师所在部门积分(任务奖励) + if (task.getReceiverDeptId() != null && task.getScore() != null) { + teamScorePoolService.addScore(task.getReceiverDeptId(), task.getScore()); + + // 如果有延误扣分,从积分池中扣除 + if (delayRecord != null && delayRecord.getPenaltyScore() > 0 + && "confirmed".equals(delayRecord.getStatus())) { + try { + teamScorePoolService.deductScore(task.getReceiverDeptId(), delayRecord.getPenaltyScore()); + log.info("已扣除延误扣分: deptId={}, penaltyScore={}", + task.getReceiverDeptId(), delayRecord.getPenaltyScore()); + } catch (Exception e) { + log.error("扣除延误扣分失败: {}", e.getMessage()); + } + } + } + } else { + // 审核驳回 + task.setStatus("rejected"); + task.setRejectReason(comment); + // 驳回后设计师可以重新提交,无需更改积分 + } + + taskMapper.updateById(task); + + // TODO: 发送通知 + } + + /** + * 取消任务 + */ + @Transactional(rollbackFor = Exception.class) + public void cancelTask(Long taskId, Long userId, String reason) { + log.info("用户 {} 取消任务 {}", userId, taskId); + + Task task = getTaskById(taskId); + + // 验证权限(必须是任务发布人) + if (!userId.equals(task.getPublisherId())) { + throw new BusinessException(ResultCode.FORBIDDEN, "只有任务发布人可以取消"); + } + + // 验证任务状态(不能是已完成) + if ("completed".equals(task.getStatus())) { + throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "已完成的任务不能取消"); + } + + // 更新任务状态 + task.setStatus("cancelled"); + task.setRejectReason(reason); + + taskMapper.updateById(task); + + // 返还积分(任务取消时返还给发布部门) + if (task.getPublisherDeptId() != null && task.getScore() != null) { + teamScorePoolService.addScore(task.getPublisherDeptId(), task.getScore()); + } + + // TODO: 发送通知 + } + + /** + * 检查用户是否是管理员(ADMIN或SUPER_ADMIN) + */ + private boolean isAdminUser(Long userId) { + try { + var roles = roleService.getRolesByUserId(userId); + return roles.stream().anyMatch(role -> + "ADMIN".equals(role.getRoleCode()) || + "SUPER_ADMIN".equals(role.getRoleCode()) + ); + } catch (Exception e) { + log.warn("检查用户角色失败: userId={}, error={}", userId, e.getMessage()); + return false; + } + } + + /** + * 检查用户是否是部门负责人 + */ + private boolean isDeptHeadUser(Long userId) { + try { + User user = userService.getUserById(userId); + if (user == null || user.getDeptId() == null) { + return false; + } + + Department dept = departmentService.getDepartmentById(user.getDeptId()); + if (dept == null) { + return false; + } + + // 检查是否是部门负责人 + return userId.equals(dept.getLeaderId()); + } catch (Exception e) { + log.warn("检查部门负责人失败: userId={}, error={}", userId, e.getMessage()); + return false; + } + } + + private void validateAllowedDemandTypes(Department department, Long demandTypeId) { + if (department == null || !StringUtils.hasText(department.getAllowedDemandTypes()) || demandTypeId == null) { + return; + } + Set allowed = Arrays.stream(department.getAllowedDemandTypes().split(",")) + .map(String::trim) + .filter(str -> !str.isEmpty()) + .map(Long::valueOf) + .collect(Collectors.toSet()); + if (!allowed.isEmpty() && !allowed.contains(demandTypeId)) { + throw new BusinessException(ResultCode.FORBIDDEN, "您所在部门不支持该需求类型的任务"); + } + } + + /** + * 提交任务审核 + */ + @Transactional(rollbackFor = Exception.class) + public boolean submitTaskForAudit(Long taskId, Long userId) { + log.info("提交任务审核: taskId={}, userId={}", taskId, userId); + + Task task = taskMapper.selectById(taskId); + if (task == null) { + throw new BusinessException("任务不存在"); + } + + // 检查是否是任务发布人 + if (!task.getPublisherId().equals(userId)) { + throw new BusinessException("只有任务发布人才能提交审核"); + } + + // 检查任务状态 + if (!"draft".equals(task.getAuditStatus())) { + throw new BusinessException("只有草稿状态的任务才能提交审核"); + } + + // 更新审核状态 + task.setAuditStatus("pending_audit"); + taskMapper.updateById(task); + + // 记录审核日志 + TaskAuditLog auditLog = new TaskAuditLog(); + auditLog.setTaskId(taskId); + auditLog.setAuditorId(userId); + auditLog.setAction("submit"); + auditLog.setComment("提交审核"); + taskAuditLogMapper.insert(auditLog); + + // 发送通知给部门负责人 + Department department = departmentService.getDepartmentById(task.getPublisherDeptId()); + if (department != null && department.getLeaderId() != null) { + User publisher = userService.getUserById(userId); + String publisherName = publisher != null ? + (publisher.getRealName() != null ? publisher.getRealName() : publisher.getUsername()) + : "未知用户"; + + notificationService.sendTaskNotification( + department.getLeaderId(), + taskId, + "【待审核】新任务提交审核", + String.format("%s 提交了任务《%s》,请及时审核", publisherName, task.getTitle()) + ); + log.info("已发送审核通知给负责人: leaderId={}, taskId={}", department.getLeaderId(), taskId); + } + + return true; + } + + /** + * 审核任务 + */ + @Transactional(rollbackFor = Exception.class) + public boolean auditTask(TaskAuditRequest request, Long auditorId) { + log.info("审核任务: taskId={}, result={}, auditorId={}", + request.getTaskId(), request.getResult(), auditorId); + + Task task = taskMapper.selectById(request.getTaskId()); + if (task == null) { + throw new BusinessException("任务不存在"); + } + + // 检查是否是部门负责人 + User auditor = userService.getUserById(auditorId); + if (auditor == null) { + throw new BusinessException("用户不存在"); + } + + Department publisherDept = departmentService.getDepartmentById(task.getPublisherDeptId()); + if (publisherDept == null || !auditorId.equals(publisherDept.getLeaderId())) { + throw new BusinessException("只有发起部门的负责人才能审核任务"); + } + + // 检查任务状态 + if (!"pending_audit".equals(task.getAuditStatus())) { + throw new BusinessException("只有待审核状态的任务才能审核"); + } + + // 更新审核状态 + if ("pass".equals(request.getResult())) { + task.setAuditStatus("audit_pass"); + task.setStatus("pending"); // 审核通过后变为待接单状态 + + // 审核通过后扣除部门积分 + if (task.getPublisherDeptId() != null && task.getScore() != null) { + // 检查积分是否充足 + if (!teamScorePoolService.checkBalance(task.getPublisherDeptId(), task.getScore())) { + throw new BusinessException("部门积分余额不足,无法通过审核"); + } + + // 扣除积分 + teamScorePoolService.consumeScore(task.getPublisherDeptId(), task.getScore()); + log.info("审核通过,扣除部门积分: deptId={}, score={}", task.getPublisherDeptId(), task.getScore()); + } + } else if ("reject".equals(request.getResult())) { + task.setAuditStatus("audit_reject"); + task.setStatus("draft"); // 审核驳回后回到草稿状态 + } else { + throw new BusinessException("审核结果参数错误"); + } + + task.setAuditorId(auditorId); + task.setAuditTime(LocalDateTime.now()); + task.setAuditComment(request.getComment()); + taskMapper.updateById(task); + + // 记录审核日志 + TaskAuditLog auditLog = new TaskAuditLog(); + auditLog.setTaskId(task.getId()); + auditLog.setAuditorId(auditorId); + auditLog.setAuditorName(auditor.getRealName() != null ? auditor.getRealName() : auditor.getUsername()); + auditLog.setAction(request.getResult()); + auditLog.setComment(request.getComment()); + taskAuditLogMapper.insert(auditLog); + + // 发送审核结果通知给任务发起人 + if (task.getPublisherId() != null) { + String auditorName = auditor.getRealName() != null ? auditor.getRealName() : auditor.getUsername(); + String resultText = "pass".equals(request.getResult()) ? "通过" : "驳回"; + String title = String.format("【审核%s】任务《%s》", resultText, task.getTitle()); + String content; + + if ("pass".equals(request.getResult())) { + content = String.format("%s 审核通过了您的任务《%s》,任务已发布到任务大厅", + auditorName, task.getTitle()); + if (request.getComment() != null && !request.getComment().isEmpty()) { + content += "。审核意见:" + request.getComment(); + } + } else { + content = String.format("%s 驳回了您的任务《%s》", auditorName, task.getTitle()); + if (request.getComment() != null && !request.getComment().isEmpty()) { + content += "。驳回原因:" + request.getComment(); + } + } + + notificationService.sendTaskNotification( + task.getPublisherId(), + task.getId(), + title, + content + ); + log.info("已发送审核结果通知: publisherId={}, result={}, taskId={}", + task.getPublisherId(), request.getResult(), task.getId()); + } + + return true; + } + + /** + * 强制指派任务给部门成员 + */ + @Transactional(rollbackFor = Exception.class) + public boolean assignTask(TaskAssignRequest request, Long assignerId) { + log.info("强制指派任务: taskId={}, assigneeId={}, assignerId={}", + request.getTaskId(), request.getAssigneeId(), assignerId); + + Task task = taskMapper.selectById(request.getTaskId()); + if (task == null) { + throw new BusinessException("任务不存在"); + } + + // 检查任务状态(只有待接单或招募中的任务可以指派) + if (!"pending".equals(task.getStatus())) { + throw new BusinessException("只有待接单状态的任务才能指派"); + } + + // 检查是否已有人接单 + if (task.getReceiverId() != null) { + throw new BusinessException("该任务已被接单"); + } + + // 检查指派人是否是部门负责人 + User assigner = userService.getUserById(assignerId); + if (assigner == null || assigner.getDeptId() == null) { + throw new BusinessException("用户不存在或未分配部门"); + } + + Department assignerDept = departmentService.getDepartmentById(assigner.getDeptId()); + if (assignerDept == null || !assignerId.equals(assignerDept.getLeaderId())) { + throw new BusinessException("只有部门负责人才能指派任务"); + } + + // 检查被指派人是否在该部门 + User assignee = userService.getUserById(request.getAssigneeId()); + if (assignee == null || !assigner.getDeptId().equals(assignee.getDeptId())) { + throw new BusinessException("只能指派给本部门成员"); + } + + // 更新任务 + task.setReceiverId(request.getAssigneeId()); + task.setReceiverDeptId(assignee.getDeptId()); + task.setStatus("in_progress"); + task.setStartTime(LocalDateTime.now()); + taskMapper.updateById(task); + + // 记录指派日志 + TaskAssignLog log = new TaskAssignLog(); + log.setTaskId(task.getId()); + log.setAssignerId(assignerId); + log.setAssigneeId(request.getAssigneeId()); + log.setAssignType(request.getAssignType()); + log.setComment(request.getComment()); + taskAssignLogMapper.insert(log); + + return true; + } + + /** + * 获取部门负责人的任务列表(需求部门负责人视图) + */ + public IPage getLeaderTaskList(Long userId, Integer page, Integer pageSize, String auditStatus) { + log.info("查询负责人任务列表: userId={}, auditStatus={}", userId, auditStatus); + + User user = userService.getUserById(userId); + if (user == null || user.getDeptId() == null) { + throw new BusinessException("用户不存在或未分配部门"); + } + + Department dept = departmentService.getDepartmentById(user.getDeptId()); + if (dept == null) { + throw new BusinessException("部门不存在"); + } + + // 检查是否是部门负责人 + if (!userId.equals(dept.getLeaderId())) { + throw new BusinessException("只有部门负责人才能查看该视图"); + } + + // 查询部门内所有任务 + Page pageParam = new Page<>(page, pageSize); + return taskMapper.selectLeaderTaskPage(pageParam, user.getDeptId(), auditStatus); + } + + /** + * 获取接单部门负责人的接单明细(接单部门负责人视图) + */ + public IPage getLeaderReceivingTaskList(Long userId, Integer page, Integer pageSize, String status) { + log.info("查询接单部门负责人视图: userId={}, status={}", userId, status); + + User user = userService.getUserById(userId); + if (user == null || user.getDeptId() == null) { + throw new BusinessException("用户不存在或未分配部门"); + } + + Department dept = departmentService.getDepartmentById(user.getDeptId()); + if (dept == null) { + throw new BusinessException("部门不存在"); + } + + // 检查是否是部门负责人 + if (!userId.equals(dept.getLeaderId())) { + throw new BusinessException("只有部门负责人才能查看该视图"); + } + + // 查询部门成员接单的所有任务 + Page pageParam = new Page<>(page, pageSize); + return taskMapper.selectDeptReceivingTaskPage(pageParam, user.getDeptId(), status); + } + + /** + * 获取工作台统计数据 + */ + public DashboardStatsVO getDashboardStats(Long userId, String month) { + try { + // 获取用户角色,判断是否为管理员 + List roles = roleService.getRolesByUserId(userId); + boolean isAdmin = roles.stream() + .anyMatch(role -> "ADMIN".equals(role.getRoleCode()) || "SUPER_ADMIN".equals(role.getRoleCode())); + + log.info("获取工作台统计数据: userId={}, isAdmin={}, month={}", userId, isAdmin, month); + + // 解析月份,如果不传则使用当前月份 + LocalDate targetMonth; + if (month != null && !month.isEmpty()) { + try { + targetMonth = LocalDate.parse(month + "-01", java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } catch (Exception e) { + log.warn("月份格式错误: {}, 使用当前月份", month); + targetMonth = LocalDate.now().withDayOfMonth(1); + } + } else { + targetMonth = LocalDate.now().withDayOfMonth(1); + } + + // 计算月份的开始和结束时间 + LocalDateTime monthStart = LocalDateTime.of(targetMonth, LocalTime.MIN); + LocalDateTime monthEnd = monthStart.plusMonths(1).minusSeconds(1); + + // 当前日期范围 + LocalDateTime todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN); + LocalDateTime todayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.MAX); + + // 本周范围 + LocalDate now = LocalDate.now(); + LocalDate weekStart = now.minusDays(now.getDayOfWeek().getValue() - 1); + LocalDate weekEnd = weekStart.plusDays(6); + LocalDateTime weekStartTime = LocalDateTime.of(weekStart, LocalTime.MIN); + LocalDateTime weekEndTime = LocalDateTime.of(weekEnd, LocalTime.MAX); + + // 上周范围 + LocalDate lastWeekStart = weekStart.minusDays(7); + LocalDate lastWeekEnd = lastWeekStart.plusDays(6); + LocalDateTime lastWeekStartTime = LocalDateTime.of(lastWeekStart, LocalTime.MIN); + LocalDateTime lastWeekEndTime = LocalDateTime.of(lastWeekEnd, LocalTime.MAX); + + // 进行中的任务数 + LambdaQueryWrapper inProgressQuery = new LambdaQueryWrapper() + .eq(Task::getStatus, "in_progress"); + if (!isAdmin) { + inProgressQuery.eq(Task::getReceiverId, userId); + } + Long inProgressCount = taskMapper.selectCount(inProgressQuery); + + // 已完成的任务数 + LambdaQueryWrapper completedQuery = new LambdaQueryWrapper() + .eq(Task::getStatus, "completed"); + if (!isAdmin) { + completedQuery.eq(Task::getReceiverId, userId); + } + Long completedCount = taskMapper.selectCount(completedQuery); + + // 待审核的任务数 + LambdaQueryWrapper reviewQuery = new LambdaQueryWrapper() + .eq(Task::getStatus, "review"); + if (!isAdmin) { + reviewQuery.eq(Task::getReceiverId, userId); + } + Long reviewCount = taskMapper.selectCount(reviewQuery); + + // 今日开始的任务数 + LambdaQueryWrapper todayStartedQuery = new LambdaQueryWrapper() + .eq(Task::getStatus, "in_progress") + .ge(Task::getStartTime, todayStart) + .le(Task::getStartTime, todayEnd); + if (!isAdmin) { + todayStartedQuery.eq(Task::getReceiverId, userId); + } + Long todayStartedCount = taskMapper.selectCount(todayStartedQuery); + + // 今日完成的任务数 + LambdaQueryWrapper todayCompletedQuery = new LambdaQueryWrapper() + .eq(Task::getStatus, "completed") + .ge(Task::getCompleteTime, todayStart) + .le(Task::getCompleteTime, todayEnd); + if (!isAdmin) { + todayCompletedQuery.eq(Task::getReceiverId, userId); + } + Long todayCompletedCount = taskMapper.selectCount(todayCompletedQuery); + + // 本周完成的任务数 + LambdaQueryWrapper weekCompletedQuery = new LambdaQueryWrapper() + .eq(Task::getStatus, "completed") + .ge(Task::getCompleteTime, weekStartTime) + .le(Task::getCompleteTime, weekEndTime); + if (!isAdmin) { + weekCompletedQuery.eq(Task::getReceiverId, userId); + } + Long weekCompletedCount = taskMapper.selectCount(weekCompletedQuery); + + // 上周完成的任务数 + LambdaQueryWrapper lastWeekCompletedQuery = new LambdaQueryWrapper() + .eq(Task::getStatus, "completed") + .ge(Task::getCompleteTime, lastWeekStartTime) + .le(Task::getCompleteTime, lastWeekEndTime); + if (!isAdmin) { + lastWeekCompletedQuery.eq(Task::getReceiverId, userId); + } + Long lastWeekCompletedCount = taskMapper.selectCount(lastWeekCompletedQuery); + + // 可接任务数(状态为pending的任务) + LambdaQueryWrapper availableQuery = new LambdaQueryWrapper() + .eq(Task::getStatus, "pending"); + // 可接任务对所有人都是全局的,不需要过滤用户 + Long availableCount = taskMapper.selectCount(availableQuery); + + // 当日新增任务数 + LambdaQueryWrapper todayNewQuery = new LambdaQueryWrapper() + .ge(Task::getCreateTime, todayStart) + .le(Task::getCreateTime, todayEnd); + if (!isAdmin) { + // 普通用户看自己相关的任务(作为发起人或接单人) + todayNewQuery.and(wrapper -> wrapper + .eq(Task::getPublisherId, userId) + .or() + .eq(Task::getReceiverId, userId)); + } + Long todayNewCount = taskMapper.selectCount(todayNewQuery); + + // 获取总积分 + LambdaQueryWrapper scoreQuery = new LambdaQueryWrapper() + .eq(Task::getStatus, "completed") + .select(Task::getScoreReward); + if (!isAdmin) { + scoreQuery.eq(Task::getReceiverId, userId); + } + List completedTasks = taskMapper.selectList(scoreQuery); + + int totalScore = completedTasks.stream() + .filter(t -> t.getScoreReward() != null) + .mapToInt(t -> t.getScoreReward().intValue()) + .sum(); + + // 获取当前用户当月发布的任务数 + LambdaQueryWrapper publishedQuery = new LambdaQueryWrapper() + .eq(Task::getPublisherId, userId) + .ge(Task::getCreateTime, monthStart) + .le(Task::getCreateTime, monthEnd) + .eq(Task::getDeleted, 0); + Long publishedCount = taskMapper.selectCount(publishedQuery); + + DashboardStatsVO result = DashboardStatsVO.builder() + .availableCount(availableCount.intValue()) + .publishedCount(publishedCount.intValue()) + .inProgressCount(inProgressCount.intValue()) + .completedCount(completedCount.intValue()) + .reviewCount(reviewCount.intValue()) + .todayNewCount(todayNewCount.intValue()) + .totalScore(totalScore) + .todayStartedCount(todayStartedCount.intValue()) + .todayCompletedCount(todayCompletedCount.intValue()) + .weekCompletedCount(weekCompletedCount.intValue()) + .lastWeekCompletedCount(lastWeekCompletedCount.intValue()) + .build(); + + log.info("工作台统计数据查询完成: userId={}, isAdmin={}, result={}", userId, isAdmin, result); + return result; + + } catch (Exception e) { + log.error("获取工作台统计数据失败: userId={}", userId, e); + // 返回空数据而不是抛异常 + return DashboardStatsVO.builder() + .availableCount(0) + .publishedCount(0) + .inProgressCount(0) + .completedCount(0) + .reviewCount(0) + .todayNewCount(0) + .totalScore(0) + .todayStartedCount(0) + .todayCompletedCount(0) + .weekCompletedCount(0) + .lastWeekCompletedCount(0) + .build(); + } + } + + /** + * 获取我的进行中任务列表 + */ + public List getMyInProgressTasks(Long userId) { + return taskMapper.selectList( + new LambdaQueryWrapper() + .eq(Task::getReceiverId, userId) + .eq(Task::getStatus, "in_progress") + .orderByDesc(Task::getStartTime) + ); + } + + /** + * 批量更新任务进度 + */ + @Transactional(rollbackFor = Exception.class) + public void batchUpdateProgress(Long userId, List> updates) { + for (java.util.Map update : updates) { + Long taskId = ((Number) update.get("taskId")).longValue(); + Integer progress = ((Number) update.get("progress")).intValue(); + String remark = (String) update.get("remark"); + + // 验证任务归属 + Task task = taskMapper.selectById(taskId); + if (task == null) { + throw new BusinessException("任务不存在: taskId=" + taskId); + } + + if (!task.getReceiverId().equals(userId)) { + throw new BusinessException("无权更新此任务进度: taskId=" + taskId); + } + + if (!"in_progress".equals(task.getStatus())) { + throw new BusinessException("只能更新进行中的任务进度: taskId=" + taskId); + } + + // 验证进度值 + if (progress < 0 || progress > 100) { + throw new BusinessException("进度值必须在0-100之间: taskId=" + taskId); + } + + Integer oldProgress = task.getProgress() != null ? task.getProgress() : 0; + if (progress < oldProgress) { + throw new BusinessException("新进度不能小于当前进度: taskId=" + taskId); + } + + // 更新进度 + Task updateTask = new Task(); + updateTask.setId(taskId); + updateTask.setProgress(progress); + updateTask.setUpdateTime(LocalDateTime.now()); + + // 如果进度达到100%,自动标记为待审核 + if (progress == 100) { + updateTask.setStatus("pending_review"); + } + + taskMapper.updateById(updateTask); + + log.info("任务进度更新成功: taskId={}, userId={}, oldProgress={}, newProgress={}, remark={}", + taskId, userId, oldProgress, progress, remark); + } + } + + /** + * 更新任务进度 + */ + @Transactional + public void updateTaskProgress(Long taskId, Long userId, Integer progress) { + log.info("更新任务进度: taskId={}, userId={}, progress={}", taskId, userId, progress); + + // 验证进度值 + if (progress < 0 || progress > 100) { + throw new BusinessException("进度值必须在0-100之间"); + } + + Task task = taskMapper.selectById(taskId); + if (task == null) { + throw new BusinessException("任务不存在"); + } + + // 验证是接单人 + if (!userId.equals(task.getReceiverId())) { + throw new BusinessException("只有接单人才能更新任务进度"); + } + + // 验证任务状态(只有进行中的任务才能更新进度) + if (!"in_progress".equals(task.getStatus())) { + throw new BusinessException("只有进行中的任务才能更新进度"); + } + + // 更新进度 + task.setProgress(progress); + task.setUpdateTime(LocalDateTime.now()); + taskMapper.updateById(task); + + log.info("任务进度更新成功: taskId={}, progress={}", taskId, progress); + } + + /** + * 完结任务(提交交付路径) + */ + @Transactional + public void completeTask(Long taskId, Long userId, String deliveryPath) { + log.info("完结任务: taskId={}, userId={}, deliveryPath={}", taskId, userId, deliveryPath); + + Task task = taskMapper.selectById(taskId); + if (task == null) { + throw new BusinessException("任务不存在"); + } + + // 验证是接单人 + if (!userId.equals(task.getReceiverId())) { + throw new BusinessException("只有接单人才能完结任务"); + } + + // 验证任务状态(只有进行中的任务才能完结) + if (!"in_progress".equals(task.getStatus())) { + throw new BusinessException("只有进行中的任务才能完结"); + } + + // 更新任务状态为待审核 + task.setStatus("review"); + task.setDeliveryPath(deliveryPath); + task.setProgress(100); // 完结时进度设为100% + task.setSubmitTime(LocalDateTime.now()); + task.setUpdateTime(LocalDateTime.now()); + taskMapper.updateById(task); + + // 发送通知给发起人 + try { + Notification notification = new Notification(); + notification.setReceiverId(task.getPublisherId()); + notification.setTitle("任务提交审核"); + notification.setContent("任务【" + task.getTitle() + "】已提交审核,请查看"); + notification.setNotificationType("TASK_REVIEW"); + notification.setRelatedType("task"); + notification.setRelatedId(taskId); + notificationService.sendNotification(notification); + } catch (Exception e) { + log.error("发送审核通知失败", e); + } + + log.info("任务已提交审核: taskId={}", taskId); + } + + /** + * 审核通过任务 + */ + @Transactional + public void approveTask(Long taskId, Long userId) { + log.info("审核通过任务: taskId={}, userId={}", taskId, userId); + + Task task = taskMapper.selectById(taskId); + if (task == null) { + throw new BusinessException("任务不存在"); + } + + // 验证权限(发起人或部门负责人) + User user = userService.getUserById(userId); + if (user == null) { + throw new BusinessException("用户不存在"); + } + + boolean isPublisher = userId.equals(task.getPublisherId()); + boolean isDeptLeader = false; + + if (user.getDeptId() != null) { + Department dept = departmentService.getDepartmentById(user.getDeptId()); + if (dept != null && userId.equals(dept.getLeaderId())) { + isDeptLeader = true; + } + } + + if (!isPublisher && !isDeptLeader) { + throw new BusinessException("只有任务发起人或部门负责人才能审核任务"); + } + + // 验证任务状态 + if (!"review".equals(task.getStatus())) { + throw new BusinessException("只有待审核的任务才能审核"); + } + + // 更新任务状态为已完成 + task.setStatus("completed"); + task.setCompleteTime(LocalDateTime.now()); + task.setUpdateTime(LocalDateTime.now()); + task.setRating(5); // 默认5分 + + // 计算积分奖励 + if (task.getScore() != null) { + task.setScoreReward(BigDecimal.valueOf(task.getScore())); + } + + taskMapper.updateById(task); + + // 发送通知给接单人 + try { + Notification notification = new Notification(); + notification.setReceiverId(task.getReceiverId()); + notification.setTitle("任务审核通过"); + notification.setContent("任务【" + task.getTitle() + "】审核通过,积分已到账"); + notification.setNotificationType("TASK_COMPLETED"); + notification.setRelatedType("task"); + notification.setRelatedId(taskId); + notificationService.sendNotification(notification); + } catch (Exception e) { + log.error("发送完成通知失败", e); + } + + log.info("任务审核通过: taskId={}", taskId); + } + + /** + * 驳回任务 + */ + @Transactional + public void rejectTask(Long taskId, Long userId, String reason) { + log.info("驳回任务: taskId={}, userId={}, reason={}", taskId, userId, reason); + + Task task = taskMapper.selectById(taskId); + if (task == null) { + throw new BusinessException("任务不存在"); + } + + // 验证权限(发起人或部门负责人) + User user = userService.getUserById(userId); + if (user == null) { + throw new BusinessException("用户不存在"); + } + + boolean isPublisher = userId.equals(task.getPublisherId()); + boolean isDeptLeader = false; + + if (user.getDeptId() != null) { + Department dept = departmentService.getDepartmentById(user.getDeptId()); + if (dept != null && userId.equals(dept.getLeaderId())) { + isDeptLeader = true; + } + } + + if (!isPublisher && !isDeptLeader) { + throw new BusinessException("只有任务发起人或部门负责人才能驳回任务"); + } + + // 验证任务状态 + if (!"review".equals(task.getStatus())) { + throw new BusinessException("只有待审核的任务才能驳回"); + } + + // 更新任务状态为进行中(驳回后重新修改) + task.setStatus("in_progress"); + task.setRejectReason(reason); + task.setUpdateTime(LocalDateTime.now()); + taskMapper.updateById(task); + + // 发送通知给接单人 + try { + Notification notification = new Notification(); + notification.setReceiverId(task.getReceiverId()); + notification.setTitle("任务被驳回"); + notification.setContent("任务【" + task.getTitle() + "】被驳回,原因:" + reason); + notification.setNotificationType("TASK_REJECTED"); + notification.setRelatedType("task"); + notification.setRelatedId(taskId); + notificationService.sendNotification(notification); + } catch (Exception e) { + log.error("发送驳回通知失败", e); + } + + log.info("任务已驳回: taskId={}, reason={}", taskId, reason); + } + + /** + * 初始化审核状态(两级审核流程) + */ + private void initApprovalStatus(Task task) { + try { + // 设置审核状态为待需求部门审核 + task.setApprovalStatus("pending_demand_approval"); + + // 确定需求部门负责人 + if (task.getPublisherDeptId() != null) { + Department demandDept = departmentService.getDepartmentById(task.getPublisherDeptId()); + if (demandDept != null && demandDept.getLeaderId() != null) { + task.setCurrentApproverId(demandDept.getLeaderId()); + task.setCurrentApproverRole("demand_leader"); + log.info("任务已设置待审核状态,等待需求部门负责人审核:leaderId={}", demandDept.getLeaderId()); + } else { + log.warn("需求部门未配置负责人,任务将直接发布:deptId={}", task.getPublisherDeptId()); + // 如果没有配置负责人,跳过审核直接发布 + task.setApprovalStatus("approved"); + task.setStatus("pending"); + } + } + } catch (Exception e) { + log.error("初始化审核状态失败", e); + // 失败时跳过审核直接发布 + task.setApprovalStatus("approved"); + task.setStatus("pending"); + } + } + + /** + * 发送审核通知(两级审核流程) + */ + private void sendApprovalNotification(Task task) { + try { + if (task.getCurrentApproverId() != null) { + User approver = userService.getUserById(task.getCurrentApproverId()); + if (approver != null) { + Notification notification = new Notification(); + notification.setReceiverId(task.getCurrentApproverId()); + notification.setTitle("新任务待审核"); + notification.setContent(String.format("任务【%s】需要您审核", task.getTitle())); + notification.setNotificationType("TASK_APPROVAL"); + notification.setRelatedType("task"); + notification.setRelatedId(task.getId()); + notificationService.sendNotification(notification); + log.info("已发送审核通知给用户:userId={}, taskId={}", task.getCurrentApproverId(), task.getId()); + } + } + } catch (Exception e) { + log.error("发送审核通知失败", e); + } + } + + /** + * 计算任务总积分(用于审核时修改) + */ + public int calculateTotalScore(List taskTypes) { + int totalScore = 0; + for (TaskUpdateDTO.TaskTypeUpdateDTO taskType : taskTypes) { + DemandType demandType = configService.getDemandTypeById(taskType.getTaskTypeId()); + if (demandType != null) { + totalScore += demandType.getUnitScore() * taskType.getQuantity(); + } + } + return totalScore; + } + + /** + * 创建流水线任务的子任务项(用于审核通过后拆分) + */ + public void createPipelineTaskItems(Task mainTask, com.designhub.dto.PipelineTaskDTO dto) { + // 委托给已有的createPipelineTask逻辑中的子任务创建部分 + // 这里简化处理,实际应该提取createPipelineTask中的子任务创建逻辑 + log.info("开始拆分流水线子任务:taskId={}, mode={}", mainTask.getId(), dto.getPipelineMode()); + + // TODO: 实现流水线子任务拆分逻辑 + // 可以参考createPipelineTask方法中的实现 + } +} + diff --git a/backend/src/main/java/com/designhub/service/TaskSubTypeService.java b/backend/src/main/java/com/designhub/service/TaskSubTypeService.java new file mode 100644 index 0000000..74cb851 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/TaskSubTypeService.java @@ -0,0 +1,145 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.TaskSubType; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.TaskSubTypeMapper; +import com.designhub.common.ResultCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 任务子类型服务 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class TaskSubTypeService { + + private final TaskSubTypeMapper taskSubTypeMapper; + + /** + * 获取所有子类型 + */ + public List getAllSubTypes() { + return taskSubTypeMapper.selectList( + new LambdaQueryWrapper() + .orderByAsc(TaskSubType::getSortOrder) + ); + } + + /** + * 获取启用的子类型 + */ + public List getEnabledSubTypes() { + return taskSubTypeMapper.selectList( + new LambdaQueryWrapper() + .eq(TaskSubType::getIsEnabled, 1) + .orderByAsc(TaskSubType::getSortOrder) + ); + } + + /** + * 根据ID获取子类型 + */ + public TaskSubType getById(Long id) { + TaskSubType subType = taskSubTypeMapper.selectById(id); + if (subType == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "子类型不存在"); + } + return subType; + } + + /** + * 根据编码获取子类型 + */ + public TaskSubType getByCode(String code) { + return taskSubTypeMapper.selectOne( + new LambdaQueryWrapper() + .eq(TaskSubType::getCode, code) + ); + } + + /** + * 创建子类型 + */ + @Transactional(rollbackFor = Exception.class) + public TaskSubType createSubType(TaskSubType subType) { + // 验证编码唯一性 + TaskSubType existing = getByCode(subType.getCode()); + if (existing != null) { + throw new BusinessException(ResultCode.PARAM_ERROR, "子类型编码已存在"); + } + + // 设置默认值 + if (subType.getIsEnabled() == null) { + subType.setIsEnabled(1); + } + if (subType.getSortOrder() == null) { + subType.setSortOrder(0); + } + + taskSubTypeMapper.insert(subType); + log.info("创建任务子类型:{}", subType.getName()); + return subType; + } + + /** + * 更新子类型 + */ + @Transactional(rollbackFor = Exception.class) + public TaskSubType updateSubType(Long id, TaskSubType subType) { + TaskSubType existing = getById(id); + + // 如果修改了编码,验证新编码的唯一性 + if (!existing.getCode().equals(subType.getCode())) { + TaskSubType duplicate = getByCode(subType.getCode()); + if (duplicate != null && !duplicate.getId().equals(id)) { + throw new BusinessException(ResultCode.PARAM_ERROR, "子类型编码已存在"); + } + } + + subType.setId(id); + taskSubTypeMapper.updateById(subType); + log.info("更新任务子类型:{}", subType.getName()); + return getById(id); + } + + /** + * 删除子类型 + */ + @Transactional(rollbackFor = Exception.class) + public void deleteSubType(Long id) { + TaskSubType subType = getById(id); + + // TODO: 可以添加检查,确保没有任务使用此子类型 + + taskSubTypeMapper.deleteById(id); + log.info("删除任务子类型:{}", subType.getName()); + } + + /** + * 切换启用状态 + */ + @Transactional(rollbackFor = Exception.class) + public void toggleEnabled(Long id) { + TaskSubType subType = getById(id); + subType.setIsEnabled(subType.getIsEnabled() == 1 ? 0 : 1); + taskSubTypeMapper.updateById(subType); + log.info("切换任务子类型启用状态:{} -> {}", subType.getName(), subType.getIsEnabled()); + } + + /** + * 更新排序 + */ + @Transactional(rollbackFor = Exception.class) + public void updateSortOrder(Long id, Integer sortOrder) { + TaskSubType subType = getById(id); + subType.setSortOrder(sortOrder); + taskSubTypeMapper.updateById(subType); + } +} diff --git a/backend/src/main/java/com/designhub/service/TeamPKService.java b/backend/src/main/java/com/designhub/service/TeamPKService.java new file mode 100644 index 0000000..1e7c5ad --- /dev/null +++ b/backend/src/main/java/com/designhub/service/TeamPKService.java @@ -0,0 +1,455 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.designhub.common.ResultCode; +import com.designhub.entity.Task; +import com.designhub.entity.TeamPK; +import com.designhub.entity.User; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.TaskMapper; +import com.designhub.mapper.TeamPKMapper; +import com.designhub.mapper.UserMapper; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 团队PK Service + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TeamPKService { + + // 手动添加Logger以解决Lombok编译问题 + private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TeamPKService.class); + + private final TeamPKMapper teamPKMapper; + private final UserMapper userMapper; + private final TaskMapper taskMapper; + private final NotificationService notificationService; + private final ObjectMapper objectMapper; + + /** + * 创建团队PK + */ + @Transactional + public TeamPK createTeamPK(TeamPK teamPK) { + // 验证时间 + if (teamPK.getStartTime().isAfter(teamPK.getEndTime())) { + throw new BusinessException(ResultCode.PARAM_ERROR, "开始时间不能晚于结束时间"); + } + + // 验证部门 + if (teamPK.getTeamADeptId().equals(teamPK.getTeamBDeptId())) { + throw new BusinessException(ResultCode.PARAM_ERROR, "不能与自己PK"); + } + + teamPK.setStatus("draft"); + teamPK.setTeamAScore(0); + teamPK.setTeamBScore(0); + teamPK.setTeamATaskCount(0); + teamPK.setTeamBTaskCount(0); + teamPK.setTeamAFiveStarCount(0); + teamPK.setTeamBFiveStarCount(0); + teamPK.setTeamAOnTimeRate(0); + teamPK.setTeamBOnTimeRate(0); + teamPK.setDeleted(0); + + teamPKMapper.insert(teamPK); + + log.info("创建团队PK成功: id={}, name={}", teamPK.getId(), teamPK.getName()); + return teamPK; + } + + /** + * 发布团队PK + */ + @Transactional + public TeamPK publishTeamPK(Long id) { + TeamPK teamPK = teamPKMapper.selectById(id); + if (teamPK == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "PK不存在"); + } + + if (!"draft".equals(teamPK.getStatus())) { + throw new BusinessException(ResultCode.PARAM_ERROR, "只有草稿状态的PK可以发布"); + } + + LocalDateTime now = LocalDateTime.now(); + if (teamPK.getStartTime().isBefore(now)) { + teamPK.setStatus("active"); + } else { + teamPK.setStatus("scheduled"); + } + + teamPKMapper.updateById(teamPK); + + // 发送通知给双方部门成员 + notifyTeamPKStart(teamPK); + + return teamPK; + } + + /** + * 发送PK开始通知 + */ + private void notifyTeamPKStart(TeamPK teamPK) { + // 获取甲方成员 + LambdaQueryWrapper wrapperA = new LambdaQueryWrapper<>(); + wrapperA.eq(User::getDeptId, teamPK.getTeamADeptId()).eq(User::getDeleted, 0); + List teamA = userMapper.selectList(wrapperA); + + // 获取乙方成员 + LambdaQueryWrapper wrapperB = new LambdaQueryWrapper<>(); + wrapperB.eq(User::getDeptId, teamPK.getTeamBDeptId()).eq(User::getDeleted, 0); + List teamB = userMapper.selectList(wrapperB); + + String message = String.format("团队PK「%s」已开始!%s VS %s,为团队荣誉而战!", + teamPK.getName(), teamPK.getTeamAName(), teamPK.getTeamBName()); + + for (User user : teamA) { + notificationService.sendNotification(user.getId(), "团队PK开始", message, "team_pk", teamPK.getId()); + } + for (User user : teamB) { + notificationService.sendNotification(user.getId(), "团队PK开始", message, "team_pk", teamPK.getId()); + } + } + + /** + * 更新PK数据 + */ + @Transactional + public void updatePKStats(Long deptId) { + LocalDateTime now = LocalDateTime.now(); + + // 查找包含该部门的进行中的PK + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(TeamPK::getStatus, "active") + .and(w -> w.eq(TeamPK::getTeamADeptId, deptId).or().eq(TeamPK::getTeamBDeptId, deptId)); + + List activePKs = teamPKMapper.selectList(wrapper); + + for (TeamPK pk : activePKs) { + refreshPKStats(pk); + } + } + + /** + * 刷新PK统计数据 + */ + @Transactional + public void refreshPKStats(TeamPK pk) { + LocalDateTime startTime = pk.getStartTime(); + LocalDateTime endTime = pk.getEndTime().isBefore(LocalDateTime.now()) ? pk.getEndTime() : LocalDateTime.now(); + + // 统计甲方数据 + LambdaQueryWrapper wrapperA = new LambdaQueryWrapper<>(); + wrapperA.eq(Task::getStatus, "completed") + .ge(Task::getCompleteTime, startTime) + .le(Task::getCompleteTime, endTime); + // 这里需要根据接收人的部门来统计,简化处理 + + // 获取甲方成员 + LambdaQueryWrapper userWrapperA = new LambdaQueryWrapper<>(); + userWrapperA.eq(User::getDeptId, pk.getTeamADeptId()).eq(User::getDeleted, 0); + List teamAUsers = userMapper.selectList(userWrapperA); + + int teamATaskCount = 0; + int teamAFiveStarCount = 0; + int teamAOnTimeCount = 0; + int teamATotalCount = 0; + + for (User user : teamAUsers) { + LambdaQueryWrapper taskWrapper = new LambdaQueryWrapper<>(); + taskWrapper.eq(Task::getReceiverId, user.getId()) + .eq(Task::getStatus, "completed") + .ge(Task::getCompleteTime, startTime) + .le(Task::getCompleteTime, endTime); + List tasks = taskMapper.selectList(taskWrapper); + + teamATotalCount += tasks.size(); + for (Task task : tasks) { + teamATaskCount++; + if (task.getRating() != null && task.getRating() >= 5) { + teamAFiveStarCount++; + } + if (task.getCompleteTime() != null && task.getDeadline() != null + && !task.getCompleteTime().isAfter(task.getDeadline())) { + teamAOnTimeCount++; + } + } + } + + // 获取乙方成员 + LambdaQueryWrapper userWrapperB = new LambdaQueryWrapper<>(); + userWrapperB.eq(User::getDeptId, pk.getTeamBDeptId()).eq(User::getDeleted, 0); + List teamBUsers = userMapper.selectList(userWrapperB); + + int teamBTaskCount = 0; + int teamBFiveStarCount = 0; + int teamBOnTimeCount = 0; + int teamBTotalCount = 0; + + for (User user : teamBUsers) { + LambdaQueryWrapper taskWrapper = new LambdaQueryWrapper<>(); + taskWrapper.eq(Task::getReceiverId, user.getId()) + .eq(Task::getStatus, "completed") + .ge(Task::getCompleteTime, startTime) + .le(Task::getCompleteTime, endTime); + List tasks = taskMapper.selectList(taskWrapper); + + teamBTotalCount += tasks.size(); + for (Task task : tasks) { + teamBTaskCount++; + if (task.getRating() != null && task.getRating() >= 5) { + teamBFiveStarCount++; + } + if (task.getCompleteTime() != null && task.getDeadline() != null + && !task.getCompleteTime().isAfter(task.getDeadline())) { + teamBOnTimeCount++; + } + } + } + + pk.setTeamATaskCount(teamATaskCount); + pk.setTeamBTaskCount(teamBTaskCount); + pk.setTeamAFiveStarCount(teamAFiveStarCount); + pk.setTeamBFiveStarCount(teamBFiveStarCount); + pk.setTeamAOnTimeRate(teamATotalCount > 0 ? (teamAOnTimeCount * 100 / teamATotalCount) : 0); + pk.setTeamBOnTimeRate(teamBTotalCount > 0 ? (teamBOnTimeCount * 100 / teamBTotalCount) : 0); + + // 计算总分 + pk.setTeamAScore(calculateTeamScore(pk, true)); + pk.setTeamBScore(calculateTeamScore(pk, false)); + + teamPKMapper.updateById(pk); + } + + /** + * 计算团队得分 + */ + private int calculateTeamScore(TeamPK pk, boolean isTeamA) { + try { + Map> dimensions = objectMapper.readValue( + pk.getDimensions(), new TypeReference>>() {}); + + int score = 0; + + if (dimensions.containsKey("taskCount")) { + int weight = dimensions.get("taskCount").getOrDefault("weight", 30); + int value = isTeamA ? pk.getTeamATaskCount() : pk.getTeamBTaskCount(); + score += value * weight / 10; + } + if (dimensions.containsKey("fiveStarCount")) { + int weight = dimensions.get("fiveStarCount").getOrDefault("weight", 25); + int value = isTeamA ? pk.getTeamAFiveStarCount() : pk.getTeamBFiveStarCount(); + score += value * weight / 5; + } + if (dimensions.containsKey("onTimeRate")) { + int weight = dimensions.get("onTimeRate").getOrDefault("weight", 25); + int value = isTeamA ? pk.getTeamAOnTimeRate() : pk.getTeamBOnTimeRate(); + score += value * weight / 10; + } + + return score; + } catch (JsonProcessingException e) { + log.error("解析PK维度配置失败", e); + return 0; + } + } + + /** + * 获取PK列表 + */ + public IPage getPKList(Integer page, Integer pageSize, String status) { + Page pageParam = new Page<>(page, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + if (status != null && !status.isEmpty()) { + wrapper.eq(TeamPK::getStatus, status); + } + + wrapper.orderByDesc(TeamPK::getCreateTime); + return teamPKMapper.selectPage(pageParam, wrapper); + } + + /** + * 获取PK详情 + */ + public TeamPK getPKById(Long id) { + TeamPK pk = teamPKMapper.selectById(id); + if (pk == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "PK不存在"); + } + + // 如果进行中,刷新数据 + if ("active".equals(pk.getStatus())) { + refreshPKStats(pk); + } + + return pk; + } + + /** + * 结束PK并发放奖励 + */ + @Transactional + public void endTeamPK(Long id) { + TeamPK pk = teamPKMapper.selectById(id); + if (pk == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "PK不存在"); + } + + if (!"active".equals(pk.getStatus())) { + throw new BusinessException(ResultCode.PARAM_ERROR, "只有进行中的PK可以结束"); + } + + // 最后刷新一次数据 + refreshPKStats(pk); + + // 判断胜负 + if (pk.getTeamAScore() > pk.getTeamBScore()) { + pk.setWinner("A"); + } else if (pk.getTeamBScore() > pk.getTeamAScore()) { + pk.setWinner("B"); + } else { + pk.setWinner("draw"); + } + + pk.setStatus("ended"); + teamPKMapper.updateById(pk); + + // 发放奖励 + distributeRewards(pk); + + log.info("团队PK结束: id={}, winner={}", pk.getId(), pk.getWinner()); + } + + /** + * 发放PK奖励 + */ + private void distributeRewards(TeamPK pk) { + try { + Map rewards = objectMapper.readValue( + pk.getRewards(), new TypeReference>() {}); + + int winnerReward = rewards.getOrDefault("winnerReward", 200); + int loserReward = rewards.getOrDefault("loserReward", 100); + + Long winnerDeptId = "A".equals(pk.getWinner()) ? pk.getTeamADeptId() : pk.getTeamBDeptId(); + Long loserDeptId = "A".equals(pk.getWinner()) ? pk.getTeamBDeptId() : pk.getTeamADeptId(); + String winnerName = "A".equals(pk.getWinner()) ? pk.getTeamAName() : pk.getTeamBName(); + + if ("draw".equals(pk.getWinner())) { + // 平局,双方都获得参与奖 + distributeToTeam(pk.getTeamADeptId(), loserReward, pk, "平局"); + distributeToTeam(pk.getTeamBDeptId(), loserReward, pk, "平局"); + } else { + distributeToTeam(winnerDeptId, winnerReward, pk, "胜利"); + distributeToTeam(loserDeptId, loserReward, pk, "参与"); + } + + } catch (JsonProcessingException e) { + log.error("解析PK奖励配置失败", e); + } + } + + /** + * 向团队成员发放奖励 + */ + private void distributeToTeam(Long deptId, int rewardScore, TeamPK pk, String result) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(User::getDeptId, deptId).eq(User::getDeleted, 0); + List users = userMapper.selectList(wrapper); + + for (User user : users) { + int currentScore = user.getScore() != null ? user.getScore() : 0; + user.setScore(currentScore + rewardScore); + userMapper.updateById(user); + + notificationService.sendNotification( + user.getId(), + "团队PK奖励", + String.format("团队PK「%s」已结束,您的团队%s!获得%d积分奖励。", + pk.getName(), result, rewardScore), + "team_pk", + pk.getId() + ); + } + } + + /** + * 定时任务:自动开始和结束PK + */ + @Scheduled(fixedRate = 60000) // 每分钟检查一次 + @Transactional + public void checkPKStatus() { + LocalDateTime now = LocalDateTime.now(); + + // 自动开始 + LambdaQueryWrapper startWrapper = new LambdaQueryWrapper<>(); + startWrapper.eq(TeamPK::getStatus, "scheduled") + .le(TeamPK::getStartTime, now); + List toStart = teamPKMapper.selectList(startWrapper); + + for (TeamPK pk : toStart) { + pk.setStatus("active"); + teamPKMapper.updateById(pk); + notifyTeamPKStart(pk); + log.info("团队PK自动开始: id={}", pk.getId()); + } + + // 自动结束 + LambdaQueryWrapper endWrapper = new LambdaQueryWrapper<>(); + endWrapper.eq(TeamPK::getStatus, "active") + .le(TeamPK::getEndTime, now); + List toEnd = teamPKMapper.selectList(endWrapper); + + for (TeamPK pk : toEnd) { + endTeamPK(pk.getId()); + } + } + + /** + * 取消PK + */ + @Transactional + public void cancelTeamPK(Long id) { + TeamPK pk = teamPKMapper.selectById(id); + if (pk == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "PK不存在"); + } + + pk.setStatus("cancelled"); + teamPKMapper.updateById(pk); + } + + /** + * 删除PK + */ + @Transactional + public void deleteTeamPK(Long id) { + TeamPK pk = teamPKMapper.selectById(id); + if (pk == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "PK不存在"); + } + + if ("active".equals(pk.getStatus())) { + throw new BusinessException(ResultCode.PARAM_ERROR, "进行中的PK不能删除"); + } + + teamPKMapper.deleteById(id); + } +} diff --git a/backend/src/main/java/com/designhub/service/TeamScorePoolService.java b/backend/src/main/java/com/designhub/service/TeamScorePoolService.java new file mode 100644 index 0000000..36899bb --- /dev/null +++ b/backend/src/main/java/com/designhub/service/TeamScorePoolService.java @@ -0,0 +1,307 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.Department; +import com.designhub.entity.TeamScorePool; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.DepartmentMapper; +import com.designhub.mapper.TeamScorePoolMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +/** + * 团队积分池Service + */ +@Service +@RequiredArgsConstructor +public class TeamScorePoolService { + + private final TeamScorePoolMapper teamScorePoolMapper; + private final DepartmentMapper departmentMapper; + + /** + * 根据部门ID获取积分池 + */ + public TeamScorePool getByDeptId(Long deptId) { + return teamScorePoolMapper.selectOne( + new LambdaQueryWrapper() + .eq(TeamScorePool::getDeptId, deptId) + ); + } + + /** + * 检查积分是否足够 + */ + public boolean checkBalance(Long deptId, Integer score) { + TeamScorePool pool = getByDeptId(deptId); + // 如果积分池不存在,自动创建 + if (pool == null) { + Department department = departmentMapper.selectById(deptId); + if (department != null) { + pool = upsertPool(deptId, department.getDeptName()); + } else { + // 部门不存在,返回false + return false; + } + } + return pool.getBalance() >= score; + } + + /** + * 检查是否超过周期限制 + */ + public boolean checkPeriodLimit(Long deptId, Integer score) { + TeamScorePool pool = getByDeptId(deptId); + // 如果积分池不存在,自动创建 + if (pool == null) { + Department department = departmentMapper.selectById(deptId); + if (department != null) { + pool = upsertPool(deptId, department.getDeptName()); + } else { + // 部门不存在,返回true(允许通过) + return true; + } + } + if (pool.getLimitEnabled() == 0) { + return true; // 没有限制或未启用限制 + } + + // 检查周期是否需要重置 + resetPeriodIfNeeded(pool); + + // 检查本周期是否超限 + Integer periodUsed = pool.getPeriodUsed() != null ? pool.getPeriodUsed() : 0; + Integer periodLimit = pool.getPeriodLimit() != null ? pool.getPeriodLimit() : 0; + + return periodUsed + score <= periodLimit; + } + + /** + * 消耗积分 + */ + @Transactional(rollbackFor = Exception.class) + public void consumeScore(Long deptId, Integer score) { + TeamScorePool pool = getByDeptId(deptId); + // 如果积分池不存在,自动创建 + if (pool == null) { + Department department = departmentMapper.selectById(deptId); + if (department != null) { + pool = upsertPool(deptId, department.getDeptName()); + } else { + throw new BusinessException("部门不存在"); + } + } + + // 检查余额 + if (pool.getBalance() < score) { + throw new BusinessException("积分余额不足"); + } + + // 检查周期限制 + if (!checkPeriodLimit(deptId, score)) { + throw new BusinessException("超过本周期积分消耗限制"); + } + + // 更新余额和消耗 + pool.setBalance(pool.getBalance() - score); + pool.setTotalConsume((pool.getTotalConsume() != null ? pool.getTotalConsume() : 0) + score); + + // 更新周期使用 + if (pool.getLimitEnabled() == 1) { + pool.setPeriodUsed((pool.getPeriodUsed() != null ? pool.getPeriodUsed() : 0) + score); + } + + teamScorePoolMapper.updateById(pool); + } + + /** + * 增加积分(任务取消或完成奖励) + */ + @Transactional(rollbackFor = Exception.class) + public void addScore(Long deptId, Integer score) { + TeamScorePool pool = getByDeptId(deptId); + if (pool == null) { + throw new BusinessException("部门积分池不存在"); + } + + pool.setBalance(pool.getBalance() + score); + pool.setTotalEarn((pool.getTotalEarn() != null ? pool.getTotalEarn() : 0) + score); + + teamScorePoolMapper.updateById(pool); + } + + /** + * 扣除积分(用于延误扣分等特殊情况,不检查周期限制) + */ + @Transactional(rollbackFor = Exception.class) + public void deductScore(Long deptId, Integer score) { + TeamScorePool pool = getByDeptId(deptId); + if (pool == null) { + throw new BusinessException("部门积分池不存在"); + } + + if (pool.getBalance() < score) { + // 如果余额不足,只扣除现有余额 + score = pool.getBalance(); + } + + pool.setBalance(pool.getBalance() - score); + pool.setTotalConsume((pool.getTotalConsume() != null ? pool.getTotalConsume() : 0) + score); + + teamScorePoolMapper.updateById(pool); + } + + /** + * 充值积分 + */ + @Transactional(rollbackFor = Exception.class) + public void rechargeScore(Long deptId, Integer score) { + TeamScorePool pool = getByDeptId(deptId); + if (pool == null) { + throw new BusinessException("部门积分池不存在"); + } + + pool.setBalance(pool.getBalance() + score); + pool.setTotalRecharge((pool.getTotalRecharge() != null ? pool.getTotalRecharge() : 0) + score); + + teamScorePoolMapper.updateById(pool); + } + + /** + * 检查周期是否需要重置 + */ + private void resetPeriodIfNeeded(TeamScorePool pool) { + if (pool.getLimitEnabled() == 0 || pool.getPeriodStartTime() == null) { + return; + } + + LocalDateTime now = LocalDateTime.now(); + boolean needReset = false; + + switch (pool.getLimitPeriod()) { + case "DAILY": + // 如果不是同一天,需要重置 + if (!now.toLocalDate().equals(pool.getPeriodStartTime().toLocalDate())) { + needReset = true; + } + break; + case "WEEKLY": + // 如果不是同一周,需要重置 + if (now.minusWeeks(1).isAfter(pool.getPeriodStartTime())) { + needReset = true; + } + break; + case "MONTHLY": + // 如果不是同一月,需要重置 + if (now.getMonthValue() != pool.getPeriodStartTime().getMonthValue() || + now.getYear() != pool.getPeriodStartTime().getYear()) { + needReset = true; + } + break; + } + + if (needReset) { + pool.setPeriodUsed(0); + pool.setPeriodStartTime(now); + teamScorePoolMapper.updateById(pool); + } + } + + /** + * 创建积分池 + */ + public TeamScorePool createPool(TeamScorePool pool) { + teamScorePoolMapper.insert(pool); + return pool; + } + + /** + * 更新或创建积分池 + */ + @Transactional(rollbackFor = Exception.class) + public TeamScorePool upsertPool(Long deptId, String deptName) { + TeamScorePool pool = getByDeptId(deptId); + if (pool == null) { + pool = new TeamScorePool(); + pool.setDeptId(deptId); + pool.setDeptName(deptName); + pool.setBalance(0); + pool.setTotalRecharge(0); + pool.setTotalConsume(0); + pool.setTotalEarn(0); + pool.setLimitEnabled(0); + pool.setAlertThreshold(80); + pool.setPeriodUsed(0); + pool.setPeriodLimit(0); + pool.setPeriodStartTime(LocalDateTime.now()); + createPool(pool); + } else if (deptName != null && !deptName.equals(pool.getDeptName())) { + pool.setDeptName(deptName); + teamScorePoolMapper.updateById(pool); + } + return pool; + } + + /** + * 更新积分池配置 + */ + public boolean updatePool(TeamScorePool pool) { + return teamScorePoolMapper.updateById(pool) > 0; + } + + /** + * 更新积分池的限制配置 + */ + @Transactional(rollbackFor = Exception.class) + public void updateLimitConfig(Long deptId, Integer limitEnabled, String limitPeriod, + Integer periodLimit, Integer alertThreshold) { + TeamScorePool pool = upsertPool(deptId, null); + if (limitEnabled != null) { + pool.setLimitEnabled(limitEnabled); + } + if (limitPeriod != null) { + pool.setLimitPeriod(limitPeriod); + } + if (periodLimit != null) { + pool.setPeriodLimit(periodLimit); + } + if (alertThreshold != null) { + pool.setAlertThreshold(alertThreshold); + } + if (pool.getPeriodStartTime() == null) { + pool.setPeriodStartTime(LocalDateTime.now()); + } + teamScorePoolMapper.updateById(pool); + } + + /** + * 直接设置积分余额(用于初始化或同步) + */ + @Transactional(rollbackFor = Exception.class) + public void setBalance(Long deptId, Integer balance) { + TeamScorePool pool = upsertPool(deptId, null); + pool.setBalance(balance != null ? balance : 0); + teamScorePoolMapper.updateById(pool); + } + + /** + * 计算预警状态 + */ + public boolean isAlert(Long deptId) { + TeamScorePool pool = getByDeptId(deptId); + if (pool == null || pool.getLimitEnabled() == 0) { + return false; + } + + Integer periodUsed = pool.getPeriodUsed() != null ? pool.getPeriodUsed() : 0; + Integer periodLimit = pool.getPeriodLimit() != null ? pool.getPeriodLimit() : 1; + Integer threshold = pool.getAlertThreshold() != null ? pool.getAlertThreshold() : 80; + + double usageRate = (double) periodUsed / periodLimit * 100; + return usageRate >= threshold; + } +} + diff --git a/backend/src/main/java/com/designhub/service/ThirdPartyCallbackService.java b/backend/src/main/java/com/designhub/service/ThirdPartyCallbackService.java new file mode 100644 index 0000000..e8804d0 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/ThirdPartyCallbackService.java @@ -0,0 +1,173 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.entity.Task; +import com.designhub.entity.ThirdPartyCallback; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.TaskMapper; +import com.designhub.mapper.ThirdPartyCallbackMapper; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 第三方回调处理Service + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ThirdPartyCallbackService { + + private final ThirdPartyCallbackMapper callbackMapper; + private final TaskMapper taskMapper; + private final ObjectMapper objectMapper; + + /** + * 处理任务完成回调 + */ + @Transactional(rollbackFor = Exception.class) + public void handleTaskCompleteCallback(String thirdPartyTaskNo, Map payload) { + log.info("处理任务完成回调: thirdPartyTaskNo={}", thirdPartyTaskNo); + + // 查找对应的任务 + Task task = taskMapper.selectOne( + new LambdaQueryWrapper() + .eq(Task::getThirdPartyTaskNo, thirdPartyTaskNo) + ); + + if (task == null) { + log.error("未找到对应的任务: thirdPartyTaskNo={}", thirdPartyTaskNo); + throw new BusinessException("任务不存在"); + } + + // 创建回调记录 + ThirdPartyCallback callback = new ThirdPartyCallback(); + callback.setTaskId(task.getId()); + callback.setThirdPartyTaskNo(thirdPartyTaskNo); + callback.setCallbackType("complete"); + callback.setCallbackStatus("pending"); + callback.setCallbackTime(LocalDateTime.now()); + callback.setRetryCount(0); + + try { + callback.setRequestData(objectMapper.writeValueAsString(payload)); + } catch (JsonProcessingException e) { + log.warn("序列化请求数据失败", e); + } + + callbackMapper.insert(callback); + + // 处理回调逻辑 + try { + // 更新任务状态为已完成 + Task updateTask = new Task(); + updateTask.setId(task.getId()); + updateTask.setStatus("completed"); + updateTask.setProgress(100); + updateTask.setCompleteTime(LocalDateTime.now()); + updateTask.setUpdateTime(LocalDateTime.now()); + + taskMapper.updateById(updateTask); + + // 更新回调记录状态 + callback.setCallbackStatus("success"); + callback.setProcessedTime(LocalDateTime.now()); + callback.setResponseData("{\"status\":\"success\"}"); + callbackMapper.updateById(callback); + + log.info("任务完成回调处理成功: taskId={}, thirdPartyTaskNo={}", task.getId(), thirdPartyTaskNo); + + } catch (Exception e) { + log.error("处理任务完成回调失败", e); + + // 更新回调记录状态 + callback.setCallbackStatus("failed"); + callback.setErrorMessage(e.getMessage()); + callback.setProcessedTime(LocalDateTime.now()); + callbackMapper.updateById(callback); + + throw new BusinessException("处理回调失败: " + e.getMessage()); + } + } + + /** + * 处理任务状态更新回调 + */ + @Transactional(rollbackFor = Exception.class) + public void handleTaskStatusUpdateCallback(String thirdPartyTaskNo, String status, Map payload) { + log.info("处理任务状态更新回调: thirdPartyTaskNo={}, status={}", thirdPartyTaskNo, status); + + // 查找对应的任务 + Task task = taskMapper.selectOne( + new LambdaQueryWrapper() + .eq(Task::getThirdPartyTaskNo, thirdPartyTaskNo) + ); + + if (task == null) { + log.error("未找到对应的任务: thirdPartyTaskNo={}", thirdPartyTaskNo); + throw new BusinessException("任务不存在"); + } + + // 创建回调记录 + ThirdPartyCallback callback = new ThirdPartyCallback(); + callback.setTaskId(task.getId()); + callback.setThirdPartyTaskNo(thirdPartyTaskNo); + callback.setCallbackType("status_update"); + callback.setCallbackStatus("pending"); + callback.setCallbackTime(LocalDateTime.now()); + callback.setRetryCount(0); + + try { + callback.setRequestData(objectMapper.writeValueAsString(payload)); + } catch (JsonProcessingException e) { + log.warn("序列化请求数据失败", e); + } + + callbackMapper.insert(callback); + + // 处理回调逻辑(根据第三方状态更新本地状态) + try { + // 这里可以根据第三方的status做相应的状态映射 + // 示例:status可能是 "submitted", "processing", "completed", "cancelled" 等 + + log.info("第三方任务状态: {}", status); + + // 更新回调记录状态 + callback.setCallbackStatus("success"); + callback.setProcessedTime(LocalDateTime.now()); + callback.setResponseData("{\"status\":\"success\",\"message\":\"状态已记录\"}"); + callbackMapper.updateById(callback); + + log.info("任务状态更新回调处理成功: taskId={}, thirdPartyTaskNo={}, status={}", + task.getId(), thirdPartyTaskNo, status); + + } catch (Exception e) { + log.error("处理任务状态更新回调失败", e); + + callback.setCallbackStatus("failed"); + callback.setErrorMessage(e.getMessage()); + callback.setProcessedTime(LocalDateTime.now()); + callbackMapper.updateById(callback); + + throw new BusinessException("处理回调失败: " + e.getMessage()); + } + } + + /** + * 查询回调记录 + */ + public List getCallbackRecords(String thirdPartyTaskNo) { + return callbackMapper.selectList( + new LambdaQueryWrapper() + .eq(ThirdPartyCallback::getThirdPartyTaskNo, thirdPartyTaskNo) + .orderByDesc(ThirdPartyCallback::getCallbackTime) + ); + } +} diff --git a/backend/src/main/java/com/designhub/service/UserGoalService.java b/backend/src/main/java/com/designhub/service/UserGoalService.java new file mode 100644 index 0000000..80d64b5 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/UserGoalService.java @@ -0,0 +1,358 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.designhub.common.ResultCode; +import com.designhub.entity.User; +import com.designhub.entity.UserGoal; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.UserGoalMapper; +import com.designhub.mapper.UserMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.time.temporal.TemporalAdjusters; +import java.util.List; + +/** + * 用户目标Service + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class UserGoalService { + + private final UserGoalMapper userGoalMapper; + private final UserMapper userMapper; + private final NotificationService notificationService; + + /** + * 创建目标 + */ + @Transactional + public UserGoal createGoal(UserGoal goal) { + // 计算周期时间 + calculatePeriodTime(goal); + + goal.setCurrentValue(0); + goal.setStatus("active"); + goal.setRewardClaimed(0); + goal.setDeleted(0); + + userGoalMapper.insert(goal); + + log.info("创建目标成功: userId={}, goalName={}", goal.getUserId(), goal.getGoalName()); + return goal; + } + + /** + * 计算周期开始和结束时间 + */ + private void calculatePeriodTime(UserGoal goal) { + LocalDateTime now = LocalDateTime.now(); + + switch (goal.getPeriod()) { + case "daily": + goal.setPeriodStart(now.toLocalDate().atStartOfDay()); + goal.setPeriodEnd(now.toLocalDate().atTime(23, 59, 59)); + break; + case "weekly": + goal.setPeriodStart(now.with(TemporalAdjusters.previousOrSame(java.time.DayOfWeek.MONDAY)).toLocalDate().atStartOfDay()); + goal.setPeriodEnd(now.with(TemporalAdjusters.nextOrSame(java.time.DayOfWeek.SUNDAY)).toLocalDate().atTime(23, 59, 59)); + break; + case "monthly": + goal.setPeriodStart(now.with(TemporalAdjusters.firstDayOfMonth()).toLocalDate().atStartOfDay()); + goal.setPeriodEnd(now.with(TemporalAdjusters.lastDayOfMonth()).toLocalDate().atTime(23, 59, 59)); + break; + case "quarterly": + int quarter = (now.getMonthValue() - 1) / 3; + LocalDateTime quarterStart = LocalDateTime.of(now.getYear(), quarter * 3 + 1, 1, 0, 0); + goal.setPeriodStart(quarterStart); + goal.setPeriodEnd(quarterStart.plusMonths(3).minusSeconds(1)); + break; + case "yearly": + goal.setPeriodStart(now.with(TemporalAdjusters.firstDayOfYear()).toLocalDate().atStartOfDay()); + goal.setPeriodEnd(now.with(TemporalAdjusters.lastDayOfYear()).toLocalDate().atTime(23, 59, 59)); + break; + case "custom": + // 自定义周期,使用传入的时间 + if (goal.getPeriodStart() == null || goal.getPeriodEnd() == null) { + throw new BusinessException(ResultCode.PARAM_ERROR, "自定义周期必须指定开始和结束时间"); + } + break; + default: + goal.setPeriodStart(now); + goal.setPeriodEnd(now.plusMonths(1)); + } + } + + /** + * 更新目标 + */ + @Transactional + public UserGoal updateGoal(Long id, UserGoal goal) { + UserGoal existing = userGoalMapper.selectById(id); + if (existing == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "目标不存在"); + } + + goal.setId(id); + userGoalMapper.updateById(goal); + + return userGoalMapper.selectById(id); + } + + /** + * 更新目标进度 + */ + @Transactional + public UserGoal updateProgress(Long goalId, Integer progressValue) { + UserGoal goal = userGoalMapper.selectById(goalId); + if (goal == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "目标不存在"); + } + + goal.setCurrentValue(progressValue); + + // 检查是否完成 + if (progressValue >= goal.getTargetValue() && !"completed".equals(goal.getStatus())) { + goal.setStatus("completed"); + goal.setCompletedTime(LocalDateTime.now()); + + // 发送完成通知 + notificationService.sendNotification( + goal.getUserId(), + "目标达成", + String.format("恭喜!您的目标「%s」已完成!", goal.getGoalName()), + "goal", + goal.getId() + ); + } + + userGoalMapper.updateById(goal); + return goal; + } + + /** + * 增量更新目标进度 + */ + @Transactional + public void incrementProgress(Long userId, String goalType, int increment) { + // 获取用户当前进行中的该类型目标 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(UserGoal::getUserId, userId) + .eq(UserGoal::getGoalType, goalType) + .eq(UserGoal::getStatus, "active") + .le(UserGoal::getPeriodStart, LocalDateTime.now()) + .ge(UserGoal::getPeriodEnd, LocalDateTime.now()); + + List goals = userGoalMapper.selectList(wrapper); + + for (UserGoal goal : goals) { + int newValue = goal.getCurrentValue() + increment; + updateProgress(goal.getId(), newValue); + } + } + + /** + * 获取用户目标列表 + */ + public List getUserGoals(Long userId, String status) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(UserGoal::getUserId, userId); + + if (status != null && !status.isEmpty()) { + wrapper.eq(UserGoal::getStatus, status); + } + + wrapper.orderByDesc(UserGoal::getCreateTime); + return userGoalMapper.selectList(wrapper); + } + + /** + * 获取用户当前活跃目标 + */ + public List getActiveGoals(Long userId) { + LocalDateTime now = LocalDateTime.now(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(UserGoal::getUserId, userId) + .eq(UserGoal::getStatus, "active") + .le(UserGoal::getPeriodStart, now) + .ge(UserGoal::getPeriodEnd, now) + .orderByAsc(UserGoal::getPeriodEnd); + + return userGoalMapper.selectList(wrapper); + } + + /** + * 获取目标详情 + */ + public UserGoal getGoalById(Long id) { + UserGoal goal = userGoalMapper.selectById(id); + if (goal == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "目标不存在"); + } + return goal; + } + + /** + * 领取目标奖励 + */ + @Transactional + public boolean claimReward(Long goalId, Long userId) { + UserGoal goal = userGoalMapper.selectById(goalId); + if (goal == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "目标不存在"); + } + + if (!goal.getUserId().equals(userId)) { + throw new BusinessException(ResultCode.FORBIDDEN, "无权操作此目标"); + } + + if (!"completed".equals(goal.getStatus())) { + throw new BusinessException(ResultCode.PARAM_ERROR, "目标未完成,无法领取奖励"); + } + + if (goal.getRewardClaimed() == 1) { + throw new BusinessException(ResultCode.PARAM_ERROR, "奖励已领取"); + } + + if (goal.getRewardScore() == null || goal.getRewardScore() <= 0) { + throw new BusinessException(ResultCode.PARAM_ERROR, "此目标无积分奖励"); + } + + // 更新用户积分 + User user = userMapper.selectById(userId); + int currentScore = user.getScore() != null ? user.getScore() : 0; + user.setScore(currentScore + goal.getRewardScore()); + userMapper.updateById(user); + + // 标记已领取 + goal.setRewardClaimed(1); + userGoalMapper.updateById(goal); + + return true; + } + + /** + * 取消目标 + */ + @Transactional + public void cancelGoal(Long id, Long userId) { + UserGoal goal = userGoalMapper.selectById(id); + if (goal == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "目标不存在"); + } + + if (!goal.getUserId().equals(userId)) { + throw new BusinessException(ResultCode.FORBIDDEN, "无权操作此目标"); + } + + goal.setStatus("cancelled"); + userGoalMapper.updateById(goal); + } + + /** + * 删除目标 + */ + @Transactional + public void deleteGoal(Long id, Long userId) { + UserGoal goal = userGoalMapper.selectById(id); + if (goal == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "目标不存在"); + } + + if (!goal.getUserId().equals(userId)) { + throw new BusinessException(ResultCode.FORBIDDEN, "无权操作此目标"); + } + + userGoalMapper.deleteById(id); + } + + /** + * 定时任务:检查过期目标 + */ + @Scheduled(cron = "0 0 0 * * ?") // 每天凌晨执行 + @Transactional + public void checkExpiredGoals() { + LocalDateTime now = LocalDateTime.now(); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(UserGoal::getStatus, "active") + .lt(UserGoal::getPeriodEnd, now); + + List expiredGoals = userGoalMapper.selectList(wrapper); + + for (UserGoal goal : expiredGoals) { + if (goal.getCurrentValue() >= goal.getTargetValue()) { + goal.setStatus("completed"); + goal.setCompletedTime(now); + } else { + goal.setStatus("failed"); + + // 发送未完成通知 + notificationService.sendNotification( + goal.getUserId(), + "目标未完成", + String.format("很遗憾,您的目标「%s」未能在期限内完成。继续努力!", goal.getGoalName()), + "goal", + goal.getId() + ); + } + userGoalMapper.updateById(goal); + } + + log.info("检查过期目标完成,处理了{}个目标", expiredGoals.size()); + } + + /** + * 创建预设目标(系统推荐) + */ + @Transactional + public UserGoal createPresetGoal(Long userId, String presetType) { + UserGoal goal = new UserGoal(); + goal.setUserId(userId); + + switch (presetType) { + case "weekly_task": + goal.setGoalType("task_count"); + goal.setGoalName("本周完成10个任务"); + goal.setDescription("在本周内完成10个设计任务"); + goal.setTargetValue(10); + goal.setPeriod("weekly"); + goal.setRewardScore(50); + break; + case "monthly_task": + goal.setGoalType("task_count"); + goal.setGoalName("本月完成40个任务"); + goal.setDescription("在本月内完成40个设计任务"); + goal.setTargetValue(40); + goal.setPeriod("monthly"); + goal.setRewardScore(200); + break; + case "monthly_score": + goal.setGoalType("score"); + goal.setGoalName("本月获得1000积分"); + goal.setDescription("在本月内累计获得1000积分"); + goal.setTargetValue(1000); + goal.setPeriod("monthly"); + goal.setRewardScore(100); + break; + case "top_10": + goal.setGoalType("rank"); + goal.setGoalName("进入排行榜前10"); + goal.setDescription("在本月排行榜中进入前10名"); + goal.setTargetValue(10); + goal.setPeriod("monthly"); + goal.setRewardScore(150); + break; + default: + throw new BusinessException(ResultCode.PARAM_ERROR, "未知的预设目标类型"); + } + + return createGoal(goal); + } +} diff --git a/backend/src/main/java/com/designhub/service/UserService.java b/backend/src/main/java/com/designhub/service/UserService.java new file mode 100644 index 0000000..1befdfa --- /dev/null +++ b/backend/src/main/java/com/designhub/service/UserService.java @@ -0,0 +1,857 @@ +package com.designhub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.designhub.dto.LoginDTO; +import com.designhub.dto.RegisterDTO; +import com.designhub.entity.Department; +import com.designhub.entity.User; +import com.designhub.exception.BusinessException; +import com.designhub.mapper.UserMapper; +import com.designhub.mapper.DepartmentMapper; +import com.designhub.common.ResultCode; +import com.designhub.util.JwtUtil; +import com.designhub.vo.LoginVO; +import com.designhub.vo.UserInfoVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * 用户管理Service + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class UserService { + + private final UserMapper userMapper; + private final DepartmentMapper departmentMapper; + private final TeamScorePoolService teamScorePoolService; + private final RoleService roleService; + private final DingTalkService dingTalkService; + private final DepartmentService departmentService; + private final JwtUtil jwtUtil; + private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + + private Department getDepartmentOrThrow(Long deptId) { + if (deptId == null) { + return null; + } + Department department = departmentMapper.selectById(deptId); + if (department == null) { + throw new BusinessException("部门不存在"); + } + if (department.getStatus() != null && department.getStatus() == 0) { + throw new BusinessException("该部门已被禁用"); + } + return department; + } + + /** + * 分页查询用户列表 + */ + public Page getUserList(int pageNum, int pageSize, String keyword, Long deptId, Integer status) { + Page page = new Page<>(pageNum, pageSize); + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (StringUtils.hasText(keyword)) { + queryWrapper.and(wrapper -> wrapper + .like(User::getUsername, keyword) + .or() + .like(User::getRealName, keyword) + .or() + .like(User::getPhone, keyword) + .or() + .like(User::getEmail, keyword) + ); + } + if (deptId != null) { + queryWrapper.eq(User::getDeptId, deptId); + } + if (status != null) { + queryWrapper.eq(User::getStatus, status); + } + queryWrapper.orderByDesc(User::getCreateTime); + + return userMapper.selectPage(page, queryWrapper); + } + + /** + * 根据ID获取用户 + */ + public User getUserById(Long id) { + User user = userMapper.selectById(id); + if (user != null) { + // 不返回密码 + user.setPassword(null); + } + return user; + } + + /** + * 根据用户名获取用户 + */ + public User getUserByUsername(String username) { + return userMapper.selectOne( + new LambdaQueryWrapper() + .eq(User::getUsername, username) + ); + } + + /** + * 用户注册 + */ + @Transactional(rollbackFor = Exception.class) + public User register(RegisterDTO registerDTO) { + log.info("用户注册: username={}, roleId={}, deptId={}", + registerDTO.getUsername(), registerDTO.getRoleId(), registerDTO.getDeptId()); + + // 验证用户名唯一性 + User existUser = getUserByUsername(registerDTO.getUsername()); + if (existUser != null) { + throw new BusinessException("用户名已存在"); + } + + // 验证角色ID(只允许注册为设计师或需求方) + if (registerDTO.getRoleId() != 5L && registerDTO.getRoleId() != 6L) { + throw new BusinessException("无效的角色选择"); + } + + // 验证部门存在 + Department department = departmentMapper.selectById(registerDTO.getDeptId()); + if (department == null) { + throw new BusinessException("部门不存在"); + } + + // 验证角色和部门类型匹配 + String roleCode = registerDTO.getRoleId() == 5L ? "DESIGNER" : "REQUESTER"; + String expectedDeptType = roleCode.equals("DESIGNER") ? "receiving" : "demand"; + if (!expectedDeptType.equals(department.getDeptType())) { + throw new BusinessException("角色与部门类型不匹配"); + } + + // 创建用户 + User user = new User(); + user.setUsername(registerDTO.getUsername()); + user.setPassword(passwordEncoder.encode(registerDTO.getPassword())); + user.setRealName(registerDTO.getRealName()); + user.setEmail(registerDTO.getEmail()); + user.setPhone(registerDTO.getPhone()); + user.setDeptId(registerDTO.getDeptId()); + user.setDeptName(department.getDeptName()); + user.setRoleIds(String.valueOf(registerDTO.getRoleId())); + user.setStatus(1); + user.setDeleted(0); + + userMapper.insert(user); + log.info("用户注册成功: userId={}, username={}", user.getId(), user.getUsername()); + + // 不返回密码 + user.setPassword(null); + return user; + } + + /** + * 创建用户 + */ + @Transactional(rollbackFor = Exception.class) + public User createUser(User user) { + // 验证用户名唯一性 + User existUser = getUserByUsername(user.getUsername()); + if (existUser != null) { + throw new BusinessException("用户名已存在"); + } + + // 验证手机号唯一性 + if (StringUtils.hasText(user.getPhone())) { + long count = userMapper.selectCount( + new LambdaQueryWrapper() + .eq(User::getPhone, user.getPhone()) + ); + if (count > 0) { + throw new BusinessException("手机号已存在"); + } + } + + // 验证邮箱唯一性 + if (StringUtils.hasText(user.getEmail())) { + long count = userMapper.selectCount( + new LambdaQueryWrapper() + .eq(User::getEmail, user.getEmail()) + ); + if (count > 0) { + throw new BusinessException("邮箱已存在"); + } + } + + // 加密密码 + if (StringUtils.hasText(user.getPassword())) { + user.setPassword(passwordEncoder.encode(user.getPassword())); + } else { + // 默认密码 + user.setPassword(passwordEncoder.encode("123456")); + } + + // 设置部门名称 + if (user.getDeptId() != null) { + Department department = getDepartmentOrThrow(user.getDeptId()); + user.setDeptName(department.getDeptName()); + } + + // 设置默认状态 + if (user.getStatus() == null) { + user.setStatus(1); + } + + userMapper.insert(user); + return user; + } + + /** + * 更新用户 + */ + @Transactional(rollbackFor = Exception.class) + public boolean updateUser(User user) { + User existUser = getUserById(user.getId()); + if (existUser == null) { + throw new BusinessException("用户不存在"); + } + + // 如果修改了用户名,验证唯一性 + if (StringUtils.hasText(user.getUsername()) && + !user.getUsername().equals(existUser.getUsername())) { + User sameUsernameUser = getUserByUsername(user.getUsername()); + if (sameUsernameUser != null) { + throw new BusinessException("用户名已存在"); + } + } + + // 如果更新了部门 + if (user.getDeptId() != null) { + Department department = getDepartmentOrThrow(user.getDeptId()); + user.setDeptName(department != null ? department.getDeptName() : null); + } + + // 不允许通过此接口修改密码 + user.setPassword(null); + + return userMapper.updateById(user) > 0; + } + + /** + * 删除用户(逻辑删除) + */ + @Transactional(rollbackFor = Exception.class) + public boolean deleteUser(Long id) { + User user = getUserById(id); + if (user == null) { + throw new BusinessException("用户不存在"); + } + + // 不允许删除自己 + // TODO: 获取当前登录用户ID + // if (id.equals(currentUserId)) { + // throw new BusinessException("不能删除自己"); + // } + + // 注意:已移除删除用户角色关联的代码,因为sys_user_role表不存在 + // 系统使用sys_user表中的role_id字段管理角色,不使用关联表 + + return userMapper.deleteById(id) > 0; + } + + /** + * 重置密码 + */ + @Transactional(rollbackFor = Exception.class) + public boolean resetPassword(Long id, String newPassword) { + User user = getUserById(id); + if (user == null) { + throw new BusinessException("用户不存在"); + } + + user.setPassword(passwordEncoder.encode(newPassword)); + return userMapper.updateById(user) > 0; + } + + /** + * 修改密码 + */ + @Transactional(rollbackFor = Exception.class) + public boolean changePassword(Long id, String oldPassword, String newPassword) { + User user = userMapper.selectById(id); + if (user == null) { + throw new BusinessException("用户不存在"); + } + + // 验证旧密码 + if (!passwordEncoder.matches(oldPassword, user.getPassword())) { + throw new BusinessException("原密码不正确"); + } + + user.setPassword(passwordEncoder.encode(newPassword)); + return userMapper.updateById(user) > 0; + } + + /** + * 为用户分配角色 + */ + @Transactional(rollbackFor = Exception.class) + public boolean assignRoles(Long userId, List roleIds) { + User user = getUserById(userId); + if (user == null) { + throw new BusinessException("用户不存在"); + } + + // 将角色ID列表转换为逗号分隔的字符串 + if (roleIds != null && !roleIds.isEmpty()) { + String roleIdsStr = roleIds.stream() + .map(String::valueOf) + .reduce((a, b) -> a + "," + b) + .orElse(""); + user.setRoleIds(roleIdsStr); + userMapper.updateById(user); + } else { + user.setRoleIds(null); + userMapper.updateById(user); + } + + return true; + } + + /** + * 获取用户的角色ID列表 + */ + public List getUserRoleIds(Long userId) { + User user = getUserById(userId); + if (user == null || user.getRoleIds() == null || user.getRoleIds().isEmpty()) { + return List.of(); + } + + // 将逗号分隔的字符串转换为Long列表 + return java.util.Arrays.stream(user.getRoleIds().split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .map(Long::parseLong) + .toList(); + } + + /** + * 启用/禁用用户 + */ + @Transactional(rollbackFor = Exception.class) + public boolean toggleUserStatus(Long id, Integer status) { + User user = new User(); + user.setId(id); + user.setStatus(status); + return userMapper.updateById(user) > 0; + } + + /** + * 获取全部用户列表(不分页) + */ + public List getAllUsers() { + return userMapper.selectList( + new LambdaQueryWrapper() + .orderByAsc(User::getDeptId) + .orderByDesc(User::getCreateTime) + ); + } + + /** + * 调整用户所属部门 + */ + @Transactional(rollbackFor = Exception.class) + public boolean updateUserDepartment(Long userId, Long deptId) { + log.info("开始调整用户部门: userId={}, 新deptId={}", userId, deptId); + + User user = getUserById(userId); + if (user == null) { + log.error("用户不存在: userId={}", userId); + throw new BusinessException("用户不存在"); + } + log.info("当前用户信息: username={}, 原deptId={}, 原deptName={}", + user.getUsername(), user.getDeptId(), user.getDeptName()); + + // 使用 LambdaUpdateWrapper 强制更新,包括 null 值 + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(User::getId, userId); + + if (deptId == null || deptId == 0) { + // 清除部门 + log.info("清除用户部门: userId={}", userId); + updateWrapper.set(User::getDeptId, null); + updateWrapper.set(User::getDeptName, null); + } else { + Department department = getDepartmentOrThrow(deptId); + log.info("设置新部门: deptId={}, deptName={}", department.getId(), department.getDeptName()); + updateWrapper.set(User::getDeptId, department.getId()); + updateWrapper.set(User::getDeptName, department.getDeptName()); + + // 确保新部门有积分池(如果不存在则自动创建) + teamScorePoolService.upsertPool(department.getId(), department.getDeptName()); + log.info("已确保部门积分池存在: deptId={}, deptName={}", department.getId(), department.getDeptName()); + } + + int rows = userMapper.update(null, updateWrapper); + log.info("更新结果: 影响行数={}", rows); + return rows > 0; + } + + /** + * 尝试匹配系统部门(根据钉钉部门名称) + */ + private Long matchSystemDepartment(List> dingDepartments) { + if (dingDepartments == null || dingDepartments.isEmpty()) { + return null; + } + + // 获取所有系统部门 + List systemDepts = departmentMapper.selectList( + new LambdaQueryWrapper() + .eq(Department::getStatus, 1) + .eq(Department::getDeleted, 0) + ); + + for (Map dingDept : dingDepartments) { + String dingDeptName = (String) dingDept.get("name"); + Object dingDeptIdObj = dingDept.get("dept_id"); + String dingDeptId = dingDeptIdObj != null ? dingDeptIdObj.toString() : null; + + // 1. 优先通过钉钉部门ID精确匹配 + if (dingDeptId != null) { + Department matched = systemDepts.stream() + .filter(d -> dingDeptId.equals(d.getDingDeptId())) + .findFirst() + .orElse(null); + if (matched != null) { + log.info("通过钉钉部门ID精确匹配: {} -> {}", dingDeptId, matched.getDeptName()); + return matched.getId(); + } + } + + // 2. 通过部门名称模糊匹配 + if (dingDeptName != null) { + Department matched = systemDepts.stream() + .filter(d -> d.getDeptName().contains(dingDeptName) || + dingDeptName.contains(d.getDeptName())) + .findFirst() + .orElse(null); + if (matched != null) { + log.info("通过部门名称模糊匹配: {} -> {}", dingDeptName, matched.getDeptName()); + return matched.getId(); + } + } + } + + return null; + } + + /** + * 同步钉钉用户信息到系统 + */ + private void syncUserFromDingTalk(User user, Map detailedInfo, + String dingtalkUserId, List> dingDepartments) { + boolean needUpdate = false; + + // 1. 同步基本信息 + if (detailedInfo != null) { + if (!StringUtils.hasText(user.getRealName()) && detailedInfo.get("name") != null) { + user.setRealName((String) detailedInfo.get("name")); + needUpdate = true; + } + if (!StringUtils.hasText(user.getAvatar()) && detailedInfo.get("avatar") != null) { + user.setAvatar((String) detailedInfo.get("avatar")); + needUpdate = true; + } + if (!StringUtils.hasText(user.getPhone()) && detailedInfo.get("mobile") != null) { + user.setPhone((String) detailedInfo.get("mobile")); + needUpdate = true; + } + } + + // 2. 同步钉钉userId + if (!StringUtils.hasText(user.getDingUserId()) && dingtalkUserId != null) { + user.setDingUserId(dingtalkUserId); + needUpdate = true; + } + + // 3. 检查部门是否需要更新 + if (user.getDeptId() != null && dingDepartments != null && !dingDepartments.isEmpty()) { + Department currentDept = departmentMapper.selectById(user.getDeptId()); + if (currentDept != null && StringUtils.hasText(currentDept.getDingDeptId())) { + // 检查用户是否还在原部门 + boolean stillInDept = dingDepartments.stream() + .anyMatch(d -> currentDept.getDingDeptId().equals(d.get("dept_id").toString())); + + if (!stillInDept) { + String newDeptNames = dingDepartments.stream() + .map(d -> String.valueOf(d.get("name"))) + .collect(Collectors.joining(",")); + log.warn("用户钉钉部门已变化: userId={}, 原部门={}, 钉钉部门={}", + user.getId(), currentDept.getDeptName(), newDeptNames); + + // 尝试重新匹配部门 + Long newDeptId = matchSystemDepartment(dingDepartments); + if (newDeptId != null && !newDeptId.equals(user.getDeptId())) { + Department newDept = departmentMapper.selectById(newDeptId); + user.setDeptId(newDeptId); + user.setDeptName(newDept.getDeptName()); + needUpdate = true; + log.info("自动同步到新部门: userId={}, 新部门={}", + user.getId(), newDept.getDeptName()); + } + } + } + } + + if (needUpdate) { + userMapper.updateById(user); + log.info("钉钉用户信息已同步: userId={}", user.getId()); + } + } + + /** + * 处理钉钉OAuth登录 + */ + @Transactional(rollbackFor = Exception.class) + private LoginVO handleDingTalkLogin(String code) { + log.info("开始钉钉OAuth登录流程, code: {}...", code.substring(0, Math.min(10, code.length()))); + + try { + // 1. 获取用户访问令牌 + Map tokenData = dingTalkService.getUserAccessToken(code); + String accessToken = (String) tokenData.get("accessToken"); + if (accessToken == null) { + throw new BusinessException(ResultCode.DINGTALK_AUTH_ERROR, "获取钉钉访问令牌失败"); + } + + // 2. 获取用户信息(包含unionId) + Map userMeData = dingTalkService.getUserInfo(accessToken); + String unionId = (String) userMeData.get("unionId"); + String nick = (String) userMeData.get("nick"); + String avatarUrl = (String) userMeData.get("avatarUrl"); + + if (unionId == null) { + throw new BusinessException(ResultCode.DINGTALK_AUTH_ERROR, "获取钉钉用户unionId失败"); + } + + log.info("钉钉用户信息: unionId={}, nick={}", unionId, nick); + + // 3. 根据unionId获取userId(用于获取更详细信息) + String dingtalkUserId = dingTalkService.getUserIdByUnionId(unionId); + Map detailedInfo = null; + List> dingDepartments = new ArrayList<>(); + + if (dingtalkUserId != null) { + detailedInfo = dingTalkService.getDetailedUserInfo(dingtalkUserId); + if (detailedInfo != null) { + log.info("获取到详细用户信息: userId={}", dingtalkUserId); + // 获取钉钉部门信息 + dingDepartments = (List>) detailedInfo.get("departments"); + if (dingDepartments != null && !dingDepartments.isEmpty()) { + String deptNames = dingDepartments.stream() + .map(d -> String.valueOf(d.get("name"))) + .collect(Collectors.joining(", ")); + log.info("钉钉用户所属部门: {}", deptNames); + } + } + } + + // 4. 查找或创建用户 + // 使用unionId作为唯一标识 + String email = "dingtalk_" + unionId + "@dingtalk.local"; + User user = userMapper.selectOne( + new LambdaQueryWrapper() + .eq(User::getEmail, email) + ); + + if (user == null) { + // 首次登录:创建用户但不分配部门 + // 需要用户手动选择部门类型和具体部门 + log.info("钉钉用户首次登录,创建临时账号: email={}", email); + + // 尝试匹配系统部门 + Long matchedDeptId = matchSystemDepartment(dingDepartments); + String username = detailedInfo != null && detailedInfo.get("job_number") != null + ? (String) detailedInfo.get("job_number") : "DT_" + unionId.substring(0, 8); + + user = new User(); + user.setUsername(username); + user.setEmail(email); + user.setPassword(passwordEncoder.encode(UUID.randomUUID().toString())); + user.setRealName(detailedInfo != null && detailedInfo.get("name") != null + ? (String) detailedInfo.get("name") : nick); + user.setAvatar(detailedInfo != null && detailedInfo.get("avatar") != null + ? (String) detailedInfo.get("avatar") : avatarUrl); + user.setPhone(detailedInfo != null ? (String) detailedInfo.get("mobile") : null); + user.setDingUserId(dingtalkUserId); + user.setStatus(1); + user.setDeleted(0); + + if (matchedDeptId != null) { + // 自动匹配到部门 + Department dept = departmentMapper.selectById(matchedDeptId); + user.setDeptId(matchedDeptId); + user.setDeptName(dept.getDeptName()); + // 根据部门类型设置默认角色 + user.setRoleIds("receiving".equals(dept.getDeptType()) ? "5" : "6"); + log.info("自动匹配到系统部门: deptId={}, deptName={}, type={}", + matchedDeptId, dept.getDeptName(), dept.getDeptType()); + } else { + // 无法自动匹配,需要用户手动选择 + String deptNamesStr = dingDepartments.stream() + .map(d -> String.valueOf(d.get("name"))) + .collect(Collectors.joining(", ")); + log.warn("无法自动匹配系统部门,钉钉部门: {}", deptNamesStr); + user.setDeptId(null); + user.setDeptName(null); + user.setRoleIds(null); + user.setStatus(0); // 暂时禁用,待选择部门后启用 + } + + userMapper.insert(user); + log.info("钉钉用户创建成功: userId={}, username={}, needSelectDept={}", + user.getId(), user.getUsername(), matchedDeptId == null); + + // 如果需要选择部门,抛出特殊异常 + if (matchedDeptId == null) { + String deptInfo = dingDepartments.isEmpty() ? "无钉钉部门信息" : + dingDepartments.stream() + .map(d -> String.valueOf(d.get("name"))) + .collect(Collectors.joining(",")); + throw new BusinessException(ResultCode.PARAM_ERROR, + "NEED_SELECT_DEPT:" + user.getId() + ":" + deptInfo); + } + } else { + // 已有用户:同步更新信息 + log.info("钉钉用户已存在,同步更新: userId={}", user.getId()); + syncUserFromDingTalk(user, detailedInfo, dingtalkUserId, dingDepartments); + } + + // 5. 生成Token + String token = jwtUtil.generateToken(user.getId(), user.getUsername()); + String refreshToken = jwtUtil.generateRefreshToken(user.getId(), user.getUsername()); + + return LoginVO.builder() + .token(token) + .refreshToken(refreshToken) + .userId(user.getId()) + .username(user.getUsername()) + .realName(user.getRealName()) + .avatar(user.getAvatar()) + .roleIds(user.getRoleIds()) + .build(); + + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("钉钉登录失败", e); + throw new BusinessException(ResultCode.DINGTALK_AUTH_ERROR, "钉钉登录失败: " + e.getMessage()); + } + } + + /** + * 完成钉钉用户设置(选择部门) + */ + @Transactional(rollbackFor = Exception.class) + public LoginVO completeDingTalkUserSetup(Long userId, Long deptId) { + log.info("完成钉钉用户设置: userId={}, deptId={}", userId, deptId); + + // 1. 获取用户 + User user = userMapper.selectById(userId); + if (user == null) { + throw new BusinessException(ResultCode.USER_NOT_EXIST); + } + + // 2. 获取部门 + Department dept = departmentMapper.selectById(deptId); + if (dept == null) { + throw new BusinessException(ResultCode.DEPT_NOT_EXIST); + } + + // 3. 更新用户部门和角色 + user.setDeptId(deptId); + user.setDeptName(dept.getDeptName()); + // 根据部门类型设置角色 + user.setRoleIds("receiving".equals(dept.getDeptType()) ? "5" : "6"); + user.setStatus(1); // 启用用户 + + userMapper.updateById(user); + log.info("钉钉用户部门设置完成: userId={}, deptName={}, roleIds={}", + userId, dept.getDeptName(), user.getRoleIds()); + + // 4. 生成Token并返回 + String token = jwtUtil.generateToken(user.getId(), user.getUsername()); + String refreshToken = jwtUtil.generateRefreshToken(user.getId(), user.getUsername()); + + return LoginVO.builder() + .token(token) + .refreshToken(refreshToken) + .userId(user.getId()) + .username(user.getUsername()) + .realName(user.getRealName()) + .avatar(user.getAvatar()) + .roleIds(user.getRoleIds()) + .build(); + } + + /** + * 用户登录 + */ + public LoginVO login(LoginDTO loginDTO) { + String loginType = loginDTO.getLoginType(); + + if ("password".equals(loginType)) { + // 用户名密码登录 + User user = getUserByUsername(loginDTO.getUsername()); + if (user == null) { + throw new BusinessException(ResultCode.USER_NOT_EXIST); + } + + if (!passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) { + throw new BusinessException(ResultCode.PASSWORD_ERROR); + } + + if (user.getStatus() == 0) { + throw new BusinessException(ResultCode.USER_DISABLED); + } + + // 生成Token + String token = jwtUtil.generateToken(user.getId(), user.getUsername()); + String refreshToken = jwtUtil.generateRefreshToken(user.getId(), user.getUsername()); + + return LoginVO.builder() + .token(token) + .refreshToken(refreshToken) + .userId(user.getId()) + .username(user.getUsername()) + .realName(user.getRealName()) + .avatar(user.getAvatar()) + .roleIds(user.getRoleIds()) + .build(); + } else if ("dingtalk".equals(loginType)) { + // 钉钉OAuth 2.0登录 + return handleDingTalkLogin(loginDTO.getAuthCode()); + } else if ("casdoor".equals(loginType)) { + // Casdoor登录 - 暂时返回错误,待后续实现 + throw new BusinessException(ResultCode.PARAM_ERROR, "Casdoor登录功能尚未开放"); + } else { + throw new BusinessException(ResultCode.PARAM_ERROR, "不支持的登录类型"); + } + } + + /** + * 根据Token获取用户信息 + */ + public UserInfoVO getUserInfoByToken(String token) { + if (!StringUtils.hasText(token)) { + throw new BusinessException(ResultCode.UNAUTHORIZED); + } + + if (!jwtUtil.validateToken(token)) { + throw new BusinessException(ResultCode.UNAUTHORIZED, "Token无效或已过期"); + } + + String username = jwtUtil.getUsernameFromToken(token); + log.info("getUserInfoByToken - username: {}", username); + + User user = getUserByUsername(username); + if (user == null) { + throw new BusinessException(ResultCode.USER_NOT_EXIST); + } + + log.info("getUserInfoByToken - 用户: id={}, username={}, deptId={}, deptName={}", + user.getId(), user.getUsername(), user.getDeptId(), user.getDeptName()); + + // 获取用户角色代码列表 + List roleCodes = List.of(); + try { + var roles = roleService.getRolesByUserId(user.getId()); + roleCodes = roles.stream() + .map(role -> role.getRoleCode()) + .toList(); + log.info("getUserInfoByToken - 用户角色: {}", roleCodes); + } catch (Exception e) { + log.warn("获取用户角色失败: userId={}, error={}", user.getId(), e.getMessage()); + } + + // 检查是否为部门负责人 + Integer isDepartmentLeader = 0; + try { + boolean isLeader = departmentService.isUserDepartmentLeader(user.getId()); + isDepartmentLeader = isLeader ? 1 : 0; + log.info("getUserInfoByToken - 是否为部门负责人: userId={}, isLeader={}", user.getId(), isLeader); + } catch (Exception e) { + log.warn("检查部门负责人状态失败: userId={}, error={}", user.getId(), e.getMessage()); + } + + UserInfoVO userInfoVO = UserInfoVO.builder() + .id(user.getId()) + .username(user.getUsername()) + .realName(user.getRealName()) + .email(user.getEmail()) + .phone(user.getPhone()) + .avatar(user.getAvatar()) + .deptId(user.getDeptId()) + .deptName(user.getDeptName()) + .roleIds(user.getRoleIds()) + .roles(roleCodes) + .status(user.getStatus()) + .isDepartmentLeader(isDepartmentLeader) + .build(); + + log.info("getUserInfoByToken - 返回VO: id={}, deptId={}, deptName={}, roles={}, isDepartmentLeader={}", + userInfoVO.getId(), userInfoVO.getDeptId(), userInfoVO.getDeptName(), roleCodes, isDepartmentLeader); + + return userInfoVO; + } + + /** + * 刷新Token + */ + public LoginVO refreshToken(String authHeader) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + + if (!StringUtils.hasText(token)) { + throw new BusinessException(ResultCode.UNAUTHORIZED); + } + + if (!jwtUtil.validateToken(token)) { + throw new BusinessException(ResultCode.UNAUTHORIZED, "Token无效或已过期"); + } + + String username = jwtUtil.getUsernameFromToken(token); + Long userId = jwtUtil.getUserIdFromToken(token); + + User user = getUserById(userId); + if (user == null) { + throw new BusinessException(ResultCode.USER_NOT_EXIST); + } + + // 生成新的Token + String newToken = jwtUtil.generateToken(userId, username); + String newRefreshToken = jwtUtil.generateRefreshToken(userId, username); + + return LoginVO.builder() + .token(newToken) + .refreshToken(newRefreshToken) + .userId(user.getId()) + .username(user.getUsername()) + .realName(user.getRealName()) + .avatar(user.getAvatar()) + .roleIds(user.getRoleIds()) + .build(); + } +} diff --git a/backend/src/main/java/com/designhub/util/ExcelExportUtil.java b/backend/src/main/java/com/designhub/util/ExcelExportUtil.java new file mode 100644 index 0000000..61b0a0c --- /dev/null +++ b/backend/src/main/java/com/designhub/util/ExcelExportUtil.java @@ -0,0 +1,190 @@ +package com.designhub.util; + +import jakarta.servlet.http.HttpServletResponse; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +/** + * Excel导出工具类 + */ +public class ExcelExportUtil { + + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + /** + * 导出Excel到HTTP响应 + * + * @param response HTTP响应 + * @param fileName 文件名(不包含扩展名) + * @param sheetName 工作表名称 + * @param headers 表头数组 + * @param fieldNames 字段名数组(与表头对应) + * @param dataList 数据列表 + */ + public static void exportExcel( + HttpServletResponse response, + String fileName, + String sheetName, + String[] headers, + String[] fieldNames, + List dataList + ) throws IOException { + + // 创建工作簿 + Workbook workbook = new XSSFWorkbook(); + Sheet sheet = workbook.createSheet(sheetName); + + // 创建表头样式 + CellStyle headerStyle = createHeaderStyle(workbook); + CellStyle dataStyle = createDataStyle(workbook); + + // 创建表头行 + Row headerRow = sheet.createRow(0); + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + cell.setCellStyle(headerStyle); + sheet.setColumnWidth(i, 4000); // 设置列宽 + } + + // 填充数据 + for (int i = 0; i < dataList.size(); i++) { + Row row = sheet.createRow(i + 1); + T data = dataList.get(i); + + for (int j = 0; j < fieldNames.length; j++) { + Cell cell = row.createCell(j); + Object value = getFieldValue(data, fieldNames[j]); + setCellValue(cell, value); + cell.setCellStyle(dataStyle); + } + } + + // 设置响应头 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("UTF-8"); + String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20"); + response.setHeader("Content-Disposition", "attachment; filename=" + encodedFileName + ".xlsx"); + + // 输出到响应流 + try (OutputStream out = response.getOutputStream()) { + workbook.write(out); + out.flush(); + } finally { + workbook.close(); + } + } + + /** + * 创建表头样式 + */ + private static CellStyle createHeaderStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + + // 设置背景色 + style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + + // 设置边框 + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + + // 设置字体 + Font font = workbook.createFont(); + font.setBold(true); + font.setFontHeightInPoints((short) 12); + style.setFont(font); + + // 设置对齐 + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + + return style; + } + + /** + * 创建数据样式 + */ + private static CellStyle createDataStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + + // 设置边框 + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + + // 设置对齐 + style.setAlignment(HorizontalAlignment.LEFT); + style.setVerticalAlignment(VerticalAlignment.CENTER); + + // 设置自动换行 + style.setWrapText(false); + + return style; + } + + /** + * 通过反射获取字段值 + */ + private static Object getFieldValue(Object obj, String fieldName) { + try { + Field field = findField(obj.getClass(), fieldName); + if (field != null) { + field.setAccessible(true); + return field.get(obj); + } + } catch (Exception e) { + // 忽略异常 + } + return null; + } + + /** + * 查找字段(包括父类) + */ + private static Field findField(Class clazz, String fieldName) { + try { + return clazz.getDeclaredField(fieldName); + } catch (NoSuchFieldException e) { + if (clazz.getSuperclass() != null) { + return findField(clazz.getSuperclass(), fieldName); + } + } + return null; + } + + /** + * 设置单元格值 + */ + private static void setCellValue(Cell cell, Object value) { + if (value == null) { + cell.setCellValue(""); + return; + } + + if (value instanceof String) { + cell.setCellValue((String) value); + } else if (value instanceof Number) { + cell.setCellValue(((Number) value).doubleValue()); + } else if (value instanceof Boolean) { + cell.setCellValue((Boolean) value); + } else if (value instanceof LocalDateTime) { + cell.setCellValue(((LocalDateTime) value).format(DATE_FORMATTER)); + } else { + cell.setCellValue(value.toString()); + } + } +} + diff --git a/backend/src/main/java/com/designhub/util/JwtUtil.java b/backend/src/main/java/com/designhub/util/JwtUtil.java new file mode 100644 index 0000000..419e253 --- /dev/null +++ b/backend/src/main/java/com/designhub/util/JwtUtil.java @@ -0,0 +1,184 @@ +package com.designhub.util; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * JWT Token工具类 + */ +@Component +public class JwtUtil { + + // Token过期时间:7天 + private static final long EXPIRATION_TIME = 7 * 24 * 60 * 60 * 1000; + + // Refresh Token过期时间:30天 + private static final long REFRESH_EXPIRATION_TIME = 30 * 24 * 60 * 60 * 1000; + + @Value("${jwt.secret:designhub-secret-key-for-jwt-token-generation-please-change-in-production}") + private String secret; + + /** + * 生成密钥 + */ + private SecretKey getSecretKey() { + return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + } + + /** + * 生成Access Token + */ + public String generateToken(Long userId, String username) { + Map claims = new HashMap<>(); + claims.put("userId", userId); + claims.put("username", username); + claims.put("type", "access"); + + return Jwts.builder() + .setClaims(claims) + .setSubject(username) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) + .signWith(getSecretKey(), SignatureAlgorithm.HS256) + .compact(); + } + + /** + * 生成Refresh Token + */ + public String generateRefreshToken(Long userId, String username) { + Map claims = new HashMap<>(); + claims.put("userId", userId); + claims.put("username", username); + claims.put("type", "refresh"); + + return Jwts.builder() + .setClaims(claims) + .setSubject(username) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + REFRESH_EXPIRATION_TIME)) + .signWith(getSecretKey(), SignatureAlgorithm.HS256) + .compact(); + } + + /** + * 从Token中获取用户ID + */ + public Long getUserIdFromToken(String token) { + Claims claims = getClaimsFromToken(token); + if (claims != null) { + Object userId = claims.get("userId"); + if (userId instanceof Integer) { + return ((Integer) userId).longValue(); + } else if (userId instanceof Long) { + return (Long) userId; + } + } + return null; + } + + /** + * 从Token中获取用户名 + */ + public String getUsernameFromToken(String token) { + Claims claims = getClaimsFromToken(token); + return claims != null ? (String) claims.get("username") : null; + } + + /** + * 从Token中获取类型 + */ + public String getTypeFromToken(String token) { + Claims claims = getClaimsFromToken(token); + return claims != null ? (String) claims.get("type") : null; + } + + /** + * 从Token中获取Claims + */ + private Claims getClaimsFromToken(String token) { + try { + return Jwts.parser() + .setSigningKey(getSecretKey()) + .build() + .parseClaimsJws(token) + .getBody(); + } catch (Exception e) { + return null; + } + } + + /** + * 验证Token是否有效 + */ + public boolean validateToken(String token) { + try { + Jwts.parser() + .setSigningKey(getSecretKey()) + .build() + .parseClaimsJws(token); + return true; + } catch (JwtException | IllegalArgumentException e) { + return false; + } + } + + /** + * 验证Token是否过期 + */ + public boolean isTokenExpired(String token) { + try { + Claims claims = getClaimsFromToken(token); + if (claims != null) { + Date expiration = claims.getExpiration(); + return expiration.before(new Date()); + } + } catch (Exception e) { + return true; + } + return true; + } + + /** + * 刷新Token(使用Refresh Token生成新的Access Token) + */ + public String refreshAccessToken(String refreshToken) { + if (validateToken(refreshToken)) { + String type = getTypeFromToken(refreshToken); + if ("refresh".equals(type)) { + Long userId = getUserIdFromToken(refreshToken); + String username = getUsernameFromToken(refreshToken); + if (userId != null && username != null) { + return generateToken(userId, username); + } + } + } + return null; + } + + /** + * 从Authorization请求头中解析Token + */ + public String parseTokenFromHeader(String authHeader) { + if (authHeader != null && authHeader.startsWith("Bearer ")) { + return authHeader.substring(7); + } + return null; + } + + /** + * 从Authorization请求头中提取Token(别名方法) + */ + public String extractTokenFromHeader(String authHeader) { + return parseTokenFromHeader(authHeader); + } +} + diff --git a/backend/src/main/java/com/designhub/vo/DashboardStatsVO.java b/backend/src/main/java/com/designhub/vo/DashboardStatsVO.java new file mode 100644 index 0000000..2be8d9f --- /dev/null +++ b/backend/src/main/java/com/designhub/vo/DashboardStatsVO.java @@ -0,0 +1,67 @@ +package com.designhub.vo; + +import lombok.Data; +import lombok.Builder; + +/** + * 工作台统计数据VO + */ +@Data +@Builder +public class DashboardStatsVO { + + /** + * 可接任务数 + */ + private Integer availableCount; + + /** + * 发布的任务数(当前用户当月发布的任务总数) + */ + private Integer publishedCount; + + /** + * 进行中任务数 + */ + private Integer inProgressCount; + + /** + * 已完成任务数 + */ + private Integer completedCount; + + /** + * 待审核任务数 + */ + private Integer reviewCount; + + /** + * 当日新增任务数 + */ + private Integer todayNewCount; + + /** + * 总积分 + */ + private Integer totalScore; + + /** + * 今日开始任务数 + */ + private Integer todayStartedCount; + + /** + * 今日完成任务数 + */ + private Integer todayCompletedCount; + + /** + * 本周完成任务数 + */ + private Integer weekCompletedCount; + + /** + * 上周完成任务数 + */ + private Integer lastWeekCompletedCount; +} diff --git a/backend/src/main/java/com/designhub/vo/DemandStatisticsVO.java b/backend/src/main/java/com/designhub/vo/DemandStatisticsVO.java new file mode 100644 index 0000000..1330c24 --- /dev/null +++ b/backend/src/main/java/com/designhub/vo/DemandStatisticsVO.java @@ -0,0 +1,141 @@ +package com.designhub.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 需求统计数据VO + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DemandStatisticsVO { + + /** + * 用户维度统计 + */ + private List userStats; + + /** + * 部门维度统计 + */ + private List deptStats; + + /** + * 趋势数据 + */ + private List trendData; + + /** + * 类型分布 + */ + private List typeDistribution; + + /** + * 总览数据 + */ + private OverviewData overview; + + /** + * 用户需求统计 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class UserDemandStat { + private Long userId; + private String userName; + private String deptName; + private Integer totalDemands; // 总需求数 + private Integer completedDemands; // 已完成数 + private Integer pendingDemands; // 进行中数 + private BigDecimal totalScore; // 总消耗积分 + private BigDecimal avgScore; // 平均积分 + private Double completionRate; // 完成率 + private Double satisfactionRate; // 满意度(五星好评率) + } + + /** + * 部门需求统计 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class DeptDemandStat { + private Long deptId; + private String deptName; + private Integer totalDemands; // 总需求数 + private Integer completedDemands; // 已完成数 + private Integer pendingDemands; // 进行中数 + private BigDecimal totalScore; // 总消耗积分 + private BigDecimal remainingScore; // 剩余积分 + private BigDecimal dailyLimit; // 日限额 + private BigDecimal weeklyLimit; // 周限额 + private BigDecimal monthlyLimit; // 月限额 + private BigDecimal dailyUsed; // 今日已用 + private BigDecimal weeklyUsed; // 本周已用 + private BigDecimal monthlyUsed; // 本月已用 + private Double utilizationRate; // 积分使用率 + private String alertLevel; // 预警级别:safe/warning/danger + } + + /** + * 趋势数据 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class TrendData { + private String date; // 日期 + private Integer demandCount; // 需求数量 + private Integer completedCount; // 完成数量 + private BigDecimal scoreUsed; // 积分消耗 + } + + /** + * 类型分布 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class TypeDistribution { + private String typeName; // 类型名称 + private Integer count; // 数量 + private Double percentage; // 占比 + private BigDecimal totalScore; // 总积分 + } + + /** + * 总览数据 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class OverviewData { + private Integer totalDemands; // 总需求数 + private Integer todayDemands; // 今日需求数 + private Integer weekDemands; // 本周需求数 + private Integer monthDemands; // 本月需求数 + private Integer completedDemands; // 已完成数 + private Integer pendingDemands; // 进行中数 + private Integer canceledDemands; // 已取消数 + private BigDecimal totalScoreUsed; // 总积分消耗 + private BigDecimal todayScoreUsed; // 今日积分消耗 + private BigDecimal weekScoreUsed; // 本周积分消耗 + private BigDecimal monthScoreUsed; // 本月积分消耗 + private Double avgCompletionTime; // 平均完成时长(小时) + private Double avgSatisfaction; // 平均满意度 + } +} + diff --git a/backend/src/main/java/com/designhub/vo/DepartmentOverviewVO.java b/backend/src/main/java/com/designhub/vo/DepartmentOverviewVO.java new file mode 100644 index 0000000..377daba --- /dev/null +++ b/backend/src/main/java/com/designhub/vo/DepartmentOverviewVO.java @@ -0,0 +1,36 @@ +package com.designhub.vo; + +import lombok.Data; + +import java.util.List; + +/** + * 部门概览 VO + */ +@Data +public class DepartmentOverviewVO { + private Long id; + private String deptName; + private String deptCode; + private String deptType; + private Long parentId; + private Long leaderId; + private String leaderName; + private String description; + private Integer sortOrder; + private Integer status; + private Integer scoreBalance; + private Integer totalRecharge; + private Integer totalConsume; + private Integer totalEarn; + private Integer limitEnabled; + private String limitPeriod; + private Integer periodLimit; + private Integer periodUsed; + private Integer alertThreshold; + private String overLimitAction; + private Boolean limitAlert; + private List allowedDemandTypes; + private Integer memberCount; +} + diff --git a/backend/src/main/java/com/designhub/vo/DesignerStatsVO.java b/backend/src/main/java/com/designhub/vo/DesignerStatsVO.java new file mode 100644 index 0000000..08847ee --- /dev/null +++ b/backend/src/main/java/com/designhub/vo/DesignerStatsVO.java @@ -0,0 +1,138 @@ +package com.designhub.vo; + +/** + * 设计师统计信息VO + * 注意:已移除@Data注解,改用手动getter/setter以解决Lombok编译问题 + */ +public class DesignerStatsVO { + + /** + * 设计师ID + */ + private Long userId; + + /** + * 设计师姓名 + */ + private String userName; + + /** + * 设计师头像 + */ + private String avatar; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 当前进行中任务数 + */ + private Integer inProgressCount; + + /** + * 任务队列数(待处理) + */ + private Integer queueCount; + + /** + * 累计获得积分 + */ + private Integer totalScore; + + /** + * 延误任务数 + */ + private Integer delayedCount; + + /** + * 总接收任务数 + */ + private Integer totalTaskCount; + + /** + * 已完成任务数 + */ + private Integer completedCount; + + /** + * 平均评分 + */ + private Double avgRating; + + /** + * 好评数(5星) + */ + private Integer goodReviewCount; + + /** + * 完成率(百分比) + */ + private Double completionRate; + + /** + * 准时率(百分比) + */ + private Double ontimeRate; + + /** + * 在线状态:0-离线,1-在线 + */ + private Integer onlineStatus; + + /** + * 最后活跃时间 + */ + private String lastActiveTime; + + // Getters and Setters (手动添加以解决Lombok编译问题) + public Long getUserId() { return userId; } + public void setUserId(Long userId) { this.userId = userId; } + + public String getUserName() { return userName; } + public void setUserName(String userName) { this.userName = userName; } + + public String getAvatar() { return avatar; } + public void setAvatar(String avatar) { this.avatar = avatar; } + + public String getDeptName() { return deptName; } + public void setDeptName(String deptName) { this.deptName = deptName; } + + public Integer getInProgressCount() { return inProgressCount; } + public void setInProgressCount(Integer inProgressCount) { this.inProgressCount = inProgressCount; } + + public Integer getQueueCount() { return queueCount; } + public void setQueueCount(Integer queueCount) { this.queueCount = queueCount; } + + public Integer getTotalScore() { return totalScore; } + public void setTotalScore(Integer totalScore) { this.totalScore = totalScore; } + + public Integer getDelayedCount() { return delayedCount; } + public void setDelayedCount(Integer delayedCount) { this.delayedCount = delayedCount; } + + public Integer getTotalTaskCount() { return totalTaskCount; } + public void setTotalTaskCount(Integer totalTaskCount) { this.totalTaskCount = totalTaskCount; } + + public Integer getCompletedCount() { return completedCount; } + public void setCompletedCount(Integer completedCount) { this.completedCount = completedCount; } + + public Double getAvgRating() { return avgRating; } + public void setAvgRating(Double avgRating) { this.avgRating = avgRating; } + + public Integer getGoodReviewCount() { return goodReviewCount; } + public void setGoodReviewCount(Integer goodReviewCount) { this.goodReviewCount = goodReviewCount; } + + public Double getCompletionRate() { return completionRate; } + public void setCompletionRate(Double completionRate) { this.completionRate = completionRate; } + + public Double getOntimeRate() { return ontimeRate; } + public void setOntimeRate(Double ontimeRate) { this.ontimeRate = ontimeRate; } + + public Integer getOnlineStatus() { return onlineStatus; } + public void setOnlineStatus(Integer onlineStatus) { this.onlineStatus = onlineStatus; } + + public String getLastActiveTime() { return lastActiveTime; } + public void setLastActiveTime(String lastActiveTime) { this.lastActiveTime = lastActiveTime; } +} + diff --git a/backend/src/main/java/com/designhub/vo/LoginVO.java b/backend/src/main/java/com/designhub/vo/LoginVO.java new file mode 100644 index 0000000..364a20e --- /dev/null +++ b/backend/src/main/java/com/designhub/vo/LoginVO.java @@ -0,0 +1,64 @@ +package com.designhub.vo; + +import lombok.Data; +import lombok.Builder; + +/** + * 登录响应VO + */ +@Data +@Builder +public class LoginVO { + + /** + * 访问令牌 + */ + private String token; + + /** + * 刷新令牌 + */ + private String refreshToken; + + /** + * 令牌类型 + */ + @Builder.Default + private String tokenType = "Bearer"; + + /** + * 过期时间(毫秒) + */ + private Long expiresIn; + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户名 + */ + private String username; + + /** + * 真实姓名 + */ + private String realName; + + /** + * 头像 + */ + private String avatar; + + /** + * 角色ID列表 + */ + private String roleIds; + + /** + * 用户信息(可选,兼容旧版) + */ + private UserInfoVO userInfo; +} + diff --git a/backend/src/main/java/com/designhub/vo/SystemHealthVO.java b/backend/src/main/java/com/designhub/vo/SystemHealthVO.java new file mode 100644 index 0000000..4c5a736 --- /dev/null +++ b/backend/src/main/java/com/designhub/vo/SystemHealthVO.java @@ -0,0 +1,73 @@ +package com.designhub.vo; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 系统健康检查结果 + */ +@Data +public class SystemHealthVO { + + /** + * 当前运行环境 + */ + private String profile; + + /** + * 后端服务可用状态 + */ + private boolean backendUp = true; + + /** + * 数据库连接状态 + */ + private boolean databaseUp; + + /** + * 用户总数 + */ + private Long userCount; + + /** + * 任务总数 + */ + private Long taskCount; + + /** + * 角色总数 + */ + private Long roleCount; + + /** + * 部门总数 + */ + private Long departmentCount; + + /** + * 检查项列表 + */ + private List checks = new ArrayList<>(); + + /** + * 检查时间 + */ + private LocalDateTime checkedAt = LocalDateTime.now(); + + @Data + public static class CheckItem { + private String name; + private boolean success; + private String message; + + public CheckItem(String name, boolean success, String message) { + this.name = name; + this.success = success; + this.message = message; + } + } +} + diff --git a/backend/src/main/java/com/designhub/vo/UserInfoVO.java b/backend/src/main/java/com/designhub/vo/UserInfoVO.java new file mode 100644 index 0000000..a6767b3 --- /dev/null +++ b/backend/src/main/java/com/designhub/vo/UserInfoVO.java @@ -0,0 +1,79 @@ +package com.designhub.vo; + +import lombok.Data; +import lombok.Builder; +import java.util.List; + +/** + * 用户信息VO + */ +@Data +@Builder +public class UserInfoVO { + + /** + * 用户ID + */ + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 真实姓名 + */ + private String realName; + + /** + * 邮箱 + */ + private String email; + + /** + * 手机号 + */ + private String phone; + + /** + * 头像URL + */ + private String avatar; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 角色ID列表(逗号分隔) + */ + private String roleIds; + + /** + * 角色列表 + */ + private List roles; + + /** + * 权限列表 + */ + private List permissions; + + /** + * 用户状态:0-禁用 1-启用 + */ + private Integer status; + + /** + * 是否为部门负责人:0-否 1-是 + */ + private Integer isDepartmentLeader; +} + diff --git a/backend/src/main/resources/application-dev.yml b/backend/src/main/resources/application-dev.yml new file mode 100644 index 0000000..3aaec7e --- /dev/null +++ b/backend/src/main/resources/application-dev.yml @@ -0,0 +1,94 @@ +# ================================ +# 开发环境配置 +# ================================ + +spring: + # 数据源配置 + datasource: + url: jdbc:mysql://localhost:3306/design_hub?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false + username: root + password: 123456 # 请修改为您的本地 MySQL 密码 + driver-class-name: com.mysql.cj.jdbc.Driver + + # 连接池配置 + hikari: + minimum-idle: 5 + maximum-pool-size: 20 + connection-timeout: 30000 + idle-timeout: 600000 + max-lifetime: 1800000 + + # Redis 配置 + redis: + host: localhost + port: 6379 + password: # 本地开发如果没有密码,留空 + database: 0 + timeout: 3000 + lettuce: + pool: + max-active: 8 + max-wait: -1 + max-idle: 8 + min-idle: 0 + + # 文件上传配置 + servlet: + multipart: + enabled: true + max-file-size: 50MB + max-request-size: 100MB + +# JWT 配置 +jwt: + secret: DesignHub_Dev_Secret_Key_2025 # 开发环境密钥 + access-token-expiration: 7200000 # 2小时 + refresh-token-expiration: 604800000 # 7天 + +# 服务器配置 +server: + port: 8080 + servlet: + context-path: / + encoding: + charset: UTF-8 + enabled: true + force: true + +# 日志配置 +logging: + level: + root: INFO + com.designhub: DEBUG # 开发环境显示详细日志 + org.springframework.web: DEBUG + org.springframework.security: DEBUG + pattern: + console: '%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n' + +# 文件存储配置 +file: + upload: + # 本地开发路径(项目目录下) + path: ./uploads + max-size: 52428800 # 50MB + allowed-extensions: .xls,.xlsx,.doc,.docx,.pdf,.zip + +# MyBatis Plus 配置 +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印 SQL + map-underscore-to-camel-case: true # 开启下划线转驼峰映射 + global-config: + db-config: + # 禁用逻辑删除功能 + logic-delete-field: "" + logic-delete-value: 1 + logic-not-delete-value: 0 + +# Swagger 配置(开发环境启用) +springdoc: + swagger-ui: + enabled: true + path: /swagger-ui.html + api-docs: + enabled: true diff --git a/backend/src/main/resources/application-prod.yml b/backend/src/main/resources/application-prod.yml new file mode 100644 index 0000000..b27f0e9 --- /dev/null +++ b/backend/src/main/resources/application-prod.yml @@ -0,0 +1,101 @@ +# ================================ +# 生产环境配置 +# ================================ + +spring: + # 数据源配置 + datasource: + url: jdbc:mysql://localhost:3306/designhub?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true + username: designhub + password: DesignHub@2025_DB # 生产环境数据库密码(建议修改) + driver-class-name: com.mysql.cj.jdbc.Driver + + # 连接池配置(生产环境优化) + hikari: + minimum-idle: 10 + maximum-pool-size: 50 + connection-timeout: 30000 + idle-timeout: 600000 + max-lifetime: 1800000 + + # Redis 配置 + redis: + host: localhost + port: 6379 + password: DesignHub@2025_Redis # Redis 密码 + database: 0 + timeout: 3000 + lettuce: + pool: + max-active: 20 + max-wait: -1 + max-idle: 10 + min-idle: 5 + + # 文件上传配置 + servlet: + multipart: + enabled: true + max-file-size: 100MB + max-request-size: 100MB + +# JWT 配置 +jwt: + # ⚠️ 重要:生产环境必须修改为强密钥 + secret: DesignHub_Prod_Secret_Key_2025_Very_Strong_Change_This_In_Production + access-token-expiration: 7200000 # 2小时 + refresh-token-expiration: 604800000 # 7天 + +# 服务器配置 +server: + port: 5123 + servlet: + context-path: / + encoding: + charset: UTF-8 + enabled: true + force: true + # 生产环境压缩 + compression: + enabled: true + mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json + +# 日志配置 +logging: + level: + root: INFO + com.designhub: INFO # 生产环境只显示 INFO 级别 + file: + name: /opt/designhub/logs/application.log + max-size: 10MB + max-history: 30 + pattern: + file: '%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n' + +# 文件存储配置 +file: + upload: + # 生产环境路径 + path: /opt/designhub/uploads + max-size: 104857600 # 100MB + allowed-extensions: .xls,.xlsx,.doc,.docx,.pdf,.zip + +# MyBatis Plus 配置 +mybatis-plus: + configuration: + log-impl: # 生产环境不打印 SQL + map-underscore-to-camel-case: true # 开启下划线转驼峰映射 + global-config: + db-config: + # 禁用逻辑删除功能 + logic-delete-field: "" + logic-delete-value: 1 + logic-not-delete-value: 0 + +# Swagger 配置(生产环境禁用) +springdoc: + swagger-ui: + enabled: false + api-docs: + enabled: false + diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml new file mode 100644 index 0000000..9d43713 --- /dev/null +++ b/backend/src/main/resources/application.yml @@ -0,0 +1,32 @@ +# ================================ +# 主配置文件 +# ================================ + +spring: + application: + name: designhub-backend + + # 激活的配置文件 + # 开发环境使用 dev,生产环境使用 prod + profiles: + active: dev + + # Jackson 配置 + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + default-property-inclusion: non_null + serialization: + write-dates-as-timestamps: false + + # 国际化配置 + messages: + basename: i18n/messages + encoding: UTF-8 + +# 应用信息 +info: + app: + name: DesignHub + description: 设计师任务管理平台 + version: 6.0 diff --git a/backend/src/main/resources/db/migration/V8__add_incentive_tables.sql b/backend/src/main/resources/db/migration/V8__add_incentive_tables.sql new file mode 100644 index 0000000..785632a --- /dev/null +++ b/backend/src/main/resources/db/migration/V8__add_incentive_tables.sql @@ -0,0 +1,235 @@ +-- 竞赛活动表 +CREATE TABLE IF NOT EXISTS `contest` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` VARCHAR(100) NOT NULL COMMENT '竞赛名称', + `description` TEXT COMMENT '竞赛描述', + `contest_type` VARCHAR(20) NOT NULL DEFAULT 'monthly' COMMENT '竞赛类型:monthly-月度,weekly-周度,special-专项', + `start_time` DATETIME NOT NULL COMMENT '开始时间', + `end_time` DATETIME NOT NULL COMMENT '结束时间', + `participant_scope` VARCHAR(20) NOT NULL DEFAULT 'all' COMMENT '参与范围:all-全体,department-部门,user-指定', + `department_ids` JSON COMMENT '指定部门ID列表', + `user_ids` JSON COMMENT '指定用户ID列表', + `rules` JSON COMMENT '竞赛规则配置', + `rewards` JSON COMMENT '奖励配置', + `participation_reward` JSON COMMENT '参与奖配置', + `status` VARCHAR(20) NOT NULL DEFAULT 'draft' COMMENT '状态:draft-草稿,scheduled-待开始,active-进行中,ended-已结束,cancelled-已取消', + `participant_count` INT NOT NULL DEFAULT 0 COMMENT '参与人数', + `creator_id` BIGINT COMMENT '创建人ID', + `deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + INDEX `idx_status` (`status`), + INDEX `idx_start_time` (`start_time`), + INDEX `idx_end_time` (`end_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='竞赛活动表'; + +-- 竞赛参与者表 +CREATE TABLE IF NOT EXISTS `contest_participant` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `contest_id` BIGINT NOT NULL COMMENT '竞赛ID', + `user_id` BIGINT NOT NULL COMMENT '用户ID', + `user_name` VARCHAR(50) COMMENT '用户姓名', + `dept_id` BIGINT COMMENT '部门ID', + `dept_name` VARCHAR(100) COMMENT '部门名称', + `task_completed` INT NOT NULL DEFAULT 0 COMMENT '完成任务数', + `five_star_count` INT NOT NULL DEFAULT 0 COMMENT '五星好评数', + `early_completion_count` INT NOT NULL DEFAULT 0 COMMENT '提前完成次数', + `praise_count` INT NOT NULL DEFAULT 0 COMMENT '被表扬次数', + `no_delay_days` INT NOT NULL DEFAULT 0 COMMENT '无延误天数', + `total_score` INT NOT NULL DEFAULT 0 COMMENT '总得分', + `current_rank` INT NOT NULL DEFAULT 0 COMMENT '当前排名', + `reward_claimed` TINYINT NOT NULL DEFAULT 0 COMMENT '是否已领取奖励', + `reward_score` INT DEFAULT 0 COMMENT '奖励积分', + `reward_claim_time` DATETIME COMMENT '奖励领取时间', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE INDEX `uk_contest_user` (`contest_id`, `user_id`), + INDEX `idx_contest_id` (`contest_id`), + INDEX `idx_user_id` (`user_id`), + INDEX `idx_total_score` (`total_score`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='竞赛参与者表'; + +-- 用户目标表(如果不存在则创建,如果存在则添加缺失字段) +CREATE TABLE IF NOT EXISTS `user_goal` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `user_id` BIGINT NOT NULL COMMENT '用户ID', + `goal_type` VARCHAR(30) NOT NULL COMMENT '目标类型:task_count-任务数,score-积分,rank-排名,rating-评分', + `goal_name` VARCHAR(100) NOT NULL COMMENT '目标名称', + `description` VARCHAR(500) COMMENT '目标描述', + `target_value` INT NOT NULL COMMENT '目标值', + `current_value` INT NOT NULL DEFAULT 0 COMMENT '当前进度值', + `period` VARCHAR(20) NOT NULL DEFAULT 'monthly' COMMENT '周期:daily,weekly,monthly,quarterly,yearly,custom', + `period_start` DATETIME COMMENT '周期开始时间', + `period_end` DATETIME COMMENT '周期结束时间', + `status` VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT '状态:active-进行中,completed-已完成,failed-未完成,cancelled-已取消', + `completed_time` DATETIME COMMENT '完成时间', + `reward_score` INT DEFAULT 0 COMMENT '奖励积分', + `reward_claimed` TINYINT NOT NULL DEFAULT 0 COMMENT '是否已领取奖励', + `reminder_type` VARCHAR(20) DEFAULT 'none' COMMENT '提醒设置:none,daily,before_deadline', + `deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + INDEX `idx_user_id` (`user_id`), + INDEX `idx_status` (`status`), + INDEX `idx_period_end` (`period_end`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户目标表'; + +-- 团队PK表 +CREATE TABLE IF NOT EXISTS `team_pk` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` VARCHAR(100) NOT NULL COMMENT 'PK名称', + `description` TEXT COMMENT 'PK描述', + `team_a_dept_id` BIGINT NOT NULL COMMENT '甲方部门ID', + `team_a_name` VARCHAR(100) COMMENT '甲方部门名称', + `team_b_dept_id` BIGINT NOT NULL COMMENT '乙方部门ID', + `team_b_name` VARCHAR(100) COMMENT '乙方部门名称', + `start_time` DATETIME NOT NULL COMMENT '开始时间', + `end_time` DATETIME NOT NULL COMMENT '结束时间', + `dimensions` JSON COMMENT 'PK维度配置', + `team_a_score` INT NOT NULL DEFAULT 0 COMMENT '甲方得分', + `team_b_score` INT NOT NULL DEFAULT 0 COMMENT '乙方得分', + `team_a_task_count` INT NOT NULL DEFAULT 0 COMMENT '甲方完成任务数', + `team_b_task_count` INT NOT NULL DEFAULT 0 COMMENT '乙方完成任务数', + `team_a_five_star_count` INT NOT NULL DEFAULT 0 COMMENT '甲方五星好评数', + `team_b_five_star_count` INT NOT NULL DEFAULT 0 COMMENT '乙方五星好评数', + `team_a_on_time_rate` INT NOT NULL DEFAULT 0 COMMENT '甲方准时完成率', + `team_b_on_time_rate` INT NOT NULL DEFAULT 0 COMMENT '乙方准时完成率', + `rewards` JSON COMMENT '奖励配置', + `status` VARCHAR(20) NOT NULL DEFAULT 'draft' COMMENT '状态:draft-草稿,scheduled-待开始,active-进行中,ended-已结束,cancelled-已取消', + `winner` VARCHAR(10) COMMENT '获胜方:A-甲方,B-乙方,draw-平局', + `mvp_user_id` BIGINT COMMENT 'MVP用户ID', + `creator_id` BIGINT COMMENT '创建人ID', + `deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + INDEX `idx_status` (`status`), + INDEX `idx_team_a` (`team_a_dept_id`), + INDEX `idx_team_b` (`team_b_dept_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='团队PK表'; + +-- 自动奖励规则表 +CREATE TABLE IF NOT EXISTS `reward_rule` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` VARCHAR(100) NOT NULL COMMENT '规则名称', + `description` VARCHAR(500) COMMENT '规则描述', + `rule_type` VARCHAR(20) NOT NULL COMMENT '规则类型:daily-每日,weekly-每周,monthly-每月,task-任务,rating-好评', + `trigger_condition` JSON NOT NULL COMMENT '触发条件', + `reward_type` VARCHAR(20) NOT NULL DEFAULT 'score' COMMENT '奖励类型:score-积分,badge-徽章,both-两者', + `reward_score` INT DEFAULT 0 COMMENT '奖励积分数', + `reward_badge` JSON COMMENT '奖励徽章', + `scope` VARCHAR(20) NOT NULL DEFAULT 'all' COMMENT '适用范围:all-全员,designer-设计师,department-指定部门', + `scope_dept_ids` JSON COMMENT '指定部门ID列表', + `enabled` TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用', + `cron_expression` VARCHAR(50) COMMENT '执行周期cron表达式', + `last_execute_time` DATETIME COMMENT '上次执行时间', + `priority` INT NOT NULL DEFAULT 0 COMMENT '优先级', + `creator_id` BIGINT COMMENT '创建人ID', + `deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + INDEX `idx_rule_type` (`rule_type`), + INDEX `idx_enabled` (`enabled`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自动奖励规则表'; + +-- 激励积分池表 +CREATE TABLE IF NOT EXISTS `score_pool` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` VARCHAR(100) NOT NULL COMMENT '积分池名称', + `pool_type` VARCHAR(20) NOT NULL COMMENT '积分池类型:monthly-月度,quarterly-季度,yearly-年度,special-专项', + `year` INT NOT NULL COMMENT '周期年份', + `month` INT COMMENT '周期月份', + `quarter` INT COMMENT '周期季度', + `total_budget` INT NOT NULL DEFAULT 0 COMMENT '总预算积分', + `used_score` INT NOT NULL DEFAULT 0 COMMENT '已使用积分', + `remaining_score` INT NOT NULL DEFAULT 0 COMMENT '剩余积分', + `task_reward_budget` INT NOT NULL DEFAULT 0 COMMENT '任务奖励预算', + `task_reward_used` INT NOT NULL DEFAULT 0 COMMENT '任务奖励已使用', + `rating_reward_budget` INT NOT NULL DEFAULT 0 COMMENT '好评奖励预算', + `rating_reward_used` INT NOT NULL DEFAULT 0 COMMENT '好评奖励已使用', + `contest_reward_budget` INT NOT NULL DEFAULT 0 COMMENT '竞赛奖励预算', + `contest_reward_used` INT NOT NULL DEFAULT 0 COMMENT '竞赛奖励已使用', + `checkin_reward_budget` INT NOT NULL DEFAULT 0 COMMENT '签到奖励预算', + `checkin_reward_used` INT NOT NULL DEFAULT 0 COMMENT '签到奖励已使用', + `other_reward_budget` INT NOT NULL DEFAULT 0 COMMENT '其他奖励预算', + `other_reward_used` INT NOT NULL DEFAULT 0 COMMENT '其他奖励已使用', + `overflow_strategy` VARCHAR(20) NOT NULL DEFAULT 'alert' COMMENT '超支策略:stop-停止,alert-提醒,auto_increase-自动增加', + `alert_threshold` INT DEFAULT 80 COMMENT '预警阈值百分比', + `status` VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT '状态:active-进行中,closed-已关闭,settled-已结算', + `creator_id` BIGINT COMMENT '创建人ID', + `deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + INDEX `idx_pool_type` (`pool_type`), + INDEX `idx_year_month` (`year`, `month`), + INDEX `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='激励积分池表'; + +-- 积分池使用记录表 +CREATE TABLE IF NOT EXISTS `score_pool_record` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `pool_id` BIGINT NOT NULL COMMENT '积分池ID', + `user_id` BIGINT NOT NULL COMMENT '用户ID', + `user_name` VARCHAR(50) COMMENT '用户姓名', + `reward_type` VARCHAR(20) NOT NULL COMMENT '奖励类型:task,rating,contest,checkin,goal,other', + `score_change` INT NOT NULL COMMENT '积分变动', + `before_remaining` INT COMMENT '变动前剩余', + `after_remaining` INT COMMENT '变动后剩余', + `biz_type` VARCHAR(50) COMMENT '关联业务类型', + `biz_id` BIGINT COMMENT '关联业务ID', + `remark` VARCHAR(500) COMMENT '备注', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + INDEX `idx_pool_id` (`pool_id`), + INDEX `idx_user_id` (`user_id`), + INDEX `idx_reward_type` (`reward_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='积分池使用记录表'; + +-- 钉钉配置表 +CREATE TABLE IF NOT EXISTS `dingtalk_config` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` VARCHAR(100) NOT NULL DEFAULT '默认配置' COMMENT '配置名称', + `agent_id` VARCHAR(100) COMMENT '应用AgentId', + `app_key` VARCHAR(100) COMMENT '应用AppKey', + `app_secret` VARCHAR(200) COMMENT '应用AppSecret', + `corp_id` VARCHAR(100) COMMENT '企业ID', + `robot_webhook` VARCHAR(500) COMMENT '机器人Webhook', + `robot_secret` VARCHAR(200) COMMENT '机器人密钥', + `enabled` TINYINT NOT NULL DEFAULT 0 COMMENT '是否启用', + `enabled_features` JSON COMMENT '启用功能配置', + `access_token` VARCHAR(500) COMMENT 'AccessToken缓存', + `token_expire_time` DATETIME COMMENT 'Token过期时间', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='钉钉配置表'; + +-- 插入当月积分池 +INSERT INTO `score_pool` (`name`, `pool_type`, `year`, `month`, `total_budget`, `remaining_score`, + `task_reward_budget`, `rating_reward_budget`, `contest_reward_budget`, `checkin_reward_budget`, `other_reward_budget`, + `overflow_strategy`, `alert_threshold`, `status`) VALUES +(CONCAT(YEAR(NOW()), '年', MONTH(NOW()), '月激励积分池'), 'monthly', YEAR(NOW()), MONTH(NOW()), 100000, 100000, + 40000, 20000, 15000, 10000, 15000, 'alert', 80, 'active'); + +-- 插入默认奖励规则 +INSERT INTO `reward_rule` (`name`, `description`, `rule_type`, `trigger_condition`, `reward_type`, `reward_score`, `scope`, `enabled`, `priority`) VALUES +('每日任务达人', '每天完成5个任务,额外奖励20积分', 'daily', '{"type":"task_count","operator":">=","value":5}', 'score', 20, 'all', 1, 100), +('每周效率之星', '每周完成20个任务,额外奖励100积分', 'weekly', '{"type":"task_count","operator":">=","value":20}', 'score', 100, 'all', 1, 90), +('每月MVP', '每月完成80个任务,额外奖励500积分', 'monthly', '{"type":"task_count","operator":">=","value":80}', 'score', 500, 'all', 1, 80), +('五星好评奖励', '获得五星好评,奖励10积分', 'rating', '{"type":"rating","operator":">=","value":5}', 'score', 10, 'all', 1, 70), +('好评达人', '累计获得10个五星好评,奖励50积分', 'task', '{"type":"five_star_count","operator":">=","value":10}', 'score', 50, 'all', 1, 60); + +-- 插入示例竞赛数据 +INSERT INTO `contest` (`name`, `description`, `contest_type`, `start_time`, `end_time`, `participant_scope`, `rules`, `rewards`, `status`, `participant_count`) VALUES +('12月设计之星挑战赛', '月度设计竞赛,争夺月度设计之星称号!', 'monthly', + DATE_FORMAT(CONCAT(YEAR(NOW()), '-', MONTH(NOW()), '-01'), '%Y-%m-%d 00:00:00'), + LAST_DAY(NOW()), + 'all', + '{"taskCompleted":{"score":2,"weight":30},"fiveStarReview":{"score":10,"weight":25},"earlyCompletion":{"score":5,"weight":20},"praise":{"score":15,"weight":15}}', + '[{"rank":1,"score":1500,"badge":"月度冠军"},{"rank":2,"score":1000,"badge":"月度亚军"},{"rank":3,"score":800,"badge":"月度季军"},{"rank":10,"score":300,"badge":"优秀设计师"}]', + 'active', 0); diff --git a/database/.gitkeep_if_needed b/database/.gitkeep_if_needed new file mode 100644 index 0000000..e69de29 diff --git a/database/README_DATABASE_SYNC.md b/database/README_DATABASE_SYNC.md new file mode 100644 index 0000000..60b3139 --- /dev/null +++ b/database/README_DATABASE_SYNC.md @@ -0,0 +1,170 @@ +# 数据库结构同步方案 + +## 问题说明 + +每次部署数据库时,经常会遇到字段缺失的问题,导致应用报错。这是因为: +1. 数据库脚本分散在多个文件中 +2. 没有统一的数据库初始化流程 +3. 缺少自动化的数据库同步机制 + +## 解决方案 + +我们提供了两种数据库同步方案: + +### 方案一:使用同步脚本(推荐) + +**适用场景**:已有数据库,需要添加缺失的字段 + +**使用方法**: + +```bash +# 1. 设置数据库连接信息(可选,默认使用配置的默认值) +export DB_HOST=localhost +export DB_USER=root +export DB_PASS=DesignHub@2025_Root +export DB_NAME=designhub + +# 2. 执行同步脚本 +cd database +./sync_database.sh +``` + +**脚本功能**: +- ✅ 自动检测每个字段是否存在 +- ✅ 只添加缺失的字段,不影响已有数据 +- ✅ 可以安全地重复执行 +- ✅ 提供详细的执行日志 + +### 方案二:使用SQL脚本 + +**适用场景**:全新安装或需要完整重建数据库 + +**使用方法**: + +```bash +# 1. 执行完整的schema脚本 +mysql -uroot -p designhub < database/schema.sql + +# 2. 执行初始化数据脚本 +mysql -uroot -p designhub < database/init-data.sql + +# 3. 执行同步脚本(添加缺失字段) +mysql -uroot -p designhub < database/sync_database_structure.sql +``` + +**注意**:`sync_database_structure.sql` 使用了存储过程,适用于MySQL 5.7+版本。 + +## 部署集成 + +### 在部署脚本中自动执行 + +修改部署脚本(如 `deploy_to_public_server.sh`),在部署后端之前执行数据库同步: + +```bash +# 在部署脚本中添加 +echo "同步数据库结构..." +sshpass -p 'PASSWORD' ssh root@SERVER_IP "cd /opt/designhub/database && bash sync_database.sh" +``` + +### 在应用启动时检查(可选) + +可以在Spring Boot应用启动时添加数据库检查逻辑,但这会增加启动时间,不推荐。 + +## 维护说明 + +### 添加新字段时 + +1. **修改实体类**:在对应的Entity类中添加字段 +2. **更新同步脚本**:在 `sync_database.sh` 中添加对应的 `sync_column` 调用 +3. **更新SQL脚本**(可选):在 `schema.sql` 中添加字段定义 +4. **测试**:在测试环境执行同步脚本,验证无误后再部署到生产环境 + +### 示例:添加新字段到task表 + +```bash +# 在 sync_database.sh 中添加 +sync_column "task" "new_field" "VARCHAR(255) NULL COMMENT '新字段说明'" "existing_field" +``` + +## 字段列表 + +当前同步脚本会检查并添加以下字段: + +### sys_user 表 +- `dept_name` - 部门名称 +- `score` - 用户积分 + +### task 表 +- `third_party_task_no` - 第三方平台任务编号 +- `task_type_ids` - 任务类型ID列表 +- `score_reward` - 实际积分奖励 +- `estimated_duration` - 预估时长 +- `demand_type_id` - 需求类型ID +- `actual_start_time` - 实际开始时间 +- `submit_time` - 提交审核时间 +- `actual_end_time` - 实际结束时间 +- `priority` - 优先级 +- `remark` - 备注信息 +- `is_pipeline` - 是否流水线任务 +- `pipeline_mode` - 流水线模式 +- `approval_status` - 审核状态 +- `current_approver_id` - 当前审核人ID +- `current_approver_role` - 当前审核人角色 +- `demand_approval_time` - 需求部门审核时间 +- `receiving_approval_time` - 接单部门审核时间 + +### task_type 表 +- `allowed_dept_ids` - 可接单部门ID列表 + +### category 表 +- `description` - 描述 + +### team_score_pool 表 +- `dept_id` - 部门ID +- `total_recharge` - 累计充值积分 +- `total_consume` - 累计消耗积分 +- `total_earn` - 累计获得积分 +- `period_limit` - 周期限额 +- `period_used` - 当前周期已消耗 +- `period_start_time` - 当前周期开始时间 +- `alert_threshold` - 预警阈值 +- `limit_enabled` - 是否启用限制 +- `limit_period` - 限制周期 + +### achievement 表 +- `achievement_code` - 成就代码 +- `achievement_name` - 成就名称 + +### user_achievement 表 +- `progress_value` - 当前进度值 +- `reward_claimed` - 奖励是否已领取 +- `claim_time` - 领取时间 + +## 故障排查 + +### 问题:脚本执行失败,提示权限错误 + +**解决**:确保MySQL用户有ALTER TABLE权限 + +```sql +GRANT ALTER ON designhub.* TO 'your_user'@'localhost'; +FLUSH PRIVILEGES; +``` + +### 问题:字段添加失败,提示字段已存在 + +**解决**:这是正常情况,脚本会跳过已存在的字段 + +### 问题:需要添加的字段不在列表中 + +**解决**: +1. 在 `sync_database.sh` 中添加对应的 `sync_column` 调用 +2. 或者在MySQL中手动执行ALTER TABLE语句 + +## 最佳实践 + +1. **定期同步**:每次部署前执行同步脚本 +2. **版本控制**:将同步脚本纳入版本控制 +3. **测试先行**:在测试环境验证后再部署到生产环境 +4. **备份数据**:在生产环境执行前先备份数据库 +5. **文档同步**:添加新字段时及时更新本文档 diff --git a/database/init-data.sql b/database/init-data.sql new file mode 100644 index 0000000..27f32b8 --- /dev/null +++ b/database/init-data.sql @@ -0,0 +1,162 @@ +-- DesignHub 初始化数据 +-- 版本: v1.0 + +USE `designhub`; + +-- 1. 初始化角色数据 +INSERT INTO `sys_role` (`role_name`, `role_code`, `description`) VALUES +('超级管理员', 'SUPER_ADMIN', '拥有系统所有权限'), +('管理员', 'ADMIN', '任务调配和系统配置权限'), +('需求部门负责人', 'DEMAND_DEPT_HEAD', '部门积分管理和需求统计查看'), +('接单部门主管', 'RECEIVING_DEPT_SUPERVISOR', '任务分配和团队管理'), +('设计师', 'DESIGNER', '任务接收和完成'), +('需求方', 'REQUESTER', '任务发起'), +('观察者', 'OBSERVER', '只读权限'); + +-- 2. 初始化部门数据 +INSERT INTO `sys_dept` (`dept_name`, `dept_type`, `score_balance`, `limit_enabled`, `period_limit`) VALUES +('产品部', 'demand', 10000, 1, 5000), +('市场部', 'demand', 8000, 1, 4000), +('运营部', 'demand', 6000, 1, 3000), +('设计部', 'receiving', 0, 0, 0), +('创意部', 'receiving', 0, 0, 0); + +-- 3. 初始化任务类型 +INSERT INTO `task_type` (`type_name`, `unit_score`, `estimated_hours`, `description`) VALUES +('主图设计', 100, 2.0, '产品主图设计'), +('详情页设计', 200, 4.0, '详情页设计'), +('Banner设计', 150, 3.0, 'Banner海报设计'), +('视频剪辑', 300, 6.0, '短视频剪辑制作'), +('3D建模', 500, 10.0, '3D产品建模'), +('图片优化', 50, 1.0, '图片优化调整'); + +-- 4. 初始化品牌数据 +INSERT INTO `brand` (`brand_name`, `brand_code`, `description`) VALUES +('品牌A', 'BRAND_A', '品牌A描述'), +('品牌B', 'BRAND_B', '品牌B描述'), +('品牌C', 'BRAND_C', '品牌C描述'); + +-- 5. 初始化类目数据 +INSERT INTO `category` (`parent_id`, `category_name`, `level`, `is_leaf`) VALUES +(0, '服装', 1, 0), +(1, '男装', 2, 0), +(2, 'T恤', 3, 1), +(2, '衬衫', 3, 1), +(0, '数码', 1, 0), +(5, '手机', 2, 1), +(5, '电脑', 2, 1); + +-- 6. 初始化平台数据 +INSERT INTO `platform` (`platform_name`, `platform_code`, `description`) VALUES +('淘宝', 'TAOBAO', '淘宝平台'), +('天猫', 'TMALL', '天猫平台'), +('京东', 'JD', '京东平台'), +('拼多多', 'PDD', '拼多多平台'), +('抖音', 'DOUYIN', '抖音平台'); + +-- 7. 初始化延误扣分规则 +INSERT INTO `delay_penalty_rule` (`level_name`, `min_days`, `max_days`, `base_score`, `per_day_score`, `description`) VALUES +('light', 0, 1, 10, 5, '轻微延误:0-1天'), +('general', 2, 3, 30, 10, '一般延误:2-3天'), +('severe', 4, 7, 60, 15, '严重延误:4-7天'), +('major', 8, NULL, 100, 20, '重大延误:8天以上'); + +-- 8. 初始化成就勋章 +INSERT INTO `achievement` (`name`, `category`, `description`, `condition_json`, `reward_score`, `level`) VALUES +('新手上路', 'quantity', '完成第1个任务', '{"task_count": 1}', 10, 1), +('初露锋芒', 'quantity', '完成10个任务', '{"task_count": 10}', 50, 2), +('熟能生巧', 'quantity', '完成50个任务', '{"task_count": 50}', 200, 3), +('业界精英', 'quantity', '完成100个任务', '{"task_count": 100}', 500, 4), +('完美主义', 'quality', '连续10个5星好评', '{"consecutive_5_star": 10}', 100, 1), +('质量之星', 'quality', '平均评分4.8以上', '{"avg_rating": 4.8}', 150, 1), +('速度之王', 'efficiency', '连续5个任务提前完成', '{"early_completion": 5}', 100, 1), +('零延误', 'efficiency', '本月无延误记录', '{"no_delay_month": 1}', 50, 1); + +-- 9. 初始化激励配置 +INSERT INTO `incentive_config` (`config_key`, `config_value`, `description`, `enabled`) VALUES +('ranking_enabled', 'true', '实时排名功能开关', 1), +('achievement_enabled', 'true', '成就勋章功能开关', 1), +('milestone_enabled', 'true', '里程碑提醒开关', 1), +('sprint_enabled', 'true', '积分冲刺提示开关', 1), +('comparison_enabled', 'true', '同事对比功能开关', 1), +('daily_card_enabled', 'true', '每日激励卡片开关', 1), +('instant_feedback_enabled', 'true', '即时激励弹窗开关', 1), +('competition_enabled', 'true', '竞赛活动功能开关', 1), +('team_pk_enabled', 'true', '团队PK功能开关', 1), +('smart_reminder_enabled', 'true', '智能提醒功能开关', 1), +('dashboard_enabled', 'true', '可视化激励看板开关', 1); + +-- 10. 初始化自动奖励规则 +INSERT INTO `auto_reward_rule` (`rule_name`, `reward_period`, `rank_limit`, `task_threshold`, `rating_threshold`, `no_delay_required`, `reward_score`, `auto_issue`) VALUES +('周冠军奖励', 'weekly', 1, NULL, NULL, 0, 500, 1), +('周前三名', 'weekly', 3, NULL, 4.5, 0, 200, 1), +('月冠军奖励', 'monthly', 1, NULL, NULL, 0, 2000, 1), +('月前五名', 'monthly', 5, NULL, 4.5, 0, 500, 1), +('月度优秀', 'monthly', NULL, 30, 4.8, 1, 1000, 1); + +-- 11. 初始化测试用户(密码:123456,MD5加密) +INSERT INTO `sys_user` (`username`, `password`, `real_name`, `email`, `phone`, `dept_id`, `role_ids`, `status`) VALUES +('admin', 'e10adc3949ba59abbe56e057f20f883e', '管理员', 'admin@designhub.com', '13800138000', 4, '1', 1), +('designer1', 'e10adc3949ba59abbe56e057f20f883e', '设计师张三', 'designer1@designhub.com', '13800138001', 4, '5', 1), +('designer2', 'e10adc3949ba59abbe56e057f20f883e', '设计师李四', 'designer2@designhub.com', '13800138002', 4, '5', 1), +('requester1', 'e10adc3949ba59abbe56e057f20f883e', '需求方王五', 'requester1@designhub.com', '13800138003', 1, '6', 1), +('requester2', 'e10adc3949ba59abbe56e057f20f883e', '需求方赵六', 'requester2@designhub.com', '13800138004', 2, '6', 1); + +-- ================================= +-- 新增配置数据(使用新表结构) +-- ================================= + +-- 配置数据:需求类型 +INSERT INTO `config_demand_type` (name, unit_score, estimated_hours, description, sort_order, is_enabled, create_time, update_time) VALUES +('主图设计', 50, 2, '商品主图设计', 1, 1, NOW(), NOW()), +('详情页设计', 100, 4, '商品详情页设计', 2, 1, NOW(), NOW()), +('Banner设计', 80, 3, '活动Banner设计', 3, 1, NOW(), NOW()), +('视频剪辑', 150, 6, '产品视频剪辑', 4, 1, NOW(), NOW()), +('3D建模', 200, 8, '产品3D建模', 5, 1, NOW(), NOW()), +('图片优化', 30, 1, '图片优化处理', 6, 1, NOW(), NOW()); + +-- 配置数据:品牌 +INSERT INTO `config_brand` (name, logo_url, description, sort_order, is_enabled, create_time, update_time) VALUES +('品牌A', '', '品牌A', 1, 1, NOW(), NOW()), +('品牌B', '', '品牌B', 2, 1, NOW(), NOW()), +('品牌C', '', '品牌C', 3, 1, NOW(), NOW()), +('品牌D', '', '品牌D', 4, 1, NOW(), NOW()); + +-- 配置数据:平台 +INSERT INTO `config_platform` (name, icon_url, description, sort_order, is_enabled, create_time, update_time) VALUES +('淘宝', '', '淘宝平台', 1, 1, NOW(), NOW()), +('天猫', '', '天猫平台', 2, 1, NOW(), NOW()), +('京东', '', '京东平台', 3, 1, NOW(), NOW()), +('拼多多', '', '拼多多平台', 4, 1, NOW(), NOW()), +('抖音', '', '抖音电商', 5, 1, NOW(), NOW()), +('快手', '', '快手电商', 6, 1, NOW(), NOW()); + +-- 配置数据:类目 +INSERT INTO `config_category` (parent_id, name, level, is_leaf, path, description, sort_order, is_enabled, create_time, update_time) VALUES +-- 一级类目 +(0, '女装', 1, 0, '/1', '女装类目', 1, 1, NOW(), NOW()), +(0, '男装', 1, 0, '/2', '男装类目', 2, 1, NOW(), NOW()), +(0, '家居', 1, 0, '/3', '家居类目', 3, 1, NOW(), NOW()), +-- 二级类目(女装) +(1, '连衣裙', 2, 1, '/1/4', '女装-连衣裙', 1, 1, NOW(), NOW()), +(1, 'T恤', 2, 1, '/1/5', '女装-T恤', 2, 1, NOW(), NOW()), +-- 二级类目(男装) +(2, 'T恤', 2, 1, '/2/6', '男装-T恤', 1, 1, NOW(), NOW()), +(2, '衬衫', 2, 1, '/2/7', '男装-衬衫', 2, 1, NOW(), NOW()), +-- 二级类目(家居) +(3, '床上用品', 2, 1, '/3/8', '家居-床上用品', 1, 1, NOW(), NOW()), +(3, '家纺', 2, 1, '/3/9', '家居-家纺', 2, 1, NOW(), NOW()); + +-- 配置数据:团队积分池 +INSERT INTO `team_score_pool` (dept_id, dept_name, balance, total_recharge, total_consume, total_earn, limit_enabled, limit_period, period_limit, period_used, period_start_time, alert_threshold, create_time, update_time) VALUES +-- 需求部门积分池 +(1, '产品部', 10000, 10000, 0, 0, 1, 'WEEKLY', 500, 0, NOW(), 80, NOW(), NOW()), +(2, '市场部', 8000, 8000, 0, 0, 1, 'WEEKLY', 400, 0, NOW(), 80, NOW(), NOW()), +(3, '运营部', 12000, 12000, 0, 0, 1, 'MONTHLY', 2000, 0, NOW(), 80, NOW(), NOW()), +-- 接单部门积分池 +(4, '设计部', 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NOW(), NOW()), +(5, '视频组', 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NOW(), NOW()); + +-- 初始化完成 +SELECT '初始化数据插入完成!' AS message; + diff --git a/database/schema.sql b/database/schema.sql new file mode 100644 index 0000000..2db0484 --- /dev/null +++ b/database/schema.sql @@ -0,0 +1,432 @@ +-- DesignHub 设计师任务抢单管理系统 数据库Schema +-- 版本: v1.7 +-- 创建时间: 2025-11-07 + +-- 创建数据库 +CREATE DATABASE IF NOT EXISTS `designhub` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE `designhub`; + +-- ============================== +-- 一、用户与组织架构模块 +-- ============================== + +-- 1. 用户表 +CREATE TABLE `sys_user` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `username` VARCHAR(50) NOT NULL COMMENT '用户名', + `password` VARCHAR(255) NULL COMMENT '密码(如果使用Casdoor可为空)', + `real_name` VARCHAR(50) NOT NULL COMMENT '真实姓名', + `email` VARCHAR(100) NULL COMMENT '邮箱', + `phone` VARCHAR(20) NULL COMMENT '手机号', + `avatar` VARCHAR(255) NULL COMMENT '头像URL', + `ding_user_id` VARCHAR(100) NULL COMMENT '钉钉用户ID', + `dept_id` BIGINT NULL COMMENT '部门ID', + `role_ids` VARCHAR(255) NULL COMMENT '角色ID列表(逗号分隔)', + `status` TINYINT DEFAULT 1 COMMENT '状态:0-禁用 1-启用', + `deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除:0-否 1-是', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_username` (`username`), + UNIQUE KEY `uk_ding_user_id` (`ding_user_id`), + KEY `idx_dept_id` (`dept_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表'; + +-- 2. 部门表 +CREATE TABLE `sys_dept` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '部门ID', + `parent_id` BIGINT DEFAULT 0 COMMENT '父部门ID', + `dept_name` VARCHAR(100) NOT NULL COMMENT '部门名称', + `dept_code` VARCHAR(50) NULL COMMENT '部门编码', + `dept_type` VARCHAR(20) DEFAULT 'receiving' COMMENT '部门类型:demand-需求部门 receiving-接单部门', + `ding_dept_id` VARCHAR(100) NULL COMMENT '钉钉部门ID', + `leader_id` BIGINT NULL COMMENT '部门负责人ID', + `score_balance` INT DEFAULT 0 COMMENT '部门积分余额', + `limit_enabled` TINYINT DEFAULT 0 COMMENT '是否启用消耗限制:0-否 1-是', + `limit_period` VARCHAR(20) DEFAULT 'month' COMMENT '限制周期:day-每日 week-每周 month-每月', + `period_limit` INT DEFAULT 0 COMMENT '周期限额', + `alert_threshold` INT DEFAULT 80 COMMENT '预警阈值(百分比)', + `over_limit_action` VARCHAR(20) DEFAULT 'alert' COMMENT '超限操作:alert-仅提醒 block-禁止发起', + `allowed_demand_types` VARCHAR(255) NULL COMMENT '允许的需求类型(逗号分隔)', + `sort_order` INT DEFAULT 0 COMMENT '排序', + `status` TINYINT DEFAULT 1 COMMENT '状态:0-禁用 1-启用', + `deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除:0-否 1-是', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_parent_id` (`parent_id`), + KEY `idx_dept_type` (`dept_type`), + KEY `idx_leader_id` (`leader_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='部门表'; + +-- 3. 角色表 +CREATE TABLE `sys_role` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '角色ID', + `role_name` VARCHAR(50) NOT NULL COMMENT '角色名称', + `role_code` VARCHAR(50) NOT NULL COMMENT '角色编码', + `description` VARCHAR(255) NULL COMMENT '角色描述', + `permissions` TEXT NULL COMMENT '权限列表(JSON)', + `status` TINYINT DEFAULT 1 COMMENT '状态:0-禁用 1-启用', + `deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除:0-否 1-是', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_role_code` (`role_code`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色表'; + +-- 4. 部门消耗限制记录表 +CREATE TABLE `dept_limit_record` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '记录ID', + `dept_id` BIGINT NOT NULL COMMENT '部门ID', + `period_type` VARCHAR(20) NOT NULL COMMENT '周期类型:day/week/month', + `period_start` DATE NOT NULL COMMENT '周期开始日期', + `period_end` DATE NOT NULL COMMENT '周期结束日期', + `period_limit` INT NOT NULL COMMENT '周期限额', + `consumed` INT DEFAULT 0 COMMENT '已消耗积分', + `remaining` INT DEFAULT 0 COMMENT '剩余积分', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_dept_period` (`dept_id`, `period_type`, `period_start`), + KEY `idx_period` (`period_start`, `period_end`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='部门消耗限制记录表'; + +-- ============================== +-- 二、任务管理模块 +-- ============================== + +-- 5. 任务表 +CREATE TABLE `task` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '任务ID', + `task_no` VARCHAR(50) NOT NULL COMMENT '任务编号', + `title` VARCHAR(200) NOT NULL COMMENT '任务标题', + `task_type_id` BIGINT NOT NULL COMMENT '任务类型ID', + `task_sub_type` VARCHAR(50) NULL COMMENT '任务子类型:new-新品 optimize-优化 redesign-改图 other-其他', + `quantity` INT DEFAULT 1 COMMENT '数量', + `score` INT NOT NULL COMMENT '预估积分', + `estimated_hours` DECIMAL(5,2) NULL COMMENT '预估工时(小时)', + `brand_id` BIGINT NULL COMMENT '品牌ID', + `category_id` BIGINT NULL COMMENT '叶子类目ID', + `spu_info` VARCHAR(255) NULL COMMENT 'SPU信息', + `platform_id` BIGINT NULL COMMENT '平台ID', + `attachment_url` TEXT NULL COMMENT '附件URL(多个用逗号分隔)', + `delivery_path` TEXT NULL COMMENT '交付路径', + `publisher_id` BIGINT NOT NULL COMMENT '发布人ID', + `publisher_dept_id` BIGINT NOT NULL COMMENT '发布人部门ID', + `receiver_id` BIGINT NULL COMMENT '接收人ID', + `receiver_dept_id` BIGINT NULL COMMENT '接收人部门ID', + `release_type` VARCHAR(20) NOT NULL COMMENT '发布类型:assigned-指定 recruitment-招募 default-默认', + `status` VARCHAR(20) DEFAULT 'pending' COMMENT '状态:pending-待接单 in_progress-进行中 review-待审核 rejected-已驳回 completed-已完成 cancelled-已取消', + `progress` INT DEFAULT 0 COMMENT '完成进度(0-100)', + `start_time` DATETIME NULL COMMENT '开始时间', + `deadline` DATETIME NULL COMMENT '截止时间', + `complete_time` DATETIME NULL COMMENT '完成时间', + `rating` INT NULL COMMENT '评分(1-5星)', + `review_comment` TEXT NULL COMMENT '审核评论', + `reject_reason` TEXT NULL COMMENT '驳回原因', + `reject_deadline` DATETIME NULL COMMENT '驳回后的最晚交付时间', + `is_urgent` TINYINT DEFAULT 0 COMMENT '是否紧急:0-否 1-是', + `is_delayed` TINYINT DEFAULT 0 COMMENT '是否延误:0-否 1-是', + `delay_days` INT DEFAULT 0 COMMENT '延误天数', + `deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除:0-否 1-是', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_task_no` (`task_no`), + KEY `idx_publisher_id` (`publisher_id`), + KEY `idx_receiver_id` (`receiver_id`), + KEY `idx_status` (`status`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='任务表'; + +-- 6. 任务类型表 +CREATE TABLE `task_type` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '类型ID', + `type_name` VARCHAR(50) NOT NULL COMMENT '类型名称', + `unit_score` INT NOT NULL COMMENT '单价积分', + `estimated_hours` DECIMAL(5,2) NULL COMMENT '预估工时(小时)', + `description` VARCHAR(255) NULL COMMENT '描述', + `sort_order` INT DEFAULT 0 COMMENT '排序', + `status` TINYINT DEFAULT 1 COMMENT '状态:0-禁用 1-启用', + `deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除:0-否 1-是', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='任务类型表'; + +-- 7. 任务调配记录表 +CREATE TABLE `task_transfer_log` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '记录ID', + `task_id` BIGINT NOT NULL COMMENT '任务ID', + `transfer_type` VARCHAR(20) NOT NULL COMMENT '调配类型:transfer-转移 assign-指定 withdraw-撤回 priority-优先级调整 promote-推广', + `from_user_id` BIGINT NULL COMMENT '原接收人ID', + `to_user_id` BIGINT NULL COMMENT '新接收人ID', + `operator_id` BIGINT NOT NULL COMMENT '操作人ID', + `reason` VARCHAR(500) NULL COMMENT '调配原因', + `old_deadline` DATETIME NULL COMMENT '原截止时间', + `new_deadline` DATETIME NULL COMMENT '新截止时间', + `old_priority` INT NULL COMMENT '原优先级', + `new_priority` INT NULL COMMENT '新优先级', + `delay_days` INT NULL COMMENT '延误天数', + `penalty_score` INT NULL COMMENT '扣除积分', + `remark` TEXT NULL COMMENT '备注', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_task_id` (`task_id`), + KEY `idx_from_user` (`from_user_id`), + KEY `idx_to_user` (`to_user_id`), + KEY `idx_operator` (`operator_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='任务调配记录表'; + +-- ============================== +-- 三、积分管理模块 +-- ============================== + +-- 8. 积分记录表 +CREATE TABLE `score_record` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '记录ID', + `user_id` BIGINT NULL COMMENT '用户ID', + `dept_id` BIGINT NULL COMMENT '部门ID', + `task_id` BIGINT NULL COMMENT '关联任务ID', + `change_type` VARCHAR(50) NOT NULL COMMENT '变动类型:task_consume-任务消耗 task_reward-任务奖励 manual_adjust-手动调整 delay_penalty-延误扣分 incentive_reward-激励奖励 appeal_return-申诉返还', + `change_amount` INT NOT NULL COMMENT '变动积分(正数为增加,负数为减少)', + `before_amount` INT NULL COMMENT '变动前积分', + `after_amount` INT NULL COMMENT '变动后积分', + `reason` VARCHAR(500) NULL COMMENT '变动原因', + `operator_id` BIGINT NULL COMMENT '操作人ID', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_dept_id` (`dept_id`), + KEY `idx_task_id` (`task_id`), + KEY `idx_change_type` (`change_type`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='积分记录表'; + +-- ============================== +-- 四、延误扣分模块 +-- ============================== + +-- 9. 延误扣分记录表 +CREATE TABLE `delay_penalty_record` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '记录ID', + `task_id` BIGINT NOT NULL COMMENT '任务ID', + `designer_id` BIGINT NOT NULL COMMENT '设计师ID', + `delay_days` INT NOT NULL COMMENT '延误天数', + `delay_level` VARCHAR(20) NOT NULL COMMENT '延误等级:light-轻微 general-一般 severe-严重 major-重大', + `penalty_score` INT NOT NULL COMMENT '扣除积分', + `original_deadline` DATETIME NOT NULL COMMENT '原截止时间', + `actual_time` DATETIME NOT NULL COMMENT '实际完成/转移时间', + `status` VARCHAR(20) DEFAULT 'confirmed' COMMENT '状态:confirmed-已确认 appealed-已申诉 approved-申诉通过 rejected-申诉驳回', + `appeal_reason` TEXT NULL COMMENT '申诉原因', + `appeal_time` DATETIME NULL COMMENT '申诉时间', + `appeal_result` VARCHAR(255) NULL COMMENT '申诉结果', + `approver_id` BIGINT NULL COMMENT '审批人ID', + `approve_time` DATETIME NULL COMMENT '审批时间', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_task_id` (`task_id`), + KEY `idx_designer_id` (`designer_id`), + KEY `idx_delay_level` (`delay_level`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='延误扣分记录表'; + +-- 10. 延误扣分规则表 +CREATE TABLE `delay_penalty_rule` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '规则ID', + `level_name` VARCHAR(20) NOT NULL COMMENT '等级名称:light-轻微 general-一般 severe-严重 major-重大', + `min_days` INT NOT NULL COMMENT '最小延误天数', + `max_days` INT NULL COMMENT '最大延误天数(NULL表示无上限)', + `base_score` INT NOT NULL COMMENT '基础扣分', + `per_day_score` INT NOT NULL COMMENT '每天递增扣分', + `description` VARCHAR(255) NULL COMMENT '规则描述', + `enabled` TINYINT DEFAULT 1 COMMENT '是否启用:0-否 1-是', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_level_name` (`level_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='延误扣分规则表'; + +-- ============================== +-- 五、激励系统模块 +-- ============================== + +-- 11. 成就勋章表 +CREATE TABLE `achievement` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '成就ID', + `name` VARCHAR(100) NOT NULL COMMENT '成就名称', + `category` VARCHAR(50) NOT NULL COMMENT '成就类别:quantity-数量 quality-质量 efficiency-效率 special-特殊', + `icon` VARCHAR(255) NULL COMMENT '图标', + `description` VARCHAR(500) NULL COMMENT '描述', + `condition_json` TEXT NULL COMMENT '达成条件(JSON)', + `reward_score` INT DEFAULT 0 COMMENT '奖励积分', + `level` INT DEFAULT 1 COMMENT '成就等级', + `sort_order` INT DEFAULT 0 COMMENT '排序', + `enabled` TINYINT DEFAULT 1 COMMENT '是否启用:0-否 1-是', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_category` (`category`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='成就勋章表'; + +-- 12. 用户成就记录表 +CREATE TABLE `user_achievement` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '记录ID', + `user_id` BIGINT NOT NULL COMMENT '用户ID', + `achievement_id` BIGINT NOT NULL COMMENT '成就ID', + `achieved_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '获得时间', + `is_displayed` TINYINT DEFAULT 1 COMMENT '是否展示:0-否 1-是', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_user_achievement` (`user_id`, `achievement_id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_achievement_id` (`achievement_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户成就记录表'; + +-- 13. 用户目标表 +CREATE TABLE `user_goal` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '目标ID', + `user_id` BIGINT NOT NULL COMMENT '用户ID', + `goal_type` VARCHAR(50) NOT NULL COMMENT '目标类型:daily-每日 weekly-每周 monthly-每月 custom-自定义', + `target_value` INT NOT NULL COMMENT '目标值', + `current_value` INT DEFAULT 0 COMMENT '当前值', + `start_date` DATE NOT NULL COMMENT '开始日期', + `end_date` DATE NOT NULL COMMENT '结束日期', + `status` VARCHAR(20) DEFAULT 'active' COMMENT '状态:active-进行中 completed-已完成 failed-未完成', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户目标表'; + +-- 14. 用户签到记录表 +CREATE TABLE `user_check_in` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '记录ID', + `user_id` BIGINT NOT NULL COMMENT '用户ID', + `check_in_date` DATE NOT NULL COMMENT '签到日期', + `continuous_days` INT DEFAULT 1 COMMENT '连续天数', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_user_date` (`user_id`, `check_in_date`), + KEY `idx_user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户签到记录表'; + +-- 15. 激励配置表 +CREATE TABLE `incentive_config` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '配置ID', + `config_key` VARCHAR(100) NOT NULL COMMENT '配置键', + `config_value` TEXT NULL COMMENT '配置值(JSON)', + `description` VARCHAR(500) NULL COMMENT '描述', + `enabled` TINYINT DEFAULT 1 COMMENT '是否启用:0-否 1-是', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_config_key` (`config_key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='激励配置表'; + +-- 16. 自动奖励规则表 +CREATE TABLE `auto_reward_rule` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '规则ID', + `rule_name` VARCHAR(100) NOT NULL COMMENT '规则名称', + `reward_period` VARCHAR(20) NOT NULL COMMENT '奖励周期:daily-每日 weekly-每周 monthly-每月 quarterly-季度 yearly-年度', + `rank_limit` INT NULL COMMENT '排名限制(前N名)', + `task_threshold` INT NULL COMMENT '任务数量阈值', + `rating_threshold` DECIMAL(3,2) NULL COMMENT '评分阈值', + `no_delay_required` TINYINT DEFAULT 0 COMMENT '是否要求零延误:0-否 1-是', + `reward_score` INT NOT NULL COMMENT '奖励积分', + `auto_issue` TINYINT DEFAULT 1 COMMENT '是否自动发放:0-否 1-是', + `enabled` TINYINT DEFAULT 1 COMMENT '是否启用:0-否 1-是', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自动奖励规则表'; + +-- ============================== +-- 六、系统配置模块 +-- ============================== + +-- 17. 品牌表 +CREATE TABLE `brand` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '品牌ID', + `brand_name` VARCHAR(100) NOT NULL COMMENT '品牌名称', + `brand_code` VARCHAR(50) NULL COMMENT '品牌编码', + `logo` VARCHAR(255) NULL COMMENT '品牌Logo', + `description` VARCHAR(500) NULL COMMENT '描述', + `sort_order` INT DEFAULT 0 COMMENT '排序', + `status` TINYINT DEFAULT 1 COMMENT '状态:0-禁用 1-启用', + `deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除:0-否 1-是', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='品牌表'; + +-- 18. 类目表 +CREATE TABLE `category` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '类目ID', + `parent_id` BIGINT DEFAULT 0 COMMENT '父类目ID', + `category_name` VARCHAR(100) NOT NULL COMMENT '类目名称', + `category_code` VARCHAR(50) NULL COMMENT '类目编码', + `level` INT DEFAULT 1 COMMENT '层级', + `is_leaf` TINYINT DEFAULT 1 COMMENT '是否叶子节点:0-否 1-是', + `sort_order` INT DEFAULT 0 COMMENT '排序', + `status` TINYINT DEFAULT 1 COMMENT '状态:0-禁用 1-启用', + `deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除:0-否 1-是', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_parent_id` (`parent_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='类目表'; + +-- 19. 平台表 +CREATE TABLE `platform` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '平台ID', + `platform_name` VARCHAR(100) NOT NULL COMMENT '平台名称', + `platform_code` VARCHAR(50) NULL COMMENT '平台编码', + `icon` VARCHAR(255) NULL COMMENT '图标', + `description` VARCHAR(500) NULL COMMENT '描述', + `sort_order` INT DEFAULT 0 COMMENT '排序', + `status` TINYINT DEFAULT 1 COMMENT '状态:0-禁用 1-启用', + `deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除:0-否 1-是', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='平台表'; + +-- ============================== +-- 七、通知模块 +-- ============================== + +-- 20. 通知表 +CREATE TABLE `sys_notification` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '通知ID', + `receiver_id` BIGINT NOT NULL COMMENT '接收人ID', + `receiver_ding_user_id` VARCHAR(100) NULL COMMENT '接收人钉钉ID', + `type` VARCHAR(50) NOT NULL COMMENT '通知类型', + `title` VARCHAR(200) NOT NULL COMMENT '通知标题', + `content` TEXT NOT NULL COMMENT '通知内容', + `link` VARCHAR(500) NULL COMMENT '链接地址', + `mobile_link` VARCHAR(500) NULL COMMENT '移动端链接', + `related_task_id` BIGINT NULL COMMENT '关联任务ID', + `is_read` TINYINT DEFAULT 0 COMMENT '是否已读:0-否 1-是', + `read_time` DATETIME NULL COMMENT '阅读时间', + `ding_enabled` TINYINT DEFAULT 1 COMMENT '是否发送钉钉:0-否 1-是', + `ding_message_type` VARCHAR(20) DEFAULT 'text' COMMENT '钉钉消息类型:text/markdown/oa/actionCard', + `ding_priority` INT DEFAULT 0 COMMENT '钉钉优先级', + `ding_pushed` TINYINT DEFAULT 0 COMMENT '钉钉是否已推送:0-否 1-是', + `ding_push_time` DATETIME NULL COMMENT '钉钉推送时间', + `ding_push_result` TEXT NULL COMMENT '钉钉推送结果(JSON)', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_receiver_id` (`receiver_id`), + KEY `idx_is_read` (`is_read`), + KEY `idx_type` (`type`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通知表'; + +-- 创建索引以优化查询性能 +CREATE INDEX idx_task_status_time ON `task` (`status`, `create_time`); +CREATE INDEX idx_score_record_time ON `score_record` (`create_time`, `change_type`); +CREATE INDEX idx_user_dept ON `sys_user` (`dept_id`, `status`); + diff --git a/database/sync_database.sh b/database/sync_database.sh new file mode 100755 index 0000000..28109cb --- /dev/null +++ b/database/sync_database.sh @@ -0,0 +1,154 @@ +#!/bin/bash +# ======================================== +# DesignHub 数据库结构同步脚本 +# 用途: 自动检测并添加缺失的表和字段 +# 版本: v2.0 +# ======================================== + +set -e + +# 配置 +DB_HOST="${DB_HOST:-localhost}" +DB_USER="${DB_USER:-root}" +DB_PASS="${DB_PASS:-DesignHub@2025_Root}" +DB_NAME="${DB_NAME:-designhub}" + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}DesignHub 数据库结构同步工具${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" + +# 检查字段是否存在的函数 +check_column_exists() { + local table=$1 + local column=$2 + mysql -h"$DB_HOST" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -sN -e " + SELECT COUNT(*) + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA='$DB_NAME' + AND TABLE_NAME='$table' + AND COLUMN_NAME='$column' + " 2>/dev/null +} + +# 添加字段的函数 +add_column() { + local table=$1 + local column=$2 + local definition=$3 + local after=$4 + + if [ -n "$after" ]; then + local sql="ALTER TABLE \`$table\` ADD COLUMN \`$column\` $definition AFTER \`$after\`;" + else + local sql="ALTER TABLE \`$table\` ADD COLUMN \`$column\` $definition;" + fi + + echo -e "${YELLOW} 添加字段: ${table}.${column}${NC}" + mysql -h"$DB_HOST" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "$sql" 2>/dev/null +} + +# 同步字段的主函数 +sync_column() { + local table=$1 + local column=$2 + local definition=$3 + local after=$4 + + if [ $(check_column_exists "$table" "$column") -eq 0 ]; then + add_column "$table" "$column" "$definition" "$after" + else + echo -e "${GREEN} ✓ 字段已存在: ${table}.${column}${NC}" + fi +} + +echo -e "${GREEN}开始同步数据库结构...${NC}" +echo "" + +# ======================================== +# 同步 sys_user 表 +# ======================================== +echo -e "${GREEN}[1/7] 同步 sys_user 表${NC}" +sync_column "sys_user" "dept_name" "VARCHAR(100) NULL COMMENT '部门名称'" "dept_id" +sync_column "sys_user" "score" "INT DEFAULT 0 COMMENT '用户积分'" "status" +echo "" + +# ======================================== +# 同步 task 表 +# ======================================== +echo -e "${GREEN}[2/7] 同步 task 表${NC}" +sync_column "task" "third_party_task_no" "VARCHAR(100) NULL COMMENT '第三方平台任务编号'" "task_no" +sync_column "task" "task_type_ids" "VARCHAR(255) NULL COMMENT '任务类型ID列表(逗号分隔)'" "task_type_id" +sync_column "task" "score_reward" "INT DEFAULT 0 COMMENT '实际积分奖励'" "score" +sync_column "task" "estimated_duration" "INT NULL COMMENT '预估时长(小时)'" "estimated_hours" +sync_column "task" "demand_type_id" "BIGINT NULL COMMENT '需求类型ID'" "estimated_duration" +sync_column "task" "actual_start_time" "DATETIME NULL COMMENT '实际开始时间'" "start_time" +sync_column "task" "submit_time" "DATETIME NULL COMMENT '提交审核时间'" "complete_time" +sync_column "task" "actual_end_time" "DATETIME NULL COMMENT '实际结束时间'" "submit_time" +sync_column "task" "priority" "INT DEFAULT 0 COMMENT '优先级:0-低 1-普通 2-高 3-紧急'" "actual_end_time" +sync_column "task" "remark" "VARCHAR(500) NULL COMMENT '备注信息'" "priority" +sync_column "task" "is_pipeline" "TINYINT DEFAULT 0 COMMENT '是否流水线任务:0-否,1-是'" "delay_days" +sync_column "task" "pipeline_mode" "VARCHAR(20) NULL COMMENT '流水线模式:sequential(顺序)/parallel(并行)'" "is_pipeline" +sync_column "task" "approval_status" "VARCHAR(20) DEFAULT 'draft' COMMENT '审核状态:draft-草稿 pending_demand_approval-待需求审核 pending_receiving_approval-待接单审核 approved-已通过 rejected-已驳回'" "audit_status" +sync_column "task" "current_approver_id" "BIGINT NULL COMMENT '当前审核人ID'" "approval_status" +sync_column "task" "current_approver_role" "VARCHAR(50) NULL COMMENT '当前审核人角色'" "current_approver_id" +sync_column "task" "demand_approval_time" "DATETIME NULL COMMENT '需求部门审核时间'" "current_approver_role" +sync_column "task" "receiving_approval_time" "DATETIME NULL COMMENT '接单部门审核时间'" "demand_approval_time" +echo "" + +# ======================================== +# 同步 task_type 表 +# ======================================== +echo -e "${GREEN}[3/7] 同步 task_type 表${NC}" +sync_column "task_type" "allowed_dept_ids" "VARCHAR(255) NULL COMMENT '可接单部门ID列表(逗号分隔)'" "description" +echo "" + +# ======================================== +# 同步 category 表 +# ======================================== +echo -e "${GREEN}[4/7] 同步 category 表${NC}" +sync_column "category" "description" "VARCHAR(500) NULL COMMENT '描述'" "is_leaf" +echo "" + +# ======================================== +# 同步 team_score_pool 表 +# ======================================== +echo -e "${GREEN}[5/7] 同步 team_score_pool 表${NC}" +sync_column "team_score_pool" "dept_id" "BIGINT NULL COMMENT '部门ID(与team_id相同)'" "id" +sync_column "team_score_pool" "total_recharge" "INT DEFAULT 0 COMMENT '累计充值积分'" "balance" +sync_column "team_score_pool" "total_consume" "INT DEFAULT 0 COMMENT '累计消耗积分'" "total_recharge" +sync_column "team_score_pool" "total_earn" "INT DEFAULT 0 COMMENT '累计获得积分'" "total_consume" +sync_column "team_score_pool" "period_limit" "INT DEFAULT 0 COMMENT '周期限额'" "monthly_used" +sync_column "team_score_pool" "period_used" "INT DEFAULT 0 COMMENT '当前周期已消耗'" "period_limit" +sync_column "team_score_pool" "period_start_time" "DATETIME NULL COMMENT '当前周期开始时间'" "period_used" +sync_column "team_score_pool" "alert_threshold" "INT DEFAULT 80 COMMENT '预警阈值(百分比)'" "monthly_used" +sync_column "team_score_pool" "limit_enabled" "INT DEFAULT 0 COMMENT '是否启用限制:0-否 1-是'" "alert_threshold" +sync_column "team_score_pool" "limit_period" "VARCHAR(20) DEFAULT 'month' COMMENT '限制周期:day/week/month'" "limit_enabled" +echo "" + +# ======================================== +# 同步 achievement 表 +# ======================================== +echo -e "${GREEN}[6/7] 同步 achievement 表${NC}" +sync_column "achievement" "achievement_code" "VARCHAR(50) NULL COMMENT '成就代码'" "id" +sync_column "achievement" "achievement_name" "VARCHAR(100) NULL COMMENT '成就名称'" "achievement_code" +echo "" + +# ======================================== +# 同步 user_achievement 表 +# ======================================== +echo -e "${GREEN}[7/7] 同步 user_achievement 表${NC}" +sync_column "user_achievement" "progress_value" "INT NULL COMMENT '当前进度值'" "achievement_id" +sync_column "user_achievement" "reward_claimed" "INT DEFAULT 0 COMMENT '奖励是否已领取:0-否 1-是'" "progress_value" +sync_column "user_achievement" "claim_time" "DATETIME NULL COMMENT '领取时间'" "reward_claimed" +echo "" + +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}数据库结构同步完成!${NC}" +echo -e "${GREEN}========================================${NC}" diff --git a/database/sync_database_structure.sql b/database/sync_database_structure.sql new file mode 100644 index 0000000..b8f5d3a --- /dev/null +++ b/database/sync_database_structure.sql @@ -0,0 +1,203 @@ +-- ======================================== +-- DesignHub 数据库结构同步脚本 +-- 用途: 自动检测并添加缺失的表和字段 +-- 版本: v2.0 +-- 生成时间: 2026-01-02 +-- ======================================== +-- +-- 使用说明: +-- 1. 此脚本会自动检测并创建缺失的表 +-- 2. 自动检测并添加缺失的字段 +-- 3. 可以安全地重复执行,不会影响已有数据 +-- +-- 执行方式: +-- mysql -uroot -p designhub < sync_database_structure.sql +-- 或者在MySQL客户端中执行: source sync_database_structure.sql +-- + +USE `designhub`; + +-- ======================================== +-- 第一部分: 确保所有必需的表存在 +-- ======================================== + +-- 如果表不存在,则创建。如果存在,则跳过。 +-- 注意:MySQL不支持 CREATE TABLE IF NOT EXISTS 的完整字段检查, +-- 所以这里只创建表的基本结构,字段的添加在第二部分完成 + +-- ======================================== +-- 第二部分: 为每个表添加缺失的字段 +-- ======================================== + +-- 1. sys_user 表字段补全 +ALTER TABLE `sys_user` + ADD COLUMN IF NOT EXISTS `dept_name` VARCHAR(50) NULL COMMENT '部门名称' AFTER `dept_id`, + ADD COLUMN IF NOT EXISTS `score` INT DEFAULT 0 COMMENT '用户积分' AFTER `status`; + +-- 2. task 表字段补全(所有缺失的字段) +ALTER TABLE `task` + ADD COLUMN IF NOT EXISTS `third_party_task_no` VARCHAR(100) NULL COMMENT '第三方平台任务编号' AFTER `task_no`, + ADD COLUMN IF NOT EXISTS `task_type_ids` VARCHAR(255) NULL COMMENT '任务类型ID列表(逗号分隔)' AFTER `task_type_id`, + ADD COLUMN IF NOT EXISTS `score_reward` DECIMAL(10,2) NULL COMMENT '实际积分奖励' AFTER `score`, + ADD COLUMN IF NOT EXISTS `estimated_duration` INT NULL COMMENT '预估时长(小时)' AFTER `estimated_hours`, + ADD COLUMN IF NOT EXISTS `demand_type_id` BIGINT NULL COMMENT '需求类型ID' AFTER `estimated_duration`, + ADD COLUMN IF NOT EXISTS `actual_start_time` DATETIME NULL COMMENT '实际开始时间' AFTER `start_time`, + ADD COLUMN IF NOT EXISTS `submit_time` DATETIME NULL COMMENT '提交审核时间' AFTER `complete_time`, + ADD COLUMN IF NOT EXISTS `actual_end_time` DATETIME NULL COMMENT '实际结束时间' AFTER `submit_time`, + ADD COLUMN IF NOT EXISTS `priority` INT DEFAULT 1 COMMENT '优先级:0-低 1-普通 2-高 3-紧急' AFTER `actual_end_time`, + ADD COLUMN IF NOT EXISTS `remark` VARCHAR(500) NULL COMMENT '备注信息' AFTER `priority`, + ADD COLUMN IF NOT EXISTS `is_pipeline` TINYINT DEFAULT 0 COMMENT '是否流水线任务:0-否,1-是' AFTER `delay_days`, + ADD COLUMN IF NOT EXISTS `pipeline_mode` VARCHAR(20) NULL COMMENT '流水线模式:sequential(顺序)/parallel(并行)' AFTER `is_pipeline`, + ADD COLUMN IF NOT EXISTS `approval_status` VARCHAR(20) DEFAULT 'draft' COMMENT '审核状态:draft-草稿 pending_demand_approval-待需求审核 pending_receiving_approval-待接单审核 approved-已通过 rejected-已驳回' AFTER `audit_status`, + ADD COLUMN IF NOT EXISTS `current_approver_id` BIGINT NULL COMMENT '当前审核人ID' AFTER `approval_status`, + ADD COLUMN IF NOT EXISTS `current_approver_role` VARCHAR(50) NULL COMMENT '当前审核人角色' AFTER `current_approver_id`, + ADD COLUMN IF NOT EXISTS `demand_approval_time` DATETIME NULL COMMENT '需求部门审核时间' AFTER `current_approver_role`, + ADD COLUMN IF NOT EXISTS `receiving_approval_time` DATETIME NULL COMMENT '接单部门审核时间' AFTER `demand_approval_time`; + +-- 3. task_type 表字段补全 +ALTER TABLE `task_type` + ADD COLUMN IF NOT EXISTS `allowed_dept_ids` VARCHAR(255) NULL COMMENT '可接单部门ID列表(逗号分隔)' AFTER `description`; + +-- 4. category 表字段补全 +ALTER TABLE `category` + ADD COLUMN IF NOT EXISTS `description` VARCHAR(500) NULL COMMENT '描述' AFTER `is_leaf`; + +-- 5. task_sub_type 表字段重命名和补全 +-- 注意:如果字段名不一致,需要先检查并重命名 +-- 这里使用存储过程来处理字段重命名(如果存在旧字段名) +ALTER TABLE `task_sub_type` + ADD COLUMN IF NOT EXISTS `name` VARCHAR(100) NULL COMMENT '子类型名称' AFTER `id`, + ADD COLUMN IF NOT EXISTS `code` VARCHAR(50) NULL COMMENT '子类型编码' AFTER `name`, + ADD COLUMN IF NOT EXISTS `is_enabled` TINYINT DEFAULT 1 COMMENT '是否启用:0-禁用 1-启用' AFTER `code`; + +-- 6. team_score_pool 表字段补全 +ALTER TABLE `team_score_pool` + ADD COLUMN IF NOT EXISTS `dept_id` BIGINT NULL COMMENT '部门ID(与team_id相同)' AFTER `id`, + ADD COLUMN IF NOT EXISTS `total_recharge` INT DEFAULT 0 COMMENT '累计充值积分' AFTER `balance`, + ADD COLUMN IF NOT EXISTS `total_consume` INT DEFAULT 0 COMMENT '累计消耗积分' AFTER `total_recharge`, + ADD COLUMN IF NOT EXISTS `total_earn` INT DEFAULT 0 COMMENT '累计获得积分' AFTER `total_consume`, + ADD COLUMN IF NOT EXISTS `period_limit` INT DEFAULT 0 COMMENT '周期限额' AFTER `monthly_used`, + ADD COLUMN IF NOT EXISTS `period_used` INT DEFAULT 0 COMMENT '当前周期已消耗' AFTER `period_limit`, + ADD COLUMN IF NOT EXISTS `period_start_time` DATETIME NULL COMMENT '当前周期开始时间' AFTER `period_used`, + ADD COLUMN IF NOT EXISTS `alert_threshold` INT DEFAULT 80 COMMENT '预警阈值(百分比)' AFTER `monthly_used`, + ADD COLUMN IF NOT EXISTS `limit_enabled` TINYINT DEFAULT 0 COMMENT '是否启用限制:0-否 1-是' AFTER `alert_threshold`, + ADD COLUMN IF NOT EXISTS `limit_period` VARCHAR(20) DEFAULT 'month' COMMENT '限制周期:day/week/month' AFTER `limit_enabled`; + +-- 7. achievement 表字段补全 +ALTER TABLE `achievement` + ADD COLUMN IF NOT EXISTS `achievement_code` VARCHAR(50) NULL COMMENT '成就代码' AFTER `id`, + ADD COLUMN IF NOT EXISTS `achievement_name` VARCHAR(100) NULL COMMENT '成就名称' AFTER `achievement_code`; + +-- 8. user_achievement 表字段补全 +ALTER TABLE `user_achievement` + ADD COLUMN IF NOT EXISTS `progress_value` INT NULL COMMENT '当前进度值' AFTER `achievement_id`, + ADD COLUMN IF NOT EXISTS `reward_claimed` TINYINT DEFAULT 0 COMMENT '奖励是否已领取:0-否 1-是' AFTER `progress_value`, + ADD COLUMN IF NOT EXISTS `claim_time` DATETIME NULL COMMENT '领取时间' AFTER `reward_claimed`; + +-- ======================================== +-- 第三部分: 创建缺失的表(如果不存在) +-- ======================================== + +-- 这里使用存储过程来检查表是否存在,如果不存在则创建 +-- 由于MySQL的限制,我们需要使用不同的方法 + +-- 注意:由于MySQL不支持 ALTER TABLE ADD COLUMN IF NOT EXISTS 和 CREATE TABLE IF NOT EXISTS 的完整功能, +-- 上面的脚本在实际执行时可能会报错。我们需要使用存储过程或应用程序来处理这个问题。 + +-- ======================================== +-- 第四部分: 创建存储过程来安全地添加字段 +-- ======================================== + +DELIMITER $$ + +-- 存储过程:安全地添加列(如果不存在) +DROP PROCEDURE IF EXISTS AddColumnIfNotExists $$ +CREATE PROCEDURE AddColumnIfNotExists( + IN tableName VARCHAR(64), + IN columnName VARCHAR(64), + IN columnDefinition TEXT +) +BEGIN + DECLARE columnExists INT DEFAULT 0; + + SELECT COUNT(*) INTO columnExists + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = tableName + AND COLUMN_NAME = columnName; + + IF columnExists = 0 THEN + SET @sql = CONCAT('ALTER TABLE `', tableName, '` ADD COLUMN `', columnName, '` ', columnDefinition); + PREPARE stmt FROM @sql; + EXECUTE stmt; + DEALLOCATE PREPARE stmt; + END IF; +END$$ + +DELIMITER ; + +-- ======================================== +-- 第五部分: 使用存储过程添加所有缺失的字段 +-- ======================================== + +-- sys_user 表 +CALL AddColumnIfNotExists('sys_user', 'dept_name', 'VARCHAR(50) NULL COMMENT ''部门名称'' AFTER `dept_id`'); +CALL AddColumnIfNotExists('sys_user', 'score', 'INT DEFAULT 0 COMMENT ''用户积分'' AFTER `status`'); + +-- task 表 +CALL AddColumnIfNotExists('task', 'third_party_task_no', 'VARCHAR(100) NULL COMMENT ''第三方平台任务编号'' AFTER `task_no`'); +CALL AddColumnIfNotExists('task', 'task_type_ids', 'VARCHAR(255) NULL COMMENT ''任务类型ID列表(逗号分隔)'' AFTER `task_type_id`'); +CALL AddColumnIfNotExists('task', 'score_reward', 'DECIMAL(10,2) NULL COMMENT ''实际积分奖励'' AFTER `score`'); +CALL AddColumnIfNotExists('task', 'estimated_duration', 'INT NULL COMMENT ''预估时长(小时)'' AFTER `estimated_hours`'); +CALL AddColumnIfNotExists('task', 'demand_type_id', 'BIGINT NULL COMMENT ''需求类型ID'' AFTER `estimated_duration`'); +CALL AddColumnIfNotExists('task', 'actual_start_time', 'DATETIME NULL COMMENT ''实际开始时间'' AFTER `start_time`'); +CALL AddColumnIfNotExists('task', 'submit_time', 'DATETIME NULL COMMENT ''提交审核时间'' AFTER `complete_time`'); +CALL AddColumnIfNotExists('task', 'actual_end_time', 'DATETIME NULL COMMENT ''实际结束时间'' AFTER `submit_time`'); +CALL AddColumnIfNotExists('task', 'priority', 'INT DEFAULT 1 COMMENT ''优先级:0-低 1-普通 2-高 3-紧急'' AFTER `actual_end_time`'); +CALL AddColumnIfNotExists('task', 'remark', 'VARCHAR(500) NULL COMMENT ''备注信息'' AFTER `priority`'); +CALL AddColumnIfNotExists('task', 'is_pipeline', 'TINYINT DEFAULT 0 COMMENT ''是否流水线任务:0-否,1-是'' AFTER `delay_days`'); +CALL AddColumnIfNotExists('task', 'pipeline_mode', 'VARCHAR(20) NULL COMMENT ''流水线模式:sequential(顺序)/parallel(并行)'' AFTER `is_pipeline`'); +CALL AddColumnIfNotExists('task', 'approval_status', 'VARCHAR(20) DEFAULT ''draft'' COMMENT ''审核状态:draft-草稿 pending_demand_approval-待需求审核 pending_receiving_approval-待接单审核 approved-已通过 rejected-已驳回'' AFTER `audit_status`'); +CALL AddColumnIfNotExists('task', 'current_approver_id', 'BIGINT NULL COMMENT ''当前审核人ID'' AFTER `approval_status`'); +CALL AddColumnIfNotExists('task', 'current_approver_role', 'VARCHAR(50) NULL COMMENT ''当前审核人角色'' AFTER `current_approver_id`'); +CALL AddColumnIfNotExists('task', 'demand_approval_time', 'DATETIME NULL COMMENT ''需求部门审核时间'' AFTER `current_approver_role`'); +CALL AddColumnIfNotExists('task', 'receiving_approval_time', 'DATETIME NULL COMMENT ''接单部门审核时间'' AFTER `demand_approval_time`'); + +-- task_type 表 +CALL AddColumnIfNotExists('task_type', 'allowed_dept_ids', 'VARCHAR(255) NULL COMMENT ''可接单部门ID列表(逗号分隔)'' AFTER `description`'); + +-- category 表 +CALL AddColumnIfNotExists('category', 'description', 'VARCHAR(500) NULL COMMENT ''描述'' AFTER `is_leaf`'); + +-- team_score_pool 表 +CALL AddColumnIfNotExists('team_score_pool', 'dept_id', 'BIGINT NULL COMMENT ''部门ID(与team_id相同)'' AFTER `id`'); +CALL AddColumnIfNotExists('team_score_pool', 'total_recharge', 'INT DEFAULT 0 COMMENT ''累计充值积分'' AFTER `balance`'); +CALL AddColumnIfNotExists('team_score_pool', 'total_consume', 'INT DEFAULT 0 COMMENT ''累计消耗积分'' AFTER `total_recharge`'); +CALL AddColumnIfNotExists('team_score_pool', 'total_earn', 'INT DEFAULT 0 COMMENT ''累计获得积分'' AFTER `total_consume`'); +CALL AddColumnIfNotExists('team_score_pool', 'period_limit', 'INT DEFAULT 0 COMMENT ''周期限额'' AFTER `monthly_used`'); +CALL AddColumnIfNotExists('team_score_pool', 'period_used', 'INT DEFAULT 0 COMMENT ''当前周期已消耗'' AFTER `period_limit`'); +CALL AddColumnIfNotExists('team_score_pool', 'period_start_time', 'DATETIME NULL COMMENT ''当前周期开始时间'' AFTER `period_used`'); +CALL AddColumnIfNotExists('team_score_pool', 'alert_threshold', 'INT DEFAULT 80 COMMENT ''预警阈值(百分比)'' AFTER `monthly_used`'); +CALL AddColumnIfNotExists('team_score_pool', 'limit_enabled', 'TINYINT DEFAULT 0 COMMENT ''是否启用限制:0-否 1-是'' AFTER `alert_threshold`'); +CALL AddColumnIfNotExists('team_score_pool', 'limit_period', 'VARCHAR(20) DEFAULT ''month'' COMMENT ''限制周期:day/week/month'' AFTER `limit_enabled`'); + +-- achievement 表 +CALL AddColumnIfNotExists('achievement', 'achievement_code', 'VARCHAR(50) NULL COMMENT ''成就代码'' AFTER `id`'); +CALL AddColumnIfNotExists('achievement', 'achievement_name', 'VARCHAR(100) NULL COMMENT ''成就名称'' AFTER `achievement_code`'); + +-- user_achievement 表 +CALL AddColumnIfNotExists('user_achievement', 'progress_value', 'INT NULL COMMENT ''当前进度值'' AFTER `achievement_id`'); +CALL AddColumnIfNotExists('user_achievement', 'reward_claimed', 'TINYINT DEFAULT 0 COMMENT ''奖励是否已领取:0-否 1-是'' AFTER `progress_value`'); +CALL AddColumnIfNotExists('user_achievement', 'claim_time', 'DATETIME NULL COMMENT ''领取时间'' AFTER `reward_claimed`'); + +-- ======================================== +-- 第六部分: 清理存储过程 +-- ======================================== + +DROP PROCEDURE IF EXISTS AddColumnIfNotExists; + +-- ======================================== +-- 完成提示 +-- ======================================== + +SELECT '数据库结构同步完成!' AS message; diff --git a/deploy_to_public_server.sh b/deploy_to_public_server.sh new file mode 100755 index 0000000..3cb36fb --- /dev/null +++ b/deploy_to_public_server.sh @@ -0,0 +1,505 @@ +#!/bin/bash + +############################################################################### +# DesignHub 公网服务器自动化部署脚本 +# 服务器: 45.192.99.48 +# 使用方法: bash deploy_to_public_server.sh +############################################################################### + +set -e + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# 服务器配置 +SERVER_IP="45.192.99.48" +SERVER_PORT="22" +SERVER_USER="root" +SERVER_PASSWORD="1blZllMIp7" + +# 应用配置 +APP_NAME="designhub" +APP_DIR="/opt/${APP_NAME}" +DB_NAME="designhub" +DB_USER="designhub" +DB_PASSWORD="DesignHub@2025_DB" +REDIS_PASSWORD="DesignHub@2025_Redis" +MYSQL_ROOT_PASSWORD="DesignHub@2025_Root" + +echo "========================================" +echo -e "${GREEN} DesignHub 公网服务器部署脚本${NC}" +echo "========================================" +echo "" +echo "服务器信息:" +echo " IP: ${SERVER_IP}" +echo " 端口: ${SERVER_PORT}" +echo " 用户: ${SERVER_USER}" +echo "" + +# 检查 sshpass 是否安装 +check_sshpass() { + if ! command -v sshpass &> /dev/null; then + echo -e "${YELLOW}正在安装 sshpass...${NC}" + if [[ "$OSTYPE" == "darwin"* ]]; then + brew install hudochenkov/sshpass/sshpass 2>/dev/null || { + echo -e "${RED}请先安装 sshpass: brew install hudochenkov/sshpass/sshpass${NC}" + exit 1 + } + else + sudo apt-get install -y sshpass 2>/dev/null || sudo yum install -y sshpass 2>/dev/null + fi + fi + echo -e "${GREEN}✓ sshpass 已就绪${NC}" +} + +# SSH 命令封装 +ssh_cmd() { + sshpass -p "${SERVER_PASSWORD}" ssh -p ${SERVER_PORT} \ + -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null \ + -o ConnectTimeout=30 \ + ${SERVER_USER}@${SERVER_IP} "$@" +} + +# SCP 命令封装 +scp_cmd() { + sshpass -p "${SERVER_PASSWORD}" scp -P ${SERVER_PORT} \ + -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null \ + "$@" +} + +# rsync 命令封装 +rsync_cmd() { + sshpass -p "${SERVER_PASSWORD}" rsync -avz \ + -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \ + "$@" +} + +# 测试连接 +test_connection() { + echo -e "${BLUE}[0/10] 测试服务器连接...${NC}" + if ssh_cmd "echo 'Connection successful'" 2>/dev/null; then + echo -e "${GREEN}✓ 服务器连接成功${NC}" + else + echo -e "${RED}✗ 无法连接到服务器${NC}" + exit 1 + fi + echo "" +} + +# 步骤1:安装系统依赖 +install_dependencies() { + echo -e "${BLUE}[1/10] 安装系统依赖...${NC}" + + ssh_cmd << 'ENDSSH' +set -e + +# 安装基础工具 +echo "安装基础工具..." +dnf install -y -q curl wget git unzip + +# 检查并安装 Java 17 +if ! command -v java &> /dev/null || ! java -version 2>&1 | grep -q "17"; then + echo "安装 Java 17..." + dnf install -y -q java-17-openjdk java-17-openjdk-devel +fi + +# 检查并安装 Maven +if ! command -v mvn &> /dev/null; then + echo "安装 Maven..." + dnf install -y -q maven +fi + +# 检查并安装 MySQL +if ! command -v mysql &> /dev/null; then + echo "安装 MySQL..." + dnf install -y -q mysql-server mysql + systemctl enable mysqld + systemctl start mysqld + sleep 3 + # 设置root密码 + mysqladmin -u root password "${MYSQL_ROOT_PASSWORD}" 2>/dev/null || mysqladmin -u root password "${MYSQL_ROOT_PASSWORD}" +fi + +# 检查并安装 Redis +if ! command -v redis-cli &> /dev/null; then + echo "安装 Redis..." + dnf install -y -q redis + systemctl enable redis + systemctl start redis +fi + +# Nginx 已安装,检查状态 +systemctl enable nginx || true +systemctl start nginx || true + +# Node.js 和 pnpm 已安装,跳过 + +echo "✓ 系统依赖安装完成" +ENDSSH + + echo -e "${GREEN}✓ 系统依赖安装完成${NC}" + echo "" +} + +# 步骤2:创建目录结构 +create_directories() { + echo -e "${BLUE}[2/10] 创建目录结构...${NC}" + + ssh_cmd << ENDSSH +set -e +mkdir -p ${APP_DIR}/{backend,frontend,logs,uploads} +mkdir -p /etc/nginx/conf.d +echo "✓ 目录创建完成" +ENDSSH + + echo -e "${GREEN}✓ 目录结构创建完成${NC}" + echo "" +} + +# 步骤3:配置 MySQL +setup_mysql() { + echo -e "${BLUE}[3/10] 配置 MySQL 数据库...${NC}" + + ssh_cmd << ENDSSH +set -e + +# 启动 MySQL (CentOS使用mysqld) +systemctl start mysqld || true +systemctl enable mysqld || true + +# 等待MySQL启动 +sleep 5 + +# 创建数据库和用户 +mysql -uroot -p${MYSQL_ROOT_PASSWORD} << 'EOSQL' 2>/dev/null || mysql -uroot -p${MYSQL_ROOT_PASSWORD} << 'EOSQL' +CREATE DATABASE IF NOT EXISTS ${DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE USER IF NOT EXISTS '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASSWORD}'; +GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'localhost'; +FLUSH PRIVILEGES; +EOSQL + +echo "✓ MySQL 配置完成" +ENDSSH + + echo -e "${GREEN}✓ MySQL 配置完成${NC}" + echo "" +} + +# 步骤4:配置 Redis +setup_redis() { + echo -e "${BLUE}[4/10] 配置 Redis...${NC}" + + ssh_cmd << ENDSSH +set -e + +# 启动 Redis (CentOS使用redis服务) +systemctl start redis || true +systemctl enable redis || true + +# 设置密码(如果还没设置) +redis-cli CONFIG SET requirepass "${REDIS_PASSWORD}" 2>/dev/null || true + +echo "✓ Redis 配置完成" +ENDSSH + + echo -e "${GREEN}✓ Redis 配置完成${NC}" + echo "" +} + +# 步骤5:上传后端代码 +upload_backend() { + echo -e "${BLUE}[5/10] 上传后端代码...${NC}" + + # 上传整个backend目录 + rsync_cmd --exclude='target' --exclude='*.class' \ + backend/ ${SERVER_USER}@${SERVER_IP}:${APP_DIR}/backend/ + + echo -e "${GREEN}✓ 后端代码上传完成${NC}" + echo "" +} + +# 步骤6:上传前端代码 +upload_frontend() { + echo -e "${BLUE}[6/10] 上传前端代码...${NC}" + + # 上传整个frontend目录(不包括node_modules) + rsync_cmd --exclude='node_modules' --exclude='dist' \ + frontend/ ${SERVER_USER}@${SERVER_IP}:${APP_DIR}/frontend/ + + echo -e "${GREEN}✓ 前端代码上传完成${NC}" + echo "" +} + +# 步骤7:配置后端 +setup_backend() { + echo -e "${BLUE}[7/10] 配置并构建后端...${NC}" + + ssh_cmd << ENDSSH +set -e +cd ${APP_DIR}/backend + +# 创建生产环境配置文件 +cat > src/main/resources/application-prod.yml << EOF +spring: + datasource: + url: jdbc:mysql://localhost:3306/${DB_NAME}?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true + username: ${DB_USER} + password: ${DB_PASSWORD} + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + minimum-idle: 10 + maximum-pool-size: 50 + connection-timeout: 30000 + idle-timeout: 600000 + max-lifetime: 1800000 + + redis: + host: localhost + port: 6379 + password: ${REDIS_PASSWORD} + database: 0 + timeout: 3000 + +jwt: + secret: DesignHub_Prod_Secret_Key_2025_Very_Strong + access-token-expiration: 7200000 + refresh-token-expiration: 604800000 + +server: + port: 5123 + +logging: + level: + root: INFO + com.designhub: INFO + file: + name: ${APP_DIR}/logs/application.log + max-size: 10MB + max-history: 30 + +file: + upload: + path: ${APP_DIR}/uploads + max-size: 52428800 +EOF + +# 构建项目 +echo "开始构建后端项目..." +mvn clean package -DskipTests 2>&1 | tail -20 + +# 复制 JAR 文件 +cp target/designhub-backend-1.0.0.jar ${APP_DIR}/designhub.jar + +echo "✓ 后端构建完成" +ENDSSH + + echo -e "${GREEN}✓ 后端配置完成${NC}" + echo "" +} + +# 步骤8:配置前端 +setup_frontend() { + echo -e "${BLUE}[8/10] 配置并构建前端...${NC}" + + ssh_cmd << ENDSSH +set -e +cd ${APP_DIR}/frontend + +# 创建生产环境配置 +cat > .env.production << EOF +VITE_APP_BASE_API=http://${SERVER_IP}/api +VITE_APP_TITLE=DesignHub +VITE_APP_VERSION=6.0 +EOF + +# 安装依赖 +echo "安装前端依赖..." +pnpm install + +# 构建项目 +echo "构建前端项目..." +pnpm build + +echo "✓ 前端构建完成" +ENDSSH + + echo -e "${GREEN}✓ 前端配置完成${NC}" + echo "" +} + +# 步骤9:配置系统服务 +setup_services() { + echo -e "${BLUE}[9/10] 配置系统服务...${NC}" + + ssh_cmd << ENDSSH +set -e + +# 配置防火墙开放5123端口 +if command -v firewall-cmd &> /dev/null; then + firewall-cmd --permanent --add-port=5123/tcp + firewall-cmd --reload + echo "✓ 防火墙端口5123已开放" +elif command -v ufw &> /dev/null; then + ufw allow 5123/tcp + echo "✓ 防火墙端口5123已开放" +else + echo "未找到防火墙工具,请手动开放端口5123" +fi + +# 创建后端服务 +cat > /etc/systemd/system/designhub-backend.service << EOF +[Unit] +Description=DesignHub Backend Service +After=network.target mysqld.service redis.service + +[Service] +Type=simple +User=root +WorkingDirectory=${APP_DIR} +ExecStart=/usr/bin/java -jar -Xms512m -Xmx1024m ${APP_DIR}/designhub.jar --spring.profiles.active=prod +Restart=on-failure +RestartSec=10 + +StandardOutput=append:${APP_DIR}/logs/backend.log +StandardError=append:${APP_DIR}/logs/backend-error.log + +[Install] +WantedBy=multi-user.target +EOF + +# 创建 Nginx 配置 +cat > /etc/nginx/conf.d/designhub.conf << EOF +server { + listen 80; + server_name ${SERVER_IP}; + + root ${APP_DIR}/frontend/dist; + index index.html; + + access_log ${APP_DIR}/logs/nginx-access.log; + error_log ${APP_DIR}/logs/nginx-error.log; + + # 前端路由 + location / { + try_files \$uri \$uri/ /index.html; + } + + # API 代理 + location /api/ { + proxy_pass http://localhost:5123/api/; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + } + + # 静态资源缓存 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 30d; + add_header Cache-Control "public, immutable"; + } + + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml; + gzip_min_length 1000; +} +EOF + +# 重新加载 systemd +systemctl daemon-reload + +# 测试 Nginx 配置 +nginx -t + +# 重启 Nginx +systemctl restart nginx +systemctl enable nginx + +echo "✓ 系统服务配置完成" +ENDSSH + + echo -e "${GREEN}✓ 系统服务配置完成${NC}" + echo "" +} + +# 步骤10:启动服务 +start_services() { + echo -e "${BLUE}[10/10] 启动服务...${NC}" + + ssh_cmd << ENDSSH +set -e + +# 启动后端服务 +systemctl start designhub-backend +systemctl enable designhub-backend + +# 等待服务启动 +sleep 5 + +# 检查服务状态 +echo "" +echo "=== 服务状态 ===" +systemctl status designhub-backend --no-pager -l | head -15 + +echo "" +echo "✓ 服务启动完成" +ENDSSH + + echo -e "${GREEN}✓ 服务启动完成${NC}" + echo "" +} + +# 显示部署信息 +show_deployment_info() { + echo "" + echo "========================================" + echo -e "${GREEN} 部署完成!${NC}" + echo "========================================" + echo "" + echo "访问地址:" + echo " http://${SERVER_IP}" + echo "" + echo "API 地址:" + echo " http://${SERVER_IP}/api" + echo "" + echo "服务管理:" + echo " 查看后端状态: ssh ${SERVER_USER}@${SERVER_IP} 'systemctl status designhub-backend'" + echo " 查看后端日志: ssh ${SERVER_USER}@${SERVER_IP} 'tail -f ${APP_DIR}/logs/application.log'" + echo " 重启后端: ssh ${SERVER_USER}@${SERVER_IP} 'systemctl restart designhub-backend'" + echo "" + echo "数据库信息:" + echo " 数据库名: ${DB_NAME}" + echo " 用户名: ${DB_USER}" + echo " 密码: ${DB_PASSWORD}" + echo "" +} + +# 主执行流程 +main() { + check_sshpass + test_connection + install_dependencies + create_directories + setup_mysql + setup_redis + upload_backend + upload_frontend + setup_backend + setup_frontend + setup_services + start_services + show_deployment_info +} + +# 执行主函数 +main diff --git a/frontend/.env.development b/frontend/.env.development new file mode 100644 index 0000000..1e20cad --- /dev/null +++ b/frontend/.env.development @@ -0,0 +1,18 @@ +# ================================ +# 开发环境配置 +# ================================ + +# API 基础路径(本地开发) +VITE_APP_BASE_API=http://localhost:8080/api + +# 应用标题 +VITE_APP_TITLE=DesignHub + +# 应用版本 +VITE_APP_VERSION=6.0 + +# 是否开启 Mock 数据(可选) +VITE_APP_USE_MOCK=false + +# 是否显示调试信息 +VITE_APP_DEBUG=true diff --git a/frontend/.env.production b/frontend/.env.production new file mode 100644 index 0000000..ce659a0 --- /dev/null +++ b/frontend/.env.production @@ -0,0 +1,3 @@ +VITE_APP_BASE_API=http://38.246.244.140:8888/api +VITE_APP_TITLE=DesignHub +VITE_APP_VERSION=6.0 diff --git a/frontend/.eslintrc-auto-import.json b/frontend/.eslintrc-auto-import.json new file mode 100644 index 0000000..181d852 --- /dev/null +++ b/frontend/.eslintrc-auto-import.json @@ -0,0 +1,91 @@ +{ + "globals": { + "Component": true, + "ComponentPublicInstance": true, + "ComputedRef": true, + "DirectiveBinding": true, + "EffectScope": true, + "ExtractDefaultPropTypes": true, + "ExtractPropTypes": true, + "ExtractPublicPropTypes": true, + "InjectionKey": true, + "MaybeRef": true, + "MaybeRefOrGetter": true, + "PropType": true, + "Ref": true, + "VNode": true, + "WritableComputedRef": true, + "acceptHMRUpdate": true, + "computed": true, + "createApp": true, + "createPinia": true, + "customRef": true, + "defineAsyncComponent": true, + "defineComponent": true, + "defineStore": true, + "effectScope": true, + "getActivePinia": true, + "getCurrentInstance": true, + "getCurrentScope": true, + "h": true, + "inject": true, + "isProxy": true, + "isReactive": true, + "isReadonly": true, + "isRef": true, + "mapActions": true, + "mapGetters": true, + "mapState": true, + "mapStores": true, + "mapWritableState": true, + "markRaw": true, + "nextTick": true, + "onActivated": true, + "onBeforeMount": true, + "onBeforeRouteLeave": true, + "onBeforeRouteUpdate": true, + "onBeforeUnmount": true, + "onBeforeUpdate": true, + "onDeactivated": true, + "onErrorCaptured": true, + "onMounted": true, + "onRenderTracked": true, + "onRenderTriggered": true, + "onScopeDispose": true, + "onServerPrefetch": true, + "onUnmounted": true, + "onUpdated": true, + "onWatcherCleanup": true, + "provide": true, + "reactive": true, + "readonly": true, + "ref": true, + "resolveComponent": true, + "setActivePinia": true, + "setMapStoreSuffix": true, + "shallowReactive": true, + "shallowReadonly": true, + "shallowRef": true, + "storeToRefs": true, + "toRaw": true, + "toRef": true, + "toRefs": true, + "toValue": true, + "triggerRef": true, + "unref": true, + "useAttrs": true, + "useCssModule": true, + "useCssVars": true, + "useId": true, + "useLink": true, + "useModel": true, + "useRoute": true, + "useRouter": true, + "useSlots": true, + "useTemplateRef": true, + "watch": true, + "watchEffect": true, + "watchPostEffect": true, + "watchSyncEffect": true + } +} diff --git a/frontend/dist.tar.gz b/frontend/dist.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..f56fe6d9ab8c2122d99fa1dc0bf36cfc62298603 GIT binary patch literal 863638 zcmV)cK&ZbTiwFRQ0!e8A1MIzPb0f)?7km|3qS&>W}ymGRRGBb5#g{b$7;j!S`oG^%PZTvmMw)8TCcs~&^lI+2>+Iy z`}m~4;B)d-Sr3xkbEo@;?y!1>sLJQb%#$b2IeGGA^`z%U@lQVMQ>)b;-oIaBzYq9t zZRH;S)LKPlBdFIfDUM6nYBD4yewCfB5cWc~g%@w_P| z_51J3^B?@D|KU&m#!r5->vT)|hov_q@nG0b{v-H*1^)jQ{>ShC?c<7EJ34wHUNOf% zfdBvFOp@P9!vB+QFseG^u}7=pFql%`@w@aVzx6kM^7sC>@%)!-zvcb_jl6xXdJdfP zE$Z}Wc<1xcfg9(4y_T8(_v#Ps{-kvNdB90OKl1s1w^ka(?ugp;hYudy{d#5PUcFlX z>i$=C7>NhJeg9!;XM26^MRRNW)kgK)iQ}+ZG+phrZ?@M?k6#9d%ij9h>(k%9_jTzI zGVL5+mr0xBzy0g(hac;Mv;NL!uH&ZpkMkw7{;xcQ`G5a2!^NH-`TS?&Up;aC9zCxP zVW=uaPQuy@PGI2*Y4f&e_!Oe<^O)v51ao96Zl!^z>V`i$NxQ8xw~@9|9y$4 znE(6w-q$N%t$cmU|NX{4Z2o`N)&Iu%52D8X?E1g*aOHOW{}Rumg(v&XqvL~(5)S|4 z-+qKYCFqQSUH&kvH22Dnf4fwAG=${=U*L5_W2e*|I$=a(yZrKKv+`BBbVo#bu76qz zsb`nnZs3cKOcKjcFAEliaLm-vMe=p1SmG9E5SX z1m(okhpNupUOcpWbn13#g?*5c>$|b*c$KK@ctFFdwZg)BH0p-#ICc>vRo*ZD?BD$2 z&;I1Qzx;#0{;R+G?hpUuXMgyo-~HYH{^x)8CqMsh|Er(>#b5pWul|>$tk{h``uGWr z+=0I}>69v^OlF?#yZ`quzWbv;e{_c@=UI8pq9aB@Z{ksaAq=7@2;G6}m!hx>o#8~# zoe`gjmHyH8ljG5-SA8FqA3wUoi^%nG6km8W8d4gk3l!a(DzSNY@XcgxW53#sB0i#b z@Zy@Rt66 zk>^(a`%ym``R}vPflt@}-@o_Z{;mA?C7vtv|6kX>`ugjyZsot<_=nAZ!76^PIzC@%lRa-tHQ_rqXRt`?}F8{NQ<;!l;sUIqxjaqWL>W zrf2UKRNJE-jcIZARA%prx(mQ&x$lf=IC7)Ng}%xH(klR@pwsf@q)M55T5sF|tc@oC zi$C!^G8;RgGm5Mk54D{6Tra#q&lxWC5EY>D_|qX{{OOV|{`5$QKLawspP0D#GnqGT zsrSFPPg?%l3*yaS;`b`+cc<=GFT8JmkX7K*<-dpb?%&e?FZ0~We?RJ{SpNHA>ATAq_ZK@?hSIj&Dbh@V~&PyHBT&s8i9!yv3MBeXf`WP+V8nu0|T^FhT7 zsWaso+Fhd@29ubE44$$nl9eC}nHc4s zJ1vtF(u&3FAbFjVR@8dfzPvQ>$CknA4(J9w?CkQr+PyNV)J>DfbmycM7(SWt209)M z<4V^JyB>u`x*s6y{M;*(Zr}x>RsNj^>-eucH`^wOT3_*Mzhc!6>)BeFSsYgT%Rl*l ze)*67^k;wk&wloI{>^uP@aF=AsP#3^|24}$&SuxG_@I!|&;IcL`q{tuJOA!q{ntPH zFaGH-{_NlW@_+cVpZ~-Ek{A8gfBAR6`xpP#Lci;U_|MM^ZkG~Hv3&7e8w5#BW zD4<6#1yL^zkpmrdR`F;*{v^2S9ImzrXwh3)}wuA;>DI{ z-~`ee&1PoOT-Kr$4%-TYEgCz}n@j~#{_bD??|=QT|Mm97I3+ zgTDt8h{JUyL_KB$3v_Adc;2UiHHl~#W#PHc;kloKN150bby8dXne=&u5AW7*&;NgsN9cb#5i-P6(p|^j<~14|k%4Xf?x=Lp;l-#Ec$Fu? z{=-SM_m(A{I$>$ozVkPuJB>cz6$54p<&sSfHfd`2a?HvQw*45z>N2V%v+s0-h}S=p$u7m zc~ylPrsX$$dkS5%8ZMX1Ryfz{UBE+uq3aKldTraTt@_q7=E9z0gA&^(qNi2w#+R4x zW!UNyL-yhd$mpvp)>o#rsbWu|G}etnTS9245olJQI5DmI!I@#s$(CK-I3Lq)OnW7# zq}oFc>|G}wR!7b`c`069Kk3g8ATov|=3O%(UE-05xTH@4yGF+LLY?&R&yl@QYowd6 zsxaeW7@U=SdR97u=nd2$8C%d?`w;r>^0G#zMra}G6413apSTJ zokBdjw|Zb48gW$wv#gk@1#Kr)?A>&o7#M1KYCzaAl zAF8F5fNa=d#gWzbR$m$$#H)-t08$EoFE5?Pb_jnT!E0}6Y4q6kk}?Jc zFM`@b!^9E7LM2LGzEC5SNy^Fsh-f z#5EcAOzb{nmW3vH*A1;kS;T5&T^P^=t&VNIX~eNRtTlZA$q5IO5%uGUbnSTPMx+lT zrPuKrEcv!pj|M_K}Tm8?E`YG1` z{IGQ3)Ac_!X8(Pw|M~pS75bn0O1*yf;jRAXH~!J*|L0l#Z<_zG*gwpj|A4RC_5Vve zLY>1*eOzwW|D0R-Z*aNb8`5Dqp<~i%;_qA1dBUxIPPhrsh@Bjv{GdP#BF~Un2*JxD zGaRCX85g}G2l%s2&Y4vaB^@SxxR8$75;>Hxaw7;}%B80yW)?@3A2WDEI`{adEpfJ3 z&DWh)5zS)l8B^Y%O_@HV(KzrUcS`p=5e=ub(%gAbdHvw&H!QCv>m_0+U9fRidd>}W z-rH0f>&$T5`yix!_q^#vR!o4@LFpg}S{}jeY%3t1)1lrWv%>I+uvX%q4(S9Tthlsi z^>{KXlqsx;nc4W(WDb(#*!B{^-8IJ2+@dvWv6CNc`@O=7c-OYJ4Uic12vH`B0-3pu zMISQgv>ki4UyoUrmRz|PKjYRT{+zVJwn;E|J{ApQqPE$do0bk*QvbVaDsd)0Ax`3p*#Sbd1E1~%d6KH(8O-6Ki8C_9wK^KyAo3Yh%GNB1d zlw#YMkTKaoi!P7GGd_$XGFE7Eo{W2cZex!rthpel&$7-)Gz`vm7)l(l_ohILT^*kW zX?!}I+dE3VR2?~E!|0G9!huSQMKTo@cu(vOuEb+#`3%M*0qlc)&aw7|UDxYbv!Are z?7PC6Uv8%>-okhhqEc{#er+35+gOAeyF=IOh155pvvD?EUM?oO7XD)`R#~nj$uyT_ zTJ1WXXDpg?`w7{zkByE=PVBe%vu~f`&+L?5SVwcaqtu&RMR;K=4Jpi__dFd8@!7nx zI6Gt!#*uk>Ih?-}rY$F8wDt^f%RgEOGLY^6vMT_mkDZD_;v zct&&kx$!KOVgn%0j5-UND_wd3xb+P;VnX5;hvm#bIjTzl_q=%pfI|uw8$O{rDJaLo zGN%T7>XkK`uu#)6GUOWs8Q^&!GtNxGk&M&FL(-FwNNd{^P5Lxw+PF+4@Hig@LT|Oj z`U62}>i{vkvvkjVf6(S+q*3yudcL-6CaBTj2#C;u)5CoOz%@PU318l1WRQy7b$dOJ z$~Zq2w?2Sn#ijz0UwWB~4d%-I*S z**zS4ZVZE7eq4&34p3=p#(sC4i(mrd>`daIEapcu@H}T6u`%aw<@ucC)>=|3Vl!rA z)p}mqwvXU!_`UA|?2-it5?9#zQI?HP3M~6DMuo7kE^@l3gAnbso6Kt2!SXDpAJY)Z z0s(03ITyJy5DiuTt_N`(jD9G*+Hn zZYRr)M{%18COG?@gGwDdYI9G6pCV7&2uTc9BAozDLi?#PQq#Yi9&g13Cu~ ziHy=9$TFB2^t-lX415X|V;aYK56Hb?Y4CVd#0*OYY#ttKC^q6Yqe#U7&lERVz2 zou>p*a7*S%K6akYl@vg6xUel8P(FDMLH3Q)pm;@9sOCtr!`$y$<$A65yOLVGaC+_} zf=v1+z{bc+CP#|oz;aD{`I(0lYDh}(7iZ2mjZyQDo>~P2;o3fjtRc*xa#1&2)z2h` zLe>x|tTL(-U~$w*jAT@PuWXfnkBo!J705j}1~Ahtn7ze%>WB zJSEbIYIwV3J>}oav>j5b=IfyG3auKsCa*>+)I4AVSm{!<0cYr3zXFp#wg=B(_|`+} zoLc1${!}oaOafrx`d)DMI&{XVm=uT_6%z~q)#(4Mr&U;vS ziBty(ql`3jer-x7IPw&G%;HL=sN=Da0kvNljEQk#kO%tiz?$vuKiRPE!cxqRq^v#3 zG`~!kUc|bfhAEEqQ8YzDJx=5WG02s3JC?`xq-I5$$QXg`RRsu%3M0s=FaoZ^@c8MK zZ|&Ln`MGD^CE_ps@dof`)Pq0K$XX%i5j06NIinn`#QcfotUYpP%V`36(fx zf)MinWl%RCKXO(Lr(%zZNZT__%Sh!L>-n$|1Y2X`c`Ko3o?bbvlm>$%|9TL3)baE4 z1D9W(L}ZjH%Hjo)aK8oKnlp2v63&@i584k- z44~hh+1w7*(64-mm?q#H@SZe3i5!%P>knz@#%pmLM%F@IkcKHE4U85DNRV(?19}C$ zAP&nPAl=|%RWh8=FG~6V(LbPR&|$4L8J_n928TA+KeZwa|j6BSv+5k<*@CVo;>4--T*qZNA!tu80u z<&1`mMe8AD)R*xl?bso&4r!VIjtGEEGfQblB#B`OX!Ew<+n^M!#rb z=;yNaRdqufNMDESh>nm1E;8hP$s6Mhij)CJlykKaA}odZ_J{aD<@UuKn7<1k;cDKK zR0>VSw6JiNjsu|>f*pY(Pz0BfVGYAN$%IF=5i5evYM#=2ysM3T~4czkJ+H_oU z#$;`i9A2B0%eIj8uq||*SEXd~>zu1)nfgmp)8Zv3@_WelP8P`&@Xj@`Ls3&X6&mEJ z#3yY_NA|o@Iv-$H$*;18rA!&UD74n<94li58qKN;m<=e9*j8(skX5CRF`8ARSfmMf znF>WxB%;_m#LwlaSGIxs1h^u$;mQm?)AoN&5U^r;m`(f$8_c;Ig5buAzwg3D4&sFz-JU}PBxAuR(@sB?L zKhNrance~!L;+DVje#D>f=kPKfqF|Q8x%_rQ>)= z&?;Rg>=m_-xn1gTWiK`&)kAfP{~BT=zYaYZ1s=$Pzq7K2|3pM3?ncQP^zxnk4oK1$L&@+^n*)7qgsDJ>ZE#?)Xk3q*c#%li`Y=B@NJ#nS#j`R3hl9|bB*JWmX%RbLIssojhh25?9YhQJe!eV!PL&FQ2 zHgLw)SK460JHx6}AM={(P6i7eNZ5EgSFUa+fL5x)ALa`0+RjS#EoO_@j&nXwsuNOO z^q>YcGs4ji!cjiwuMh372I>>B{TjM)O~WPRoc2^FV=r)e6$dD7Nq+oFI3%VCZX zP7o_ot@gXBTd^(qey!ibe-fcrZbE2W=(>|If*u|RE~_(#(oqSU)^x|OlOu6*#deW|7F;Kduj5|l73{XaBt;WSnlR} zkC|*pcDH)(it-}}L^l_}1@+vn69)y{V;U{eIPCcD$YIT4SDA17DqpF3SG4T+Pw7QJ zbVf8PWfRS6zdH+nnR4R`tENq5md>c&H|KGn64Y}EV2K3!9uCkbxNsp71Ayd*aSGQj z6}kMSs^iCOZPd(b9<#r*)JWf)HKgB8TvVfXCyIZfxgwGeo%jjDtCF!Y zDtw3i`ZQyZr_o61s#IgQdwO*lIE@2=SCzBS8IP|{8=bjvcldE#$kS@C73L+B_Vj3I z0SG>_KoDdX_(>{A@vD_Pbo`!2<)Eb`#E+?jb*yDEM?S;A>s`|&`e?#@s(eHqJ|Mi3 zQobtL9S=JAZ(e1{D11nNqQJvQ0QGyEzj%;l-R^0;8w=pFBB$>7o#T?{zp~yez5g?b z=nLA9D$VF<>%RNq#Se1^^wa%6JY0Ej>;LP^Jh%HlKkBD=|L2FL1K(r+r-tXhZ})#b z|8s@^huYUG^}DzJKY#Ndeg1!*)&FDG|CQ|eU%TD^`7)1ilo0YeA3MyMLvZ5-E`5HB ziM*}EG0@NAt}`}CWQX?S7~-h3L8P&Pz8vT3bSyV56!Esm$xRw*TiGi0Z||1;5>Ju; zx0eUb^yzf->tCP$w}-Wr+xs8C%yY~C|EQnq{J;HBbl}GMUwfFf|GaG2A~vcX)u#iYZokKYSOhk0&;0CtV! znNJ*+bR4~{+V_HQlVZvS=y;r8!2 zfLz0uZy_l|f;T?*__qu2us+#=?qb!$fPKw83PPh;o|jM$Yt>U%eulL4ol-ZH#-DmSp$dTkmqoGbR3T6p(-G9V|qyA*!2fdWj*L`hEZ=n z=UC8nIG#tDikt4s%KPcW$z|v|{HD}iHpBhXdn@CO4|)c6R%cUO!aB)h2-h}0ygG26 z=@}TRug4spca;n;&Wv+s8YQaPG^40QAf&hWHew zGi6~rQzn{0z&macfDQmg0fs!8@UjlQ;Ea3a=?C3OL`qBCN~Ub(!PvqG=4ON&jTEI> zV=;JaOC47pJM7%EPlDvgvR_fhlj$8hxU)j&qx!0MXXU39|1HNBz8eOu*efOrSuvO6 z73>bGrxwM2Q@o4o7#ts^#(hePd{1lH$0ub5(aGvsUd7_So*BTPSa?}k2&pQgn zWp>)sv1>|QgO#tsU{!gVV~dv95j7ZBDD??+B;IN$Aqj0;_6iNx4`hx=pRC0sO8!B?$_ovkUQHqOcL5KpblOKkzA`nB3=8|(_$(v>DqvSy~p0FXZ1)( zDDlh&^s56ySe1ip@cPB~8`3o`r+7@Wov4Z!+g;1izew+qw>plE$h5@x1^Nv(OG^P> zcFYna-8^F5EN=sf?Mj`%0P}HLt)YV$*5f3wp*NS8<41mjW3~ra6So^MZmn9oZF|t+ z(glI$I^YAk3Q&$pzA@Ap+9%?8b1Za_6z{5;vFkCca&+N$@n+IkoWcJPs6*vPA*JFl z`EzVer}B6-DRM#Ke}W4N zpSz&g=a~6as{_xOLj~`X_Cuqi!x+$wx4y$j_zd8B9}o&%_&ShL?7sa@~ygW>o`Bm}u1Q{Ci0L?mPA+X>EZ zNR8I8aum4T7Y(W)wPK4!Xx_q=`+$xHgx?wzVylf%By0WDm2g& zp=abmxT@g=w39&=;nEGYM9UbK=qCx$5EHxaQm?m%*0vc`VxDGE6cLVbBtTiuIBHHp zdNvC_Ve)+JH0Dd*8b2E$$)rf!{@6)Af)96mV_hFhgIGEQr5k$}H_?~S84R>CTM{Ky zBjz&C25ahpPP(r|g-INILh@7vYo7Nh$pr(F!W?>KjL539E~)r#E}|utCzc@Tj6tzlGzSs}0rWd?s<;@)RS5&RYb&uV@S9Z+9nEi#(!-$mDy7^2B<&J4c`n=zivz5! zI&`A_Gyed(kB0FDm(3^%P#;odtJi+t2+T)#;iKOQ+BQ1hV^>Hjg^-ZDMacsiYvpkX zF49GEQ$pm~c>m~i+3=1_?@^%p3Dkf>dSaa}K zNe;eHpOlV$y9~vE4Dl_zb-f@0!QsrY%aux{Og#Ta0enp&)>ZD@UZDu3u&{bbyrk3K}4i=b9nHGmX(g^(IO8|Cxq~;G*Vd`Glk8<O$Q*y}Vo)EG;cS z74UPwli@d1<55x18V_DYa%>I7$FXmHi{3qH8W_czb8ysW0#4Gd!R6&CYJ~ckG7^TI z7m|GEOTjW!YX(r*VWu!m(A zS&MmsV(rRXMxdA8+IvVdx4zdb5bCS^9N>L3Cr@JjOQml{)Z-@EOy()ojSXHQn+97% zRW3EmGC*ohxA+mh6ZBwjJx6+Hy{%4tpoh460R!Y2&*W6A*{>;hKTp3J^ z*kBz0l|8+fubO4sVI2RJ*}4S~b7^iDrMWBfJI~S17+$h2 zc4-!BY*Ycq;_za2%6O$Cc95BdXl9OhY?Kj}J7dprfnzKKp9DNtcQ~8R$$l{ixqd1@ zrXr{~D08x1Tr4hyze1@e#iiy(=ejbsu4-`aqkFur3fm|u3`D(8Z(>2=2l>K<`P&5t^NK2xxs$*x5T3qmiW}eF*lL&WlnltebbqXi!pC+{)dMas4Uil{ z$zP&Kk?aPZ=5_;5TgJX^3~V97sFZ_T%0afU{alj$6eMkv^AkF>J({d%BWfRCUiPxQ z#fa+C>gtFZFU6p<^*cz7iel$Z3CAC%uPMSF7?+`9-`3RIx_VR0-M~;-*67B3oeprT z*d%AZ{wWB(n}R68jnky51N)7^g!Z-6GE^EYFxD)K9vx}`hMdkTvhUuJ^moM%g7G_( zJ4t59B4F9KZ_InLI7{)U^LI-4&VeJfatBoG+3jmp&dE7VA?OvW>OMn*q+gDce(6zL zuFMh!xx`-4DaUi}W&jF)N5*o<(h<@T!8@{`QtV!l6tx+!5R6L3C}pl(A<3{9q-^?^?tt(i!(Cgy1Wbw zdD65IC4BqW+9=dTALXR)Cz`fyCr965j_)%RURrtrJw8}HGG^J@^YAGUQh@38$RRqV z8G=lpbx6nr=CiGw4Is$)!exsi?4TzQ#I~w`y~LS;&^8f>Ioe52x*a*`I3;jDP)4N1 z%C?za**3M6Z4<`D3UO7TUPMc;mzLBFHK2Zs;rBrdX+xCuBvsgu+E0?=@Qc8oq1LuD z2ZEDpdkR>R9nJavkhTk~IbhsD4&+uB8W{3w-6vLA8h7yS8PGZFmjSzrK%+zAvb8yq zXl&!6j1gQSr@#_Lqfv=>Na@*ABEzHu)?#t_xB6G|Mr7~b- z4d-D;PB@^fC#GCGI)Jg4^m(Z9cNf}}2h?Bmjc>^e&4JLah;_A0A=Y_JJXiBrvk9#lxi!TRW6NW2--@|l}p0W4^nItWC!G};9ZRf zM?qf2H9-8U1eGj525bgItHcb`%7ABVka?hXk>5atJA(?h=jNy(piLqw180n$lthBf z;so5yoJiA=Y4|*0gs5x*VSb&62d2DGzRG?zs|IKfN5VnqbUKKG&?V77IZPY^eb|#!tJ%~6-MSye;VH>&9dl1#9#Oc)i$!S!$Bjg>mj=NQIoC z1q4rC1|H$vNG|?4$3Lf>tfoxl-y~O;El3Hd)fOuf8hA%Qn=vyQvdUe?IJsT>v0YnQ zg03F3WC5uIVNQ&GzfIj2=GzA4y8@z6wZEv94A@htW0A}bGENn@k-mQgD~*kErL0*o zzYKVdJP}?*h>Ki2f2Uny=IEs?Edd90F;>6TWcxA6a_Xep(P~3~#4XxRI?^-DnUOP3 zuj%ILwLkSLqDA!!^`ydmN4N6+=sIdi32%LQW`lUd zAt7-|MEZR1UfjC{%#jNKy|KE4za8VgC$PZimaQob5Y8Z|%BdA@q_3lcJD_Aumg5D` zSwQNEZ5VxdiPCDNZq?178U|jAr*o%X^&FVanpx?ZmHLW#C*Vws!#yb&Rj6)=zqtAK{E=N-ty#}2b+T@Ut&=uJG_d6Lwy=0# z)5Hjn6Sw>mOaLfu-DDBkO}z0 zO5k0P^xWz;OnA!}EBw+o4wsgA+YZdtkE;ck7bvi10q}Ke7Xe+JJg(HM_dha!o#n6d z{GV3kXRAQZLejGHc20EO&G3U9dy+873SlrQb7K5Kp1a&;9muv&LC#YMq)B*r0gvJK zr1EsEz&ER9S*jRm<=g_f8a zj-4H1nm)Ar0t(N5@#68=p6O*)pV&z~!Y_)U#!#cK&55QH2h+ zRKX?Bn~Ykt59HgtOI;DYdka6w}1}t}O zI}Nugx#`Qx5YG;UY={N*mrJ@_zdY2$;!S(NwN$HBzdc9Fy>V-b^@EvWC98X^^ExHE zx~iedR;*Uyna~Z86aM0}?cyL|F7UVtlkfmRE-%Y0Raw+1GCL|W47bzfB|zF!6pE9A zAVhwYwfP=0#!Y>~(mUh##eQ#wN%qHLZ1~(V%M&_4!80%{2Ubtbfs&??$NL|Xw8=knc*6g;P1jci-ZJ$V8BUeIC6||H>Byu~2r7(9fVjMzq*BbuqExzcO)@lz zK!~wzz?{xr+3Dog1BYXFpDdbO7kOXON&QbsEd)iQf{IdMOKbg$@oVqO8-X<Dv3g$8Yu3tj{m=?2=KU)@#}zw)Iwf$d*{;S{dTV znGNKv3$%|kxsr9N4$KXKyB#KEcZl5WOmfRXYsgl(ZfjAyBzrcsF`&^2sdG9Pto1}9 z_D+l_0jm|0IohTgyeaFsGkHd>$E?hRjMyTCGM~&kwtG<85KTj8 zjCmS~`)QjDYlaM!#sZa)3qNC`)+(!jV-i>CoN*y*xz2d7$XbMv0Rd1e z*aX5CA}iz-iDFh&K`476{WIrXgYP0HB*Pbn>u0X-#;^Hy53mZb!gXmBG2Jr^I0PkwNHMVFv57KL8?LOUn__6sSdVimG0}!rj=`cB!tvZ5DZFUP zKvjsy&WaKl=VTiR-~>>!(Qi%LHb`f!ZTvY9LYUnLN~gX6=7X%F#?FN>Dv=B2Waa?K zp1-u@8waq^&q;R9M>meHNdlA(pv$@me(lAH@qX5b+|QYh2N}b0FKaUHWR1ncA~SI} zC)-A8i8?#<@-BElksfOG1naA|mo#frE?Lp!1iKZ;5DvTL>EEb3cVvJxVGU(~ZjtKB zK*R%nu*K-EcKMoEHsu=Bl}a4hL_I40tL9m~$ab0Bh;`R7#Y_84-yHIw>&{sSX`NXg+102E{4>r4^SeM8JG% zAWj9Sj$vYcfMU6(AKrGfah5_toD&LsP4ozateBYZfyn5a+^L1G3f?Pnz9YCzz;Y*1 zUZngW*uoKl!DMd3Mc9RF2Gz8_qQ;zyW@NRSuo5HSedcz*!DnL6M0YkomzI`BRlad# zH0{R_)MP8LnH1bapw&e5V zHv>iLYglp>!{MKW3}&u+69Y3x5JzaI+k|0hKxA6KDmvnfb;Mb=BhIv4i0RxuYe@Ep zcPT7NBj{A%NDQc=Zjyy2yiU+kfJdVW1M00IzClV)gO5N`NB`D=S2HH1QsZhn^kW6$ zd`_LF#lqx($!(fCDRYwj1c``_hKP(a(BvRfjc)`)8$GgvM#S*ZXX6_cjc+f7k6j|T zF@=v!hL63*0`DHg#Dy?<^?-?7T59S;;uQ~x15^V*qY$gmGeJyY`Itl02-|>&8dW~J zA#$}5Z9(V(^AJ)%)Sg0AzG(HXKvXY@5s2!tvF_#&6>$ZKIwESf7*VIJSEkusnQDkS zncGv*2}7HA^~7udx-ASjIwGQ;!Rt`Yun9xdGl8g0#9z_D97F=CLk(o2!+#^LIyd2J zNSqX|c4B?+i%Vu9h(Mw3SfRb`WJlPbvUh;OEG+mSxCo1+NHMcqu8(XW$1ao_kkEux z{|SD@)eLwdoq$AP|~*Q7=b7LX$MO*}~;UC1*iJiDee&&te5SFn{r)cQIm;L1lS zejR{LLIV;(D{My5I z9*NAkbVB73U%Ulq4FmCPk43utQLLjcbqU{Uj;Yw%$t|7^*phCN7s@-+x7<6^F_qq# zAYcY{oH%y*Mf2onZGC6sbEJ5r;w9(8U_PRpsXB@GQl<%@<8Q*2pQzc~zSyP2jj}@}x$U-1s7WgkH z-fq}r*Uegt+#pwc9&1x(d+%uD#opS^NppYa)lfI zLcoj}1-%H)tZ$48EFIMEB_Dy$3Itq1NV>i_@cqFN#@XVCLRXRz`Eq%=2f)ZHa8dCb zyWP`^GOp$1J@|$yO%;QNfty-79w`%xSow*nLchnI zIh66DMC>ca29A@y8M3S1;FO7&x{-a+V8J`Devwt*WYtS4elZ3ay;V~j0uZVRZYm*RgA$&2+6GgG z%}gT9)u#SsN$i+|AomU5XOQK;phl}t_(^s}?Z{c1XrjZ^et}XiAH>EK=(%tkvKrus zqbX;ScV0niQ31TEFsIS)Ow0S2REQnfxkAR|Kw7^j1KVvPhG$pc$sW-tF1%#i=HP6_w=dwSC-%T`uTPJ+-KxIX7WPfkGi2d-dXu3{YnH4E@4 z|3H|BSQs-$G08Z`n~X~zfG6P4cY@vpx?vW_rtlM2I$#+fF%X*%+4E2dSz$JmI>MPr z^Gt9U z%(#7uhxT7P2E4Qic|Vp3SuD(h<}L4SSfsaUvTAQr>C-7$)EATJMdpgjHpcdemg$5_ zhfhhSaS}bY>NpACh!gpr2A{ki@N7LW~M}18iaTv)dPQnHSqTOqvRW<$V5{Y^a^J(Og2eDf+?97da z6~3TC@Jqcojxi8w1Td5?_WmnX=A670r_?rR=3pfAs$rbMJTf!>B}Zabx88~~OXrll zAzM_l1-dX!0hA&OSvWgOh{s%r;q3PK($Od{EkpruX(@$3Ui zGKB(np$;58{ORH!++S))Gcl-dx!wl;5@4v*75nO`BOGo*eITQ8wzPDXfylHXm4+F5 zEEr9``H}+7G%c;HelAr}S-o71vMe%_ETEgPfKiI&>t{H=UYg>ycrl$<_A7ce4VGA_?a989`HnT~zv=~w_vf$|&}!@#~@BQI~hWZ{Xz zI(5fF&X4q&+lz-OciXU>e>-tQ+C!>IMy~%lpG2ZL4IqVceocz{9rS(7`GM;^%d49n z|Cna*5gvQ2#8|^uJ23{8f z@SB8h7{KbxzDpzE?MfY5a!Q=&m(E}Wn19L;wV*7m+Lq4hMxD962x3;~bqB!;@YTCBr$^ol;H`xQ~ zSGJWNp&`RvALI$i9FcJhmCYIG&j;k*g zDivC8Tif>Ip|PFZ)}1*YuVjz$yuxS$tqd96EiFpqSuM*G+)z4tb`X3Aa|S*Ec91g? z(jmj*oSil1wOn0(1uy@4Cr%mj6UF4Iqv^N#&9yIrvqZ^_%794icksh%_${tm;Zr5z zQcF`;WkWvw+f8Y|lZx62C6{}P$H@`n!U6F$e=-4H^E6{XGR?6DB-%NqfV4uQ1Bph= zI*@28nFbCPcP^v$P_xJBU7s-@eZyn+@J;)1Q&10+^b`j_vOmNHDKxGS>dvr#Jh{9q;L6t)S;m`#2Z_}#Y46dIrhPH$v7mH>;4QYly zO+b7f3xws&Ym=j#(iurPDA7DktMXN-pr%#!t0~qZq7NhP5RY1CZ6Ff7@T_KQsB~DW zHq|8=_IWD&=_I@}3tp_M)pzy6c$qqk?+bQjQP5nqgf6PpG+|d3)zT*1rES$3F>ZgHCZ)FRZ+EJD3} z7l=h@BrgUNbcXnem4v)U1=33+iEovWgwKs6-YO#r2td=Nz|#;*E8%Ly(s#VO*DUtZ zpgXmt?M54;aeQ&*mN4O$1O&;}u*Y%FvEQO>gt9pA73f<8gHt6pSzb7$j>r($_z&tv zlm1*0#X>~wY3oxrGa94Be!fX0;W;CsDXIO<3qT2hM1fExaqSRVXpcST!fNsp`TX+L zc^13af;;PKRi{A|b!W1Pok@PY?$h^YM7K;=#h#r~CNq{nag?GVe7S@vBDBX9LkL}~ zp18)c6VI3X0aoP@{skydMzDlL%INuP3ak;&$q(;1F zL2d{IY>snbTI^U}!1|k*LRp@$d(~2+@b5LQE#EN<(1mSLGF@UIF>Qj)vu;xmYRH&R9CeFPzN4lbTPZWpE zJK|;nY^$)ZCtW>Ij*!8 zk-JrB8Zse6rk<;U&>gyi0S!68ECTH{G^?74Byh}BMh*K%&_+rmknqCr&oohEj^^fx zdEbN^_o3mA>=r0~{ens5YHwj_sjGStBpqc2IL@1i9%yEwX4_e!mx^6JK-EJ6*Hjry^%SzGivuxe4DpUqVc0KUU8$ToyQms}Gn8qu0xq{fc zod}j>DE+-IGCxi@?bIDlW29%C#5qS z;fZ11vXUSETFBNg*I~=T_>D_ zeoMBrq0>!qo@rJ(FNhLItygpVE%{(iTU%|s_Mrp6jPrTgcq`pSb8W{{TMsbMROoG@ zMlNzOr@*xyt+nH(J*@z0pzPhQS&ydsCk=H)a(%*N)&KD zko;8AY;KUisju|-rG^$S>?l_DJe7Kew?z|5x0_m|DE+Vl@yMAl9C(Q)3vPBv!~;Vd z|D`Y*mh(?yYw-&Be+|mEvp_ zek|8ver=hlKJQ*lkd!oVBG(NmkSx@*eB#@7;!8rq@Iy*oW_?PAUiui5JV1$Fd$?vq z_M37w9*YiiG`_=Qr>yNDZTZMgTf5v`eQ7CUb=~sO5&FJg==M< zsU8W*jg`klqC7``9n1bPOnHIXISdyvp9kUO-?G0O4SWisg&NWK7RR2`rNhAM;k_vOcy_OWSy@NsmabZM z=`wogZWJTaw$Vk-D-%;FX|9Z0aNF8w?4~zH8Peobyv?O0RIG%+`Ei%iLLXv5*o2l- z%@^~9ukKlmNs9EE>?8wAz{%s4z^yj0MISU~$QVj(0+6Nvq7l%EDsL zvlX0vV)^Gu^Eb;+S^Rm@c#h}*qi2!?9yd%@Tz#KaY#8y2eO<847esRzIVZ`$a3>CTk%2qHf4zF6@|^yk{4WQF}tPVB5H3TPlI2 zlXN^mFa`wfdXpZFj4)O*$eP`g;z_Ns#$2(Fr7QL|;Qk<~VA9a~NlpO>ZH(btZ0kZw zv=4o}wqV;~e0k}iChkCzzikOl>wwG1yA63rM-BDmr98H?w6q`%Pj*G5r6=b|pr`Mt zNZD+$gzhTz*|oPTb}&dw55dGvE>EC&lTT&BK28h-JZ41PqfNtZqI?= zCHaa`d`nf1XJ&*wK~su@y5PGe6#ruVPjL@x(!Hs=kp~0`;;_3a?LF>OhT^TTo2#1; zW8y;m*6!$d z={Dlv>bkY2L=}k^0$a!S<1s^2+!qpL z=?w`p9znQ5%IDi&nkQ;AEo}I*My-jVsXzi!jphO3D=U=9R7~k06G;cPOgKq=WGB9} zM6PW60%nohLRX(jwr(i>VHOGW9`>bU3+tE=co5f4d=1JAb^1!|?@98br1>tUf7EYu zQECU0-jldw&YV`aZM$eI62TkNa%Brs=~n6uuQh5z|AZJn!gzKc)y%_eBDg4f^e{4pD;Cb9ouc)Lpx$&YH>P zZiH@V_$fHZR-H*y{E1EMSKrWaN zXcUMxiGeGWItYWwxGWTmp3bLE{6b9(L(ZI$EX!^LMWzfvPVx(sDn1;+a`9n>{jZ_b?E)ks=HhVS}{Z$WL&x?l{z^iP0}^98&HBtmv_D; zZICJ;X11aOEcjF9lbZv{K1CZ<4@|W7=lDN{SIUFNB5+kXOe%E9ftVDafz42EDj`Z8 z#aS}383gflR+l$e$bT$3QOtE{xUrG3nB?l^bWLV<_;{jB^(M(9LWWhMnJsqZ+nhML zIL%F!Be?-5F#=ollvT7wki49`2J{K0Q-VJ9v3`QVnM|1{pn4hdFaj1S zOcwV+C>M9_Vm5__iIfy1GL@%`R8F$uGZ$rXP8@ufx~nC3$8akoNj8Y-;?;pd<)^mM zBF6Co)qs&J5XP>$zj)RP+bAElvVgJc2|Ya=W|ilRBNnlMx*Q??xl#q3I5<>Hw#3v{ zJ0tQKPWnC>AQLWMruc<$>>JNYg}B$J-+_kbZH2nyTqnZ(a3nY4Tz9USc-)qbCuEda zUTO$4h37^~Gj_vg@{P(_G~s}GiI#wD0`n5CuoOfL2qIcYzrR8EiXs1T*^jc)lD1j2 zny*^~G5!+xe0hYeI4!?2O{JCFJ&u%?MdqJMo6I|kXTaSQ7y}F4m&IkJA;rpRuAkjJAp+!c*psmS|RgTo@IJ6$xL;4~@ zM7}+K)U48%`Z857E0CxHYGf~eQj1J^nT(KhCUFce*5=&aySy~cV6_R((p#fEjNe+u zb#f!Ad)s7=4J5$m6IX$ra&p?SMGLCu%My4MqTX2J8l6X|%`-YsFzGCdL3(T_wt8xd zsiKZ1`i4Y3q7Opnjv7)il*=o~->Ir?0Sun8lRC)-9d`idMIEW@r1tFhc)?#$x+)&q z+qCzV3!{a7>lWMOARXpyOUyOsO5W3}R~-rz1s(=g&Eh`iOLO+%aziFgaxDY-kVz zd&Z{9y=kUKZcom=pN2}$ivw@(>&- zcMHjhaMcDj)N-Z}V-JQMs@tpP@Mgib0Xb4+XfvM*weAI(mzH`!BqUe%+%BAna`k_{ zDlo8<#=z$qRlW0EUuS;$Twh6Wmv^4+ShYgz+Bna2!2~FbK=l@8dbejR&ho1yq$i~s{Aa$K zr_b{GfxYG#<|?1>eA7j7Y0WTo>s-pLSJHKMxW-529PdTWdRsvf!jIIT{*D=Kh>2?P zqeVXQM$+YgP`oSuQ6j`*4oDmW-Ixc!EG;b16iUyOgA7-Qsfnemi^?de9F~193=phx znWUSMZA7LCM3EUQoa>vIhgWx$yh2zijy?1-UqX3aQrdB(y~zE*(dGPN5AaTV{m zOPyRo{v9_&NR&8UoOLH5x^3VWv04+tSiLuB-84mikA|l)aM`pPQJ!d%czK8_wj6s4 zq`1#FWaJtw*JeQnaD{Z`_T|}-Qm8ZHE3I7rn2W@QF_pWkMNS3bU0dLkV-KaJOzQyv z#_$4N_D}gqX*^@jUKZGe!pTRuHa4GzhW*e`%!M%}?|Cge#Q~ZQyQ&y}@*K zs_iEeHE_%@DRZjb;vTFMO+wDSq{{vdy00OH6nUZzjWE=FvNVDwwYIe= zHjt9ULuPR@EKHvGD@x`N!?rW~P?}06WI@?WCd46qPJ`nnZ#JhiXpfd`rVL4jWB|B% zl3|T=OTz)l(krwXq$F8Rc9UbESuO*5vEV@B?kc;O9B^kb!)1&aV_~7%7#RgOVHBj= zbJjX&TMG*Z$Xl_adfZf1ay9I8$GHH`+7i1BWa>t4$Mx{qwu!Q|5GO{pQ+dV$#4c=K zUiOqN#)Z-Kh_+F6rs1VLv>}ERZ@)|R_i|nBYDq>#oK%4MUZ6p}(l2Qs4VKWlKciok z_7I|?uH1G_t1T6y#7-QhP18Mgm4e(pjPnPleC^;={J6dv^Mg}?U2g=B;zqE%3@dJs zJ~$O96{Z^-BL@ag+(+B;I36QoWy*E<3=QGQVu)T3`#wyaZiVmqxIVvd4LFhDv`kKQ z9oDO^a1!+~C#r>Mbh3P_BZwNlloO$-te>~SRN`vXzED(`T|7LA?hDm|tkm#Xo9a9x zps(qgO;kB;HIp^h<_27>H5_#|auK;BO)4z0%~C<^URP3;;d|wyOxj$io0^i zh8Y9053%0_itibK?69v6n<_85?C>f8oH=wjNS!YnSo`+l{iUV-)}TF4PMXR?w}=3j zmxB;ln5)mS>bGT^>cnYsa93gWDl3^HD5{hQXG7BaJ-<;%cO0$)!?T*zYmJxP7zHqpQ-SuPUyA+BeCu z&2D^^9cZP#$$p#21|AdO-{ovs=plY#$Q6@;9Ggk{^rSjw9?SS$ORYsRZrg4sFP46Z zS4ya>4%s%6oDT(fW2Q}Gb#wlH&NOzSo5uD4sU+k{s~?m~kMq&MZw}DF zl1jon+h3&=sHweg@ySrR)EOye{Mb^w4siEkr{Hy} z`=LzLiAFb9!W*mXOyU5zdDWoedL)O9Aj)X2s7ppt*!!~rQ(&B%iSMv-y;l3(vUUOG zcG6S3p0TaCc840rT%&p{P!t5{eRVL9${X2?V`UQ1qwFp;ERFP(dE_w@GEOKu9=4-U zH|E@+EHT>{+Se}l(>%W?#(A@SfI;qiaVU=M{punGwtv5ItSK)5P*|}HJ;)v?lXYrK z1WxNh_H|vzF=90{UzA(vBeqJ(sV9X~vLQ8!$C^#rv9+&n8y?yFmzRqnw#-=AqNrQM zzfi^z8vLD5hw^JK`9QJ|9+`h{}=Cg!RDZIHsHyw^> zQX<=GeS^!jz_2K9EsW8^EVzMtP0vWeMH0D-#8x4(1*Q7qy>Znxcgsb$+-zZ~nT-$Z zewmDF4bc#$Ndr6JoEkfxArV8m`rVWyK_5{A2=!hTLk2kn8R)w&CT`v9h+r7|`9k({ zh3xB_Hdx4MJ05 zT+^8_zIhf0z02sr6n+;;PsR*)OyzW06~CO?i4SSQbcOvyNY%ox$>*!7FYARUKedXW zZe89wQI-pe&;waluDrN;S->SIuMQbTVO18$%Lj&=UtP(OL?)CvOr~c-bP4#0OB5!r zM;T!^2SZ-o3rcL$0A{U&Mr#Sri+UT9R&r=+g}nHRY}zb!95^B={k-MUjQf=|Psh&f z^5u5hH}=Us5i#Vg_|+B}tWD_$1#g*UcSzdGmieAQrmwJ}8HLOh%7ZJF9Ys2kyh=jT zTd?j@{CbX4CpspEG$ki!25sDYJTF(UhWeqAJ;1$d)hHJ4FEho-wy zVLDTXlr6T=dn#I$R?F%rTCjK~Ggm1}rKm;pOS!Z;oZU#592U?`edIx;O|}$9RGfp` zfT_-1=5l?Zb!6j(qxeeP9`GxP;?Ua3Ii>tItz3_3dh_*lX7hDS)4Q(&n$zG6z~>;JZ7Zwzk0D-UhW1lWG`tfQI=dV%^;3(m zG9^QKC^vvLReD;g%$QieP2rZ-jrx=kY2+Brk)KjXAB%IQlQ;oQaK@W7rQ$L~l1-`< zH?u4al~aTEtv#Y9<^dw_9%Hh2L+un<4Px>pHdB~;5YO#Es{NX={RZ!?UQG2wrM`L) zS7eotBcq>#T>7O1{6dH-znh>Lo2yN9UI=WlXjN|OhEZTC`Jt>S-?V0lhU)el`|}F( z0KhkBE@-B}eO1gXO++Q0WIYF*0`qu^a{0nj$&{{9+?)CT*?ae<#*t)w`2X`MjDy3< zGi9$0bPpp|O>k`(pql|2nD*-c*%nxhY`JCGT>ahO%&fa41Jko-H{utwJB{s9sjit> zSy@?6p5|b0B{9q}tYiva$8a+EpA?$gsSFP!PS7$I)%;1AQj`D4s~=OC@CrLpy|pAo z0gqb5vU(^5xB?bB0F7?0fgO)yjY77aa?Gdf>E5~a&VdQxAiY;KTZblWSe#mQJUD60 z;#3?|eN;P)avBfIV$9)j(Kw>|IL2rJYL>*8Ipg)&Ew@i01FQAYQJtFK&NqK%qcD)Q z-qT?N?h_8|d6t)&rZ1SyyO#JgGD;6(cgI>zU?|A>L`~sa`6|Ojf{?BX81CQ5TAHj< zV{$Cg<|;Uj^PZY zBU|bi8*6KiA*S!Vp+!^SrBC&A?2`?J2MrL#_ zymjnF#*L9c7#b%7uTj^1{w&<~WaFe=jI?#nUCqX4gpV`xo6g zziXa@hX`N*&tNo~Aq6`@ae%nkQq`Bn!`lfo3b7GTLjwvA*3-ZDKAmkJpO)E!PWtru({`DiCf0qqaim-+ z$e~zn>{I&&T}(0R==NL;T~tF8n?n=e(8MTb=y-d8=u%^jb`Z3XJfwyZGdro_3ox)H#!2@1 zDQLi400_5&;)1X;ynd$+i4(8Q57p!g!z365$t1|sAje=|Qu1nB4g94*rrGcaoZ^@+ zhHgB-YtCh5b#4Q1y7I!&0pSv@*BkO49udzaFxNg&H6jRN69s+lPp2BbUB-OQW46n- zTSLoDv`0WDbRHt*W^m7K+8=`yjaa8doCuDmgsum;F@p{80b=iBv`+k#UB)Z(f5c9RPCacQ%=bkr z<|xMZzVuX~iOzM#X;&&*urCN|WJ(nMZYzB5rY$31R9=i&RnBbq>D z<9&9ynn2;&Tv{X$+zqBy$MvaN)t+ntfcThRy1KCjgwO`cZIfjT=(t>%c!Sf>4VephP=zu;N48ja}BcH<$zo z!s5$>Pw^md_Zb)6VN6K)y|620U>&5|hS5seQ?D~+nYC>6P<<4Gh7=Imy)-4xT|Pf= zh$E~^?xoQj4q$19rMTr@a-_vC-iI&nY&cjW;G43KZY30xUU?3w7DAg;>tQE#T@cC6X>%v1fsboOZV;;gdzC|m!iTL1j)e5H1uRo1>r54^Jq zqRMx>@I^0hyB^2=zE0vk4oMsLcj)n@gXvz`WOP-!3!@0*>hkL*h^L8l6b5eAp&7)NO9jAANB;tZ7_ zhUvJGZp>T<8MZt@?ZMRUz8HL}kDb1fjmjF~7Oa zhf|DITMp}m_u&HM<5z*a_Xt8)LTm~XN01|P_E92A63hOUnJFl8&YYo(ie^Q0ZTl#$ zJVCyIv*ZQdYQhI}_HL)|i-UFPS_2yWe6aRWBlw}r50r0e2+h0Tf;~i)#Ledq0fYJACWbjuef}h za)HUxc}jG2jFCn*Qd(ECb6RXrJv620ZNP5z;(<(g!};;EqGI$CyEOu=ZKSomH0W~x z<(uc{TLoD{A@vp)H&rc-ORGK8~Ks~pCCaYUd5V3f`ZY&vd%avWVJ(DV;E2!xf z*6W+fRB^yK5a)Q3?%i8hP_<5Wixw9klDWB9ilBuB^%W$kIIUp!ffok!h_j3;ST>-b z+(f-Y-1LT5RzE{iPROeN`poe;+ew)!Fbvl2VgEm;j#%b7{3?t@KOCTK0CLRTBZ>-S zo5zSvfmR3i@%&tj(kEqNsXsr50plp)k>3uYvU{;s-lPNGl4?uYEpu8fk-jmy4Tm2Q zCXe9Gxu$vnB!E1&harXyRd+A&E^N@)Sc1leFhDXOI!u71416BY@QPW#SGKSxj6m)J z0UD<#CjH!#cluNhSrRDb!ug@NKG{IpYJ|py+`31(wFe)#^Jv}&Ee7wx7>5%cz?|X{ z@Sf|sVDWpkySVDzE8wMEug9v_Nr1;d(z8(~SqzhiW(mbEqmThG#!mG27Fm2hxVx`$c5-sE&ok zuxdc=?K#Z6u+Ya*+qNqIDAbXDz_x#GRM-|O0OMfoR4wb@HFtbRYta^8$-kp_zSIi7 zm5`Hz8h-5K~@XckW1l)GY7+QKugMZT=L;Hwq^Z91>k@m2CF{F9Xj+} z^>hnl^4dkK?EX!%DXB0*q107Fr?DD2e%6FWVuQWzsmvnKZsny)7bx z6lV)srPYvgHTwsZ)pI$V$ncOCT40RKInlqjnP4!P+(Ul9BX-Rk#8r&`y5tQDe@7&Wxn~2&?{}Oa6nX zx^e(cxxP9eK)nkDnl%tN^M3BJ^u0z!9&={l)hpb^n>$@!pdC66THfR8qX)mf1|os` z96%aRd2Gp@VcHY$GS&_Q{(YoD0`NP1g&0RbfW=H(%&kc_E{0%m< ztKU4sw&FkzOFSM)5LPV=zj-pzb+$9vw{zLiao$tO74~gmVVh2U_94|C)&1O{?!P>y zSd=>JR@JGrZwkpRkO&Fi1VGNZ5jC$^yA|jB!WmH6AT<>J^YiBng#Bf}GC2Ij?$$W8}2=vn=4DL4Kd$(LK)k}@~ z;xhdyv+Ed|M2?*0Yad7n0|yJkwAm0>QWs(7VEW@YGbALuC(+nT3TGeoGXyl`L%|1)_yH5{sIr_+Y-EWu<~vNzb?J& zG!|=%)y8sdvDAPsOYf){_i1m|rs6xS)&UH$2d{8q9%vkPs+y#%2NMf(00}jsNCPi7 zO&Na(n@i$?Pl*a^xcVuBWxa`Hv;|c3SrQLsF;N8&WE`tJ{tqz~vBCS5Uk}~^9|8B& zS89u$DsW8|d8LY6!@m*NG~U0fH84AH-y(9%GV%_5SpweT+}C^m?mh4wXU-BbXBC;V zG@Gjs3-1Mqsbhnuwu*S_%TROFRxwBI{2gK8 z-ydP&AvICf${3Fr#}Kb;!FNNwlD~-`>4(d21d=|xmI)-PpH$Y2AGL3BIB?3aO*UV4 zQ%&BVt@f1CHI9=!{}fEG2w8A?r86H7?92l5&T_*KoD?e4?_5U?x1|O-^qLRQP16fc z>!N0=+my<3>K&tKV}|ocDYg7Yhy*B#Xh?g4*hYXIf$V{SM7Jd;g71Vz^D?3~xlxjl zk#*rMKmV4Wd&?IwqMx73W!8~zkf9Rkly4Qrtk=>M4t-5R*tv8nl}>XjcE**K*HZyf zV&#RveN?2eb#v~sy!M0G=FFziC{&G~As1|xK;qr=uMHrh-#oer| z8aw5&=Y&Szbgf}-NJHr{vpT7n%DHF%e10BJh40XW`vCEpj#IDfk2(QDUHk#xVUtKd zG+(Z#Mo2jY<~WLz6hVHq<5A{OtuIcTj9c2dXNae(W5vY<71YG*cMv~#>psDdh(h6)3UG!_vFZ2g;Z9@jlp_{X$B5};cZ9CVenAjl1@F9~OJW9ei zK?JXL^dMu{{m%&nZLg{CYH@Xf3}QMKrR@Eo_ZczM2k0Ls31iJEt71@E`U?x#x)5=$ z(9ja&mKyq^-+)eq7%JQsQvr0IDPoP6;HK!+B)zd@j)jHU-45T>?QDq@ftba5=qFat zX%tv;-lRXA5!zUJkiytpL(Wwmp=m@n&qWudU8wKzlr4Fo1q4sTHZipq-TT+ zXf8|Kg2y0PpvN6_P@H%JvZXk}xr9!l_6^54+~eQj9TN}13iU|PsZY<_KnXR*_+)); zr8!Os#BFO}Toa%bM{Lroykns5Ua=>RD@*8o$0?qr#HhBDQ_CPFJ8M}UEDX&`{@|5- zf=UWrVJ}vUyH~sFKT`C1<<&g#pkp{>?h#A*9n$qt$K8`ZP$4D!JreymXP>;I)gG0z z_Q^YRd(;~s&QIQ+G-~X4hV+!~&zrETA%n%I>1xN5msBcZ;Kc8K=~V;!4=-cbasCn)s=ygHM+b+45 zbxZ{0QLI7*$}vif(HUU;6LGAl9>@F>!w=CHsT!9c6n;V?u*Tkgeq3#K*&~I zBSzen#1c>Wk}!%ZTK84Q(zIa^p-9CzU~iiGHfl@XnRi7bLRkIO<(0--3aSCpt+_k6 z8D!n#Q|W|4LSBYD32RxxsV2E3yt$#m#A3WS+3`f4gY(us@!7|CEP5zLQ%}bALKP|m zsXo%j0y5a}T4XirN#5!idG*!Nzhth}k%3Zu?fK17i)AbCnw58^-ZkrcP;Zy?=KjJ$ zN4~%5^|3be=!Ykp2n{SZa#2j`p25C!@|yJTqs5M=Puc2aq1>_1*RI>bB2|tyR|Xb$A%SGd=sNI z#I22`ZJ?&5qfN+RohWs4$U`OYeYJx{vY}R3F#2kaaQVd&=vBj=_vQ$z2v?(AKDtDM z@mMgD3d5r5+O2`ND5m^Q9myjpOl?N~g76R&yKawnv;%a^5Nj-gmWug7xY z5Gm)586}q8X~;$y`|-qn)qFX-+0Q?sPbR6#p?13zVZuvTJ~r@ zdCD3MKO@@~J^tGk{dQaQ;cK?&!~cRU`m)#-J$}g+J+^Go2Zk;BGGmM0p<&(Obc0W` zhG=n2&f{1rpGj0}K~I5YDYdlQ0&lG;drUmCi%9kAl|Za29C+k?hzgx~gqH2@Cet5_ z-3jvH)gYsMWKIG<>GWpdXp7nPQ`5)j6t$~s<8Aj_;50Y5j~3eza|t^$n(-qYlMqW0 zw+to}a!kMpjv>%%;iK_R@Ytw^1M zxOfuCfBg5E$-nE>Pv)*W>XX#@M0LBnS|TY8JByjqxn5D3gw4iar(K@(ze!SQKy= zT^yi877P^0YdT5&By}YxspRcJS10m9$on6!5eyJK_U-X;e|JF5ge}tNfx`nVqCy!e z+&y0Sn*8GWzQZxYL2Hqhu{*5KHrd@O;YIkvArN(W&FOovQI;2@m9*;Qq*bRD57w`N zEw&LmQ0MF$ceeX{E<5bH;!-UzmXiS2RnUN)&3rYRxoS3LH3#^r&Ut54bH}UMglZ~G z1>Zt+LpAJU4Hr!)699@v60RD0wy+;~8w9!$Nv~-MGa1P`8oAhE*`RC^ zYcp4qMMI7mzb2D)u;n=liKa8`6w!m;+ zO}q~Y9n*q*Br94o{<+bD^&y>=iLNv0Kx}5d5Y2TwUxc5AIg#7u{GS^zZqs~fg*F7e zzg3)nvx@T&OVIT?6M-M=Ol*^2Qyn4vX)aI`gFQ(HE%bHP<`$tk8v==essZ2nz+T}1 z!wR1z{FdI7^;7Tac=Ph~czgTu@bl^3=TDdOPsCS=Kj_ANSa7ab8)gzIw4YAdFp}Rn zTmZ&LCjSwrDIU4ydYO?AcY8peBe&Tew4fg74xUX9QhxlWEgvmX^|S>;kmkUuW@q*|BNe0zdEwFVd}?PQ)-!E;V$?iP4IaF0NG1;%$p!vh#V>Q*3br9fQs;4Z9-jpDekhB318B<=`GrBWLB5M@Bz zU*PeCQNzM<;+sFx4V94)wP`~3!ImW!J_BgiEe7hxSXc>~JvQI+CagJRM=ftGV;Xp{ zOb%I(gwmejpe0FZ5+>=`kK9Aa$Ddf327M92VF$^J-UL3C!ny4o8NzwPFoUj7y$wkz z>p?vYx(Kia= zCisO{h-c}ue5UGn5MoYw%N+aI_4VfL zAjNP;&9cqW<(9YtObUq%+K2>-d!=@)84{gQ1Vh5Qt2xqQRL%FiX%WyH$Hv7OJ;OnX zM9r@Ag?-f$WLMqj`%+o`Dt92qumjnp9msK|!f$p$66i0-XfEladlO1HbHDsW*$hyr z`8xH!uw7)`FS#x6Z=`Y^D~;<+C|r;DP5@#!b&lEBN@n-FOHQ=vhp`p@mgv#m<)f;0 zoqqpkQzGD6DmCjn~-gyaF^1c^#lpp>qsLN+Plr$lf`+wa`o zB=MM{PR5B_PEZ62dMqyv@5|HG(OsCrUKY{q{83PCxKS;Qhp^v?Y6wV+%4c=(Q;eZo zSKA;2ZdWm13G?yDuCHdT7iW#9BG)u~fWjq3Bp6Y~R7$)?oqc~@(?vV(EG_pobuMDZ zT3ySN4oR&tIW8Q81N4cd?-cL>1(#lZ;g{i*Yb?d^LkVX2K*7J@LsTkcPOdPobGUGP zXhsq|;yWLx3FD90X_>5uHIq#fYB;L30+Q6Yw5wAkikC`;;Q>W0?D%P%hq!i96SU90c>vm-wH;#0(K1g#1KH&bXf zsiz?)?+fb6Z6RMyMI4Y5JmWCP+?;$t>RI8& zdO-Bs>{RZbx7b0;+h)!0EDc)TDLeNzHMW9X;+#I~L8u#qyibG5xtFix`I*3QGO=6v zvo(K^DObi|!@EyrI!C=gN3?>%*41XdG!z)(vY1t&@eBhTKIoC@e=-cXU*ApOeSi!% zL5nuPv~4Y_hc^Ybvc>&;p^F8@m_03cTvKOtr)GPw?R`w`XXN#rX}lr4EYS@sP?ZJB!8s~H^D=V!-$H~%7`t)3#jiJ(3eYHU}FGBH`F%AB)> zDz!l^dYwQYZna?QWNp0pjay{yoe6s?TA^oX1uv}s)|BaHEUaT`apZQY{KV`@8nz*OA`6Lh+|16=mWd%AbGMWD$G_MpE1 zA=UOY@Gb&5vFxC-nwwDXfbld_5BF|;=Rhqp^4#B(WN%ykVaU#$G*BJ)yWQEnW?Ksj z*>U}j^xK&=Vd$Q}1)FveukXOv<$O0HzNG;T7@9!N_1X6@z_9|c-Uh-QqO@(pM}^Jp zRGxlw>^Q*oHvcBbQR5`Y&}=WrllDW95hOc-MZxQNRrU}X`E>xj-(jZ;rs)&=&b~;w zFFvIf4ug$-%FmlR!t4|+Y{*;HSd;L?S&F!_O?yeLt)ES>Ual2j|AcuLrW$i z{I4U7U9TZ<*KdGuE?w zXU{Wz9TxQU#_rlA+x1?as~(EE>R={UeV5X)kfVm0qb3+%-I{)sEay1;Yxcg=ldC09 zHhP_TqI#c6HwU5c6{snbF2=~?Azt$9oLt?2Okp-~{kNLTJCbYf!opDJRjplExY4O* zTNf63+3Zk(;1R2x3kwf2&1&cKvtXWo8}VeQB|}X);(3jC@a7Twtofwv*#dEHyJp2K z$Eyd}rjfG^xg*i}MoH5mXXGL?PXW~3F=tFzZZ}5#}-~b6Gv0eLWeln~B#<4R1kO&Nb=k|T*jMY|79AZOfHuXxdtdC!T<6}0 zbKzokP-)g%2uZo6!bZa!956Kj;@s^!o%;X_t)4ksYnF=Yem8$1;w>}@G@dEj%`Zy) z>c*CBqd!2x!Ko~FDDd>c!eu}&a*oDtQ?^~O!nLE$%<>#6up5utQNWAk0IAv6xo)ZFZg^wOkpgmlq zU!JcjQ=e=Ul~R`Nkl3VKnFcUDfGCj;q|Fcqa}9?6S}W%6h4qv5Xb09cH1~5)!B0;t zn-0c_MRby+}-UUpIR03Yt67Xo7o}( zNs}oG_h4_BUuA#;W}SXtSojX>;4q*|USzJ22*}&EOxdCdJA%;;M**ympc5;8LmM3} zQYm>1!7uk*YWyi^#4>mc`YxPSo}X0+7M>t)fAf&uASWxev=;+a#EGn+8T363uauhDOjdSYMCP0lJFSU_phcz0p&fs}&&t^jmk$ z)0XGpUkpO|bAu8-cz-D0^IoPKtFJW+6#{I_cW4F9udM95*$Rz*uqB$z{d{4unX-%A zT1GAo!gw-EdqlY!=a)fx^%cbvUk&rG9;2&*Sd+!8pzRJ5uHkS?I9k~9#X))Z25CM? z52#ZtW?tR)!HBb`Xl+IKe=|3EN!ghj>_!}qs+Y#u#dtHb7%L-_qISL*D?O-qF-8)R z5t@Y{*vhMgV#N+|Gifc^VzFkR3+Yu;EJ>>7VQy7qxb6I2(cA+(3*>ea;I_=*E=A-M zP2@exFnEr77?Po2eb}JTEo@vUFviHOi2ZV;uU)ZQo|HB9s4QeIzp*CC^uywlC5+%4 zZQgmsQodbbMb>XQy|^6{N2&4+f%`x)v$WDW=sMemf9ok8hzQ;~#m&4lgFHWT^7+FP z+2OHS3U`$~Fp2_VIgmwtH;Y<5^*FNrHr;p7N&a8^x1ggf`8Eh1 zq^2#?wfz7w)ODv{m5{>$&mIEppPL`3j-TlIAB-j%;jj@CD~ z-6s(8bLT3u6s*wtTvfyw){kZQkF{^qOg85S1-+=ab5Q6(u3Sx3qTnOZTRHtxDd{B> zM$Qv=9u$HdU0CGBhGWC!wr{((xK?nois`T-9!dX=oZiYGOCJuyF-AP;m`{XrjCds7 zMQp3A6_b)o@G+OPZ#-vGVOi-svF+Q=J?^I6@sZi$j@8J%H4NAJ(`c6S8p`_%Wj%%R zK09}UWu?=Yu9~H zS(Vo}EW(zPB5W~~`{F~u2txyQ7jX^0F)nL@gdU5tBkq}a7#I@wQ#OX(nGwxtATPPQ z<5Vj)+o=F?kT>bwIF-((7pdvxe3UBh=gqXx_wWv#opCIK`?Mbn#_l{tmLyJdZzJQ7 zU_W9>9D|N1feb(MgcEWi-Hu4x;=bNTk#Iskh13Dti@f=Pqi^f zZjo7zCvM9`E%NzqVQ4#&zqz^SoA^Z=+L3%ldmsWsa4*axetA7sMR1ZoM3N6xV_*=t z>-oHy#7pa#NjQ2KcTK18@;}?}VR$>05D(qGK!HdUgyIvICJ6nMdrB)L&T|Gl1q?^v zo9ELil4uoC;jdaKuSBZ2d;@7R&Xjn-^+4mf*4~8^#z=B8Uf=jaw*%<TLt5=sO#>EnTE-g-0mKF^>vn9ea!~c5km+Kw=2K*98W{Zepw$y0U zmOAw+1;4BBKw=<0pvYHnUG2EVcwjafI|P@6d=RsZSv#fZ~5_d`7wA+ehmH# z@?)b|ew@4{KTa(9k%n4+Y-HreQIr=S8^z)yeuMbx_gPI?kt{)4W3$`6&X^32e0QDN{1N@b+jFMHpb6-T*I|Hk+x@qJ>m}NBQM1x zqp+eH`7yHNjz|k$RlHlpmP-%{D&u-pBbP;HJSXScizw_4HUuqzJb%}`f!pyi)ll&& z?!j8LcD=9=uf;%Vu2WRW-1S=5D-M~Wn-2AJvyW)Ei?5ugv$8`@tDCILVQ^gs-&I^Y_Dj8>Hu@Om$5TK z-`kP*N92rjClFip`ME2R4CZ5R*@(6oY9z5&VIj29E0%^((-!V~2MTE9B}8Uc~vW5s}?Md5_T^f5RtW` z7~KJ{<3Y-yqw8#fus9b~gdcF1C2k=`oUCCvba`we zj=DfZC8#d!(zux&K!ifG3*~n(&$uY1$V>kym;XT{+EpVeI1_0H*>1p6adg0$Q2sg+ z$cYi9P#|z&O7^E-3f$=URSY%Bso>ya<)4edhiOe9E?%6-P#hStMM#`|SoY9=LJxwR zjg1?1uwr*7t(iZWNlUtvDwQ#lq*(K6Vg6|_ttKgk&LY$PKvOyqut(EK=!sY(=8>b9EAi*#{=<0Ze&Hg%J0@Du>CJ5iFlQJ$X^uIE+bC3es-p#A{T z2pb8t>@d^`l0e)diqYAQK=7U5G()$>g8_!?(hpJ1I>aM41QomUlkUjoyFyta$b`Ux zOySBgO=nE9f-$|qjSfQeL;vg0Q@(f7VhSm|jHSx;vy2H-e4@bT6m86!>W(QUPlNk( zOT5ZD<79*~3)dN+Us9o2CMt)54ZgPInq&=5{iT;`x7lGqa1O4Eq7icYKn3ukc%(hm z^0H8R`5JZ3h1R=zoezUNPS)d~k`$|RsqHfyTp{!jTANe{Jf9NlT!)-TZCtkrhcnY) zw6#S`Xo=*PWEFOo4{2!uTcb)^em52DZj6V)?D>uqAkHZQ94I6?DH2YC=-QA7cfBwT zPM~rkFr1w+FDb+`;M5M`Qj()>{R7X9(8{fgttKvmS>-&Q=i6dNBH*R(@o7S3W;H3*5j_oV2 zbI%4XLE+&xUwmE`e~Rkfyr|fFDxfW5yr4f!A538@wF=py@j$zY%wB2vXchI>UbtUT z3qc+ibtfkpr?8_FLR;yMu$${hou5?j52dDB8d^A!@%;(SFjsr(rA6S+yuQc9qVm(x z0#>hXdW|aLoPWD;`%7}Hiax2>WZjJ1To)~lGnFq|45W1kO3M&dwO(ty3;JcPmx`ss zAEh%lOV;XQHkCvmilEY5*dNA8>T}^hLz92d(}Y7p$ar`~J1V?cF%FGTXL8$`THDq^ zZrd6gp=q%T=@kD}xn>+E57f26?2XHxrvbHdiHmkFuWVd{0{M6KhTViI_{>%t#1It8 zKX~y&=_kl2)c`*EsXftVqhD;PdVAKM!df2cg}T%0y9h3{(*mK*m##o>Sq_k{PLSnh zE?Giuh*EI;{=Y%V_@8KyD+ZNipNe!dIY02GQdTg)FSPsBA^ovFlk~XG7=ua!j~m%w zOX+cb*Ra=-XQ>JlV?|IDm-{D&Eb$IvCuM)hsH8%Tc5$glD!}(nQfW6bW$$u|7u>vXPM%Mvl=`U_1;4>(YQD%K>4gF$-LbZy;d~5FY=_ zu!{yDv&&`PCjHPD#@sV=(D_5W^>>-R3j4hN3hr|+a^xLxiNkCIJ8Q`2hA|F!>rgmH zi|~Fk0FVXnam<2#h^I3A9kl+g?#0fWqXB!38@s-W{3%gd^eiRD!A_{$kw;VSFm;3< zHt<*Tnsr;=1eu?8C?Lyf?2u$R4z3BC-0)mi$Eo}*b-e0~J$ZddL6(e-*m>ic`My)D z)zo1)Uj3FApT;6)U~kpw`zHaG9c*Y2ih(A1c)>XKLZUB66uEnbg}%ENbN| z>-kkF9F@8OFir;{kXQ+p6Fh|kt*NAOj7y+prh(rro55V1G3(Lt=#XLiM5?SIUo+jU#6uf0gNb!~gG@-Y0A!44Cgc|ExVLe;c&wGB&OyFz65K`&# zmzHZs#u~b3O2R{CfIoloGLs=MPVB$~YjrShPqe+omCQ1+&kQu2OFW?7AV?*7w0@8} zJhDc?5;1Fan6=zsx=}Ze&!NHn=X!)>`KMBw2dE!JwMu=3mP>rQYwsRIaS@apf-qeT1O1w1BqJa6eNcgV+*(=nY zO^wib#}ug1#G<3;=Y8#}whq%H2+FrsdLDQ==fI-+EcIM*a}9u)7|A?mJXKQy+be7mG`wpf2G!VSE4_7L#6tz_O7~ETipHi z{R}tQcfZcMRrBjgr?G^#qvhI4^%r#1`xRPt`)mi@l5SX(~$<(CFL{pJ0NeItjTn%bO#{HXCoZk_&_xAu{uZz#LkwP}k73?0 z(Tz+k*Y`vmw=#W{36ir6f67_}mE|{JSQU&c@tPa}G4y`t1BhPE@xnrkQK5yib2H{x zcJcG`oJwoyp~9HhlKv1g2lC;E3WU9jbmFsX4)-0xqu*%~L{TuKkW7KK@p;1G?XD|K z(Oo`GhI}}B;xdul$GBYhKsIpb)WgxCjH0}miJrHH0r{#N^jl_*e zhiQvNIA*$Q0UIz1Fs@9fAwqNrh%aAN70z*-Cnw^KNRa781k{inC1o zhkwV|{`bb%z7NUTEjAcvOjMDN1*ofO-{uxp6u!nKQNz(&+#S&TYK4wY*T{LE;4Zi; z`v6i724>2Gb7=k+9?xIjU?<&@hkCCVOz%TS?3-mEJgd6`3iplHQ57DJ8%Kq)l&qsT z_lXJ$w;33g(&y)7;J`N9Nq{w^rBvCKlAK*B;nNz!%2?6SQ>-9sPjYXo)1eD@0@zN* zL?IqpEi~Dxh5*&bvP3J}MXhY-TG=*QIjtDstSOxKHUH?&47?_+yuJ}pyDgf$zv}Gg z?Qi>r{dHg4U)wL(U)$37)Xo=I)S)qB&yAgCd^NJit*v3t;dWHr$R=NKCmxkSUxP;+PUmI?6%0^&{y8#707RHhlB z@}6b{yMxjvs&q3)zH}As@!AI=#rE|Si*@L7>g}&y0;SYOz3>_pM*Q9nQeS-Sq)9|y z1##KOLRjRKE!OJ{WIn$DN1dAYWNouUM9Dt1$pBtxQEyuobza%fvpZrfO=w?Uy)@z4 zdz723`II6`IeN7e=h8xCe}2Bqg0@%8(#;1VF~st2E03s{dM3u>jJG*aQ(WR#tLj)a?iwUQ*giBLrE6I<6l-U7Z$FA zHRp@hW)ht}3RJMRFVHYW?;Ix^lwv76z9=Jd#f5TCs2l^7TLOF=_~69n@Dp}CdaonP zvm~ORCcW>0W{HiDG`JUut`-H71`A>&Kw@&$qP>n8gXvIBfo8SoC-7Bj(&WRH^N?1N0I3BrWLLPmCjhIOh;+$fS$xsqQ^TgsvA8O32la51R*O<;4*zc(q!R{dhzJ{JWS*p7G zHDwC7?)Mbk=2Flj-N5_c7(P7kVhuHYc9{~eyGs9Er2n2t|0>eIV!E|s`5Y@n-n6Lw z>8LMqBP>{3R}j$#_=y`5ez5$8QlDdjeD!HJ`ZaRqMy!19CwL9XEuF$9Cn)v(hf+Hz zb;6`Gj&R!%Tto7p6O78Rjj`+ssPZQ=rH(0sg@y1#LuZOHE*)0K>%aEGpXp65%fbs+hPg+xvuEfWP33v zo4>G-sNJ}4Ug;KTiB6Noims)iX(L2ew>1;tji;~8H9(pMlOTC;I^G9tUI&}kq2^6x z`=n(jBDxYm@=nHWf;+qm86oG*hf{CEAd*|!H!HPr6!!xs6+UgsN@cjTm0+SIEb#|e z3bkqGf!C{Sp8Cf3i1zESyc=l)ig#09OYOxlt)(&F-a8fedJx|Q$)-PoEt$3n;l?rS zXP;NlnMj*pBWb?l7GZSKHC*%rP^FMBvG{o?RfP!TkDqjUXJnQ2#p4r7a}(2+K+!y$ z5Xf7}?`uzVAT*+EggS1xlBO^v1Oa~i{5)dZQW)0I`<@Ns;drPqSLIcuc!;PR)zP?( zpoCXhFiGgM>aDKt-`8#QOI($d9Ifr7?rseGjYeBRbCo!RmMIV zj6q)rZ*GEww-0{966;d|6s`-&Mc!X1x3Xp;ZXO~gmW##d0No!wV~=|*HIZXaMvB5# zRWyRXp4Vfyj1-9GDgTu+cxPsmLhgTwD1p3(p}*oqIR|(!h?~g5hiCkTBU+_1h0vAQ zOfeka(Zat8zDT%$ze%igh*6XNCpvj9VW=c1-SUy!BE{N%g$k=7wyLRXoX6 z$n}%g*a=DFEpx04Wn&&Np7p3dBo0j7`fTbQuZrO2-q{qb3dihBdbQJT`BBB$3WBM3 zwyMN~kYZ~+$1d}DT5+>!0WioT>NRUk`e?aLM9xLS&xBiI#2Da)-+pzX_o=za*=j`^ zif$gkXNB!iXJu@YY|IlGZsd5%w|13m_*@wqX1aPKUMYSoEB^$QmdhBI?^w|)pI98; zT1ueYtDn>;W3NY6ub-bC`M7L2QMQc&Ifc7tOf3sAGX_=A>;&wicdTU!$XG%v0uAJ& ziaYAe1yCRSv8B`VGwet{VkB-$BgxQsZ;cj;sr*R6YWaw7l5A}| zV>z1a(lcDBNdd((mZT5NPWP3S?153u?Pu!dex_cypQ+PL6<*|ABU2`}lwPHU2%9Q97OARCWYf(qoL_d)24kD$lOw}ISYU!ENj ztI@}QVIb_@*QxjUIawzPJjTehwsHq_BpYflD9BCcxWeY(g}IJo=zRy-R+~VgfZIUG z#oiC72B1*MDDq@NZkx_LVcId|6dfa*19crJ`6hK>5^X|)p6S7M9)ZTSy8cnB>yJ_f z&j<40{*kNWM95j)_9OC}TyGwY=q=8?j&SW=2U;iBnGmyTo&q}Pf`A6c$V zi0YrykrCHl?~C>Wy`^)`to8|}qFB)n&c^|+KK>0uxv+jvnLuf;KXn+MN$lBiJ2QvtF6l)D{YZoEfy6}%tF52hn+;v)+p zG-@QyvSDj=9kVBE(U;Z2@WmHBPGI!1*O==<_oSe^Sl!qO(O!^#fVI=+Wrz#8r zI!*kali~)DmNEL8g{=YSbMRGHLDu0zZ9ndz)i@Z*ADM|LlosgbOwWg^SiPlm_cD}b znqn-0!V1U46h*eeB!yv4&Y6}Pc_2{1vz96Q&Du0+iK-8e->k@hM)+18=1l1@XCTv$ zQBO2kxjwayI{5ZRT%)%q{>&JTN1^N@HRL7xM@bh}YAsP8;k z1od%R<@>@bG^kj&NiQ+3a%P)yi81J{6ghcP=iFeljo1-{ammzjD|PG<6PQiv%tQ6R zc6k|tq8$!4DdqB#>#wfXrKcdxL{E%P0ZDlVXYM*hBK^@YFSgUc=!!>#f#nVd&Mimb z;I_<02(efm^+9r$^{wFFGJJ*&y5t(4DSPJwliu)(vsuBAUrf)`A&cCfS=c5Y+SZgr zkk~bS#vVT=#r?~fP4|PAaIe_Yc3n;`{Q`2?3%tH07s;yb^{cZ5b0c0eN^tkErI)6w zS@GoXa`~*}uG*Z*=K_2TayiG|Anzi zgrIR6X9&Rc89El;ZR-<|YG5Buu9Z1L#Q_IT3%AI}!7xm`hlw4$(fRj*rKt2rX=z!=9hf4==L!-cgN=TrWH2+2vp)=R#{5 z+5jTG6kR_*m!f5I>=m%fEJLWP8!{C!Q{P@zy08!xE@NxO$wJ9_p#`hj zfg4AQE-RpKMT{O5KrWF!he@G1xZz>3;bk6abQAdGWsSX20U>t1kv*%UEFpGtgxJj!;%FuzPBbBo zRUB>)=4`?`=-CwWu)jkiR`1c&8%ptv3dIp#9+VzB6WlA)mVJn~5%)uu)hYs8Sya1{ zs{*|um2!My*xGuQt!+N5k@a61cV?N$5O<5~Em48gOtRwdc;JkL1zrreU#@ArT#+|@%VD>9nlk$^X2O+0uN!}2MBzlJKByc{{;*( zdkAtf^*&hak*+m~1|fTD2aImHcvDRm_*q^285#m1FC+%6A0#(Hmn(R9ny#2y4yUwj z6mq$`?IVvap=q&lVQYfiZ+X4cxz~QdXnEIXfc)h<&&<#uUxPbi*CkE$cS7#N`Y`sm z$IYGMu%2Z&6%#%`^?IxGdfA3|8}69m#DjcMGFw(|*jML4PJt9o;R`HOL%b~@S|JWQ zB)!ZVoIRX+^GdvHdLP1^Q>pW+%R_kr*X$V-b>w|Gf)3A9Wb9VPlsaEg)94dQgx<661G?u^q| z5Qo~2vG3ifUQd)UObp)G7`)!p8>_*CDZ9f_!1a-f;ydV~18aisK=BitJ}G9KP{9qd z1Sg<@_`H+OluXzgS)U~eoPc#&nqBT~ub+S>YoMAPdjf+(HZSH&nFp(y_zG_`i&taR zqO=M9J~Dn0Y-4P$SlfmJQxhtN>;on?8x^CWN2a5bI#d~CSv}T1gb_=EPMmb@ASC=N z*##*MiB#=5TwxO5Re26O6bS5L9lIe38O|6X4n5&WB4x+M~tdW_AGP%;9Tx5}DmQgq1 z_o4K#Zi@Mr*llUn)`avB3k{YIBJmRASCAL@sqYBJtMU#f5loCO*L1+2&;h@%eSfk$ zYcFQNoO7^A#QaD=u-qt4y%NSNdYA#Tsg;g2n9bFsih0~#;CDfE2T^pT(%NzNfu{(Z z8)VyztDY35p`!hfmo&rHstblSH5iXEc<^^c{(W zH5LmLjCDIu>|EC6m8E_xnNEnqcuL36O_7YQ+7>VR3kw;KOMTWvl`1v`Lhx-i^-VTC z&9Lbvu_-VpAFDkRSJOnyb5lIj9#EVe9fo2`=&L;86xeA>zewfhV5Wp0OAvw%>AE6% z2ZwA#(0UFh`k_P#7WS)^3XNbT@f)c?fWvY`TlmB@{7mo<$Fmo?CD)Kh0O7*HA;k&L zRh7fjmf$n_+3pwz8&&2~ff#C{EgBf29Lti*7Jxsh0Sbv;1S%NV2-9^0bu8+?i5SZf zLkE!ae@wv1%m5WVs?;3xSYaM}o{IBa$FNSn zQjV3Uo6S}iV%k=TUn*3edRNSkc#${8EkaYxV2FU;liR5Tg)@x?9h|E)TMx?SydrV@x%hKX|RD(8|Gdsv&CmrPOLqb-Y;k zy(Z^qLab&9nn-eU#AatBlQ540lgT9O>=v+5U7*FrPPrd;ySTI$Px&D|WwWN7KSmwW zk%tV$<(%NdnZ?jkyrZs05&6QTZY6GUHtG_jU+{(&;(GL5;u0W*iCrS7jhiW7bl9gCm*Lk|B+mEnFC31-kq(madjunO2mRT= z$YThB5e%y|_7*)Y*s=p-rV;Wto1MpyFaKkb5uRdPM=&5d28!84!DALuzJSQrZ;Id_ zgopoGrOMuwrwSC=I()Gzf29akG$w>IK4n{fZ6>rV%`zQfX%~bP$s#zbKsyo*TMFeo zsi<8mu?8Y;nf8hcVk-)Kjeo8|j++vmwMFU$gMZd5cQwY!)6H;h^ZpgCgpF847 zd^?A(6&pA1wVB4Z5WLu~9X6t)gUa>Q*ONnc(~OyL$BIOUJ7L9A!yTKI2i@cN4qDVx z{ece6qqaNr;(snyeRkvxe_H(6|7nTsc_N+xux^OT?g(_-LvOz7ze)+lZWJk1hZNGN z-kD9S5>W+YQv60y$O#@q3Mk%Jxat|S|IxT@a1j=%qa+C5sD>C4_wG6qqQWa7Zh$NcqiR+1OjY{1G>db&8jYHU)BowN z3i74`;^)v$d$mpwMvk-jQ=_&l(kr=GLKMKJKnLW}ITVeS4dqVvs%M1a(vFj(D#G~{ z#H8c?cxC`V85@d61JvTAc*jsA7mQuRL~Izw#LX&1k$51|X@$Fd%L<1BEMg8|yY1s5 zzUo9pkNB}+cD1ABPA`2SQ%f~0bb5Kb)=OeHza(~;q4(jE$dfZm+>GVx&K6~pqLq5A zo3&+l!uFUMf5M$No54HJgBihkIAl2;1O#!xFhSnEqI4mZ50!?YV~x0<>_lFvyTgHph!l!n(2n^H`Wc&WC7^e#|w>U1&E3uXr4zYyXjIQyfl3Qb!CX?4H3DL83&eBhJL76 zGHYVl%QjhWxY{)2`@-{-gz3GaKe-XO&^rMow{;PEQKxuX1#5%Yni%pZ6hSw6Kab4O zE!6GhTKp+aOFP&xv_VkQF9pFOjqvjMY#Wt`bTREAYi{Vw+>AK?w4tN4l<3!=3-t9V zSBl1s7EaT%i_I@I+2VJmy0_eM*61(v$TGaGG{uCjbn!X~m3 zA4GMtL*`P`B__p;5NOg5@3L6D@On5X1!Ig8s~M*ZT5-pL!JG6-)ly@rKD@7#ikQn~AVy_HF&_%vaGIty^ z0+g9HAk`^EdC|7%Onkf9BbPUAzc_(^dhGPrU@Ko3#8-x{`fnaPD@@|sx06<<>XJ^w zpBtI>|AiJ>o6LOJJ9`tE4Vb5Q4nvG+i(}3?R)&2>^SeJ%WK$_PIhzr_&p5l$hFYq> z)Tonzb_KT`D`=+r+;Rg z<=p1FR{eo-eu=-|%HcCcdRB8#o7i~6#_}x&;Gutm*W_T7XnEE@&4V+Fph(}js;6hi z%AQe2@b5WP=$3gLZrEXegkOP<%qIgn8u7119B6d*f6D4JMs#^@WZ`;iKBIU*nP|*s z6^3z3`FO6sFrKuS3#I;*K}`+QI{8nS2XeWflB5#Ryz+rHhn8NK)|i5Wcam20M@6VK z9)PS;$-)?t31?Y0k9f^PB{aaGeZWewUxtCj4Uj)Og9!Ys+FhR6>_Ks}2du0P6=yaR zsHd=<`AT@~(4r=1>lH2M=XBYQQBYrN(gZsm{J=4cK;ttmywi;rIbWIN0-jT=&urQ* zcu5J<7ujpJ0pBvQ&W`;4cFXAx8%}BD5C5EnP40ks93;a|<`-MMlEP$eBnsamVEIkI zFng*Z4pvX(Q{#w94Zd{RDg9tUKl?u|T5A{Qs5AkV$`=W5v4Wi4!kngRYHr)Oqk|Uk z1$#R4@r;urScno}xY!)dtT~x$Vz2cmuajU|LaPjF*`3?ZjkgqPx~RNWpk|Q58VtiftUGfp>X@yea$`45qDaIpP%xNL#JFEHC20(+tKm$+)m@GL?$uo zXqI`JaWT^@m+>o`@t~V$JsjeQFG&>3{ssGPmW#;2kXvP6ITA6BKcv5uU0EvqfoxIe z#)IgAueme;x-4Np6a`-8tj2ScW3AA0jm>zvjcrSWS%BYC{7Dpoact?SL_>CMW( ztv+Si+LVbeUeLz)VB`7Zz*fWJV$~~pLG|+A%YE(ys-@O%++@r>f5KH)xO7ydznWYD zZF6A=^bG`>sJ^1y&I$x{8icUpd5i&GncpN5Ie4(UK|E${7#i!iHy61?k`4 z5sazSB-u9^AD&+Y78n%Pkrv+AEHS41_EJ@EV$1TyVmh;f~@?LwVZXVy4T} zVC?`?uGVWyK|k-@Ddt_Q0L25#o8*(cJbyu63dwrFL(jnI^Nk*l9Ml{4+Z#7hbXtjA zLPohKG&;VS{PF?S)el{zd3{4nm2gW!mHV2O^QTpc!$S75BdPXzE-XbYa6RRNN9v;% z7gxHbH+VXj=ViuO=t!-D19K@*E9YmriYALG@m6svZu^*1y0GOGjo5Z702Y4hqQpwt z>kgNgCkLpkodm1eNifrzXexGEr}5iPPjj9{F%nngY15}oQt?|Th~NKtMKtGk z_pCtr-|*Wr!t1QZO4h{!aT*`-^ZRZ%$<}YV)K`@qm-<2DboK&+qsTg^c|S>$ShIN6 zPdJ|CXl1)QD}SyeKkCBkS?&>}%d>WL7Ub16buM(RlI~zOeZl_4?VDMlpiT2P-X)|n zbN;??fIg1zgaRaepCwurcX1x)5@q(DY(MU|b-U)xWbz3U8$Pjo${{@Xh`1nuy}3WXi7e3c`|&Xz>=Oh*(0l&HjLM-7%$i{W?3?_C)}Eo4g1Cs z?_Om2tMJpMHr|Y=;vI>yeR`5Hk;Fz3wr)y`k$F+A_;z2xeZF#W!#jd&G>0Q^=jTvU z#-{CDv}d1kt9;P@K6jr+b*c&V(Cr?6*GK&F{?Ygc-S^2`SLv=-?p~ojQq7uBj7LSz z{}rp=R>eH(owesIze9S?9;*v|wV_qj+I!Hia;xE8;b!y?xfv}Uw5rrg`xyrTai4I; zO-t3U>~f%;0QB%kTYV*axklbrvmBe1-mqs+yXCx1yFJ~g!j)-{&!}+-mg7ggx9bDl zjFSMT5)@rQ%k~{Iu@6=W`Hfb2s=cSJp1-~0s_lj!_RLpa-~(0?cQVZR&0Red7w_`s z$m}4^yhA#>2&)8j0Gu^%#(&PdDb1@~*5xj0Wl2I$;D({f85Ds&H?N%4<}R5oNS-(D zbY-ua${jXZ3~;P$TS3lRFwqxHF}Fm(v~F|i++)L#R(Ksila9VpIYZbBaXfE30+aLN zMd+=^jzoblHMP9=cUt5V&No{F?Tdb-uh1dnl}z3hAsHjnpU~$&xT2;#%8f&iP_2O@ z zEZQY~nz>6F3hX;OKR4{{prEYGCXo|OXx3)?7sy*e*2P)t?y8yj=;y_s)Y*U0 z1=s%U>raiv`U^LB7T+(wc0H=GTrakVt^5V68szlV31agsbT&Y4(BIP-|9+$Im-Kg! z{;t#CVh z%VD8KOv-P8)8vHAb_CE)OHlNI{}7K;{wn0w!Q^JVL*PKC6e69to_`O!yadFi=OphQ z1k@N|@EExhY8>Gq!gTZ3_)q?VO4NQ&R3ul&I5A_JHh4@V#@p7C8B9w1L@@&u7}qfk zf*6rZL~84q@xpO1=*rIVWTept4#9PxQ)yMDTnY}%`bC9}1i&9XDaV5^c6?>ghO+2> z2CX08=QZ*Rnnbl$4~1ld?sLPJc!+aha-rF5s#MQ4pA3P?#B)=+q>62gWky-Kan+g* zj&cRlkw-ujR0MtYPvUXX3HWGhm$c%7M9B5xhXnsk&6C>?-aUb>NW`XM28bCsrXy1^ z7^i|glw9NQHxLFRh zgsdSDXdT}IDQfVn4uH6Rkp`kM*eE=J^9 z1k#1I8^+`d`^t7(-sjLBk4QGQAgWlwU|FcoCq*%7co*{_d$Ef$Y!6VzwZN}sUo{}~ zamzb}odOkLE`lgQ&$3GxFdnQsyEYc9xt@&zjaYhtVJL&nd6FOOBjM5gEU#WL=qOvcHFNWbJ9{ zeomZ^B6DDMIVS}@3{5Xq;0tdu zn0g;GIK8R!?L*y!&E;iWNr>xqP~s;)lc2<7x1(Za&&0n*!J3`&;X);hV2cG4@~zsa z{B*&-{d7^0k%QdXS*CgxyQvuon0Lz}8>?HHhJ9OD$TVyO3j%7u4WrLm{kSUeY^bV6 z!Dxgk>Y#LGtS!`lt5SQMmLiN=na?4ccBNp3`~GcE!jqa3?9G!BSK8nsLCIOd+DjNZ z5wzcjL8sJ@yFm>l!xZ=}8jpJDz6hF!qL%$YsX5s_A*aJ?!<8X$L9HOQ1(o{3-KAFK zNUu=ar?rM#cW1nVk@qsWXXJ#I%y?2^p960bwn`Wimv_c2kO#^AEQ1GSY9oeB%KVcEk>x z95s%LHr+$Loa7emtP1y5uv%iNm*008>%cUeB&ab!Zq)kRVli0U$=JSnLphdZ>0H@ z-y6W_IVwrqR9F+wL|7sj?ab>381!c3vq9j%f4~zT)k-~;fx@hr)txUDrdH>l`oZ){ zmQ$l5THHEw0~E=(IDg2P1Uje}zu{ZvmvapqzHXppGYn)mP`ZWKLGplkU<9_NS4eAk7+zVO>%$^2w+;VJY0u$( zcK*Py3y|vE2Zt z3QrhY!Qo+CpGL!QFyNFqb?xiCXvzgt#yqcNV zDI0JaV0V(TZoPvP)UKxR<{g2CFGbI$lL=yX8;A(EgBpEO9J>M0tI_brlo82V(T{-l z9-P}mkT$1oP@`@+X=hEr)~1}omCvvX2esk{Gfc1hQgbw9)3!{rB#yGv%b+ZV$tD-= zvm?U>%GeI}lyJ6(*~p6mW@RL?VCNIJkF0F}KYMSs+(xo22-dk~ZEbz}x%(N!WwSF& z0SKT-EkLDsxr&syiHleg4~$Fz2_U6JCWK6I$76p$+dND^%)^-VpUnKpy!Bs! z0H`We-M;4tsv;7Rv4@9;yN55DMhCF$#w+T23etrfM339aPQsQLE*DActDouw+Qu1T zBF^n7`V!YVp-edPI&p1>2r>DNp1h~YPx<^EdO0%HD;dU*^Ai{+!V}SzYw|GYk^^x* zSY12>L>7gtyBX0{mjy$NFf_Jh9RRVx4s2ECAXDZ*tNP)gBZOTjWklwk#t@`I1j*Y( zlLN2L5&)y*H!I_%!d%A2Jyzh0l3o#6gOXV?pc1k3RU2fH@>!c0rb&_7VKbgIEn2-qvK|8YND)|?cpIu_C^dLIfo4lw#R1Q3p2AX zq{7CmGI6V=qVS>U4s|ytm47pgMuSu(7Zj%51d;1q}jt))_kqDjC zz&i;qZqmx=sPuoA(->lv0DXg=#PafI)^>B56LnI6n!iDQzNVIX+5-80& zQVVgX#|d67135yY2J<^YD-6rnbr=^cgqD8H(~nc>>pXoumHswi>2DJ=-Q)SaRQ`mg zCluU(5XDU~OzwjiFBmS!v5(*Gxc=9l{tiEFKTe(kV)l~wY59UG^u$SATK~LzcXs;* z<|&SUcN>*T<@wT50l!~7U#8!cMfSVGo}U$}%hkojMacNPTBuZCym(&ysZjY2lr=RxRR^i~d@T*YY!?d6N1N^@T z|9^%5@%R6HT$QUQCrA7dOZ*S`|39X({6(ezC)kQ*peh5e3=Kf!-E>~XsRJi$$5A$53eZ_P8zI0W3Rq4E}F1~!xKqto~5qATf@79Hi z`r^{yW?Ys&-*QU2H>GQ#&{-MW)VWic3Uv-LrdJ`{S6;TTFwRBW+&Z_C7cQP_hLwDs z8t#^SYPqYtn(@|3+~W1wBKDRxNwpfYEut2FiOMn;UqWZ;&ucdD=M@yIqE7NZdoBD| zH@c^HfSF#U8hlOEJoD^9R%Z{=tRG#j%o_Jt zKvStZ^xKnH72nqJPYDVS(5R~(#+cA&vsSUHIo`tN4$_4H~x1O1XQ3oZ;ovs)~N+os|pN)kS>~ znr=G}gNSK_SuD_b&6#QqE*Dd9xm>BFnwUHx+Iq9;sJitD(}QC3Z`g^(Kv8}$6@gT~ zac{lUYyWl}F>R+p7n)!E1S1*M#WQun(F)u?Y@@oCp%e{$gaYA;tu7rBF@Hp=kAB#R zW`LI1q&b}%m$5trtQnQ$Kp@tO1DrS^&-BS;dh6e~3xr3_ak+n6qDB93{4%C$j=vji zE>R50<~|=VctiPfkFwGdopXuMSstp=ItAjNrFZQM1@Ro+nO#vsrm?2K0p98I4!vtw zk;j1H1PIa>poEsnbkp!fqQBECC>woDhh^poGsiM_qoAF^6Xp>;E(a8kHzTa=4+TR8 z9eQ)WeS^~3loc-`=6{~S{4dR8RkQakFUF7Pi8x$!tpDs;wqV(hOL+)8_qfQ++_2kp zt)*9Faj8P8&t8%8GdQ22*I%*~GHQ2;jw`#9u*{@0Qad~7&Y*dj^22W>;shWLTtw#n zzzrv%ZSK^{$T{VRQ|GdIrkNGAy+Jay>x&A9KX2kcvpf*-N&JW-hCwan{#G1fwAPA- zznNSX2oA6tgMgP669TB>!r#JeJDJoW=gB7OfPjOlU)p;dcgNb!&^A| z&C^&(2~B zmByUs?rC|68m)f3f&O{`aSRe%$~4Lx1w`|GqCC`1bq1#ihl{kNdwr z<1^v@Z@F5nF2DG3|Mz$Q(f9u!bNbKR|IaHgGUxx&^Q9l>|DW;6-v6PG{Ze0nfqo6x zdbPOZbvm!6Tmz!yuTZJ$9Aw4Ce>r!9T6I(J24&@y0=?Dx7iywYuvDo+wSs)DeLt-c zRSV^T^Qw9A{N>|op`r%7VoMT8h)b!9IaNfTjH-wKa@gs`Rs8qO_fCt;aiKM8`Yqs@ z?|g4)m8;K5m6V^6s-5jyL0$-&*FmbU-}5?Xk>6KN??WN~l2Uc)krye;_)h^&rSOfd z$D`)V4rHZNvO#PLT@Im3=NZ!~C8F8sG8fwb92fsR8gS@!DBa8SZUz1H6|~=dfbJQ+ zoFK}5nd$e^?56l{lzfy>vNd6jC$4Jx4*XHA;U8Vr^2i0HFFS7E4{mGyD2yo3PiqnY z=P?VObFV1`k`zPEXoCOrV1Plkxs*I`dQIrp^qUu-2~|h1k*@uu)%(rBGcF^&X9GYY3%>CDI=@xgg zEJ2eBsUKYrx+Z~o*z&wi+BtV#m6zXK9j)lGgV*lr8FHW09K$o(BMTqz<#k#Gbol1l z^w%sxi2z%m8x{pq*Z|2%)O_(T8q&-wh2|NldO^5y^E7Y}^9{J*la{OpJP|Ihg3 z%l}_2E>@pce#rm-{y+Nu|6@-7nft%G^rD(R|Cg&jbAGkIoa9x*zdOi6pGyC!^oj-$E?_?62KW1;*yX=%BkScL3e)M{-{qwR&cj#-dH$# zaM2A8Iv2!%O^$1mkS3sV;7;2KZ4yb}qVxq5IXRI{Ece>=lR|>dudE`4)fjyTp=aT^ zHXPeFRUaOcHt!<3N6#tq6g{5s%+#BN&+->yEkC+YQ;oztI|;<-iKo{-7$w#vuYLYL z9$&(1UgA2Z^0pUviD#A&`MatGr&wKlwzT}bXs0`-mwZYc1(pV|g>gzR$$jWuAM#%? z@uK3}30=?D7X@Cdjo>tA1UQ{Jg+!70$4%2ywz!wnPn2BAdzK1amwoR)vc5LPI7{%- z>Jt3}AxNni5N;ht)SCl2#jhp_@T*0x@vBX!rw5PR;#Y^X@vBR^_%$T+bc2vSevSTq zoI#lU-+q+%9lu4rd&Q;E`~Lg(hqDjA_X7Ag=zqQVasT@#eSYwN|InX&{_p$Zf#0D2 z1;2jqe}Bejg8tXD%Cl$BU;g0#{_a2e{{Lf6|C#%L8Q8t_`MY>_XMD2yU!eg7 zy7pJ~RqJ_c>BaZb{?e-Zd$lffNN?xAW-?glP=n`T%Clex@c2+mFpHk7@q02Xv_F-oPtr^hHb*cPqxfr_JVqa zhMCxRpE#=5l{Jk2R@J_GYcz~umIjf}lyhc};M*p-j`sgCi*65FhAF5N{+WyHIynuN zN8u1Q!}OWsItJ}}zO#x+l@`o5hMt~kC}nV-uCAq&OH>)nMweqW`kd9Ko9W|ag4LX9 z%F7@z?hXC$OD(U5WpqSQif`k*B_xybCvN=Z@wpA_*4f#I-j(lNKW1GYX)m4j!C|z^ zr!Qx9sadjSR-2waQ>AqRCU~?~x83j=GTBGjdsTF+8OACCI% z5a)3Dd3FGQh}REh$t)ASAn*sVAE$<6I@);rVoX;#o^CIm(}B!t^>Y^MF#xiZ0f*cM zU{bRiF{N;qvF$Z#cpN1$)zmnRWO5A6vlPwqB}%&GI4V#c|0j|k=pke2Wg1E5W5get zL;o9nO!+Un_gP>5va~qZy!&2PfWJxq=h+Yb?@#&skpKQefAZzO-xm-3CjFo45Bcw( z@yVC}zFK_wa`A`$&p*IN-~WHi=|5xt1DGvVGUxwt^~d@Dr+m2n&jqSA95a=NfkzJU z`-o7bpbun7bsjvX330--B)$-js!n*M`GV?6grxZiA2t)x+{1@nNT{yGzDHUwskTK( z+--a~ObCsw@hfqu8pbtLLNj-ddP zRq%TcUeLlfQ05~(?0V!gey_os6MUG5H_!3m02-n*)rD6~l_Vi9ZSe$N(SGm2!>@E; zAz7z01Ep!g7CfBMMSv_lIw??^>Y%*$$bgO&I!CKJ;#w&iP=!ecQrS*cePQ2 zo-+-<&^d1q-u-}g)0e$#jUAYe1cOMc<#4SI8QS+98A~{p`htBYJ`Wlg>~Hw+FlV(2 zupqGv?$?C3exn|i2cx)W(S?MijY%kj`l9%8a6lWc4Os}Xn6S?EdFPsA7EO;vSZq4| zdec!Vh7pDwcVI5Ch)rg-gt3{7Mxb)yTJd?SL1RQiQ$o=ZfC46ggldUCdALMuWQlW3 zqmqgUSVbDs3&AmYc;F$J+g`^Hy+e564U=1#RGx{(99OZS5*m3*HS;B8@Cb7{2bIkw zVhCC+DY_LRXRfLiDYSgjq7ZcMl5)A+X*exhmpPXg#FY?Ao_;wV1b$+4+#n*doB{s<9(Je$uT%vTbj2pfZu9$;W@HkT~^m`6f;VIiTBL~jl{R%ri% zN~h6z*l;}fW8?6>IUa$MhgIY?!wP8i9pCnwQ4s4fRBh3tz^RbXnN|#-q8|JrAgvxe zd17mwg6%>3l^N22*Rm6DhOkeEXpt!ghNak8;E4_gztyaY-JC>@CpTl_U{Juh%1WhH z6?lrtl6SpMQoE;jO>ynKm;^;qYzD;!i8>u19c$+mvIIYbNci4uqv-EfKtSy2W34(K z6IRy|m3wNH4Y9J}w^o)!gH*4Y*sH|sRq1gxrTf&xJ|)9%tqLj&BWEB(z_IwkCquv6 z#VGRmas2K@*!Wc{bmh_EuKd`?z11gLjh_V%o7yag44@i-a6 z`6*u<)3}>QWfm`#(1@gn`b9n}K%UWv3RuYt4QPnsbdzrA_gO$)o}g|$^=#ST$6I`B z$xidwe==R(+Mm(ep?&HtSLWVxlU9!{2zw8s4ZVX7hGMqPd1gK~Kus_@3otqx#Ad)| zlVs8^gBbUbK*oEK?wu>l#xPKb%QQTiIhHO+OJ&w=<}3fT2^ympEK zq9FzM>yzPTBLiq@h}_hGJOkfbIiP)$144)Su(O*3G^=T7S<8UQ>DU>PH_qk!eREtA z-Myq_z?m~&$LPdo&Zcz_gCEi6=Q(yl{do>AAD!+BRPbgDBye|p2_v~fDyHv;Y#!j< z{&;Y6IVQJuD!%xJ7P|YcOmA-;8fO`@*EXPWEs@q?lM@SA$}iRz4k{Q06yw*gvK8B1 zIWM*XzxAcqr~y6N^V?qj8wwYPYF&ZgzE**#wG#WtGu=dLrU5 zW8BpiR6=3Pldz?Ol?GAd{Hy`EfJRQ;@f?bJSmb$ud=S@As*FcnXAz78;9FyuX#dWm z;gEgv_aTKl*$o8LnhU?yaZzC!I3qVqhp;yy*aL}bwd~r^=>c79uWTmp7aD?*qVF3S zayl?tLAE3Yy2`tPv2UQZLeg+~-5Zd<|=vzH*xT>EGffPwjHw9U$R+iIy)^R|9xba&Cor zA$Y?~2;NYH;BCOaaec=t>%<8%29U(>auFt-A_elbBrM_^iW?l>F@6e~i^`75oP}jC z^l8`5Ha0i%Oyh_r0utX32czT&;FFcl5F375K4DIEVt*KUxH%q6Dxtz*2VwjX`7nko znsP}hsqM3so>7!WtJ(14791CDh*nCx0>-fNA3)uQGirj?D4$@`GDl^GYROSKrd7cy z%BTQI;6>p(@0LzJYd}#E`od)2ag36Eap=ZLUivzQ*=4wqM&X*C1?D3s!LUa9kTV0VLb;yS4khgN@DFvoX=h@*RYeNkn-R33`fBQ@|Qgwwc4Dv+r5O z59_QRiO4q*Z4|}PC!=Kx*s+~lS9+Iw%9#m zd2iK%Udtt1&s(&O>@w}kh1nEK!9jM5$h~^Q;qFi%?Omt!@Ni-^LuT`be*|^0>g+PK zf>JVauR4vV4qdC-sciF&X!Fb%(Z{^b-(@==c^sGf-q=|c2g$B;m{_|6$y4@^!B&yE z^#g3a3g;+Yxgi}}{(=SW-7UhAxmYN)>vW37ax&}*hdto%U|eD+1$9Ko{0P!%yd;3q zcNroV$eT~p4E&C@+XO8$YenctGipXRMSkjt!01I-Eq)HUF(n941nI|#mKP>AE+TQ~ zk1z1mB~RA}$V-VLNFYPQ`Nb%xwk4<*sSNE934IE${gwGzu}DzqGYSKk%D#Du_E&Js z>@a1E$oUB$sl;gDwtV0xc&-Y@c9)PefF0Sh1~yq+SXhH^6Wih2TYUR*Vc{cx%is>* zT;rS5g@sf8CJknge7}Kt?iLpAvUy;vm}4JvMBc(eRYFJRekQ+?o6>Ic6J>IYAr*xTm^4OIYs)Ds7Au$TafPtO(>&a7?PX=k3R3b}usTL~|{ixgK0%R3e?#H#%?zYKX1!cHomlCu=A4P;v%9tEfr3LrpFVXG9PI-y$TNK2kCAsLv2E6#6N7gp>1_)1USHgHAJ~sU&1x zdQpH(sgPYfiVEr@JHhKjuIrdK^#_tplWC&I(>N`)-Hw?Jx#+>r>ql1xUD}KMmUogX zdFs12CvJB=8d36bMw=}234p^c1#lsCV(EwJ`KMwfgk%pW_a}w;xwHi$J{sT##|4RX&E-* z#8o}MPN>A$%ko(sOlvg2ZRK`dhDrcfbb0C?Wb^@U*luKhqlto5U!&oKfcsYq(Z#P8 z?DG)5@P;OEaMuzc4ZECYePg>*JC-j|>c#SSW!#<*4_+BJ3dJmLH!iMtWqnT;i$HhF z5=Kres`k~wf+cDUnT)}!RwkIuHB9HTQPclt%W#dc8dkZ^s zePQ9m+VLi4XSm_gAO)@y$%}5J_*$>5kjYqyRdDvte}x76fB(;--54WNE$1^<^GWvE zoz*5Mdb`dI(iVwzgW#|4bZZU)0$6h-o41^ODQZRMDVrfor0>N5I9rZoZ8}Ekd9R^J z!_5q7xLFPX6m2B4ZA~D8SDq$+(1jWF$`T`4{L=T9GZsB{m|O!b8_{RRKLjXg-r$$Q zwJmcx`c_X|mdgrm`E}|wuX|mT-55JPvhU1MVFb;)urPN^CuP&Q#tS+QIIzJ|s8~S~ zOcT#_oda^$Dxyr724L~53d!cN#}u=V#e#2fw(zFD zwXm?2MR~-!aS#+3URMiwBNq~$#XTR?o)Uv;3uXw!Vr!jj0vPU-E^O=C(7F~swWVpQ zrO6&(x6V30wAPJ73wS$n?eOMrjYe;wzS^zmA+~vqm*RXb=AE9r6}WX^w*&F#C1rk$ zhuylsc=8pKzuqGDG>$n-&4YHK0mN2MqqA-WiFM70t93;!Z5Wi&z5?t`ji~9Ggv(YQ z&2pp2mBSo*WY;`~SJUV3TK3>Qop|EDnQ+ve;evd0q)<1D_t+tIDdd^Vn2H}APl|&Z zV09526zTjFDbD?nkb7;q_yp>cOJ~~ohq@$ zykxLe^CSarvXX%_eF2&9;;V0ps!V|NP%YDzYVau)uFZqDhtXO2s|(CDT^91_~H4!VVmGwLbrGT6A|U& zpe=^C4YDeWEOyfye>J<~*G!c0o5`wm1TWlsckD(8;NauJf?sA_;xXQPi|rnH4(zdy z$eP)64p$?L)fI1C`^ZmrA97lK$2YjR$kW)keujklv=NKJ0x0;R*`-yf&R?*B< z(JWJbyz%g06lbXq47+fXY&dt8@7YYb7T&_q{&8j3`dDTQWY<2Lw-&s* z9AY5{_;0iKA(wfCXa@W^yriJ?aR4Z(&~P#py2Yu`AVY<&8dT_-qC$s~3O)V(R46C8 z;JBTWSoot(V$GUNCaPhA7*1`|iG|gmG5~8n4YKnJWakCQLXkU4hjuJZqSd2>s|5@r zd`{~VC~-)HCnAG_x$}$i=J7hYC?9X{Y@NXO+UokdMrKk+dNrS$rndEZ?-Xc{p4sD>)G7fSwGl4*oWuM_ou77$M|mb@c3f)VEr9FAMBrO z{(5q;x4C}`Kewv*XA%EA!#_*-XBq!I$3HLd&rAICie|3RzqCwsu>pf@1*5iyD!ht| z-o{G+L=j$A*}kGH+gEax?JJGi;&O$s8)p*Lx44wgxDJX8iCt&~_PqsrJmI??b$=5F z_>WW}AU)DoDy$vUvEr)h=a`(3u%SkD=ve}(c}_y5x2@3-1M4`66hL^KtsiAIU?`dpCwEo69>JIEn;b9j_TljYxm1b-xZk>z7DHza@LElBPy$7B{D ziN1K@nVk8%v>K4~=^Kr9s`eAlIj>$>Mdk2l87dfnQ9y?;?YJ$!gzPa6kk`+x`5 z^DbJ<4kMFbrdaDXVYO@mwmiZN#{C5xIDzEp1qqLp=rd-m8*)3~FCuHhX8CkMHy&!7 zs&3I=Oux@Di0O5TbwmT!I^NduF5pQuSIw`t&*}{%#XBZVT9-_zl3d0lPWOIH=2<~l zF!rLxH5N9;IRBI(FhO-;0seb>{C$Ilp^MR2Tf$?PPBNQC-YB9v1qGg zOnurV#e$y5>to6F4QE5X+9yLm;_TdrVXDWZr@=}q@CVJv9k!_@7=@W36o!Fdf<-UK zi%{k^JxrQ)1IYn__2G1B14ct6S4Ou*Wy*(?`|(LMYW0{Ru14pkP8?YcS2cj6td>cG zj$EtG7d9LpxKgnsW?$wua-rPHU02IsG#?)3Hh_fYAZ*KvI31X+Vv(u_R5MpJidQN` z)_q3`2XjV9nnFgXy`;(tR7F9PTB~DlT_BDPb`b3&2ePTDLS_(;o8@~tDGAEF$p`gv zBv^{H&F7H%6F8*im~@?e#1Z0n1txo6-Hx7EZlq~+Z`zEMt({m=l9R5n#!5SRLrGBA z1yze;Q*m8qYL;FDncC&#sU&7v5n^5v+4m;>gsd*0S>jUk({Tb0Z1ql=xxqV%B7nAl zv(k7IWnnJy^UzItmJQ_F!$U{NK8DV@nrEq5!En^XEK zi<`;fmQ&^!K&02D;AdEdoieLFCzL@MH3DGcQ%Phd?is@w(o!NP=;lP2d^j#<@i>UC#&b0`nHDx!@L&Uog>=r?hEe zxxwR#MnhTd-jn8%XwKWBD#9c2o8k&Jg^5iKPy0U%PrtL_X*Y7tg=u?Hh)Up5F(U9o z<5N(zR4`KTtg$EZm*PKU(xFj79&bQf*AG#eKyl|WRh~gZP|knBBQrJMWxB3mlx(5* z%XOx4Qf!c3L{W@&KgB*F0@8RR>N9HystHNBp~VNrq_VUq+|X0g7a%#j=VS&l3RhM3 zbWJ&o?G#=JO;A)a%(BVxsrlio9yMP&;NAf^(+1deE#VXOXeM9rj2X@?!#ZXdFlp(> z-mqb6_7lAKid7fANGNE{QO|0-pjy;H6>ZI{^3qttsgV{L&0J)u@ zX-<}&TtF>5y3`}*{gHb;HOqAQ0TV*7GUt5SScb-UvWNjWv11Gf+vKP9w#sqLx zbj=oQ#+5Ky91e$Q@I5sjCCPRDQmkA1oe{4*(hAx%S!I1-`1lb<-5JYOK80csd=4jC zi%;52dFJZMa@O2oS!>61b(&G9u}1m!JyXnq0Dq4LI>r)$c`Yf|=uAWQkGV?Lj3d*j zFs-IoC}q!PlO{VLxXD5fC_BW3@K;fT7l}p<8Y-g(YsJaT<7lS2lOjJSNnyWuRuj5n zSlFI7GRUvEA0-8vg6i@qAr`x)h=iRvoGa7@G{@c?2S{0STi2*n2Z18(^;E$n=~}ni zwYkgbeL=+Ys}1LtQuDkA9j59#guF~WCuVZCzD#J!r+N9*lTtXU#o2I>O{s?l$BzWp zsBVp%W|})2X?lC3JbA!li*jiJVLH7hQEkKvx5b5R+nG;S(a!LO?XsxA8lmigPtB&d zPG{Yr(yWa5#FiX@Ml|pW+71js+KhgXH{&^@5nUOv-ljAd+c|giZA~qhU&*)aS`opp z>oAdx4mI=~^vc>3=h+%^?>VDJa|J zQ#t3AQ#%O7N;*NLl)FH`pMhIlr z=roA+8>tx>)#iAnI{$`uZ7SM07(3v^o&GR=_T6X-ieFfcns4Q>Lk#_e8#HYqFZt z`A2d`Z)niIPYDY~HlHyD5PQx?JZ9Oo&Cb}Nh;~Q6XvvpakFw(J8*v=2;Z284?yR}F zO|h|AmGUkf2C;`qDq>?QP^xF4tsau1V2lWt``S*_N)tf`l{W2cE=O`r02U~9mH{WBF zbx~aKp1*IPY>bOQK4%ROKWPStpI=v3K9{PsiVYjSTL1dX=la(tPwY?UUrSZp6!B}L zE`f4B^-z{{ChTyStxq=LyMZUq+zzx5ny(sABZkn<4q;ClLK}wAW<%J4A?*CpuJ6DQ z_Re?AA?(Z?0?=}_FMAAO&=fZR#>+7o7>7n0g1Ae!J?Hfv!0^bfPg=qDq!k?D3X*NE zD|w)1#lF9$E}8VB5f5L+5dc_kE3oE?7W7$80<|!c1TN-MztrJSWFjUTO@!&jlBW=` zM(+eE7+V_15uk4T7bWw>R&RqG>(8gz3riNEu(YE9*rTtoU| zOO`_F63Vc}N6w4o#3yqS$H2@oIr=+oV&ZgKbzFZt*+~N7wDSR@gF{b}E1u_^HzXDB zdD3Sj0G%PYA7NiTfjlbs%(0ht3-KGv3z9Ymk&`{H2gxr-5s8z}4($2VQuW8YTO#dbFo?XwN{ZUs0{VlU&>t1JKNE4-f~d#N>Qd`LHFD+jEm% zx#jtR1z)&)~FyBb!RgpzF2BIvYW|?~^D(hR-)u~!a0m>(d(ur9^;#zmsHqxB6MYe8j z(ng8)9+Nok7*fU0OuqR^Z}6)jWqzv!%!H zx_E5_pbK9Lbn%A*U3xhrUntPU%OwH1yf_71Y)il`w#C5~9y!#)QwnHViV3sgDvZCE z5KEnurf#zgc-4MOaMjY%(5k&+u%snWSQTSy@zy1zVoyv!rT1oCSxp8^bMDzD04aU5 z#V`ICh|&iNq2j46uI~1fazT-UCq8M=$!0hOoNRRzAhPacK@-w-$i&si0H#b7Fr(cX zHf%CVoKKF)-whM|A6aRtymjCSd)hcLweDkN{mwk*J-Qt-{f1UjKR_W$D{*eox5Aor z>)}b!jU@F$)7d3_;mkX~^?=AGcb+)H?!2iv1wE8FN1hTA9eIFrA08B&{>k84nHo(} zZ=Gc5*jR|3Pq0CY)3eQq9R zD!}F178K{hgJ9etbzsgEn-^NC>7$?%rY6SqruZWeOE?Ij_EyN0spiLoAMYK*egz2# zdi(_3mIL{MtI%m7*A_9)QgX@oWWr*J>Etg2i2-kX^G!_6fUJw8CS)E{`*d}h)zEIPQt{XlyYe)=D!yX=&V0d1j1NoJLdZ)ZOL@a zpY2uJpY71gIJAw^(0<^pc!_m@d*~3q*%HNLy74NgWjKmb{$#m&Wo#^+p}`LTfweRl zv75ycg4E3?PG-|-SBehO5tN~*zLq9(a`BrWsNba3*3Xo_`XH@Je=6acV!JUzckkql z49w{}&$&u!@K>l|@+myybI7pNRY>P)Xke(eZXFeQlBV>r!Sh?mj!MClGF>t}6I2Kf zaatil-LzJ8n{gD35)6zXe6kDMN+O)VRmR+$K7i>fA`cb#3A$y)O}_0>7pnBQ!~07f zG$P^lU40-QZ^cBMi_0e>Zl<)`1b$NY6MLnfU^%<|Ir2lRND4)JeAz&xK{~*6B=Gpo zqvq@vrn_7|zDRhR4~gz960f2|YOjxRPkqz^<_>mS!ojZ4S-nTQPZ=n*h(S1u1pjVG zoKR$;LW`0SRu^AXp;dGSrYJ&B%-Wm}y}(6C%$aj+m!Bm=01S${fTHNvF6kqb^FCVh z_aW41U*;}jfX@6YWXM+<1BzDg+>z?P3?@3%SZyan50QLEVm^y(^0ry``(r1r_ZJp? z_|KwTwQ)42YSo-E(sL~smzXvQ91Sq_co>bTdtKLQW!>xMDd18`L<@a2Q13VOimGIu@6~POELN1Z z#Ezyd0W8R<)$?c=6*vvb{3kSwA#A;mHIIFB)rsrbsuPSgS#u}buVt;)`0e^YtP4xn zHdfAD#}-v^lFP`s_8bExRKMRtOrG~H8sxna#zs>66;FaAx5K=%u)a0jAG?5g3cI)46sw@NgCYJxi#(VruWd1DEC6)f0$vHr=|d4ThXPn#Zt3Nyu~Sf? z9WN4Eomd8_kQ%knxrcRIxgP+c03?V5s^L|Dm9P$onaehwXpJY2Yg`&|VR}-zHyrdVkF$9S|>Gv~BvR+a^5U=pMBPVNCYd)uBbxan$ zXVjHw&ZOi1&Uy=E{#7|0Drbx3&HB{JD?dO;6tgwMz=7RR>1%^~%_h@Jp>#qob2{KN zmj;zdFp=MEE);%Ky+D6vdNw8|{LHrCm@z4n3r?Juygi&>&+pi;xif|K(z!GE*w&|R z*a@pMx$fV+@RO^~TYDQY0i54q%L~{v@Iyj*NEVxm-F@jtQ8H%eZ=N2A^4Ecqw+h} zgc*{N|A1VgV=G0Z<%Cas;yPk*n;{AbSj|WD@*CaAm~R<y=wfyb*{Q%hefP8xXTpfpr0rihxuiX|}d0oCO_9{3^asjQ!qGM-82 zq@aR@oAaI~KUXW#=S{?h)^~5Lh}=@=1S|oVr-*n@7i~g+1EUcQ(NK%z&n54O(Y_~0 ztW-yY!c{4Gc&KO<8q=Q|nNb5tUfCS!Lh0QbHH=HOfGlqc|(GkvvJ&CFcH7)No{uGt8Ye>THk%j)4VOqR;Eqh5`Eu zc#0&~E;Ms|7~b`xaY?@8!e%{*O8ZbuiCWFN-Mv5i7H3xVL$ z^Tv+8-Y`gJ*sc|a-KJ|*NP+)XUbf+VG(@k1ZrdNlwc@K+IJIzG#nBMSdmjZ-MUsiF zlRI^C#-ukK!@_rmTlk_;VM|1n^sWng?g0Ip@h2L>Oqghn25kV~qD?)YdHf2TU&^QR za50&y8tlLpQRjoJ8E4|=>EIQCLe=u7#p;tLcO}+eSm<+|fkT%v4+4AZGL8AM)35u~ zMze`d59aaCXF?~5Uq;K#CK2_rI&2!F)NoFWy|@=1Sre-dqoYmC+oZW-1+?x)$HL1s={sQ=-5RkWYEFTNnp3xyHMHy0RTVZN%_=bq3yM=@y=ij&9+!&1 zVakW~BD4PUp@%E?%wVhu=`{=-+kxLZm}Ff$uLJ8ExxOLwpH$aZu-0=b7=rEAv98HF zU-j!>D%AG?yZbQ|zcz$ByK*b=0Ur3!u!H+}NN+Na6B}aXy3YE;!#sBuT_HE{|Dhvn zp?B4}Div{>wp89cg^Z`aIJ@=JCrV-SjRQ2r`Dw$h-#A;mo--$+knzT@A38TrtTQDA z>etR}vgI6-Q%GwrET~riqtN4uaQeF{2PV%%+Uqr@P#RzRK=)Z)=!P46K(P*rwKeCa z)MGlKb(+dhF}^q~Z9EaLguI9k;M7@tvQfYJWp!oKVf7roKBW$uMS0i?YtqRTKCPl1 z_Zn;txmowA^pMMwbGtnVU#l)rRhs-^HtM1yKtNPq%zP*3r#V zpZWignaYUiR=c>>$(W%<<7Q*b_>RKAPtzra4Cos$8*h`dqbZ)|~tB5ZMb0c&yJwkaIj%W1ZLV zH`Oi%X!e;~lNS4O9odzRbB6(^Bg27xpBcobww6L7zSy0B#S-@;Zs&eIc;YmnfhL*j z0DeVtis<2?pVhqfC3u8Z@U!KvR4cWrjox}tU~QmQltu^>2RH%%X~L0@!qb-YwlyTN za2aqAIn|}7)nzr36X~mh#x6&|$V`S@66Ti@TJmRdmK{7FX7GqZoBHKxGF%m^GeR*W z&5VeZ9gRIFC;>8Z=1FArU?!W{No49Wc<96YSS=*(VpN3T33M#IpPhq~i3)r9$^>=S-P1Rxzk?xvl-&x;$BtYsA+&A!-S9vmKfr$e2n3*Uj}-HoC^Ua_016)7K)1487nosUPpVw_Ur~?&=dT!1-;C|l*3k%zbUV_I| zMVC^mrx1z*`T(l=`9Kgv1L_MKZ@HB0rotsBdz5Krm{u7uW6ci_n-Q}`K5tM~vPBGQ z=^QN$jDQ;=V#Yv3NpQBE*W0Ea-tF^2V^X6- z8XzgAfBW=r(}Bi#Wf-w--VuAfsi6dnsz*tb?Rsv%ww(b`C2}V18YJ*051cJ)8}_`U zlZI*jz&W7Ytoq5e`8kiN({OeoiWA7bwqAU}*_fHU7uFA*>sG|Qxpe8hNdrR3BX`eJ z#ukG**RXLcSdBQX`aj6rgAYu08-;-gn>XPfS2y}<4~fftvxkBvKeqPC%*o(@DQ)-C zbCUrS(Hhw#f!U(&6EfSjP*nPs#)$$7ef={4GNG3NVJ6yIS?Ko$_iE431KH$*Kz2b+ zofwE<9Hlk%X7^Bqg|8;UlMzc-s-)Ef;=gjZhNSG(eZ%rebV+MJAMW1 z_|-3=SHIHO@sTt1&aWDz?KG_sBeM6Lb;^ym?fRb1ZSOgrbo{mB44&-Ox3$+huc0|q zK6S3hfdd=*o$<0mjj^388*t3dST6`ADj2U33Ml%x?LGC-;zX|miicjQYKx30c6j#$ zI9kspS-2Q*h}fnO@lJriK@RB2m{|7=FV}VTiYe!T^c9B=TA%|KYSFw+XJ{8}Hp-Cx% z*siB<58&V$h&#R$=A95vL9Mlgg`NfY62{}nJ5hyGwlz(JZ9y{*INfx9O5QXaV7@~F zeIzVeDxM!t1}O79!52ef4ahdS6QJMdO~5*(?yd~>?w&e@L(Y0Y18eI3P0nz55@!su zw|H!ZKQc}zdoJWM4c9%-3yS14_lk6C$&G_OO40}`gf}zt??rbs|Dk{51)|Uyx&pmU zogb>}X!I>A&2mwX>U*}>l#XXJPwchMmwnV+rQA8|ku?N-xlVwSgp;;Mw1W%ItBkqh zK!dG8N{z>^M~aZD-N{xWPY-7@VDb#fKd=prI`L@laPE~AX}wZpFN-zw^v2SPjhZOK zqgWjRF(x6+g5y)LI*!y86c;N?v&@xc!4q1k+GQla-kzr(iwvX2 z#3>G2CC+V>SVU{7o2#ASq0Zz5jHmD=Ki87{ina)~j>9hRB%?J{Tv*KSN~t2I(6;2) zaeaQb*F<7r-q}Ou+q7omJAo8c58;REA(}Qp4|xXkJxYqahiJDZddO_sge{in8&!E` zmSnUaXu#BSC|lpOiFP#Inm575A%887^>rcw%F`I~ya)p=a-V|O%`Cun4S?O!A@n8% zn1?1P?iz5qs&AFGJSaS6eM?_9f3fb3pyPdnpR9s3GKO^3dSfzg6hN@B7LI2=vJ zZct1An%ln=_1#@0dJiaOHQSArXQz&xogL2>d)atvfzr|7f0N-%P zL~+l~-VC{e!3S>`Ls>x!ohe_!)BFf;r~C+?nSO-#O+UhC%#Sc)+T9=G)$b-=RoC6R zA3qVDHOIG^r!J}aa<9#nU+R8n(^AX{GB={B`3tQGbS+JM2O&dBve5iN4?RW$2BA<3 z_6cKcI)LSXuH+`Lgi=C*a*3AC&>&ssbr*fXkq+~>H*_H7zMG@K7t=SITBcHIly2+z zymd97aQs%jJFa!n$CBbH>A~tg}F850EPq1F25fvRWV`(%C(pk7}YhX^{onG znfdQG(79^8S=O3xx^m>gN6gj=h6be}B#`XJ6A5W-Ii4;X&!K(qn|{-%2M$uIk{hYd zkVVp4NqcY+ZoUdAGa*V%zFVxT_dz-rBN>U|D1M zfp^%Vue!LhTerGSj&(y8*gomBg1#*%;;U!3R zl~D9A=lS6Q!#1iQFjX2z1S%I;Lq>a4(rKP%*){&8A#tjT!efG`0DUVb>f?>G#V-QT zIFgwX;*tm~use3P$g1-d-QA%tt2O5urd%x8){0?e$&DAWN5vZN|C%~MFr4nnT8)M8 zT4m;#cw5#bumoOhcLlkG7}kpUil}iP3`I0%rAXdyk1vqPxtPE2jz#EUG1J9b&pM=< zT!#&tHKJmjD7(&XDi+ZeX(ht|*`S*-;1I6Qv&Hmz3K!-58h-Q5|;Kweg+vdKbeDifv*Z z3smn}sm3Dc7qMN$)g08j49+iA@EBpyU!vygre}rWn4E7l&|+MVQ2ChZxdN>;R~Gim zS#w!vV%<6`QQur=^v{zjhEqJTjv{tlph9sYtJPuDgScPli3!$(5~sRQ0?0sPNQ%3< zQER0$e0L=B1g7`7hcS+`284dI^Hg4;ay#jFW9N>cozNRQ2Y3l0;)Uj5+B=D9`4jH) zf#xLA@;&E(1t3)cD1FW8i0{>aupXkB|Io{*f=e}MVV|!rH5~;|vOM-B#;|;|^2R!_ zYeTJpp$8*raL(&qI(UdZUe5EB+@|gECGU6~w654Ub;U5k`Di=4k##`Mb>BaV~ z{!P9008s2XYv_67Bl>vkS?_G}+0n$?Dyn^d=0m5BGPVg%Vp|VV zP*pR++q>8k?=;66Dp4E^khn8OkD1rj9eQq?JCv-7B9)=iUhR*y)!(!FKqneBx^tc!r9-OI_WRwWNgw zE!aJZ61JeY?{-Vt9Fr3xlO;I;)62VkKh%EanAa=_~&;Ej+1R=K^I@Dz1*i zIh6GdO+654)-O7AZ1HA!?Hbb6`_j=`W%0?1zy|6@5AUet1NV3GJJ?Y{6Jupedklk6 zi6e?q;2x?JKRnEZWn7IcInI=zB)G!~_J*Eltueuoy857)?$>bagw%ilYHf)MVRb@f zb18QPGVa52iknQw6@gEk4&e^8oZ(n{ z?TTyMuaIz_7!X>!`00zRIDBGp&!QE}b8ZrHli*-$o>M&8 zJU+QNIJ!7KJ}MGV0nX>&9mH?sS;eA?U3WQbU9dA903EwFzEpR_Tw^EE+Slc-Oq}A! z%;T(QuK)q3T0Qp~wR6ov>9faTuDCUBtkhb8nOQ$?HORbOTR-nK*zX~giwPojWeAKBl~vGeG)Ih& z1op(rwe$LuXIF z{QHo zy+$AEN@e8f4c&4mVH;Q&_t2x63JWD!LvQzN*cK?FV@!yn-NYzf6;CLy9!l5EiUn~F zLUeRQA~TiO5*f7sx~fG2m-*OsU^COxKt+FH&b!i0t?&flKCuquUlb=8rPrgby0qU& zD-dy>cJ$j#7hXw)`cr(&8c-qyB7lXl!YD((~XqqMlGXk~?n^SJ*SX z!5EL!pB`O;Vng-nG*Nev9GGbsEHW)}A}Rvlo%Iamc1e4L4aCI{pu4Jl!ui1C*vU1UM>^f-wWW9@{GSp&9dSQQ2ekf9n=5ilhez(UVy zMDVZLjryFYu59O|i6;m3_p`5TKeT2BVZ&Xa?||Y}i%T<#`M`+gYHFSrE0y#lFIFad z0HtL0q|0I!&Mh~J8xVM1B$UvNwid!o59{WN5Lk28oW$2?FDjdY3 zd42WpKx2?KojWlne5^&Au-}qAvMDEK5a#nHVcwJ5^A<(n*ABD*BM!7lp1mCn~3{0^HKO}xicuC#uDK?JP2J>#@V2}P^;prW9?*Wp;q&pbQ{jc$l6EZc}uj;44Sb8 z8OBSqU=?Tlm53Mv?IGj9D|f`SLV}grB<-1O>C6;d6lG=`jFjsz+i1ZADSBrIJw$%}9CDroz%^D{plK ztnn7=BO!_GTU^XCvU4*Bu9x*{XRJG7nA%E}d=x%=~>P6#(@SLEE>GqpCYABMky%-}AtJ&aGj7KNjF{Yp5FAWaI!Q5j!0((A#z$!+i0HXq00hFf5Bj#^b2t zmpv}cDOGD#TQUW0IWmc4>LY8)S11f8sl zBq@xEw0BDG2KPz4yeEy*FfY(3J0<|_r0}4Srler#-T;oz23ULsfcQ*w zi2BS)gDXlJ09BN3)TBNP66tCT#Q<)0P}Tt3Z6uS?c=^<)Afdt|gGJSI%Qg@Ybu3SqUuw&=7iZ9A~)ZfS+*Puy!8) z5Al*nVj(na4139hM8=78U~vpX<7x1Cbi8sFYZD!6VJmlhpCZP?y@GY~=y;R>s;ACIJOrF8iuLy7F5Gc?8jZgvDc8(q;0I1BoTH!+Wyt3l^8=3~ zHbrf`8yBm%M^}+f7_Oh^NzBPb3*4|D{Zi)ht~7&P-`0blp*f1Fcua#KXj-PIyD|yw z4wUHTUA|0$Q73t?khc>$iM1PD{xp=r63^2)byk;3fErSayUL*wAy=9*z(ZC=D@4gA zTH5ije4GjfYN##l-g0PMq}GhEYn|Hg0E ze9a8VQ)UEIiLITA689!zB0h$>QnBGgB=r0MaiJOJDVCYJ40ouWX~b;QW11)kn~Z1l zfO2b*8Ix}Ajj5fkM``&e?sc+pVJ_$IeW`XE@fjRnq7pOJ(F_twDN!YcASr9Q3n(3f zLmm>1ND`}hhN6(tX|?!9EmQ*n>+vVnL$egAw$G=?x%XRCBQ_g+;O3)NSZUg`O( z@~c+qRrzJ1QmVcxR|`w!<Bm&lU^tU!_o~ z(2OO_xLvI-wo1<-FH}=0FBM*tU%=02Sj($m@x`<9tHQIT7cKbtvQT|qhALh_F8Z`w zfqa!<>E+AvQt5g1C3I*Bs)efIHx|H(=)+xcJZ=c_ZmZ|DzMyAq4}V3YB=AG8t@}+n zpcm6ZCn<5N_2ic{yZYNg%Isa!Vrk5B))cPMiVA%brw~qTxnENf+o9ens_@fJCT7HI_F| znwr5^sh=WlrKs8&pCaV1VE#oHXjU%XgKaE3fCBUumDZ2s@9UwT@Mnyn3{&ht5o#yr zCW;TEO;dF@2No7Bj7d~xCdEoE^&}+hWk}dI`V`4vOLaH30oM_N4g}!f>vUuoIrz8H z@7A!o=bpf12@}^=1MzY&_~fS$vP-F4Ql) zPgK9C85p`ZXWv`un?HWoR8dG?$;`}y=f{9v> zDzwwbTcTE?b}ihko+OSZH7ZbL7AVL=w~YY24eb6M`qU24E4h$^=2>S;H{HOMMToTf z!X-J70=!c8n+#apcxqdzcO}$Y36o?0&ifgN0NAwLFVoY-@!+O_X5JV+s}%*&kk$*| zC*Dm$u+$lAt{6tcJ`!!Brkbjhs48oE{RDVMc#pj*GN0NdUx*|M;3sUH^A#B`}ghiFkbNY>5!Aq~;L++RrhgW6~)Qn_8SLLi? zJSO_|>#xT-aoqAI1s^`3ns*4Z=ZDZCoId4yd|jXJweDu%UAeINP4DAgS^v2UY$C(D z+}9!PcF%=bpgH;Tu?G{|c89m*{PM2_?Xy5F`s2WFd!^B!M6L95lDNjbG07zvU$MlT z67PDQgiNd59SmUSOYYb> ztx2_PvgiX!iym_Rm(3NojI5mL^Z>r*vUoPNT9LDZE{SLkb+@Z zwBIZTaE|Mqv#5yrkHT!?-k>v%_vj(6r2wgpBVode5NRFZ&%ugHNPm<<;%i>|f~Sj8 zB{d+vrto~~>H3l;r9hBj!6$5i1e|7I3y4c8&g)BPD@7W?Hal-M96BJ3gS5iRBL2Iq z6^rr)oFxmkWB%SnjB=lM>;oHEn@-B9Sf|2|F+QIS-9fE*4S#{kb8pV{&?d0NNCBC9 zm(aKmz%!sf9*^O$bV1psPa&#An;tWCDyJyIsG)hGSoJQ^#&EC2sx^(|Y?anMMoX@H zKQB^patdmc?W@?;3v{3I9fBTXin5ntk1m*7amp^7xYcTI2YarKmQM^PuZ0;bAc!t- zIhQyb99HF0r*WxZj8V+Y8u`B#|MoYxQv9!DgCbc_W26zm3&xz2R6Z@DxS8>7X(EUC;y=f^bTOH3ju+vAbK@{Lrm(UN!B$|K}C@is| zPtG*`{be??5T7=+?6}8B?pYzYlw*3pEi7{^6NCxBEjDoht z;Z-X-)E2qmDmx+(8im&wfW#CasV}7$h?qQJ|R6D)dvrTbT)C?Y41FE^yBE`Rj8f#Ue zd5M|U_W@@PRa#McEAZ)JY`J|uxaAm-{m8}9`VIyFow)5N*wBs?Q-Q_$e#l0sZbgb!FR%5Wo==-g8VTU*<27+BTsdCLXYzp<4M4phe1%#=C=fha11aL z&hs$PY`UqEH+Pca(}vmH_RQkMq$9-ZATZRCk@OfFBl!=Lw*zmiBqof(A3x??-SgXH zEqN2Dsh#j(gz^>&o3+esivqQ2BEWQM#xgOCEjjP8tn0DDjNdFmBq<{P@e;{#dMJjx z1mWzBm-1G{0<8W;cZLJ z5%3Y&Yhh9p*;Qpl?MNrVi;mNy&*wBd+- zWfqH5tKw$2nx~q>E_!E36L@o&Q&csL0?ubWWGnQ%Rkd<4>o*FZI9g8=3W{{r?>Myk zgp&E@>0)*y??*0lKE#WHoa33+rxDOqDpKY$T6xKpGXd%Qk-W>X^Dry-#o*q?aVkY- zQ<&A)1=MBEt!r$;8dFcR8EtL!nhAa~2W7!Ha#JGw*M*;H)wRHllM+>oMxHn=wtqQR z;6QJ$euD^d5(IGJXm!%oyXH#q#)AGiU_*aUPnl9PZWbhA(utCh^6Ru|)I%1xL<6^l zoOi_lBU)|}U51J(q`(W#P-np|9?`f&^n#Bq3t;c22YvQ*G|g6E@(l(~;|W5nP_>ZR z(y$_iR2^%O)N|vK(BIV<8(C-ySMfyG)uV@n|Cj^voE`&6yOh}J7I<--!PXg75dB=# z&gPeH@m{H_{JQyp6=E*{{;hjW-__K9#TuS0S8xf30X4@Fp02p3 zAMPBB8*p(Ow=V8LNZkym3Lr~4a<5Z~()_Jq3%4@0USgdy&raO$l@U_9VVmBNX68jnne!F@^9SgTSB;h71*b;r&cU_D`$>e(!2p4(!?)E42t5M=0{2&onF z&DgmJtsZ)u74d1egayCwVMKPFRb1y)-PzHBF#S9bo&%JJgaaibIjHR_|9@-bwBfjc z)l=S1HZ1D7L>PI?&8hptP&@WkdMu(R>PnH_iXHKApGx%_(85Pv>0M}XYfN$$T4!QR zUfIMn(2YA@q6OO+Px7Adq8@bNI6qWv2N+(L$);o;DlgXZ#6Y12sXUA>NDacwq9`HA zIVZ%IUfax-B2YSIKi-1hxVH2x7?XFfsC?AIb4;hKS!>xu7~-EwUucmg5L3p82N{VN zc}JarGQYV8HhOTV)I;x=1NiggiT&<8^ctFlBK)cYWR7&qO5V|!Uy#fMPDS(w@D4Ca z8EvAypv~Q`z#hb?4;V?@Vtx|HftG8HZSlHq-Q!D)B8teO7ONO1O=&$YoUqKR`4?iv z;=_ZM%A6;WZ3&gqkj?M|C#~DOGPjd^c$l*WV)tkjDlDNJOtWo7-og9<9^lf$^uZb% zz5zVX$wB>XNL@-FgB>sD2)vR$2S$@kp4;eFbI)D%8wnVfr-5Z|6oh0av5mbM9#8>{Px=Z7W@ZMqpI6IJ{@=?29!qRT=PAO zO=Ny1>!@ugzX~h`W3!nlX-+`+O^^-)tf2>6pYttht%ljP8hR290dD7A00KZf)4*`z zhwdMjXAN{0mZ*z);&wHWEjp=4r(uY7#jNdVtr};w0z@N58oum<O68#Q%W*|6?l4UsU>k0*r()uLBxR z>BTxAD>Adeg7Z2H`|^IXV}y4iw9=z|7ykD|B&$F z`A>zL?>5RQpMUWE|EyBzCw|{^sxO{DfA(r|aj9CazI?H?yj-m;{dM_8VRvV3^=N&2 z=fh_C24#okyzT0&zTa8>^6@k}eiE#$o_+c2(yPKT6x#hbt&q0Ie-HuxN*{gy|1qck z?EU}z#ZvnGuP#0NasK}qA9gj$sdsmbt~`t7r!?|msdKWk@v+};0~{2UnflOD5wctw zfyOEd(20vAvCB!%1IC4VLUt29Fkoa9MHi%c$@_sjbo+RHJf(`3l@Kpg$)&91Dc%%5 z4d@|8{SsfwTJHlNZ#=xQs2$_orE70zI+(7ze|tHlcYbzm(iH#`?)OVW?;3lTt%`F~ zEygspInRV&E#l)>n@~oo`<)mklm0)V-8vctUR+wcd$ZiUYE{dv_;;d#Z>IkjE6*xF z_Wz&r`9c5xLx1w=|L=Qj5{WL#gQ{*Pa*FsqXmy&)%DM$B`U|qVMzj70nU_SBcp@ z-PqYsktPuTONw#I{*-sH^ai8O7XdVB}#mG|$;9Hdw$Y(>`4eHm^s0D1R ziT~yzLMn`C?t-~kvod*{E+nr_T%{bqR>rJoaT2KLww<|ct5dB^OF+ds2uK=^vg!IW z;}-s#6DL|Kq358_OkBf%BBJQ=6MU6%U~VWwX&yi$}8(h z%`5a~ZjDV;n&sJAvgSqoG+j41eL)xXfnG#Ko$xwf-^nH^C_!6rR2H&~7OR>*BF4&q zRVFo`Jd@a`yXaf~cyq==ab3sUnQ}LkX^40hqsVxb;8JyRHeE6WRH2inX&G+|P?chb z*qateC8+nd4deLy&uyk~AGzLcjXL$6;)`of=N~;;3EuMhPkR1m=G^`}qu=@b&!_sA zJ^ypMdf)`}KU3r9^FL?u%bx$4o~=w&E>E4$|9s+qYWsgXz5k&0KVF%hNcI0y)05}@ z|5^OR`Y&5(q?E5THpvn`u9F%*0!psq^8vYm50_Xh(R&0}DE-1UvW$-%avL8la*uEC z+{TAPp5ntM5Ao3^)+9a;$Rm7sB#4jPo0hT2Jpy9;(0?kT%fESU@>PwnCuj(a4==&OC~h zJ8LuR?>396?Ar+%7VHPb=X+f%PJ@qLP-zgrz``%iyUpvK-?$r3vSaznPI2+R(^;8$ za*!y3&+q>#F1mMTy8fL+2^TPCw{~xJ@3|e1@u=T{VWGW8@78?k?a|`G?fbLzL z(=}+#?LwzeoPYenDL#4VXR&Z$YIkzHv-E<+)#Y`O;u<53FZ&nwL;Z!bYg&)gq9ozS z41y1%U^R!{&fd$25kB^>WfiUFHn8rNlW~Bj zh*NJGxC#$T~bX zHt{HF6YqWBf``_iZDgcDw($#|`{s51h=(qKVndgU9<5V*51O0^=ozGrsZdP;aeLH?f5redi9szKmOV`(7(%;6hBD#+i$=A z-dBJ3^B-z}4s{Ppdi>${UVZoH81$T9BBLGu?0wo*||GcmDj13EK%ie@PuRi#fkG}JLjV$#ebc@wgj0P66)nOIF!#@7P zw_gAD&t(j!F^uCxs)#jm8&O(UFY1OTwE;Nyjg9xHKVE(iyC=^ zX<16!pJ)D7^ow{L`MGKNhfbZH(G0vpan+t>TKkqA6yD3Im7XzY!8nFp-VKK8*qFXw zQYzTA*bc5z;09Lf<;${^8Y)0u%LYl4AmdVE(gf6A9goIAc^Jhm45*kp5M4X|%6DJ? z`qv-*@;k4Aao_mytFL`{{Et6*{nPLN=dZp6ti`+JYf6#V#5uhSlcfwxYv( zLY}WG3}Mx?#yu%)%LB0d@D(dkwa%8aqT=yyeC-)kD`JCxLKq~${&hjFU_3C+^&0-m zmm8bpMLcR)?)~nUzdZiR_m1EH=IihMAjUni6Hmw^HKW%AWz}jOM*OS*qvuiPjwClN zB1&q4=<@QI6;i`v&-b~rgLvRg9?0Ggh0585C%-wdff@taIvA;_EI|Qd17-*3HFP0% zm*L(GaW_glu_+QaN+LN@a~Mhi^Z<%a!YJs$wNvK4eF}CQxg4@u9C=DaW$@f5*o*dm{TE~rW$&~U~kVsgH)wR;LW7I z4;2WyiGv8GgG(DlRt!uFa`?O5gaOvOCJbVduihC{6w28(;$ z(#o<2u!Ln(r5l0(z#o`b<|7LMZyr%f?L$W?3+=aa3q3us%vj2g|MUkRfAa^&U;pdZ z|MoL3GX-6{;t$Mr@Afxxwx!kW1VQX_YOG2HeJqgi`an68o#sI1I10y;#eLpNSdJgHOnv$b; zq&FumBf3;70edBzfzKOI{^KwG064(JnH6da4GW?|V#Zl6I6*W#88~Ue<%5eEce!9E z1lS2dWn=>8LynBYT(FY@?4$r2Ga&~-7JXA5>?r|zDh6BF$uZdCNoL807?W*6mk^hi z%3ygBJeqjB@-i)oH9f4nC_{=|pfdvKjF6gW;E@lP4@q)C&I*vTBS=g*ddY=)SwOuk zphjby!8IBxh<)BS_%ImN$O6>m@t1y5Kpi72O;9a`-~Iewxb|TxMpZ9skXu-YT&HwJ z#5(H&?~xoK20NQ+Sw)~#^I16rvZ&w4xFd)f}WFpk4HIB10qy+1QOKQlgC)>yYV zE_x++nm}Af;~l#8>RW&E>YLw{)boB(h_l4E0{9>R-M4l1WuDKX(g-IYzp>=31q5ic zC8q|^2cu-n9mO)ZE-ZryjXa>l(}NP53o?M2AtNuU)$m-T0>F1%nT$s%U77G^$me^= zGx3Rjp!sX>*(UT&p8iA+BJX7r1_?^afG1?V0IyEHAQC7j3I$|K5a4$(Th$F)r$(V;TOL^3-c0kHb#lTgj%6A(;gTb z3q+@zN&o#c{l)qYMX$4`R0?4QD5X%zNfv^&Qd#d<5>@lX+uK7$R2P-^)D$4|$|wrq zz%YhycMVE1!6ogp8s0z0TF|6W6P!Koafy~G4NRJ%IBX2p6e^aL_b2cUOQ zDpEbF~K zRxRpquEsze$21ZfAjd4cut4FEAU5?}3!DP{AI=De1A+zf%3VE3SpKZJ7wfiHhgN$U ztsa;v>^69^!5+67#7IDNXV9-drYfVz^khOl662t-T>R1urBJJ^P@rqr;G7T30naRX zN?8S9t&GpIK2**`T}kxb?=nUBm|d9evAutM;w!QdMFd)jLUtyZl_(~Qz(~~C?s4Y5 zOPMEAK*%73Ay_n}z`!Vk!)C{-(oK}c?+#J!iO|qbX=st&6q*a|BuS%63egxmZ8Ig8 zOg0_;7WzM;k>9I`UJOQBg&jD{Xg!RqVrCCiH8D#kOhe(fc^Xd>U@hIRt+59yC z6XxIL7_bW!BV{hKS+9UBNGHFt72Q%)D-F1fGARrvPuGKRhAi$xD`CE%i~4{F+CTc$ z-yVPcXQGGF7jhshD5eeL+`Htc#TKTkVS~sV5-egmP$_as>!V-(&8xrr0H^=o>)Pms zH*2Wi;IM@o)#3bF#)w6XC15CV$Gj5GsohDl697JpMvU1bx*^npv|=me`OCAFH&YXW zc6c&1!3T}eJdhk*JXFRjIm1PT@Pj@?FI}I6-M6=Oog6OQVi&CIa9&+bE zU;Wpwzy9Yh0`A!SY$Pxl^SfD0c^XNOn2^m9AyJ|kAwqIK`i>Fvu$MvW>mU8>^>6;> z_)FhciT=-Dz5nqSzI^<>fBny2eHjW6rb(2<-S?}Lis}+(g{Gj4n8s4@UK#x+awcf% zSaGwbq-1(4ie(=6n}OM&x*ygiB!kd*Dt1@vJDr(b^9@Lrv-?YM* zdRBhX68pbhl+T6QqlNvlefKoZ{^4)r{oj?DN_qNx|JRxP&i8+Rs((5AzfV;UjK2T7 zJUuaazW@6yemVQUr^lhDrqB0(f8u|!{oik;_aC_bdmKU&{r~h#xpLnBpT#eI{=dbf zKt1?>xm>HwlqZ@Q2mjSmzGVyl&4ss=$vggf0wmuRs-9Vo0H4Z1S9bohFu@L!OCsb= zX>;rr_PG{q2Jm1bFnGQw+WaoGa+=6ONxWm80TkyQ3uh;=`vNXtw3|LM8X@8n9MkA5 zVMm@DhWA1Evk#Y!NR9Zm+nLf3I&%2zTqG2qopaf}GsUJ2NI9T^SaElH?BsleL~+3| znSXM=*=)|SJM2^zjleERVV`<98N=stePv9eu<{ej9Cyt<=*#2Ccg7@honbjpFHFe& zmffHaI$^d#Y0_tR&lTNn(EHvCYVAp;Zgas;Z1S46Hs2G$KKEjz!M$aF&MqT zjc%07PFIF;uP8{&nMPJkPL>ljITNqRxNiYu)SPl|8%0KE*}h? z2B>aI-UiwNN3PU*!q+oVF=#29zg5ZxN6qArMcc3VSQ@TmuJ1%tChMi=)ztJ8^ODPio6pBjRAXd;d6I5A0- z4CnQcs0Hr7@2!v(Pr_bkV@O(s6SXXf8APiZhP_j|-cMXRY8Xi1ZZ2p+KIqZBYB5S`zljhHnCI0ewEITW+!rU!E2EfsbM18oG%T&}aFX_P*7 zlmd!wba$C))dg+fZOe)8+6K7W4iYn2!zwbQ^<}_nE#=J zP%L@MOK7YkO1J8)>5setS|rnNPP{bXG9@bR=9C4N8*h$N2~uN|-6HVJ!hb__-fu{S z5*h2U^XMA7qkj8zRjp=_ZTMJ~#`wgWlamgSv}6iRhR9T`tTxa0Bln+UOv4&tRv^`k z`pmp4Yb@n*-h77=oSykmBD0rBvZoEwaoKHs1kM$3C;ds+aiXb<5ioMy0W}clRe2Gt z5dgP9NWXWK&CIdf(NHQnvo10hWQw3cjd-VnD^3-q*O_dLQWkLzxKCj!&HDKCbZ%i% z(Zd&}Y>YxFzaYL3p*c+@x9iR%v|oQdF)<3glE@4iYZC*gj3pRECmU(hyPoedp@gQ^ za`$OtPOj~hW_u;88|)PQeLJY{oZ2F&aQm$KYUY-Of~-*D&;Fw`G2xROpk=|y9*Tv_ zuh64eq_+*(>CN7iGw2`UD=i;h9;Ws;_Sb0r!T$^zz zSHlfmx{T>2DUU-diD_Y*g-W743ISY69OX3P44yIYc$tC2Hj5NlOUa#-iM)KL@)R(r zc(ShZ1g2;H!exFno`B@RJxHbr80q&DAq^E$s2HX))W%zPPJaG->%aK^|2!{|Z-(Op z{@*8O&iDVH&F_5w|EKzwz5oAo^}q@IzmJcf@BcrOU-th0%i|NXmn-M~-#__3wf(=H z-hWX0pO}~^PbK^R@#*va|4e?W|MwT9i;o3L*h$oF!knYmC|Skl9qt_d24QzoA5iY% z{gBkU!s)uWo|f7IiesQJ)g!;<26&OV z=|~TXHLCl-GImm$isSwUQM_&Pbh>4t7ZY{_*f6<2fR`^Vc;ik1q;CMLREI$r`k}zX z#h0VBSKFIfQ+On(~5jP9bx2U>@~g;OglNdjW=p)ZnE-4IQ{xywZ;4-`#hB zBOU4mha4qTl`%Hvlvu^wZR(csd344aY*?Eng+B)R4W-gsD1vdD1p#@R1`0f0eZFaG zl040j6uD-ZSZ2R$IyL6p&s)Mj)|`9klA#c_;chazQbp!NP zjuT=#Ae=Tku`kl+NjJfB>prboDCV3ot|=uvqJ3=a0xby=3}Tx%bYsrakFX?n+y=c* zn^n7K%t;w2_o zQeQU1Ss+*Vuq#(9L`qOR#I_>P2`L6P`<82!bGmiE(yX zW6@&zER2z-xFEJC9cwS41{1O%5<=Tu+R!Y|wu%Drrispd4t40Bj^w0P^}-Huo%@u{ z*sXxbpiH~XCP?`sw#YNzGgsz(eIA`=hfXd~xDNH(q@m+K;Unlh{X%DKtZoPoAIm+m zWRL}e96?5p3w9jB#^cSN!Cgc!-Td%eHJ`3(UZI0j@_lQcH@BSrJ*g16*CC-C8&Y+< z&WhXhDO=tU+F0t_o{R+Lxkoc1xg)7`I5h8uyYFPgTxSbf$#7h(mh=QxjUQ#?YCTtH zTdcG(m3<4ZI1uKC)Sxzs$_3G^xVQ8`;mm%d>2}PAOwp-2NY=`szR%hVG>07n-KfXg zf^KK&DLbX7Y^kTP?Fj476toF%UFh7e+Ul7;sRKPe(j$XWCyM6U>6sR}3kfWlAV%!2 zbYFvnB%1!lq5de@_FJT5^a!;6y9S{K;q=Lty?AbQopRG|Q8U&eSdn=(E1gA5q)V87dh;t16hHD1N|TuA6T*Llcy zYzt>^5h&kFXg;Q?A_^i^1YO>{sDtC9t2}kP3d)DjwhhW@L)%l4Ynw?O#&uV~l2AGCQ(2~wTSTu|!A=R}LhlIGcQ+hIE{G%-F6iRI5H9dzW3(hC2}o>FcJqnkk`e<@ z@FoBju^>qlYD7(_Ijme70#@)Szzni1;ILaoJaL1GY|R4rRX>MsGJRFPlziwdsa__$ z__h--A!+f9@9-f4Yk-DP?Mu3zf~}Bm5Dz4HDb9wg!zV<`+f}^J$M{{W5KB)qRN|p~|0WF~$;$1P>Ma|z3#*kLc^W3* z?X_)+cBj{cB+qq)KwNEuVSrvvkqhk-C<2z1h=gXlN7@lS(ICvb@rGeS;Tut^{V4RN z>7f@)UR}`9=_|7GHoQ$Nj;yNtRNrWkI=M^s3Fo04h%RsP@h0@UEjjwQEnk+U;l!JO zz%0~M*Nh7aX`9_*g&AC3m?LoA6$y@|{rU|{Y_j$7=oBA3AwTl%9ymV7<8(|CrX(#C6 zz-Y^-1qM-+`ihbT@J5-u;$l&7;F72r0iy?+n)Oj*V>hALm-ge3K;l)zMo*6Md7(XX zU8cKmVJXhiqj>MuO?0#EM3=dycB_ku9k7QsN4@BVy_o1zDOEbuZOYAI93C9fSO?jm zXd?@Yafga=;#~+0@Xo(Qst2JdN;DRWCMni)K+#wf3ewSy#CiRo6BYhS})QvcgH1=du;-!2pyRVY5oKJ#Lm# zwyPWO*h~&Ur5j)VO$$520Kp_Yue9b6v7AhcjMHs#Ntd6+_l{ODOE-3Wc}(OKYol z@2o99T*51$Am+})YqysdudOeEofr+XuHy`+Ljo9@9z`R_PzA%_{t|R+moC|#cjpX{ zLa^L6Y#1lOSlJN7+Jfq0r5A#|t&H9ZsQ73U_gtWi5L#vceO!bV@L2*Mj*F3d5}B2O z>{-0s3UqRES7RLq=Cb_a3L5Q69~%26+p`c{Q+fmPgB<{ zCs={5Wf7!x86A&n<Y*>_soZM!`YO#9@td3h>M+c zsqJEKx7g(;#8>PF*eV;({%^TM^x~1#E3)+u5)Z>8aF!>+o^4hm4|61RS+{PtZD(6f zA66x@CCjHEE(^9-?~#pX3>;fUK`_H)UUp>m-X^=%Iugb;?)xsg_4r^(;R%J(Y^KzF zwg4FhG#LS+G6VqDQg$98yj?}Q;`%M^_0faH}UM86* zU}GkN(OH>USey}0(V1_NFbt31ToxY>FN;qz$?M0)VS)FdZUkwhL6W{cwnl&OtcKo< zgDDV~`-Hk*yvx}V)Pn*%-y_ZORFG`)!;k}zk z{WMCQ)#K41o;+ZC>`tx6bAl7}c5<+YCum&9EKgy{_#9C+!(|X6zSR zIBLD3A8pW5IL8N^1V|ff4T>|iSLC_zOj*n7_*k*qR{fB5>0&~(DtGu0ZLG-8{z)O_ z6O+&QJ|VUUn__QI2@ZSjUSv)P{*02j5FLDd*5jPYEl+kq{$Pk#H^tQ{pK_4XA|*EK zbldiWtIB&xxG(l~$EZGID8l)}Ug24|P>)d#1h7eo5Qimj!D4TLL5!8?2+=;pHo zGS&{2ITKd^$9b6urlG?3C|baPVot6t2vl;F1Ee-WF&D#Lz{U?H^eFQ&6PA!@1-x5b zEW8BKmJGf$Lru%u?xO1?U#?`9$Hv02jq+x+mZpYQQO766Nh3}gTtx*}$plx~tX?P! zZNyj2L7g$JQguk(_i?_^b>^9i+9kH%C;%0Rw^kmuXo-ucsj=(NWT22e2-WbC)_~yr zE=XFK`=Z!9nSk<)-HTfHphP!W2wy!&Lane1EM4asde%@#8XMDHj*TP$v)R-n96m=L zmhCp%E`@-sY#@v}zEQ6ab$s)V>qOunJzkawy+CH6b6KYpG6I%SNL-8=wzxRkN-U;- z@3ED~XYcrjKKdiBv)yX%Ft;P`Kx=~LL+T=7$MpkUi>K6(f@at!LZWUO8iNDzq#c+` zRM*ra9-6wCM$H?#wn%+@+qv1TX<#W}06YWJ@(-OlXPkFF@VsK>qf6`1@#@> z=E};2dS`c)>e}&FzWe&uzy9dgfBn(F{*Tum{^p}!e|Y?lKY9Jr?^iWqgJpXnx<{oA zM9$Q0;$tOBjCM9ipJj>x_EY3YvJIK-YIX)miA-ixW9hiYPY{g+lYI~icb{z=N4Bz^ z!fcn$Le~%6Hvf)EGrwi7q2byF^ntDFk)&421`#M^%|Zu@4cwH=_zkztG%~-q(WG*1 zJcCQdQJDC>F7*!AgbT21trnC79V2+O^*Qs8(2wXJZE6NOBww;XJnFdZKy-A5T#xn5 z5E{Q~cd5nP`EgJhTi5p?SIg88U`PetTkHk?BRkm9*~M~IE$l1nrV}o1pUNj<5TR+#=aQk&`7-f|{0z zuLkDDqeTmwyt@x__-rmLDM+g*raynUZf#>t=^EY&p&8J_;%PzZG#1c}zV1s-wFYsj z#o5G;8Wsb$Ti+=TWZBhLtc2Cp;9`&y6$3gu#=R5Dj?Z@1AZ=jC)mz;L^>HT-Q04D! zV`KM2$%@tkl~xeRKfGCuHKp-X1JY&fCl+XlzJ#|#l+-8j z`7Ws~aiZAx6`fr3~pX%j$B2`3lUf^cL}pT?J) z#A2nyeWo+`vP#JY zZUNWnL4X}RrfU-JFz`NeX?Xiab&tGQMyglcPPbECAl%{N)*VyXH) zlgibzqYZbnTG#8iJr<|y4TEIvN@qLJ`QG%SEq~YUY%w3nTR6GRhu!6#pa76Z<*aJ_ zmL84l@8H-JuL6z2&|$|l<}O%c1AC1KFNK=b4P1?MVc5#Y*HsPEYD6m*EhlJjXX(YH zQ)sQ~<3g4fw6y9`!}hq}`l^NjJ&cj(*+WOD{LngIXmWef?wyOEX78GOF61<>`GnYiK>Tn93k`x*pA85gvTx#B$DNFg9ep?XYVN z*u8~XkF9L3<+ra$Xqn7*__psLoB?-Cy200wr=M+xDW8oo^caq~2Z@g}u!fzGzYIn^ ziP+1=Z(qV9;qGkQDU_=;kOQ1w0efG^qo)q9*tJf}4xrM7XsoS(xUT!(5z zWpiBTxseJ5PVa+iVA~0xI)Ohe(__sc(%^1fg9k}9T^aAmBPkmfsopX`a`CS-Hnz)N zZ1_vG7p~hb*6eM~0NJL(RfVFb=PjScBwN+t?xl&c=s7AgGh&l5PIe1*{jQG6T6$_e z)D7Y4;1K5zaj)$4{*Dx`g>1_{+WSyn$HTn>+f#-u@a*kv3i^46=VYt3I2Q_%?{=W| zGB`_#Ox&jXaZeflU|$zU6`VkV}eXw(*ee`I-%&8*tc`i%8ZDf73f zc3OLh9$qNJM4y~CXj);{(Wbs{ZTB%v6ywPj##2irFf>ijKfXrr?<3FZpe=(}gJ^`g z%wJ|6^QFbQ@$5=L#m*M23ew=w#>|_HKYoyNSU_iI;}&@5ZQ>$UdXZr_3NJ9L00u=n+m;i$sWwi5CwNq z?z7!>k-bly2H3+aO4<V(wks4EHY1 zNbX%6V{FU~H)uK1y^DkXP@Ge8@6s~M#2M(`rS*A7o?~lWx@0IsZM06{-i77jWq~(V z9DJtTyDVoICn<+4nbrzXgphr^yUYx3Qs^o@8-xQLL54>nxqBAr3{R@I3zZ(`Lv4g0 z>NrE$3P0I}pd9o(letx9;@l5YCPV!lcYlG!3y&zOucqw>e+ovy;dJn&mx}e*F96WOgm}nW%=REHTAqyVvn~8 zD>C&qK$~xxJ5&dTi9^|YIFityJwd~Ty59prh{yNsU|ePm>_3Cf_D|hHiKc6cIn{) zun>~fXlZF}VQj23HWre+%2IKS9ZHSIRQ1c3d&pH)Uv!CT0#|pW-5m7;Xkc=4c?TQA zz3@Y;1;Bf-tSQ|@H;!F+eX(h7(|AYHrlsf;6oYbn9av5-k~Oj`4DGnm*ve2D^Fo{L zo6G}j!W8f0|23xj?wY`Qbg32w4N;FlOxXocjEexHo~XQwsq7cS>_TlikaZ2R^3N#i zS}N21@-%aJ;mHCaAFB|%Su=u%SCPndD)C4Wd{BI}>uP%m4 zy!ssda4eai5~Wu@pRhynn%@QtT93?w%Ah7{O(oGmkKELkQG>=38)Pf&*3v_YZtjqx zPg-e_LaJ+QJcC!5^-IaDN0vN2uGz>+&j&($YA!pH8GvAYUAS-;r`R_3aRfx}W(}`G z0IFFCS?*>IvclBT!h@6+y}EdHQC~7(D0fK7Qo>_HKwrLmA+~ZwQoGp`{u-WNeXc{p zfxN3w>oui;vHFWwEw92YgIKJiz3$+IXbbz41;qo*Ju!xQuWJW)ojZWV_IK{HN@jaS zu~)+$iET+_y?UHtN1>Rv44^hOhU7k=#qdAug}wAB?;??%_L?cWZItj`^M&v?D>I{e z*hFRs5}DB_;+FYD-_00%!du8#1hOk%oCj)0qlJA1e`k5%HF9_hIObtcENYot4BG~! zFbLjP2EkAVZ9du!3rAvHaPEd3T6L8m0afV)Eybbv^f-8Zm~k-k*V=b99IMv_s_P?r z>o(Z-=%dKh=RVZ^w!V+Eyca6QW@z8KRF1HhaD;u0hMVq|*z(!dY3+K8@Bg}1-?8Z) z-_BT^Z#>v>w>==vZ+N3GV-yn3_7@|8*9> z?EPQUlb0vp?R@{&C;q3l|F_fo58VF+-F-qTA%f4ao^!>7-77V1 zyac!C^X*&%swsJewU1lQiZvSSQ|e^hc^xkTlhG2ks-Rf6yvBekr1)>>DjZj;5D}&C z8lLhC^={3s1HmtB>glD*G^vo%1gRK(sggHG$8Vpv@L#Msk5A#hUR+CTdv-=SC08+a z4*wK^YaR5{h48y=$FF*{L#;r^W?qC!Y7r1g^l&~g4)RDUP4vVpn@tSo$gCY7nYA&Y zz(2)kLm;O=OBljNK1$8Zh0CUL)pRcOOTl*>4pI2gYA}B1H*#kdulyEWa&s7bHFj!0 z+(4vhQ8DDc&pEd3bB=BMEYC4dFYuJNaK|s<#Wx<=&0JRGYYWF zmotEM9o#8Nv)WC@<>483wU@IPC!rQ%Q8lZPWjSz73@X7ol?cz<*rOz4 z8?5uzrplj<>xK+1knBT5aJreN_yNiwsW~S2{qaS&8}wo# zKNQE)wKy@+i%I(l!C)(j1pS0Su=&Jf{Dg3ljfZ6{l-PqUiS#oWrpuAZ&}=sQKw^8= zQ=mwG25cG`R%A*~3ge+T1(a|#$zHigPE1D<$=***AQe+B67=ds)K_v7b7YQW)X;DY z&gNIFTet9^gf?Bnf4TNk9Fy%d-P~qh1}oh@+A@wkuw0uu?+#wP|3DXaOsx&1`D;!p1={3?ALjG|nUrv28z3R1wXj zVvI>`p3xtH9HeUA+pbyqwn?x-}}$EoyGbdJ%!o76RrQ1XU@-mp3U!k{qIx#%U=IGT|F@R`d?+ba_;}( zOn%wxe>1aF(-Y-${|}%1pW6Q4PVYac{ZCF$L1f(j!_4$d<-Gqti(h*E&zDl5uKi6< z%#2qm_0wAWQ^38wwZ90-o2>n1K@n?zA*TYX=BLx2(mKz`s|gBWV#TXa5-&x*ww9*# zG*`g*V$C2LqHneVHBC~Vd6ns$d6jCpSE^Yp>|BbXL<8aWod|$pKtYRPS#a{XpA1i< z2=_|pc7hGMi*<^%6@9lswgzxNXRw`~d6^uHN_^wQRS^f@Io!x)YkcNuNNo5=ohr*> zea4Ifi)>nLyLDJ?yN=7Gt{+OjTN}ViiQH@gS~orqfJPC1Z*;1%-|h_x0aj>G3u-J{@mk2|%_p45+MG z-(Cp~*Z3}kL9!+$O><-h?k;7BNEzDtv36)_!O{1b!LzrnBZy_fETyq$DBmcK*$>c|tWf@dtD zX4oF79a#g58UHFWXZiiK%Um?B{_LE~&cGL&Hjv53q$6O&vvWIcYw%ESqPVmDNnKY2v&>^alO! zp4vbaS8@GYd^RZM3;rC%Iuy0h4?sAVwc+s1=@DDLM$l`+K#hHf`^w^7V0OuKbzx54 z)1)t+qF4)@Jc@_JDzsmbD+n@NCinxRSNbOM*P^G^hlLQ8*J6PE@0Gy266|D*9fy|K zJUWubQ8Ff+CQ%}!#gBbTA#Tm6n;~$2tP;aKofg^Hp~@Xcy%{&EP22NfT#1*E$XQHw zNh6y9%k1D;wA#16Aq6cfMSv%I`8=j@Rhdm4%X@m{DWN!T1D);xM{(4Lp5YOp`38vk zOTRM1i>|%i0MC`6F_axQs{EfGg>tKUYFGLj!%UVv3HIc@RW?>72pz)u%-1k&WK4rq z-U6CrX-^%~S&3{M>p%g1TZo5{%eRbsp!}Y8BLswfEP#;+KNUD zy!^!G74~;j9!216r_&Ac6igyn(do8p`2(9MHSb2n!yO_+$FA=t8F)H_iy=9{mCR^- z7&cFW8^dPfJjpzKIPCZ|S;(ea@A^X)QlX*9^3mLWD2F|xU=w*xgfz+DA@L@%od~t4 zqaiW!)ES5dRB!wc9{$5wTWOR|jxGmPrOz6%Qb)_0teVks3+}3!;!Bz%mt2&Be+-;* zIk`@Wl^eI>pu!%JU6?JSPEd6OO06iHSF=iC*}zY|lGhV@33te9GUIwWRS#3D{) zUJO9S7YkCs!1|2Ps2lI!_2o_>pJ>c|>LnP;Jk6W@I9VC27WIa*hff4pd6gbJM-`J4 zoHrN`G;DT9MWUBTO38rS>U(kD4tChSwLG1~#xSQeobdr$eMfoaXInETbKU@&8qHn7 zNo+ZxFXbg8`U)sX)Hv}1E@p&(~`~6D8Vs&EI{-*g5Q$sUePq;%o3{c5W0Ud1^)A&3G7khi%;*Yuk1#zKbFL&Gpb5 zsNEByELbB;NPmEDG#4-w?1IP;A-GZl2BA2cQF{YCgcTG^tKp>i*MyCocO?I$>nfB%hk+-FD~n z>z6?tP|2rPQLZ}n<$Y)7bk!e-T*b&uhqL`LFjzq!{wI;IzzuMz1(=`9;u7~fQ}ntm z3S)k}=rp~JFLtahW1DBzF^x3bLWwQV%ukAs{^uV~;CXX=Htu;dS48^|^)}DWu$^@2 zvsY@{9qv3$>O?K~vNw9c`gCnME))k~hbi~oc zwd;iy*KrB_vFa{afNs`pSw2yYG!CSx>)3h6f;pQf(!2gMfi`Gh*{u^Eic0UO?~f(^ z`3CAYUqJFP;Wwb}Y{V+c6hBHxzrNmfg04 z2ps?HP|tR=&c~+Dr}xQUDDh_=8xtp+Yv{@@@`}Jc7H2yJOeo=)@}Eqf-AM5;ljxl4 z7*fbaYqu#AAfFKTVt6AhGnu7jJFc7Gc?>2jGkb!t+U$69I!+i1>Q5LK zqJcf*$N;irGD{LI>2|ljiJ0yoBZmiylbY!J)V&k+N2Pch8LYpPgUURYBbnGTT?Gx{ zVr>tN${=~VoKWN{EasR*DuGK0ocr&%z*Z22I&rieo-~V+2|G$;#>FJ4c z|9@xlJNN(lss3gA|2AvI4wSp7xra+ty0&*iM5U6r1bIi%;6^28e<-*X%mn`HrJo z#*s&ZuICh>IlF$KYfN-}jaWu0*rARdm{)=l8pcL1%;%=_J>&V_>39?b@STfCR_|FK z2!0I0HVqNK(>~P55l&dbsN{&x)>N%tuMezxJ~c?jJN1@jx5ZU9{b;fW!9h*e%7B2g zwn3p~ZX5-N9a`1)Dcx-yZUvUVs}UR$Rkc?i{C~%P{e$BVzxwL^zp9jf_w#@G-7kN6 z{FU!%L|HGZYR4b^KqUOLFDYqXfB2i%AAV2f;038pm3u_sYs2VgCynsc2k&zrlLHW9 zLWyuP7a_Fh6h^ir5l8v@FMoFYn{QU2;e7PZKRSN@&-!5e=%+sdH0cZiVMRe0xSg#^ zSz>zq+ixF#>HDw#^1r?Q-dA7!(+9_2{u;(*;|Wv!?&m+``B?JfzxrB)2za?UM-N!@ zX<_SGB@R9;V&kJmTzu3?9Ul$i<72xw_XpO~#m9fV-9Pu9Kb?8Z+Wo2E7=8RVQ<*r| z|7Y_%AOC%-f7#=|)71kf82`cJeEfG7zr6cj%9E2bQ|IHqPyA19|8J-FAJqQGq5V%K z$A2@Gne+btEPi7A*GB7fo^}@8RPiY2f1#S>2i$Cak8I;(ht#g&^9He&@p+G1 z(<|gTKHeqPV|+d#^Z3{!xA1Y$XV}VzSx-&dymG|e;V%@~p-!;S@~ckIv@+Hf|jM;p(^Sd`zkTCHag(6*gN%wexx;=2P@M8Mh#5ev04<|g}ua9sA~ zA5}nDniaOjv|=3q_*mK{r;(Y`b|58Up*?wG zm=%Y`s%W0otyWzxq7UN2r9wpRDnPJb(6yCIVF<_jxp62#p{OKHQpi#m&xnc)>Ex&w zrsD9JE)B-ilO=^wda|tRZl_vIRZ*G`Fd_tj4vcY| zDqc>FfY9TTo=P54dYdO-hSV5iyfaR4{Sx1+IcX3TmtKVlCloX$K0^r_6Uku&EsMc@ z>Mbp!Z$WT!MD%U6hAd&S9q5POq@WSxv8v++I)g8F(iv60cZrLEl0fjAw459ys=&LXv;z_ zY9y=00->^5ViVIF7b@ipdP7U!$9EIwsui#T8gjTPFqhwABs$i=xp zW95`r)AMpBwHJKn&FXm`PqHJ1vhP##`Nnh-G?D$uY?P3D8Rdl<-Gop=Eg>wrn zpXRbkCDSiN>`f`B>4&&*pUr|`E@Sc|bjD2hMyvaLpH+fWge`F^sZ6gp%V4#A-jl3w@1cT*f3Be9kUSAK)34m5YzJd}p{u zkY(;+8<6{*G`8bhBjLpl8o&8Li%SD=TgtVDoY%QFZ3Q1Pg)t0kP4}#mwzQSF>s<|Rx!BS95CN@ruRa;FNiCkpyb6R=5wgt^#;-lzpsDI0_p&3mtF1$YmqcPP56s#ebEx$0fd2bHY} zov3BnDs30Pr8Zr$8@*=)FG5>m9tea6Wdg`HFlUG%R&6$&>sTnDvez@nUC#Q9VQVa} zV)UK>>8fXg{2boIOp0g<3!4H_&$DzNHa3NEqos7jz3keFhvy5f6&TM{+Dy&!LUCdtXXs?Ul) z9xFZs(@feheGYXP6{k-Q-mAU@u8b9`&$!!MJpEC|u^-(p+}5?vl*{82<(g*Ty~@{G zogGUrjT@!Fy}w;s3p^;VZa|~VoCi`2E)4oyX=W;&kk{5Tc66&u3j9B8`|+CT8P8%w zE4Fu4D^7s*+cSh--JrJKFu)kczc%~3i+`TNpG|1&b=$nBYiwdwBU*ztt!^vO3=-I; zZkSi}BfC-MMI#-LHthq{+aiGN+OL7BI+gXrgzgIctnahNgjY-D4F4? z9Do*HTCVF;gaRapOb_uy$VfR$fL^~)HX=5h6h_4$AV|!^izwlBu}!Ln5iPsEn-PAC z9-@=BjF=S{I+<=K6{ES%gH8jwx2h<#mXUeB)%C&v_`c4nCrn{LcWXY)M_5t+hwX>kBul7jQtjLp?jV76hJOy-)%9bUS`Euyup@ zEf>ZVciH8A1X@M61FF?Aj#?Bq*0=@SbP{t7)qHu43;z;MNPUzqVBHATlQhk zyaTF1c6A2XSS2quP0Q9>Af`aQK{}?w6ktb}V=_$6iLu zq=phQ5){k((bO&CVOEj&qw}Aq1P`bTzef{c1JqZ!gSK|rqFW7L%XksFiP|HUm|J|!h$1xj$i|B(eb10RYDx_hR`nhI20_k{ zBiV~!s*N2GhHenJz||EU|G~iKUC=}uTG;TQ(@k`T5R&Q&qg3z~N_iB@DpJiT18mG7 zy8|slXK#Y3Pr=&n5$x%iMziyDSp%X3E29o$J?vDLq`tqY^cq`)?*f&404o^2q*Bo= z&$fzT{~;7rWJiSUGLnIM%kur|1}(7{vM8#E07I)?&W=;-D`{-1zsD-qsN!GfBGG($Lf|QmZ zfW`iSAooyrdbascZ`p?2JnOL){;fZ_t+AhqUwr@XZMOaIk?ZZ&DAa9n{>A+M#Je|c zmFoT*Yz7*6|L=IEJbAwV_iTRW`+q;xzwG_Lr>h4>-~T&4HFLiI_e_4-`+uh{PfU$Z z&YbW6{lx#&_WyQz|3U4)GCn;sncDw9Svl|j&*JyT^&QKD+AnAi)~^?5wYgG@NkMTx z6oZZx`A%zAW~|D@Wx9zQVWkIOxo*0*Qz#auCp!m5=)`DZJmI5{xqP#FY+ZuQyk|hR z4vKvHP-U`Q4js!>%LN>N_DU*cPUJW+0x;OWuj&%GJQr^8;QKpzB?dR)c{Ktb^M}*l zYi)+N9M%7HVM6gDmv=>JmJ`|8IE@AI6?4=Y5v=$Zoy_+yD!c?rX3q_9`;(*q@8iO& zX8i#S6W)Syvlq~9QjrhZ)1X2(z~xLgz-na==LWd1H_bV*TqhPs^POv*q%0XXEJzo@ znb%6hz3vT#GvTZ*yM@HPWM-l)qX#=7Vy@e&S#PaXF!#43S%n~fg;8GI1bOVM3&8TJcsa0fPl5Cz0 zn@_^)l>#qv>aMD0w%Ll^4m=%qM>oyg5c<2t7MK(Rik$DQeS9*6qdN7?QSPI;{Z_R; z(}aDT<>le;=k1$;L(kUyo=)_){zNa1V}BUC8dZAsE5y9u35>bjPTakk!ta>hZWdCr ze7nw^l??(jI~0_Hl4Ninlk{Po^3)b}=;gvl1hi>j4I^3m9!L`Y34POL^d+p^)vddv>V`v1pa**B;bO0 ziw2S7<+sU$zQUMN>&orNZ-7X=pP87YQ41m4VqYhz=Y+Aw11B4AP8s{V)v*SNUw=>Q zvB1+K%cHodqCa8ML2B6H2p_63!g%25gyELQVKiqYIW7s=HBg@pY)w)<>Zna?LcCfQ9jks0r? zy}$}J{O(HrjlOBs`+(9?agzIh%IW)n1`&YQB%$A2H=~Az90p8|f~8Q3-5`<4^-6lI zGd%d1o8?UK$$6Ejw0GhNUDI`gfuq9Y-D z(VgbtAtdE={7T2y|HbW0^Y@y!#t$ABPss(y3H*PRrzX$m|Ig%izW)EI{$;QKpROJ_ ziT|(i`1$((S^RSRe^n+YCoW$;U;qEa|J3&Xc6$GT?SEo=dOCIg!@2+Gv-!pRKU<_W zh0iAO2D;SZ2QVz*hM9}bI}O^l92i|5cBrr6QH=p<-T_U3+pRS`VUm%y1|#wYLORTk zRbbSzQsFok4RZvu-#38*X(20N9R|$Xu4e4Tu?OHZkHJL%4n>F%Y7Q(1*5a&S-&ZvSyw9DCO6DbXHyMH}G)ji0fOG$x* zy~rU<5r~ko>)DhglN#iP+0->2tMSuPykSw((>FE^;)v(Ac-rFWnwEd))LD+eI~2JB z)7rP}pwQKgxnOLJdl?@a)2TRF61p9?g8Ggg7%ZC${W0}C*VDD*uYC9QuYdjV2VZ#g zlRx?BC%-)YyT3U8$Dh3Z>G!J|3Gno>-xup)0;@9;*5kkV;zwWpY8+p^FTVW*zU|TQ z{rmqu{`!yO_!@ojEhO;SBjE#ne)R1R(%K8|A*6t3;llGSSI8!kK5WDrO|8oIG* z^rW`}-k|YRCLSf^?X2pMQ1YwWB$BnN&5oQdDtTNMGSBn*>|-^|TW8-3QT_%CxYla% z6OwpZ&!bI``a3d!6$2P`MVudJT(EqqMTBZqUP36;T+O4_?i@?NXqRz*JWduzM%Uxf zqJ)Psx)DapQ&2p-r5n9{=)&B6JT2M5T4G*bbUaqQ0A0V^=HH?r@*_$O4Np_yMY-Z! zY0wjASLBhEi@dj7;!Z7}a}SuexubXH@RD*Dmrb(H+`f}*+_&W-K5TNGyU1h?E_cZq zK3atN&unwYmJZp)hs#}UcE|(nA@c@4x~F_A?w-3=K3IM; zQ}86`Kg#F!-`V`m=YKxczwG&+)71kfIsY+ne*WW3emV0$GZU4`iOKW%pHKWxZU1kl z_aAuvV`{pRI{z^>GkxCwpT$qi|1`LVcZ*$Cw9cKkU*t~P8{AF%ZEm$bcFn7-U!xGDWA%e0kztzw~7mEFV-Ghe|o4U zWMhf<;!Ieik9>El*;3L$oNotojRt}3Z2QG|w|U+38+Vn-yLU}-reBN7%Gdj~9(fW* zhCA3H)c3<+JP;)I!9WB}M6eCQ5FSL~d@8JNc?}46li_$3G=y8pa5lt-h*mN}+^7vv zPBMyZ`G*KwiGUyosmDV?=#5lLFDGR;m9m|aaxV&RyNzy(vK=K5(Md(1mj{U0f{0yu zSS^!yG~bj733fz+N?wBPJkYy2pxH(?h;K$2Sx~X=b{#C;VIIW!9Efrc6eL;5%ez0Q zpfB=r)&}J~$jiBylhd=DU5LJ})FR)B1cA$`z)0fnr{kp1uSHQF1&M61(-07`l8V3` zA`o#c6~U(-AZk4oCDB~Wh-qWQ?PO8;fexMsne@;%ujxlyTinA{qQ}>S$EqdY5EEc+ zb4kZ}^>rPmXD>#csHFEyUAN5|+ZnkGYLyH=!d^Ke*eeE z-~Zd=ulxi`f|#q7GC|H%alU8K3aE}g^Oo1{hCcI{^O zAO}5_1dV@6j=QfL#o6geI+clPLZGthJ2u^;ZQy%|@%2Cd!^i*67Xj(s?;cO0|Qyd2VOtP$R-PsA^3cFUIWb zq9557q(4EOjZ z{Y2g74fi@aAtO4}eYD)*neUONc}J#nprJq1pC^1u?XmnDzHo5#E(u?sM6bTj-ghb* z*aT!F&@FPr)eLUh-)-Sy3fts|&my`p!&A*{nm|h@bY8@56TIyc zl%RI}^*{OD&;Rn(PoZu9;6J|b#n-?24YazCzWw2=@BTS{{@dRlfBkQ{BKgl>eK76%G5=wdA9!(@lXaPq74zl9?Uj(M*T4O{kAC?>u0T`8ZKzm0#IhK}QYmvZ z@EnBa_-FqMR4LbRDLh>jPaE;<#PL)Fp2`sA`S$DYeHDa**QrFs|Ah$m6jmlI2eefS zVK*cFjL{8IR4A!yUg_vQQ^#Fa@=)XB6& zN!x~-53I#aq++F^5bGK>R`gCPao8&U=eJ&c^@}|1fBx#L{H9$YtHvgmR?%Nv)g87v z#Yx*@<@AWSmP7+9kfITl)E}i%3Cc7s$~L}fZa_=^-7mia6oO;`VpdVCj<%{5BNUo%4r{qdW@5hB}J1$(ZGs)GkQnaQ7s;M^?E!K>=1|B3@s%ZnFb^McW zeDsgMc>T41c>OP54n<2iEWJ{W3)ily7MiFQ7E~o^RJU|ZBj~{Ptm;r)b{^H9km&JL z?eUbcYCDWq8|&P&^uhe>`1R?q(nQ|}&o{0#)&X&~IH=M-{LS%)-{NVZ(oi@2@`q4| zAAjSwVWsV4SK7QJ%(rx1 zE?vjtW?@|l3#Bsirgizzzy96pfBQDKVg~AVX7TVGP?x{^#g~tH1N_1l!n)kfuFL&| z()G}1eM;kY^_JX*fi-;m!7mau9I5m(uW6i9D|U9(nid`GWU8iR+E_)uh*vbX&NDRa z)a9k}d|1)KDy(Ffg`-vU@wfl}Kj;RZ=*BQ9rOeo ztM!9~`L<(u!F;y)HakDHpr~tMcvmjJvAX`t{|0U7)jxeOsODdN=ldUh@E4Hq_-o%_ z?fcK5%Z1#>fAzJn+UK*YeKDo0wc}s^&GDDNR``r|sin`q{oYoo((8&%zAOfm<1pY& zR0Y3yg=1=MN0_c%$A7t_$=Pr)IW8=Dc(UOnH*;Tq_~YYWe+Y+i}g7n|U7OLjx@ zQ%)!bw5=)PyAtEsvhJ3!Bu1)!Ts}(&Vi68QlunCNGfOr!1cQ#BF_WfHe@&I2bu`cb z6ACaJw}mkmG3GvtYg2=ZQ@SnR(l=yz7*)XXh&=9WXO9AX4&COL@@ztjMT}vhOAxCW zm%=cEOrYQb!tq)7E4V5qd`#0!w1(S0(F(7=^(U{s`Q5loD6+p9{^M22+!{ad&71l| z;KhcvwFQ_vbQxV>iz9!t6*|?JqCoFvxQpz8&(_zt82z5yAIJB9Wv+a^F^&_Q|C_Fy z`~N$e-}(NpPxUW*|JUj2fzkJWRVF58&i8+v#V=?7*W~2Ho4UK%9@_Py+Kxkc6_MQxYg}*wL*7AcXM_j415(GqIG}u{^8oSC80Mg$6s) z!BIHo6(@(>;f~991^WEZjLo+JiYCdR=YP-Sr=I^gq}&{TLaZh`cSFe*JH^7! z$F#W3evez}?^4oXXLTsCSTqfY#mx91;D_I>fS)6BAO*|mKniwN-iH@ zTI`?`yfAj|!3$$m8(thXvVj*i3Tna&)!7176{40a?Z8J`jn|g9L30^DC1RE1jDv0LJWxln>$NX(Gea?&Nr9=|%jo z5|8s6k6KUfO~wv9G3k3wIrbNn*YZk8#eJ(`yYuX(lVau0+RXa9%~)ZW2;C335U(eQ zpWnXm?!>L_rzw29$x_-6iqH4D*03k6!X!JEzw8tj?>n89nI{K{GW%;^`W+=E6Xp7Uv(o zaEebJ`pW6A>w#G*lNW(GUXG@99#Gv9r#_CF-B!!5<0j6kNA|3i-MG&#b+40c>QK)L zsJJk_Ls~BGYOU^*=t6Isuoc$oVRRdOjWq1N`?Oiz>6sQ;H#<0;@Pt~Pe)#ew`?!P0 z7mT?U#oN!UB{g8>{5KO|woYRm$DO^D^vn#dU%m_@1M9g>;nSJ!5Qa{)&ypuT&V-;b1UP-jtAI2)c=+%Ij9tcaY>UO%F1$m5+-AygF0g9WAdk<*Ic6O*| z2iL$*^vw&ENG9(nG8xI2M-=(2tMb_qY~N4HsV(KSk4)N88TRqo=cG`ULFg(Ktd)+e zl`YcTG&^LK=Vk2Lhk8?L2w1ra?AleKH-X7fnk|{8p{CgaCQ5NWEcrXos9A*1FQmq@ zVB)nXLO&GYMwXD9DCK4)A?6DkTbq3ayq*;BP@KJ9v5j~`d6ohnHG!Cf*K|7)n0a;;QzR4n3QIt}&i!$t3j>3*YP+B$gT#N=9M~ zhP3cs?{S)YT&$i1<{{O^cB%z&FoBQ$HKJyzUZ{X>cRPM{12=Br4j}ak`8{#nd(;Zy zBi{NTfL92-2nbPyXq)29p+!7{@!Mip>pjy8JqW<)N!E zva}l%r0dA`ue)A*IiT%jr_&9f40!G-I`m|lkM+m;QIA*#sT)KQo5s-)nl-$$rAe|9dgg3*`Nlj;{9t7x*vvO#a&|u_#jwmqDLFvW)+#u*u54&>o-WiAL=zA z9iojDy`ZI4kD}WJGzs*})qv)<81_n2Lox zUQ-}O+6Dpk%Y*gEvlT8L)qDI(P0XdRbDxN-c*^2s-R*ELAV^*s;DQW5mv24x#kLBr zby{`+h7`|wpZo1EGo*D{dWMy73eW>vL{k(l6EjI!m=j#%%Ng;v6A|=x!3MFk3w^iKF@R|WEQk(?I z<5Xy7S;bW-P}NX8An|jqsB6#=7HFouh#@b-4k%v;55DN_&%Ky*a zyLQKsTnB>koU>=odOz=eE~L@bnT@C-3IJaTCd&Xw@X-VT65z8~0uxnPRY_E37BjQ( zAW-MXJCZGVN0NQKSz^l2n#)lH zoQhv5XmHF#d;sJ|pi3vOfeRpKTlN5o0+#*?Uj;+YY>odmVY*Wu58s4$lZWi}t zJm*77GQM*Q*ja@MKcFD#NcbAi0*LN?uUnv9dOSD zFZ(1tXGiJ?+(1S@0P-#e_Y}MTge5Ko9XS-gd`qC z{cn|0vegK44%h1XqpVb(>$k(wIN}tGC_>i^J&w4hJSK$M>0)Gzd(qX zXU5eMKRulRQ11)rABg|4Yl(B)Gu6_;V6Ns;;5jGwR0afgBN9GmE$2he^iXPJv-F#j z9fy8vA9@(r)2#s35g1rh9cZ zr1QdLi7w6f8d|)q}zW@cQRrVHk(uroo<+EdD8044B$K|rC)EPdiGt$pv&7P>0 z$;gWlS-Dyu&C9>_%)?CPmY$h>prm=}^H{1+4z*?ON=mH7D_m_I$bF|sO#(jyw7G}N zPNvTN(0YHYE1-C~l_+wLCz1hoAb0TypGdeaS~r;%(jckgAh-`8!<6$5Io>@r5yHo- zfYZ)9C+A%8-O_Sx22a!wNapx&PPUVOKl~V3XbsPkieAm9&=&1H-D(*2J$DjPwTOO8 zi;OVfuB2@_O`n}U42UVcTj4NQ(?s!3Xk91&FlRP@%;J6sLc2gCSI5w3Lv@UYhJt${ zNsn>u%nmBS_yB7=zOFynXX%Q24OZ@nX>fjSur{nTud&^xb1gCVE!J@Yk50}3a~Fq6 zwOPVqTTa)x+OBG?sJqo96i$tx6?&9qciBr>popnYW+vVsg7XE4Px(Z3iXnA+ZFvUL z^U_LmQk-6wo@%nn3^`{%a{%a3fXG$0=QW*G4$Dhk?CGhiwAM-~$cy&GJpf{?y~I`j z9d?sS5Q-qFRSXidEfjU=E$F&z@SPMdqvYT@*F64!y=8eF-!-Y!M4n<1ub}yaQlj5Y`f4ZlJ@dAqVj$f(J#3 zf}xGD&P`jmM&LtUE?9~tp2XoOYI*j?7*fxwJ`-v@G2wN`PMd8=#b%sXP`1=zZdCgK zij3o57Bjo2rzc}ed~G0s^?^eBi7q3~da@Kv8QXO-WIH+aZ8*KGyRKe!GF5&Cyjz?} z%TP24hTdehf^-wJ;lZ?gTAs|5I4H=VC(!34-*aZ>bunYx!|plsoE5Q0(Hcc~E4KY` z?5wD6s&ANDgk|S+&-yd0ihA)a7>hn^*{ z6?&!?tA?cKJk0LCxl?xE9?IRfDVqLI3){$PUVTh#Z+Hz&s(xiPKOSliymI*TDdxZr%KhZUBOF{?+m>9 z0N~}*ndFTm0Ns&C>ZKfq0|>}(g&8fn4FncmFzZ1)>TN`tp+|$~NutX+oy>fy7;tvm z+2yjQtTi*F<$ph(7S$2@8U?yI6-)ShfN1-1N6P9bLCXI=Z!n!7a#f}&3>Cl^C zae%5L#8jpbE0snSDzD3~;H|$dp&~I*eP)(JBPpKr{I=8_8D~S^(<|@41Op4qg3nkc? zJuHeAu4P{o#YyiS4~inuVMpW-g`3&CNzPxZo)F!@qR-aHX*oPlIl8+u5hGm?Ur%!W zg42Q{?~r?9ew1ilWHF2`FNcWDi3nNj%c3Tw{+Ms@UF8Y)vOv0S-;k?p8j8RnxZBec zT+}I?@K0(vzVhnv4Y=^(cF&ExDND4O8puVsSiy~IvT%zA1~X#JqHQvt;9;cLvm;7t zx5m=b?a*=Aq0?f%^Mstv@*C&O6ZGJ@i$sO@c?s9EYkt(i?d(Etp6FE=Rmw)>$B<^= z_dVb&foBWfo#d2aHy*+aC|!Pzt;Ud?bD)RMObz@-yAyfe6po2rE~o}fkOv)_s^J#v zp~u;_ir{!inRaBJAVy(jheOWp0pDErOx(=Ada_?G1G;wnfftSvQ?IRD-oauQy+v{) zy3f{9G47hMj4?1ke zuT1Cf#sF{AZuLWAAO@;$9x`Lixcjq+0bZrz$x>&HhQ5SDyVP7 zm+^CyBJ!?Y5;5Q1OqhCqnBa_jOL$v3Hm%f4P~L}1le?B7$2Kn#HUK#&^_P;6m!Ahb zSQ{7=i6JyL>Ly}$Be8uy-u4=2g_N)pUR z@LJlsXVh#Uu5nj&_;Bu< zMhSrY*^mA!(3D8&_fv;BRAxfmoV{EEQ7xd{uaFA523P)%p_8BEs>W zgM1dLe4{;Lyc!{`PDSy>0Z4^-A%g2bA95;!DYoTiaI3_ip_>y$&?UaC;~)x!?c`I?UqqsxZ?bshYJwmHw?d?dC)!956638r*jn;yg@tV83MtVz;I~DN*=k{)mC1^4 z=JBZ|e>j=^g-E_oko-X*MDeKA;W)ahRFM4oWb#LO47#JX58Nso z3^-+Mrg`K<_fi|WJe}dpeULW7MZbG{Pk^FQBXkg@n|sM47e2( zjOSs#IE9*n=@kvmuBcm7QDYW|P@8 zf`>_h+s=24h!&nJ52rTj2s>D+3805Jg3RJloK}=~mPkYJQsf*b=+Dnt!^co8=MLe? zZ&BvYV|dQv%_dJwLp2YrXyKbK^oA$R86fY#`y&(GrYb|^H75p0(n~|s0#V)NVPqDy z^vM^sT-z%bw&E*Y+>)j8H7Z_$wTAcc0Q!cbJ%FgE0wCcoB}9=2#xp+9a=@-(RL6<6 zE2Udi?cU&O#}C@(taEj?>bD)oiIq~`%U_FusJ38H`sUC7=I7t~4!;;0Su_@Ip0p#k z18dlizWSA)|Mfq?@*YG4TKT^I>6hMo?K`i(`fqWgeOFF7wxM|hjyx0qDCz6(ef!OS z_y-Jn!7a$}nt)&Yr~mu)pZ$fwBw~3ezmo+*@%(VVFGIinr*HoL%eab;0aT-@;05vg zEI@aOk!E0^kli7XBM;V`N=4y0bOfm?)}DzMJ?Ffk?d<5jZZ<1}}t@LuXzTzJw!G6|916b-WpY8YF~akUk;QER#|KVL4J z*Qy!Xa$<7SQcc3d?Vp~`OYOHeQHvtM;%dBnHCd8%yj(rvs+D-!z1cth)<=M6?ZOpW ze#W>1c2mR4P-#b&&p^?h&_9S9VtR7VR186C&u$eTX`}czzD^=h-kc+lCoYybJ#94E zaW+b}m#I3CQIHM9T$ zcxfLut7rL+@htFJw~dxr0Mh=FZw!(6c$@xA*SxL`jvta3Sx5p3+E8t}Th;U6u+ctg z*1D_f8nNwNP33&xLF@@|@7MU~ z`#=2Xr+@PLOW*m~kN&rx{rDSi{`3!i_T#_*=!f5b{nIb1Q8rH5hn$QCi79GD57^mi zvNKTKT(62XnrXcG`X50(cmc^Dehcrm`Ma;Av%Zx!w#IlZfPe9J2f(%^6ZEPBC=L1x zMGPuYaYD{^JZ$1y(*my(!1W*pI>-NeK`WgfesX3AON;q58F|1`^&Q-rI^g#4rnbr#Y(jD#x3 z8I~iF_EbmOpM3Ra|M=$$X)qvsF*;yTA>P)jxSj##PWn&i+mHVBKfeCS7hZq;kAVSH zt5w|0J;BVwP*z0fq?d#wF_xd0@i+AlNd_VD4+kTkxFwt^Zn`*m)WIVl}br1;`lfzD5 zsaPp?J_e`bMZ8;o=hBXG3s>}b+f!Zsf_FJ70#Gra_x5!$>3gcXUsQQYXC7X)o{l`N zs~=Ie1_T?w_+S6x^*{W>&;H@7Z+`skH(&fZgk;~p@X?q5cX&}DJiYjAHe$Yx+nh!_ z60%9+QGhzme4bK3CVBHe|0m9{voxZOS{}`3Dd?=UfrY(wc2DrK^qzRhF&haV07DWV z&b1`Pz$`9FRSnbV*B;LV@nEpxBG{6l!*;e4rQLm^`4QroW<8_}&QZeXnR``l_JHi^|T zzy>??gLZgWb=z$^g?Pu06SQyD)E|I+WGgqCk-=@&@67_bwUDSie4V)_DbU zqw~T8$#|5o8=kY0uzN6}!weIJBanqFbaMzu&YrWMFc9_7FOcW#B<#ND+)vmJWbxQF z&)GURa7v)I@K;(qT)@m zMVBE4&eg=z6=^^OM(NF$|M<<5-Im0R$Y>_rqKR$4&P9h6k zdZnI#E={Pw13BTN|L2Dj;ANND3chB#?d!U@+kZn^UhPcSHVC_a{cbn_LoN#fOYNz; zw3U|c;=VbnygZ1lt7&_*6n*v$y>7L5Ib)AF zcdO-e7_|Ga5C*;!FN8tgB<7eDTgTeEirSsECkV2=(wp8Z=|yjW@;HLMqAc!ay^qmD zQ!k*@?tyARlMC+w%HUgQCZ`ps-&#Zkri#|-Wojzespaa|7D2h1qIP<@T!lNeNJ;n$ z3RJ0NonEBU_D(HTblk$CC^gjyp9LsaEKe;|Tm}bES*TP$=WlG9pi0FNVpp@8>AMU% z`~NH{X@36 zh>sQJOh%rH$sR>&d8vWtnm6X=`MTY^nc4*hV|{XJyM;DMBhL)*D)|Ciq@(moD`fl1 zuGo*(TyZ0u;xzg+_{nR^Fr|swQvUPl)FxR~QM2Uw_fx4z zV_=hYDPHwyHA=FzqN?PIuPJW-)A)qAoEiq`VZmi+dYS`GdK|dAfg{W+d*M;hRfyuU zKXDb}f|&H-i6o479dZ!)6>E|m(%3896p`=@4c2v0YWtu5`lEmP#+(1^+i(8-D_pvS zGJf`t|5hq_|IO=v`rFrE`?1vUfA{s*zVzk~{}KrI^&frrmp}PEy2ni`vjb)7MRJKy zFVe_DmP(Pz*6*Ijgy~2U!h(tn%fzxu-Fdi|1SR-aQPy|48w!(fh_~HepbHA8OjAOJ zgC|W>rMVJ^7S>Nfe$7o->GU$u`#r5^cZ*rIAPyE3G3!9Pe=udx8WTF!D%j)i*!gS7 zR)Z{Cr8Kkp{JAYsLBV&!EcZRcsE&xl?d)4^Ybt*hBZVxfbOs$vFz7&M&@8CZcJDR? zDH0UL>&6UN2{*@)b?`5&LMuYn^U}-&Q)QYA07#7=kiA!~&ZF$u(=QqZaXWwi?6li2 zm(U>EfKiqBFnd&7^y>h@1HauyBQ}07rVv@-$*nnm@ZC3G_>L0Kb&6^tz#MBhFm#Td zU#y)~^_wsM&6~gb%i^jv>KErnPq-l|X^0Ga4+OAu|8C6ba#ar#7iLb1 zrq?)#La<+4I15B+IGqJxUGi5S@=cy~E? zLUdica5i`-dliHCSc0eO`M9u!(OowW-5m~{m`dUKR_K2AqaWo#yUu%1t0WIuQ_Uf3 zayKE%`VnN^q{uiuof1!KeJk6FNBU&BnVlSj23VgTeuep9QOIwLLTMvZWe!O z)oK|~TyO1T)%%M-_$o+~ymqShL%rT6yRcNKZkQllGPwIyFmc2o%AAWK4}hNvRWFGd z9-dqkscn@?l7_~6K^Ez`MrOjeWn$?PWlO1&@S$t7`4j&F);{oW5I_*=m?lxhLlwCmtM zQ5>7LL-|H1Au$ZNidQN5q*>_IoRTF9NU`fNrAZZLsOi3ih`^l-A$Ts)&V?`@3oM4m z9lQ$S%+Rb>wIXB?mLWvP^I91gh2@YfseLSWPS7x&O097JGZ7uPRl6j8PdnQxfz)AP z1{G*DbMr0zPuOnCQ-OW+Ucycyud|D^lOEBfv*Y)@jzRvh_xXX!H9DOujP2LBIw4~R zx2D^(DdYP!bs|>S->-?Yp0cHM^b$gqK|Z#X+h(E9hA`k(`rCTD>HHQnl#YTiLqFip z40(#5iKD1SrY36kjwen?cc+|?_8LMMx=bC`O}DGWVFMv8&;aH#gL2e)fggj`&reGQ zGC8~u2cRcZPGs>Aup{YLP^tBUbhSL<@4Y3V(;Jv(piBa@CkcXy6V9!G860BZ#jn5q zLzLX+poF6-B^+tmAVS-HKwF>gys;G%FmjDU6WGGNJC`!!9SBB&`5{op%aPAdjThza zs$G+;{bA8gqgV&QGU$PGHD`zUz+%I`+wyv0A2(BqO(K8(%`bAW-hBB_fAJ?jee<>N z0DR$LoHBXQSRY;8>xa$Lol~_bHqq{nzW;+a-}u@)v@#t`1&tKTsf((SsVV7O+Q{qw zcXOKmYNcy!neie=DO=rVPqBuwd}j#9{`XNVUo&5Jp}Wya?~h zs-SP_VPS38(wjwu73O62!i;ZSt4Ia)Vy!cOY42AfdLg{PlgsmD=s0P^qBTY=Q9gci zj?QSx_4OtA`_-Bv<_383rqGt%JDI{1F!ZUWpj=s#v-Q?UufWXp?CC%mrWR78w7aSa zYLo_>xrv|v+|(jFqh!*i25tJtHRfYXh#;S)a2VOmcdl7NVT~h4N4Dfcolh{Y)Wj+j zo`{u|Ph)1e;}s|hKCP)m%;-zPUhL=J{@zF5%PC0SX@Zd^tVJ`7Ab)t`45N%)^HYql znf%KirhSzH@kl{b}3T1-V z9)1#DDec; zrnpsVE$m%h^e(yRR%sbsFCpI=Jdb+%E$lWd})yQ1J9{l zyl`Rh^1{MWty;Tu>C)x-+QOyZK7X-v=ho`V{k5C79$v2=;pHFIqT%YS+`F~1|Kvfq zJ=b4dd9?rAOP5RAkm=5ovodL8{5QhIf1y8p{C_sH|GSL;iTQuA_M7?t*Yf8NNY-!N zMp-{*?FD=$<4zO~jOsZ*XnRMM&d#lCPX>cFkb|*|T9iGEAa(@3ui^UYAX zW5=!0uu}>h$A~GRae5jEK>2(OXOMpT4O?pp8^8r;d2kLaYzje*fzD# zV|$APd%%3c7aI_~0UHvO023v=kubN1FAoydCTKkt;q$h~sKgNz{gB!dupNReVALnC z0!A5c1nhtc0H7Y>%VT(XLN5uUZj7La2Lz1A2w4Mwj&S{9z>eq%n4!X-Kwd)Zy?{~I zJpuSS)EfcYBe()aSiP69J*wpzlukXk4=<1D1qva&c@cAoBocTb0th0ucL85Q=t_d= zX~GDVQN&u*P9I*V9WK03d11r|gbq|d{nvvR!d?rYqt0#vGz4!yV$|2`kc7|@Ln#F3 zBkzn=tRQ>pfe`>pEfwhNh$34N@5=pzW5P>#=X(JsmL%aF(vCQ_B+$py=757gh$H|# zH@+2AuH6rY8yBA(=>QP6qJYt0>#7WEsD#NWf z*iXD|FTp*^xUw2{)}y$+sh30W?}_|}Dqq{%3t<`WRaPIr3Mx+?#*{~`><0<@8p|&2 zJy_avRRA7Rxpcc)#id^|0bqA{!E897Ivx{?(xFR+2g2o*yHEdr696A_m@@+?gAToJ0VO zcP26)KKJ@zYoCSi<_CDnnYkDU)Q;8ztdAM?{o$tQ)tf|*0-G_59YO6bP@}BZsJpg; zVKY!|-3)+c-7z`~oS_k8CA}y-ECt?S2}i_r?vDCaaDaC`mJ;;ilNi==2STwG2du-o zPFwadZwHKI0nO>DijprPhV>luJ??nRQ0+0gUWVUgB0uxkF*}ZV;2h&Mt5KRqSrg&? zp!dTtxeEw0t!gifp(c#7JLojJYn}AC+dwFCFY?^|@v$82#}Kbz!mDG-Wf-CezzjU6 zK?|csm4L~CTFPZr$5>+H$O)>z4n}StsT`SCJs(izhGBG@DiJ7azeA=T5jxMsiJt8V z`VlyPe0qupSsrh8Iv^giK{oaYk9{Iyw;Z|>b`>cKI%|yv&wXHvXy4;(iRxGjUI|=w z2yaO^WNYvwyAZYxZ+l^qgaft-uYIqB_v!g)AW06m4H%X95qQvp==Vqa1Cwq%WWND=*bl7FvQ?6w0m9(%Wf#C!}OL~Sp+1GRJ7 zsLfmGM|dK|PB=^xJe`cckBv%G3@1~pQe)F3FP(XI7ydnh8Ip*Eq6|MMt<+blT2N(0 zXSS8wm8bSRTa~#uab~meFqKby`d(!SIX`?`ePpmUZ$CJf3B|kWyth{>GT%Fw4YtXf zJeowUSTA*=t?z17x?5@6SsI2(W55P;ZPtcfVMBP`#eZ`nHnMC;4kUe-le8#q$IcM= zPUR>=;P7p4q8s+Txg&POZnLNGwuArXTC9bgvVs1{2kwzM&tA?A(5JczYKb{JckkSSHMct1qROkv?R=}uS91?A z`J-Ixw&$MCJ;D~gV%zKiwr}nc+hM$QG$sNM#xCbkNcHG(RDJ`EFYnI5n5mcRI}ojE6f+;b04bAoSGk;`*7|NWF6lTZfCu| zy~2Q1*e@CK1pDq-3KOL}<5bvm15MaOF>;fnOb7&aK(Ez(?>L5U!NX?I8c$_Einv^W z^eR1jm$$pa8F;-MSO+xG`ClMnAg;sT; zubx2M1rWf(!b&lDJwrH^8c5>Oxg5#2fIJgh9l6IagskPcx!_8)Y$c7L>3Hy?kdUHl zOB5*uLRqX3T=9}BQ2`Z(JIh!SEJM&iHw=$uMJ%4i9@JVz4&<3&85SF6;GB$8Sv^3imRX<0mVG-BZ?cPm6rPunzA!Fz zBC|yyY(D|In==fJ1?*@9)MyfcazWZsI4S`rQ~c6OIOh$l6up@0RwSuPbt~J5oLdnd z>)$*KwxV$8Magm6y)V>20c!FjHF1EJIY4(3PSF6EgeF30U}TZ|fFMqyQ7Z}2O%Ia8 z%tlmgxxgK93|r^~hhSBURUJuzu77xf5Y$(2`8P9f*#w%VBjDImOe24>8Rsp^xwz`O zdSFLsDR*+E05SL}K_^%EoKi`y`ap;NT!IY`;KR2A7Eo+3=lFQ(TmtLYc{_llSFeJc z1-~GFr&PvAgpUIvy5cWeVI#r;5zQrPNQ5a^_p`(T%VQvCbR=#fv1nL$Se!6OL_(@O zkc(SxiVmI>MoE37b+8;+vx#$*)Q=K=TxPQ~F{=R9j zeEKOPt*;^Mc9fc+t%hOWbAzVA_O;M!ASXeP=chp%?y!j$bfzKvMkNRgw7~mb_xcg0 zjI_XQFM$wW4W2V zH&5#%u*@$~M+vs&#$GA(?esPf;DQu*JF@AyAK4lyMdqAL&{w%=6bB+W_IbXMcy`3I zC7x~Z9EfLEJbU7Kpo)E}7wh>MFnC}h^7z?E)rLTYKS=|lqLN${NP|%uZO4AW=8TR_ zeZd8@F$rq*waBCyg+?x!cIL-R+E<5q4fi}+i9f}M#&YH^x15FK&6GRPcM9y7rv<#4w>1e?`%@xn^5?=&sKb=#-m8RVOxF?H+kt#aFZP^i1he41htCRMROqdmX`DeUMJ&1A87F#6O8Fa z5o2m;rE`7@fxAWA>dpkWx?jYt9u{${yISBQ&)w$$U7O0o9u@Pj)#*I!=5!wRY6=g# zF^z{kn8w2%Pvc=9zJ-T9naacNP2*wg8mttTuQf)nbiLNN?&I$@`h7&dTlD*qepl)D zCjGvm-y8J%fPOdV_XYj7sI*;rzeB(G>GvW1KBnIf>Gui!-hrUFK0B2CE(X># zCY#xqnCHIn4(r`TqhYB)bel4ZkD9abvg^=P z)rG3Z@JXPvlsZIwAh#Wi-+ z9`gY2*4VO-d}4(~x!3^nVetc|at>t9WxjiMP><_zVDAKWTn`d^$75}$qr4AjYl*GM zMCBgKmfr?q7&v=u4_2dtrp0b2fUBbFm`hChDKg>Pz<*Qf+x9fhPNewMKfUd9Ic^`* zd#~E{LfT6e#LIrAB9t_)6Eu9%k+_Y8X5DSnnn1-~ z!-a)M)1EQ$N!kfnSdDfbVIA&Y384&;pX+-tG2@yY7%y0sESShnR{+hJ{Zg)6Md%$t zIjl>=hc-=w_F*+K4_M6h{Zr3{!Pl|uu2zEFbJvuK?Yx#87uqx+HMNPGT~_XRLg^?K z8G%Aq24a20>PDC5b294iALpWu|5a4X=sXP1Kqw zUIH|yX{!QDmuVtbTLZcJ!dKl`QG>N4bPIMaz(TLkN%X8WExQ;XygwPp3|1JpGA2Uw zT_8j^Lx`c9)t&@265FUzrMkp2KT`)s;x4UT3e-#GazyIgHcwzfe<#Rf-BZ`q8{T)d zZq+rpsghV%XC;7GEqDBu+#c4N5gtc+4r*u}KuEY`NX2P3JUYo~RzRs;gl9{2+kRqC z+6mQZZm1)Av9CPpDC%l^l&z|xgC&H*Ct2cdX(Uph6P94sy+%^0HN}!v8$ZRU(z%bI z%!D}m%o9?hpK!HZ7yEpfMilA(Q~{dhi|He~=rdmsn(KI?d?S?WWdngf$YZqf%QIj8vuD)-_u`sVaSE;F{5#&*ovS@8QJQMWSJX zQ}_CF#Yr3bQ0!gWTor8!t$$T^A-%3N|Iw43PT<;lLz*TXp<^WY*uHr@= zWYo>&*g0^`j?5TatPi1%f7Pii0NgTyKI_O?Zw00c;g;%bf?=7<7?m0b7=8TqY{BWf zSNwAMKooN&7L^}69gqUpk#kHlx^s;Ci*@u~J!AIjoy6?1BOqJg*GHDcTMcbmu2kgy zK-)n$Ig4*&hBnWDZK6E5QRW8#OF*>0BUm$zd&>0X2=J?nU?jsm(3W`_f_3@MUZcs^ zZm#n(N(BTpE)rzY$L<98syaWlp9bDie;9RQ5?vFopi6^hF@%3397hECjKKl&qvu z1=?p3sw*83assyQo^HCQD=wRLt#m`lPiI{cQjTyIN>WmhTTRNXlC zyFoqkRo(N0wy8}k! z)Kq(1-k8N69sM{qlb#WHs#G?VDD>yuM$W24jl2>)%b5)fJ zLi3g^VzVPL+0BGbU{n(!s=K=c!tU7G^)wt&llqkBK76mXK9T#lo!>UxX zToqast>c7}nUl{%b*9L`8qIW6S|?+enn3*3&0A3}P6^4Avyfb6Lc8m0o-^4Vflr82 z<&)K_+}v#-t7tCwi5Uw5?!#J#m`3bKO%9bKqJ3B#!W>3h2x@XKm!UHbn5z@;!I-Vd zK|#K-iU!4&&iqt$Nys%hQsvI~M1bIoM&P33vl3nu-ZXIUyP_HA-t@HjfcM;nJIsh} z2seaDwZEwOriP%1Xb44E&b(&;#z?KqBaJGe8J!{u4PduNz&~))5iiZ4wZsUoO81i< z1n+rdwp4%Y@@OQlT9PgJNiMV<6(*?u=BN&(mbr{WZ zFJ0%{dyTmL;=Sg%F57W+(+|yH>XoOgSfFRrc8P9E&b$9aU8-H6RZuczQ0@3$zYU8( zVCndCdfLE+Rn(N$1mnyWndfDvocwen_5``amt3vYK_0i~VakNbDY}v@M=%?~%mG5W zHXB7vA-T?j>=N51XMmu|{w`h>_n+TFePo-EjS z&%m-k@C;>DeYr)^=UWuUd$A!E^n9F}LBpS$YjT~w@m^a+a}94U`h{U@O!M~k=G`iX z*Y6z5q#5pKG}ip6HR`)j(-@Oo_OkT;CqV?NQp_lwl~upp$}Ihy6hqw3$rwd_`P%c2 zInVVy1))|(|$nwSoqXhCMFdJk| z^H!2-gn8_V0)Pgz(eAf}-huc_+Hbe*gglemY6a}!B> z#!7q~v`P>0sDmp^#PAwA3%VcU{VH8c7drvG62pldVA!dq$pk(~SuCyIT{l%|iz%Sv zM{y$8<;ZA2E*bbBKdT1x*fEbkN*gsuMJRY@1JNP%W3mI-M$Ia))d4Kl?4AkJ2x~ip z?d26*&IPcHO9xk2rGcqcs0#0mak0P|jsSI?x7BfG=Ihz6vNI`&>-L>&5?C(xC0jiJ zI_n^%ci+9)dxW%(-u)f62XYBe{sF)gt0~>=SgbRFl@4O1qp-5vS0mM=92g4A^hu&? zb5Ky5+=i9D5?v4COt6;1tE(vr*jDEVneShCBx~=;C6ubQeRjsB-g0 zh8|2z^EfdnO%kq_aC;=bh=h5qfzx8Mz^rdAnW`Qo9-&RdP$uv3dgR=vfu@C7Hfd)0 z2tgBE71d83!!QV@LeME-HPj3E>kKN74gn;H1Wyrk3H{?9W zXtj+=4OL>W|vqDnA?NdvV<`>$X|H;yim6pMKsl>y9z! z%>fI?F#ZZ*7&&WM204Zf9L%$DzA;}p-#lG_$5In2KCQtIJ{z^lWeR|QklB9rtorWh zt&_#E!6Lmy&665iftK%y+H2P+hDp>0q2kFa#X@4M4E~ zF$?1<0A;|id6*V(F$V^HW)l>Eu{0)X;q`-3;yhdtTC;*Tdec{q}Q9Ef&u`i$8dF?%BEO2eaq80|tCsiwa3>4ped^arSX3~j75Z=#+`#e0J%EG-Px0TdGJ6EqU zmq1D=mL9~wWtcC+3}(RMc)T1}50=vMN2>f$xjYi(yP5K_pb;;8Wbr~66msD$h4hOG z@wGx|`Z{p<9ApE_f7P6`t@^oh0BVPVpFP{2Lz;r;2Uh)et-1lSFv4$I(n_|6-xt#F zW4*j*&qnj}Zmj~(4*UV-G#i!O<_FdXQ)1_!>LXEgb$TI{vr72DfVIS#MKn(qfN-Iz zJxT`&+YoYojO`mZ?_;KO_1|eWJ`Xdm0npapXIl>M<^vdIhlhtyv}Me7<}k1y!lH~$ zbsKYCh&JYW^m{yaG}p#{pWB-oEGwa*fMmCbOqt;W>8*HUwv{+1qz16hecm)1pTBa| zw4ODd!8BcE%l5PQca7$p`K($TVSND9XH=nL>vv7_Sp&ywlV%0T@z(byWHuXod_Su| zFk)_HzVd8z?c)0Sv(ftHdHh(vwuT?q*6CyY`g(H?Ffu1IzGEKV4YTK1&pU#q{!Sfh zre1`Z|5Uv%jv^`cZ+pDqCoylTKTKd8}AXNVI#F^r}GW7a|IxVLAW zIY9e&st3Nuj1(zF*|M4yOd3I^qhTg3ism4s#|b*-q74G<`XW{S%#21Fi8Y~%XLP4T zk4r>=0L1!md$}YA4>MY{c_smRE9ngY!E2&aJ z0Kh2v1jQ}PFJ02hyXz7Pk>cO6tPCuW&jL!gR=o6~WnjAY3Y2w>b<)!l>IyN}X&8dq zfd$bJfhSiU;%!Yx(0FGH?-XY6Zh#lAsARHp8CO;^Qfo5OXg10F2ewI5Nc1f<$~Zv1 z8!gTX*JPL{M$MeIB4cuyX!jM>qC*fKJY{irWvdyI|awV$4f za;B&Uu%rlYGkAp9OUQl|%u*^&JNPuu(om&hDI9z<)xeL5fn`7r(qb&` z7^0+YLCUDfZ6$qTe%L-1i*Vv;YK zzC{~?&%O<`K%CwJNLJj487()5C|=G;t9XXA9y@^@^8-L=Jr>{?5yTeDl47JlIdI}s zsUcAza1p%~7x{HpT?|^a>03Os`xfIh-0{v@coN)DO9Kx@eKNGi3hnO4pqV~x;4+>q zH<9%@t)M2v|7SKvMYkP$&IzY=`;{y8MK@fjuX^CJJ#PDIKX#?6?n9SJo#GQ$XbA7Q zLg)9?l^UztLd^F52q||{2S3JRsT0=yD)!^9nL(Qo0Mrweru9Y=3 zYaX)RDdABaL{I6xSid3oy}0C~H)?T}Hir#E$L%Foo3#MqfK@d_JZ!k=b z4c-VeD1e0#rNDp^ufZ3tR3Wy+Q&5qlTusxq!Vy3yN~O3q%N2l5(dcbT4aS((Rk1BQ zP&5$>b*WC#ow@LmvRJVU(P?^$fU=4{oQx2JF;N$xWFk}&&qRbIpvek2Jxyc~w3i9z zc*0mtKT{%+)TTrs`4vZvnZW8v-wQHyLR&@sW5i^u=SJ}1MTl%an+*CLnjR+;>D;3n zQT0P$u2CuJ5e71e`GQ$5cF#)=J&zK)K|7aT>6x=Y1I@dAP7wk~t#VlI%c_fj>ylw{ zlqPuxkPNv2a2D?Tpu{y}Z-Xk6@@??xiQffzY4Wk+QYTX5L3zn=8k4p|k5dW7m81rU zn%jat)b<&q0VLVX(&#pG=Cfqnt(}3Q0vrtGuPhn!BXnM6)X_w6J@OI#Ze(BLLB5vE$D^y{2K2LX+aLe zUy55j7?h<$Kj{I|N(5cTWFC$3g@*j}G%FiK)?^j;v4C`N1tpQ!TGm>BGG@AMVpL#h zs^;9N5jK~7o)KPi;)!e3UAy5n$0({Qomd7fa)!kN#94%2PfY5w2>mu?qOh<7K4IxN z&PXzZXWf0Dk6r*-s0W2QF^J>!eJ29Z2c0K`8k*zm=9GEhpa*d*a3)2ZIyXdUOT(q| z8vRd7iQ=GKKH%2uAejjRPY^KTuz%ozG?LENoz%b*rywF=`H+ zSDuNMrQ}Y)vQ<0ADE+BkwuA==!h;M1ISP-)Kk#Tj0K3;>(9d0_<;Miv`YRSdulONmr?+aOuR%q9XTM#7S` z4dF`t>}c<&rx-zv)Pu$J5McZvqh)*%E`JeS_K5KVF$2bpFzmjtDQ%A}Zsp6Z zl+)uWyRO-TRZ-djY>y*#;Iyd=*p_1sI3SM3+R`AQqdxS^S@ku{zdRFH+0k-)RGmUU2b{j96PBtgZDZ65`GxfSzbicdXDDOEs;}6MmFDseR)3SA8u8PU#L8bNXES%WpF(rT zTE3HVU7cAA2*V-Pi`$#ofRu5kuem%N<&Jn-?2ek-CEjuwPER!kqFUN$E^nq(RW7gS zMA%P*!yzrgNXk#pRNP3Hl|!oS1eGLaq)kB4$}ScgtN2*^f4+=4e@m=>p- zjDwUZgS;6XpI{jWZ_Cc@ad710SwvV03XF*}SD8Ra#nR!fAS3Y)@W$0n;V3aR$%jjc zYhyNVF<>xioQx0oS(cV**@xUMc`IeQB2{M6QlUC0L*~r~jjiHOPm5E`8Kn-aK|#i~ z4|P2o(Jj<|m>WjiAWqn{^#)Du#2oD2V zH;{=BUweui%9jm&pPou}t!QXos>nBb66T_ln_Bd;?tDeAEzki}df-`fqLH4!nvuo` z@sM2ohOA~WKb5F4j(fZ?lvb*K>?w|IAA1cF0;NzBSs=>@HzLeKSLd^sy6eZ#7a8rM z-pXLg;pgnqO%@|nAlT~C-Z7z`X+cU+q8)=qIGau|MsUQ8?CiMsr7c16Qxy|C$DVfj z(aRqCB(E@yrky{9iM~ZV%^}Bu@T$@G5y85=259(kh=ZC$vnv) zU|y!f)eWCjt5sM$Cd*i{EAO=Ol(m(g+9TCb&pX*D2!%4drtLZ#bbg2&cLdsy3^kgm z3l}AB!*4c1@*9S)4gic#781$ICOl1%~)SgN21W+?k z4$c|gA;PmY_rn5xrr}caVXXJLn{`bQhM99}qMJ2;RG)T>B#JT+##t!n&|Vx4JRqa1 zoe&+R=w4=CzQb*FVrR10MF;v8o$z+8dD;=QPGkqvK0!u;hy)2~&aE?R%2eQroU5WR zpM0d?ZDjax9cYRVixhZ-)-pypZvB}2QJkKhj4c}-&1geq6n-{G?Ui>EV1*-~Cj`K9 zu5w7Sp5=EEoe`uAJe zI(+P0B?kr2(;)g#b^DI-{_IKM4!rUEc`fto3H7nTHazED_A^vL`nE=<^!)Bd8kyVV zXw05qig7c$*O8`w%a%u`)`A@mZr3XIJhW0LcRlFNN=mJEI|>V@H2EEmeYwT4FBH!E za^qrOYQpz99T(yj&5m-5!X6#A$fXL7Q~zUz8awrwk%EKj^~@x$s2v};sWvjslL$f1v7q+in`KpSpy*V&pR*1wh8t_5R zCM*sTsQ|f%2RzG})r8eSPAt$)+RZ*Yd3HGWY<#W@bDo@U(02_Nh!#*FQd1Mm6S{ir zLr-@iArigdPJo!W5O5C0S%SMBy^4 zp2U!>ItV=pW`fcp^-xRTiyshdh2}{EjkH5(CW{@A5&*&tn0S?rWJ79VmiwB5#v=I0 z_agTDG0l7v+YG6qg=$HlXe>Xn6mj(Mj!^E-qkiDXS5g?6$Lt8EY7`fg0-DO-L(p!7hD;Sy{wwI#0u;rGx7a4+L#W-mzT7Wc3?80g7}S5f6hMtKtt zAio+IUFRTO5VD>lz&-%574g~~&D{o2VozJKBOX{=^kZoc9VhaFx127;>D#4hmuNyd zZE4c!$YO`Ec09Z?T0R7+wADD&Wzxgua-I$aKQR%Cz0=dS3H{OH+Xs87rk7snC|Thd zvMugJM2};8g8vg>MG^W4p^zboR#1 z0n5Z5Yq3XTrz2OeGhN4-nK`caDo1v=Ub8E;F&~+<2keu2&5AQlUIYMdXIgTd-UVED zuSj<-%UxQh^FBncs9n51m=6~i6)sR&52!5UKFyW0qK?@(;bU901sbXRSB`FcQAFm?pm&8&2_VoV6InB$g zs3H!553ka7l45v)iGD;`0-egxx0a{JG>LZUKi(yWfy1wVFb!Nmtc4(=A0;1pXwrKW zI3CpSDlreyP4$@hiGu^q2H}CnLp=%^gfQP}HPCje_T36WG|xluC~}ASu(dE#j99B}xY51_ zlAPFf_-w6Z2m`S+^lTo)@-wMOyx}2Zn9ZW}R^kn|BCq2gWsrLiO61ORkd!YhmEiwNmoMNygLT5z zC?@ZMM#qKWh;}nQzu%5Pc5m=vKxXZ!a$qUAalW zbV>hLQK1$1w|(*a9@`4*%)58?Bb#fuA-+Jy_%3-kv6V)a!LA^0p& zXi0{uFB$&|1Z_5@85b57F>&oe_2Sz3%lN)iJ5o zhObNWkYn+}wMsG_C^9L8J7h3bBB`6C^y@dbumM*n00Ge5$z{;hE zkhC=q*r?V@ON-T7<r0`S1*^alOPGe2(b$bCBWH*%J~bB{^COQVr8KQ z5L|+&MFeGGp}JT(zkr3!S1(s;P-3YD&&yEIQmuN4zd=7;es}>Zek*(jP-XR^yL73# zSfZc#(tK?Z%Lb4ywa#Cr_|oNz0Kvs2`Bqz8sGjFAEmmq2ccFsWE4BH>>f)=7CFq%p ziwo`&Kma9{YV()yk$xf`=CR|NATS{#F58HGMf$P*PY7?XI4YOw-O1?~{c_=E1>^p>+$6Ve!o7b%$3h=KK$XU@l0omAcLK&EBgWa^ zF{wjo&vn@aDE%0ggU5G)mK@`rprth;rA`*F?kC$Bqx{Ft+KSAHc)x4Hbt%8-V(3w(A^2idHs7OIp$F zIItY+j4M7&pB1X+IlJTRdFBpegaL>5XTxBFL_6aOemrNJ)mGn)V|LQup@#t1^QJvB zv&IV*g7gb#OZ57Rb42nqfU%whV_kqjR|l8NNBjz5w3{oJXGT0ST}tR45VnNBpBZsN z5^75|6%G6Jmp}Pt^I81C>^bxegig$pFzhG(5bwMduTmC&;M_Njc<92)UJ6@00b5*^ znefCgtOV``RUAZVo`Z=z2Wg%r{3loU_mJ?2tRy^g$O3(}Sy4n~0E0ONyJ=?;kjWE- z6&gH9tSe)U=|us1Vm?7$unDX4vBb+A;C%VsHE4_g>N>=Ic*1*(Q^h2q*0*fK0LgZ( z@3;2t2hP>VG+Ov>um>QFHeNK5M8?Zujj^sWQ5!1y^c$`yAw^xTM*$`ybJ-A;-8T06A&!Pazn%1Kqc%VP9&STKT|Zb2lO!AftL2|} zZm=P&XK|-$o+iP_#tSLnxdW>`DY601U>z1D+x{!$(()BDqik}M=)pF`V8Zh|Q5zPa z^WsHRi5ki(rhkM`jfY1i8Gv>wynm?XRmwF6Zb@tb_YWtk9<%r9=Hv#qEQ+DST0Nur zz6}!N5hS3KO}zRFgc!N>k=yn|qsdBoV5{e~_AxOQqc<$T?a6+=3=n@kN}9v^L{&kz zFkPm1V3fulUhmKtjBw`ZdjOmWiJ`qspr)Px#~mdhX3%PRAAxzlY13kvJqt>D!``RX zPK}k#mI;s{aG{fW@Myc|wt*Eq$FcIa`ivq91 z$oHxuXnTYBeGK&4H@IEL2HH8Op5ef6Z$M=@F8~wp&PaFX2I&RIY+Y}rj@pFbf`*XH zGR==z6o$!`T3+}%Iuee7wEZX3xRaFQx%_S2x7Os=8jpG;L#Sj8H&(ewnfbOn!X$mnNh}dW zct&S;`rgqE02fB%^F2-T0Qkt(WoCnD#0~pT;g^G~|r4B>cW0BXVOJDF_?`-?A z;RbUNFr>EFHd zJsgCInF+~?(w1%Sc`$suGy0p8zCN~j$m7S`Y?As4+k!9a?!fOu;oJUTdlbNA0)wOy z!vx*QbYTq@9|_>qe&6`Oc%O(8WD%e*FWZJvxnR5~BG%Rl@6xVqB(v!)%gY|05Gc8d z%MZbP@vTu5CfYsynC#08PoW)$5?j@($ zLy<>@W}X*yUumNIcn9#^My|WbPf>urwNGylOS*V20SAhNQ7b0ONojy974_Bgq<%t< zCsYz^O*_m_wOI%X=v!w1XT=_66Qteo{C5Xl+jmPQps~X1C+dQ!+Venw+HjA6zEB)# zY~wndo$wxfu9Y1ZmOUOf-&XwgMq2!2x$$61xo<0WIVr;q9Tky+vka3fM0xjsTWz>GTKIaea3w9ER-K5_Y z`dy>nVT8Xo60|D49y+%?lg5jitkI^geX_B6?Yg}vck^k=z2nCT9xq5Y2=MC5VH6Hy zyJ2v= z%8v?^VMupzaH|?z6N$Y+KFFj{h1YNh)1Vns7@Nj;5GiA9g}0kIw>{IBo0bh0H=RD+ zIx6qD@XUtG{3avIAy@7kazomY=g6#pRZ2nET(HivG}SzgOdZJF=7`05&T4R03f5Ji z^s2xx6wg3xqZXF>yvgpHH`TV(t0|Q~xA=Zl7_8uoaUI1u z+NtuYh23n<%%hzZCBB0?{Ie6+tce}48-aP>e5?TL@%qUqsO=vZ6Y0&4BYQL55bR8B z2zGQE;rk|)#f_7X#2%aI$vWU6lz5mu5uxMfbP-nLxQREK1A#Oi1Pn-O2KOT;>rXc% zt@JK>tI7A`aFX3RYvggO1#Y!5b=0X*fnM>|2z29hA#l(@ycP~7@s6V}!i}A1E$pYm zmanGJaibWby681^SWgUJVRE6fHnJl)N$bSn8!=&XpAIo)6ff-(QZxB9FFQgHMC>3m zCrsDZ>FZES1&3UVhhM{4S}aZi8$LMt~hrdy1dY z1b#*mKWHw%x-&B6kvyrWs*RK5zIVooNGy291nTvWNmJ+}K_yIwdqU6gd>W07xxEK4 zoo>0D2ZqZo^HTW>Wp{CvFcz6m_kS!K0)8q+-ihpS(_2y#N#_$l_kp81qo5Rtm_rZ~*+IlaHam&<7__~Jr5Wx0Kr>Oi?`6+Qd&}||EI+ay zI{Z2vX_N0d(nG;=c*RBk1u#oQ@Ecgmb93r|N8jB(G5(;T}Vy#z7LOMEH!ZAGq4X4|q$9RY)fXbs^u{d!rQ1;NyCn!cN zwL%c(_HD!fJFugA)WFxKxT=0EOxka8Lu&Ix+T!smxLL}qMF8I#A9z(aIOc;)ymJ;Q zK9sc^9U2zX>sV*^*$8be6)~&)04$TtY^F9A#&X5+($j=ybf=mS4n?Mh2;vurGdf2j zZE4R;!L8^U^eLS7H5slq@A$z~i08+tz2ezGOVjvuRBpdxS}xrq!ePm>*)egjqD1j- zuz+ueE?nUU3z5g!K ztig8QzD8P+##EDQq}|<>yF0sguRq#d-@L!Edu!+V1{&0IA?_9~VKTyAu`DI*R)JX^ z4a3;u2CN^#dJ+8`3{z24+Q2%`5q`c9nmw7D+X%vC} zco8~Y>~Nv2>qr3#TZ%{cu|{02Kq|ak{TN^H)Ws3c*A^KBmm4}=&dBAI@Ypkl$jJ9( zI^a+346nCT)(%xF7Bep$nT#Kc+fDt%3{hksj(2BIUV@ko?=M2}!956ylT7G?F9RAZ z3GiTw01%C5&6(lp>5Kr`l9seE0}PdSCRpE84^SfF@DI?7;RxHl?U`FLBWua#A#DvE z<+w!>c53B25oLGsAz2I#owUd6MvF`I_K>*(vxnKLJKVXUD1$fsB>+UB{~vpA+TBQU z9SX|toH=vG@AJ%OREpwDr~+sJT#5xMi!PGI+DO(y)=G0&2_OJw5kR6c6I_bKIg+0x zwKhv$v^=?Gsr_V0XTo$M%wMHyZx7=K-Lv%B$xcJ>X71(PwR`!k^@D?Z`EAG~TAkG# zvOPIC=rB7<#nam6EV*u7xE}i7o|!Gr%KXbXvbi{H2`yx{fuC5RH0$6QP7PsRFf1TRD&uyOP{P3+IHvSGe2cD@lUFoIW8pBw&jcB`8Gb z%uVqU_QR|&S42?GCOnYgtX)rf+bQVYb_%K9b_y7+3+GvOz>0K1QRdXTE|;K9tqX0K z=XPA!H7)B1)ZB~XxEvnUn_(UlobbvdgG%c8(3Z|@NYuC)Sx^%FIWtd9@died6<+oL zK4*Jml_Ta2GXiWzrP=iibjh}8$+KcltjE512zPxiURt0=EdSUVxQ|I#nYNZaa?w)k zYG-VOwSc6hc_c*e3y%92~r1N4e^bmGCe8*$31ra-*TkH9oM=>jfZ zVY-~aKs;J=YLq-Vofw4xrUCwp@gkHN#yKBm0njr^W6^0&e(4ZV7w(cvf`| zj#y7zbmg$3penjo(#RgcGmJ!$g+xfweuvx@RdVYMTNn1DwMBtQ@i+ew&lA6kU6BjJ zd@4#luTMFF6vJYOeHErmFC#deXc@)+q?ci1j+PK?QZm2eYYGn0GWyY!(h%d0pahJZ zJ>tpw)Ovt6dD-cQ?R?iq8^LX-M$M;LB55E-+2Br(Oqe`(o>*!^SKT%7ybjdhlbN_J z5SJuI`xMn{A%oL_rCE_e`79H%J<;u<_{q{+GM}P!jKP-39^vSt>nxW=W|*W_F~6cN&Xz1j6Q%Ledacne;(;e+E-;_DADH|J7%DETyk`MQ zU6#?v;s@jC=(4~S2M1Byp=dQRP1ZfqG-GnlwY%~No=E_k(muf}AL4N__c|cjdU>0H zTf>ubZcmqUCn`dw|ZIr)r=OS=gx0O2(b<4JC5pB00jA@Ctx254S~k0g4RG?sQB zFjxT3@Q=fNhnGEcuk5a4hcpjyi*C4*2~M`CbF#%$PPVvSzJ?y|@fs*mzNBR=$@>5W z;Vaa1DKn9A1ieCBDTuT!^z5|6GY}ybSwWZ2S<;37CHtd0g>XV=u2np|*Q)E*zLKEB zE>l<*z!xeGvXFyajiFO;E5_I-U1J@Yu_x7(sf^BJAPhBdpHFifzu3s*FqnJGaZ_IXw2!v~=;LhS%leOY7s2mKzpg=Xgtvfi1`ai+3Y7 z(kg7K)lYThW?Ds;AUyA))m>X%2NwUXfDW=qcDOHu$9VzDS-cQ-+Q6zVtSzovkMrBg zaX?cn?BY6sX{(NoR^`kAt9DT#r#T+_q;4(}C{2LXVy=8cCu1geqqHb5^v=51`HCmXoivqq*@X72Eg<@5Qw+43#z2l8tY zN@DJ{VqLuz_Vf-w!=O|Q?IJ~<3+Oz5+P7|qx!kaBgwufv2sdWpN5X=BO0cdlkCA+4 zR@KZbTB{PQA6pk#=!TdCOU&3}zX7uI)GsgaL5RRBi)ep&qmF?(%gBn}NIA3?vZ2}j zM%b~{EZOLhy0#}s6`+jmi@8^IpS>>~WG|Lb*2g_muQd7A<;3X*k&fARb6$U z^o)uB#`x+eH@F~1bi=xEBV9Uq;{!-1SvVBt+%Bab>GH}|fElsUi`Iq3(b2{DN~hJe z+FxwRvLm?|W?E$eOsrmtFZnTNZdmQ8&P!X9YKElah9Vu|S-Q#Sk~6XavZ_fqRr^sH zCjfqA?t7$RAq+{*dIxdA98fyb-I*u>)6LH8Slu#Y=&qN|Ii&q|ELU=n5`MNZv=@^u z&S|1*twFNTBb)4qv{Yp+Rb?Bbs<6@yJ0o}2M@N@T{3iR+bwPHCQR<8V zXNTk-Yf`v@jhwA`Ca^IlI?*jd2NdF80K~uWsX2-G7ed5;!fC6fppx@qqoWNI{UssL07phUDB+8VCc79NL=#STk$8yD zC=z*HB=Q(>h~yqQ0=bu!YKT|>Su%i3v1~}JJK1>5fmoCljxrqB?4zSvP1CT6fHF-= zS-Z%fv@qn>j_m|9J08Z`W6xy8Hd|!Pz-I#v&T>UBjA}k-w8bZ4s*sOmvgq5uOH(fR z#Ya>vWp*P=%sr#hfTCC1b=NODnrIo=bCp_dXsRJ7cCM2a_3+v;EX|bMbW!xC)akh` zb$Xii9-PKeRgutq${@l<1~o*4H3aI1C>@>!R5s95ci3;Pi?fN3x}U4S)LlxlMx1B6YzJ$qtD5*K<8NJNaX0Ge8_*AnQ-Z1rfg&WE^l17u{p=4s&%BL#G!mjB zP?T7-GerR*|y@g_~ z9~k)c9wqOelBW8QNnRDBKe}jkFF<3Wrb;*9MeHJHWPe-UfB@$#6&9o`HB?Bt#_*xV zSZ@>NoSlqoBkLA0kaHIDm*ObGmpvdVM%2-{Vx2iNLhf2CD8L=zaSQKLJfUAPO+?HcokZQh5Uu6dt4&8sU**~bgHVzwNwrblkDj!=GiIy$<59gxTO z3Z$&$!&Av*B<2Pyk&-cT0`a`7;V}18(CrEu1ERPMHl~K>3^I}>D*&8%jhj^3YDo0l zMQTIn`mE?Ni%gitEO~53iUMoEw0g*0G(ua{*ej;MpfZi{CRbiv8Y8yl zT?-~{2A01p)dykdcVk+3Pp!MyU2*p;fd9Vrbf)H3S)J(E^iq@Jp=oa*sx*x~C+K9`<~@;PJqONb0bqh_J4s3G2FwvbomN_;@mJyG%= zmh4Rz_j|#HoZ~t zskcpYbBnig_a-mj%0KpBxs5`hFgZS+!S8dE6YRTC;=c?0c`Q?$D3(ek$T(Td6iUU? zSm|S#!U>ME@2887PoOJJyWQ#z$OqZ#^#SR8C>izrpXKwv{a^q4$NtO5KDK06GdET; z_oY)6eD|^c1O6|;|4-n5{QWjM`7a;)>(83kUtatZ=L80M`sw%4_y0|M7o9v(?~Rki14H+Ju@Kw;rE`;ok7ag0 zgz+XmAMyP^R>-shr$w#exyi|~^QF>wF<+dTI$sUu71H+j>9PDD^wIbKhjaQ5-T$!b zC*tRSp*VJW{=be7cj3QZ8%O%MMFRYC$p(Hkdv)Nwjr6FY_F`&3-xVFNz!kvV?gs(|(^!EEh&F4X?N=GKB0J@z$-Dse}`* zZmQb0x~3-uXaS4)xMX)k9NAov3F@U6Iu?ZZirua?DaJnQF-NaVpb)ZJRg01^G`nM6 zaLj5JYR=;|(*>*8BYwAn&Q~i`E5(x!4lsaW-lGjb6%=nrY!+cK4>-fL@h*x&WyJ20 zZaY#729Uxl1H8eDRMl;Vm0_vy*M2kvm+G-{)t7Os_8MI$zQt;ajD5{npZ1kQn|$j4 zlt630NmG~|p*J$xhWh=eCW4=DiWKH5gkJz)mtLtun=-yF_KDvmZBDaZss(AnA1qK6 zo#fGV$bkwHHERbpn7hLVvxIl?rjwnnpK*~NWrCDQ_7a=+a z*eSPB4OMW*+qk_wQo*n8DNp=S{zUnIj_K!ecjxF`{{x=-;pf==e{u5M`04rodOoN8 z|3~_h&i|h*9ymV#Ul>1EJmvpi$EOefUphBcIDg9j|M7qH{r};d{=@eF*u>;`A%6Z( zjGa3@|6j*P@c;G-@&Y@=zJ=fS3A;ACLMoT=`y#P#k_ed@cSug;nywF!LK{S-o)=~q*B1| zhh!JOC?)nhJ_MA^F$M-+upYRSJi%|D4v<)gfwGeASjM@Dt?^Rl(o;QSmx(}B#;#9q z)4HFV^H;A-IJa+WQV@q)Yck9odd00$7QzIcMJ&V6q1`&!5bDecg<|HBZU*O`L3y4)M6&|x6Bl$ue0}_yTwDv$x{5)qSK~T;Qe+~ra2bY;v)PGJlkpV zpP~mlkqU(K%PZ*A&F;+8hDWJPgcmnugc(*hDy!Z%Q~eHN%e3Ri>b)5qBo9^^`Z|Qh ze1Am^R~Mk%37OzEmdVMVKUKx_HTO>SxnAd?9*^ClrLfD8~>c_Rxg z2dWZS5Nw?1l5MjL{1xO5N5-_G+0Z1gsG*hJ_#j!t0uHIZb<>hdo4*Y}KUrYl8AxoKtZCY-gff}i?C$%S}q9qO8sT~~1cNdJVF=MtZ?xKZ7 z{+lV4i{sExZDwXFThph(;AE}3I-mr(PbG&C&79ky*z+W}n->kT{S2LKk{;;0#`t_cyO%})` zn)MY+1p@!C59=1px#?Q~;>^33Qs~w^9eQQDR@9O-Z$N$-n{h0&X`ws!NDcKcTRm1o zGfZk?4GP+YKjgObhwSuv%sR14HN_QF@n=!srJXSYP&DbGM>FXGTm!R($jTo1Rsk@}pklCvr~OZP z(nQWbdp4_eYRz9~{Yng}BWk(oUxEdt9vUX7fng~=7Ixr>s} z4fKY&A3vHxa|nj}NrQ#D5iSH0>bOiaOGDADt3W;ky0C)Q1sB`QdH64ijZtOR6~bS=yx&tN z%cGHfAEVuPy-z&qWraslk}28PK+bu1hxfsr|c+SC3R|D9cM{|=_WS}J>Xki?HVQmn7WltCkz6VvDHqs&uH#CscDl=wyX_ZRD;&Nbsbk1`vcL|UaA1O`w$LTj2dRW%_oH)=IYD3zh5Dh*j=Qx@U= z)@lh8x4{*OrPeCki&Q9r9RO1UTg6Rk(Dn`9;W9T+xPH`9FkR5hN{#JmPO%;ldfNDS zsa82xpDI~#|x#M`@ z30|@4!s|Tutj|lW_mVcl`l^Z|!i{^(Uww3;qiv=z`$<+_TktTC3~MTCVg&BoDS>-p z1TOB6+vv+nGRw=MsNyVDcrEzd;E!&1|lDCE31N`ezd<}25P!@2-xXRh?g=Cr@#po&8s2uIte2B%2d zn8|@mn-=7OtOf2?Y?|fav?^qhd2%V4Dm!21HWwJ z8sQNVyaBl9lje5L&%(i)!AMDX-iJB4KR@QW&wrzImPYbF()Xsp3*~C66k@#$lP4h( z(~?>9X{j7fikyrNU`Uf^V`(B#uwK|u`b6jpj-!b39^pUmAhe}NhP|O=`Q5oWE?f{K z_$9N2A>b7mp54EF)+$bi2heTw%f@N0RSZ%gL|!N%3*Tzk6V0uCQeU^4=)!%IhNk09 zOQ#*fkcSbp>BJE1rY7O6MbP~ol6(m9+m>=tBAtY=@Nh`vYhGD|&emFI{op`8dlA#d zTydRLtOV)_Tt4L--Bg(mnl)|}ED+?nG4rVFHO-H+5R#H}0*Dfa8SqT#Pe)(AIc@n? zde)M;O>9yn4ylp7?DY0o%NDb?WiiIJL(sZOKzfBpSZwlXGGLwz9jahYs*ux2_@E%R zNQ}DEZ05v!;7JQc7{3bRv@*!ZVaFtiwbBa-PXiN>-rPq=o<2sXAX`~ZeD$KvXNRq^ zb_NbuVEypij=_Z;IkA!HBUNjTGu3)DJ*{2jvsJtBL$%AFWxFyPt69|`+Eu@KDiE9c zqE@s;ox?>f5Wps6TP~|K)0ewwTG}y7I7DlEvt?Zv0)wqo7|arY6?7>spkzAJiy3iio>p51#OQx5U5q_%|>px*TOCE2Kp&asvQ$8KyF~Y~ILn<7)91 zhlCNNc}U3{L>v(}#8g`}jJ(BnBzIs@(TgOK`TKfT9{w zI8~JCYT}h-*{EzIQdC7lm2TmCp@8MFDcN-?HqUAj6z2U}ZFH2}s2^I)l#KS8JIuyS z7n8k(3#qMytcC(ceXA%HG%NlsUY*!s*G zqmo+oE=DtoG?68fx?qJ3USdC-qU7L5Mct2yGRl&$ouTM~RXc>?x<$ z;YlY%9+C4dl1&JfBJbo87n3sTr=c!4)OcBKOxmd`MSS|%T8oHIG+f?aq|}jKy!*(I zoN#ZhMZoP-RtbeWPdSp02K;TvfCBsgr~o1r7tU_zrMnyoy(+}4rU`ed3E@sP6z&Mr zyPJx7_4{vaN$#gz!(*)9p;&kYtCZyRZhr~49#oeDE!(h6a>TX?RP#wQB)Ja`3Rz@M zve54c25w6TZ6~m3JH!WVU{>SeV0M`r6BJ(#i!(pJz|tzt>{D#Z{zxuE2uI> ztxN&OaYK{0?kmRy2z~*<_$2t`YIh53!FpLBkZbJN3quEUABQ>S2?^MQQS|*~U}#}^ zGsUh1dzixRTFUz!iH!}9LQmP;abOWNts)Xop35Ia=!L7Su}ke*nTjwY_gA7(8N&BH z2JnHr1XwTk>$BcA78+%xSonCSU3al$yry_>9>F|U&0LALGPw+bpy~uO$?Dj(8sO1* ze$0NBWfvJl{+{Z(JiJrq>7ZY z<|jEMRp0PY=n}9;fCKody1ER11?IJxx#UH zk?d^3X|e+Ng{sKm1iK+X47FK!Iy=sN}go*n_zL!?UVw$oxz!D5>2(C~!>7p(?E(h2QL7wTV1Cegqg1bMq&?hFq8mS8{IhJKt%xg@_#do|CXFNr4t&NJp9pTVwiiu z1e8^iS^77XIZ3q8r?D{3*^x%oZ}(KwGWBchM8~BX!|3(o>MI=7v1YgcOK&F~vxlSO z1vA4>pa>R-&oC!NI<|)bt@O&}Y##+D8WVV)rQpTLdB@QgUSsnU6`mqXi5P4U^5O_c zLte;&P|qM~B-R5n2c62s5f-CC<*zrw$CafXkmBJ{#%E%5(2g9k&C;q#Zh*lstsU3w zoDdRB$RC-eI6)G_=0Z%;!}{j?!)zZ&WmNirLknm}MtT`qj(U^s0;vf9&TFKmyJHX= zyM$i3e6@Ux{A97vVV$7J%BF|@(hCGItz_du9dFuM60cLp&PcXs>d}d)BN7*nu-igo zzb^{o^bwo+jSqk=7;nsJbU|5ZZ^G@$G~ynmpU(i`*o(*1x@BtW>AGfzA~5c+yip>q zb;m?eQsQ7OqvSW0@`!9?nM1fgOpca*}oh3FFNE$p7!3U;=>7R_Ffz+wYnIBX}@cO4R&jQ`Yu9Q zFw~V&CNF0SBzCXB4NwdrW9XwOg_jRZDkgMpj(8KH-7xx^3TPd6HZ?oG-d4N`g^Q|O3Dre`8Q(F1Wby`L^*$13oZNm^{evbs^A#rq{nuJ zCFhj*Y%Y0Bb~MX{(b30dCA^Rc<-oG=$bKhr!=S7lI-2A4IYncR>`S3_G*uH%RKw~k zn5!axb2seuxEppM8OQ3l{wALrX&s?2xNdH7bhuF}y|%9Vpk})xEy$|e#H^yW)SNWC z!iCu4z{DbR%WEj+mm7tmt!WuSr+RQ;CX85k16fGAY+ZoKxZL~1qbympD#`*zg`qn* za3vO$scOIvF=gRBWnhGhuQ*LmMye3TM9X-m4L~q!Vqs#c>yCru|@zY5o}f6 zjq<+4L({Hpt+N}H`k-2!zO`ytRo;lD)o*qpc1xY4-BQbHm|O8aq>Uo2R}rJb+JKJW zrF-NS%X492)KO5eJ?5KUzt@uYc5~}$8MAEJxukoUT0S)|!u}HudX|~{+Kt!XnkVZ2 zWuE##&QtFSoc+0ZU|g1Ro0}il5d8T5UkjzlQ~U4N^EvhZ@{#_e`+qrEJn#zsUkk;F zQ~xioi^}B{-f{z59jnBw*O1T$%$h8{4b#Y=hO55!~Y0xC(NfH59i`WzReUtl_yiS;pmSw^m zzwD8#%u|a`7MXt)!u-wnq=8>mau>h0$vy6cmN}hilRLsS4RcX@n>^(HWtexFIbs)? zOBkQr=l*5vCigmHGuJWHC$^0bwm;l849n>Hyym7q+(is?c+-w&4BaQmjF9Hzy$202 zma(6kb?XbBU%L^_GRVV>&sh7bDD*RpH)>*^`fs4v!pHb0kYN>A%c|>9gKS#G0y9)s zv358wxk{Q2h6dSGiq&Wg*U7iscCcxAS(0p^$>;&jVWAqjXSYy5gx*DcOK>-a%%0ms z#L=-a6fCKQ=~a2j3$HiA*QhRms@u6L)b2ytcAvBi4wF4Wrf!&ty+%d+Dw1^lfdfPiwnJX>B>ZwWobsTkX@@O88!b zmt0_1U{7z_o@((ym=P0mn=E$@FOqs>J~a^%7JX~Y!21-Zx@D}Br7#}@+?-FFv>LdW z^=5ic!XexaGkA7;3*O8|-{gd^k{($|0dSN7LekZgBzxNq80UXCC9y_f12hrFpQNOs zbR3dKQVVvl+gJL`{H^f4e04{g0*tZ*uO25W_F>le?Lv=Sj7)r!%GJ8$bF-iVk@dMY zUi0Sk{d`$uDw0GC4bQ=9Zloa!xwiMhL_UEz5&Q|#?#I*M-NTg1Dz@)bA?;e2CT8@y zo|{diA0*3jB-$fE5Ze!Uh1nxCNSXT#3i@&)kQ!vN)62SPW#N4@J$t)|Ch~W3Dmp;| zvMc@aHj;U_f*9;NZDn2$h{@l=*{TN`;KQpBEcfCmOlpUyI9@MLM)%u@j{BtYeS>&e zWGDjmjW)qhip_?tjvoLD5pJTH78RuyQX6{yNQ-)vc%y( z{pQ6_{&G+WTCY1*hqkL>p=V!z^Z8$WK31sWHUm|t<}{rKlnJ|b_@(b3e(4LbGIa+! z-&-e7td{P6!N$Onfpyc&D*bfFDoi_{Dwd|5vuEXuZJ>XmZYA*SYG68qAB#qI+7?cY zxI3Z^ao=JZ58#tqS~5grzOuuNH;rd+{mZkr-ZWwv5C8q=OH243?JuRz8#EsiU5%Qz zX33SBF{?`5ik0$cty`sNUyGiR?6W3bV=LP;)V@Pg1yaan z$(`XvpLA)LMoT@8&$KL#HhLH!A#ZOtZrez(V;SY`NA#G_SRr39%BHQFbjSu&>73nw zCf<-)v-!YXaCT|UEaIK(Mavdy^?|gRuLut^+;W~na#=MPxPjeV!I#hxZWAwoH_X&< z-P&!6be(8Q#6~@$Vwz}r*nZ130(a#SYnkE&b4E5H(S9U>qA zW~T$v-;X& zEwB0Fj?VLmdah~D8`|@>_S}W%s<};g+*||rat~edFclLFa8niDb;gSI!c+xd5Ph7A zvMneZtQ7$oP5iR_sKpE?>m*%n9KQ9V=fC=$0t|!XirKKPO`o{t6)~-6jW>+5t~nxu z!W6kZu*}=h`d^9mzuI&+l%w@Yt{p^M1bk)^`uz{AR4TbJ*KEeR{jA^>#VhTSg}wv9nKE zXUBPG$A?15zyI6ecRmkzk>ScnXIHVakE5N{+F8-M`#^i1hi9|N0u|5keY0cUMYTc5 zfDY!+IRT?xC|a`%YVNkVhP+`;W#D>}<%-gxV7YtHzF^eL4eP=Ulf4Bjp-mReOR`5y z7zT-oO_(%$Gr0a@PB@A&#yh(PQE`8BO!w4K)(ygxjZBd>&HzGhx~7rAeIr5+vw1>H zx#+`8PL5~w8CznDx#X2cXcuh(g z(tvPF$JcG$(vgJ|_O8~Hh(VLu2<}cn?rjdmT=!-cp}%pqCr_VcBvd-{sWx_d+0LYu_MSWS%UNr6a?Dk`ge9Wki6eR^*1ZI`_2a`)th{ z{nxzFXU!uTU5M_4MxfKK0j@#mBB2rfWyavY0ROo4I(veJY3{Dc0v{`)uHt7 z%A(5~iTM|#8xI=ey^z1^Nx&Hw-Q``W1rmV`WY@Oh>{@h!H`qwe^O2rE%1A%|xo6+} zMRKIugGZW?dX&i#yKNL84H<2hjdtGBv&Tj{pE^>V{MNd-$;Mq2XEl5$M?Qqi;+Z=^ zUs~@3NtcjRG0jDo10{WkO~IljO~@i&o-Y^)K(FwjOE#o9Xz0&A{a&1M=Q$hNep0-> zYAS&_E)=4t$dTXP)(5gR$BlXA5ql8)tWm&&~MCsQr!5G3f@7LkazJB=YPY>x=+N?=P z*#Ur{c_AcENuLWM^a$@ir$-ViI69hmCB0k5;Bz-fqh1oFNlg&j5t5~rcWNXey^qnV z%R0nA#hp~kGrBBFZ>)+GsAAcT9gHh%*!M%{KQ(3G2JonytO-wHHeTI_8xr1`k^I@n z>T8^m3^@|3P{+&e9b z6j;OHC1y>-AOJMc=a66*Y`EG8T*3QFVA;NT8)jwA*x2x^ZiimQ0Ldtn%WdZhrO8UI zHd!#xu1ctYUt!8L(m&CMrU)L@|GwSg9~pI@SOFqyOmp|HC=`hwlH$ ziQ+^e{&(?I|NFIk{tV@-=w{xyv$~L*GN$tl1_h|ayC%rfsma>d_<2g#_glbx3vYtO zLScJzT6%sd0W03iE5*Q+lJ+k52aN>_o!x2aJSJmz1GksQ_Jsabvas|+g5E@?AH#|{ z^*yQ7S@zMLOs+zM9ZK8NOa{Q%F`s46G0s7>mgAmN%3~$$Eo+i7Wyu!N%SMjzE2Syu zEc>~`2R>QAmI`DKW&1O zBaGY#3>su{_#mV87bl^>ka5S_&j5II+dr}wcS?RFMG5@?Z8B^V{%J$OM@Pa0sC}zNwMx_v(pW`!-l9@!~2J1 zYoU{v^2bXy{!Mc?X60gMH{-iar15>)iZn)8oP6V)L<>FNDBOE zEyLcXFE`Ok)NQ9ZIQzcxWIELwlm<~_&7<$ z<{Zdab0})fRqR^Y!Fg0LlamLyK2t=rpf3iWn4%q<^~rD_h`p`>YuLRG^&B^43j3Zn zVbP)c{J6)*l&bppXJE=nBu6L<XctKr=!e8a1zvv0@cK!zfT>YnJTKRHAM;__`iC z<{L<6jK`+%TsVb%RTH)P6z#DBHwD5|j#Pz^6ZHgm!9TEb#mP9T6*JED@K1P$Dahc0 z+dgP7r|=2WPJi_+*B92tG$7?&$>crh+w5q)8dP9Ewz{nN2R)`F{$Q9`0A}Fw115q? z>h&5xSOS+!Mb5VL*@)N?9Aoc}MZ3f{3bPMj7C0#@$P8cDVC66sdJOw1fIL!Frqd4v zu!+bNdijJc(=mZKr9-{{j6#g4{GXe!&eMCoyHRhRfay2;yo&sPV)B&#e=VO=`Ts}y zlP>>1Sv>Fx^8a&%@l*N#>-eP0|Id$&O%*4{Pv!rA^dEiye>kWAu>D^w7RQST`TzK- z{QtFlge#L4Dw`~l4tKFqW$suWlMU|2#oom48>Gs^xazRg5@D#7t5%t@T4!s{*F2=-ii7Cc}&WQu%k=rIs6c5a5b$I-h@U~5A zwztKfgPzqq`YhP`z-eTp9`oqlW>F>$HEW@7@I`EY1qFk89YLgyp!b32CQDowXGgk2 z7IA#;uav7S%mDWj-XwwDxXIJ5xSsPAIlLweFXE%!>scJY2HNt zp*Ku-K=37T4QAjyf{}1t?}o8mBJqe|VwEp(X{@kA-ZgZqn)&I)F(De-Q0U|6V5M47x4CwBS0)7WfmbW&3pRrsuRUOhCLF zZb~cx?0cEpBa+Ohta&h+3dhTG_Gw1;LR-(lIj>-@p4OqEC3q+UKK~+Um=<=U?K0mQ zYmY8ORyK1sgSxLIbAf%cux4Ru`WB#wKE-x9BFar5mswSAw!vbLSc|5yYBvlLB4xFg zsyaq%YWUe!`we%RM8sNL%IYVZSwPCJ7V3gDESj@FSt4_m1_Bq+vjlN0ZHYLoP6*klZ2FKGRmV!J0 zTl_gCV6Y0&8JIPx8DB9g$q2jB-?@sgD_3(4@q^j6n#Ty$+0050=FR@V#jQyk6T=QKh9GA1c6*O(JHFGuga)(h(CY`X7;#s#=jL^WV9Gr5hz2=Y?7K^3 zG>2h8Jph)Q43?82NnQo8H6Qvx6v!K7P5`-M-9>Yk=_lMVhnpAUKNE&2Fa>y6DlrS_ zi*r?N%ZE~a9^=zv*crRQNFI}ce>Xa#1RXfBT>-0M5I;-K_!6#eVz_!B4r&+zLKLu4 zx&T%5NLTEo%cj7r*@J^?>KVQ1#P`}<>RwZ1E*_6vktLEtC&NVE(a|&MS=m92yVgld z=45RU4+NrTiJZV^B$3T_Hd$ix9HJCI8XJNI0wNt!7eK&3cFQt-*tz%|Zd*UGZGE|I z#jYIDw%ML#cAyINBCMyAsHYRwBX{x+TC%}OzQORo&dlCSEg2>wL~kL%uJ$Hu)3v?z zY`KQUYez1^V{|ZUre!NZY>k9VI5VQK#HzIgNDYhZT4p^BHVxZr{5aa7wZ1qdf zwOeUH(63?yjjIe6}-w%yzdoT{Ije|WBP)0w<>5?bJ|aQ}O*a2o&P^?Xk6 ze?QWn^!wkF#RJE`|1FM>oyPxoEuTK|KMGUl#>Y?XzyA0?`u_iLPXD3zzhe{0^M9gH zJU#zk$0uq3Wh*GqZNTcaYJKwjIeUN&7}^|V3K1>Sf{lMEhGANDpjsuSx88i?WG$;nlx`C;7G$1^sikEf4)T)2Fic79=ifx`s;#a4>jL#o&tF{?ct z`jhLcVss?4>wBw6@h`oAWpAn!AI06vhHGk@>hdAnIB}nY@ZslAIn6oRhd% zn{u5{?>#ybh2f@){nWj6?S}BCY&vc&%Ir=>)ca)LMV%?|2Ib1dz6HcD=DBMSu(G9= zTKEhD$I(ax%2FF)!l!{dVt~C$>W(-UdkJm*+uTjx{G?+?Nxd6YP7v3HzE$&5t=P13ETfT6%dF$|qurmpqk)KF={x0LL+G zh2^PIO5m7@!Y2}&BQlRR{zW&b-f8|=9K0CZaHupHGyY2~j@PKz=Jkln`OT-u(|k|StbqnD1`4N2MUfA9)z z&vSS7Bm3p3_2g=v+X2F&9i5YL{5$fT9I=VW%U2(I|N11-8?d^sT6O$)&Slzh;uWo> z;)5jA@RZ@*VVHeCouGPkSY)+okGTMuiVJ~vA=vyd_C%a1IC{S-Zfy_xHFO4!T_XpP z3qkFbprmiLtilt;8;PbmHsDsKRKVYczxiJJ0PA;BeaP6y-Os)zg zPJZWv9_p|IelUVL{`ntvKxnW=I>6^`KnLOzJAM2KR~=w_wUOqzE13b!^9(5q5PSaz z?iC=a|FP&IceA^E?q*}VcK$!m{=ZNdJH7vTEuT~UkB{^xUH{``@xUwC|4)n+PxU`u z$0uF?V`{80F;zI#|M=to===Y}IsJ#~f1H~fOW6NUoIBP3d@Ubg|6k*l_&@|OD}E2Z zI^+_!(-#*0JKRdY!maaJ&U@rGcM)(4zdW+Q9R%FPuMKh!zjnxF{AzN~06wt``2CPv z04>7*3b9r84?xI86f#r{w2m z-TH#(*KX+fnezf$<*%VQ_}E-pO1d(*zAWmIW??OyZYt?@m*64shW`HZ(!WoVi0&a$|ix#IH5xz%g+oL-DM zRd8Z$&rNKNmpYf8M%&)B{j2TV{OxvU>D+_eSik(0y;j9-=H}fS=eqv#!)WpxrfgMi z-Q0fcL{otI@3?K>*`_xtKJ~U~Zf@~*?%w3(ThV+qS^>_hN^`UKpSE)k?)sXGhZip=OfNBi;CWp+Qn%VcT0_s(6o1gdw33DdvVg;2^C-tu@2V90hS?B+m={dQ zu^n$zFJ6x)R$PqpxeIrB33kJyl))S`^Wz08T6#Cw%<+ifkg(Y=VNXjy`&g7d z!KAZhA$f~hm(Bf+4Q`&sFuoz|&aP4!P=3KeL%G}575KMn`P4-7&5p_YZlKv)$3zRS zy7=A{-;VXf+{fsKymo_hpdUNHHp6o-OY8y3q>A=)UhAgz%Ak5;L( zjYhN>h_Yq3YlgJ0*^5TBrx?f!dxMS?ps<@|vEwl~>%I)c>eL8R*C>af9eY`f7Bpvt z6S21M(I;I8Z4J*90BU_7pu23m_{lF{{Pu4TKl@M5|L#Z6-u&g^*S>-t2%JU(gUeQ$ zT>!WBa0Z7}a`=i$zZ`z`-4|c{S$c0yU`Pb@3(I4qW$%_t zT2WQv<=20A_^Thh|L(8Ga;3ui@BZ`QkH7i+Uw-f*ObzeJWLSXbd-3^yJN)TiPY!B1 z4$A#IaQL~eJo}fohjasg$*G~A*Rtsn3II)K&}P%l%Ms4p1l;cUH1AO~BQ=fp-~GO- z_W8H}lGptHyI*<#-A}*x{LhVSIV|A2&28A<<#-k8oy#T%D>i2TG4eRbFMjwpFTU`v z+USJ+G}It=^spLU{`Vif{N|6+YUoX0V;f!WWWsj6o#zz`+dE~)Ixo;dH;-7IM_b)h z@9;pl<2AG7Y3OMtLoIh2cQBy&=ICfQ&#hfoG^)as$*R(W1A|A@yV9*->{yX3>W5S# ziQqs3j1KDi=W@YCF%q1-6DN z@f%hKtqE(0M1cdCF)Mi?kv5HI|MsK9&tc%z!!LgK_rLx!0K@yg`5oedZ~XMd&wp_E z)n7jUAMYLh=Ia2MEipK8NRe>HEIV!TwEdGUSMp2(Y`S>?cd$OZ>BH~;?8VRj31+uy z!}*G4kvMHIMo}V??f1X_HZ!_DLUnSn>iS}hXp5CxAshB-WTw!78fDv~qvoEn+r}3} z_7Ud7H2B8hF`y5>_`Mgu`Hh@U=&dN2?LRN-Nz3NUEq+pWnP1+>#>$cr_$zsEZ+n)7JO-KIzDA`}+* zT$x=z|wlaej#26=3`lw7qQl4T1xjYX?X$&v-s|3%B8a?$`$$W0Nvutv$6C!V|7 zb4z<}!1Jz*_$>-Mt%f0Eg#y{DqTU1T$jC$2AUdh+v^eE0!bf%}22sSS?wA`g1S+TQ zHr*DLAs?HKL`c^jiwWvUYv?K^JLbHAMG<9Hgg|BAmKO^=EGT*&30Cw6`*SUUXZ6)egm=r;mwp8nY?3Pl_)bx>n+2D6uh!oG^gotmN}~ z80xqf^7y*7=EdVThuk7wtfP~t8pa5I^REwo`L8d(_1PCcdGGMwe{uM=FF*UsuVxIk zCu}IZ>CrmuK{XL!Ei6JXhJ$|VD~Dfv``LHie)ccFg>qQw;op4uSXI3I_Sc^O@H2;B z`Ra>z-Z8T4$+*CBO7t+P{pZ>@uM+Nq#prOSSw?ipnL|G00fkTZvLJv+&0)SNfSy## zvKh2^pbpsQez($cf*4R#m?R|yQfuapDJqt@;{A7j3@ZgSDI%m88K>wE#(5Pz(Sb|C zPz7KJR(V3K_5{Oq(Z%M>y|fu0_SP3T3H1A4e^nG(Xhl36 zwDdVa*94v&V}Pj$DUUYr4D*ms1<+zxk+%ro5M5D*1bO`F2zo3E^k@t8cqv4WcaMS| zGqMjyK$eme9gHkBk~Ia(h%GnHCL+vwR4fzWj$a*L9u4i=1JS+!rwK8VKU}nL4@CQu zd>E5r7_SfQ%gEJHR({w}V%gJ1bdHbcoETB8#L$7INeo_nc$`@?gpkUJvg)E*+<9CR z4v@ztH$O_9hb3)~aV9Wj_)~oNQ*wuK(OZ1fqG?QJdhl)z^>R9?%`VDjCzHie3vB57 z%r{hEbVK;q**AoiKPN8B#&~BJaJWQMJR%9aZaK9Y3UPakRuzy6-{|8f^YX1vKl|BV zJp0DKXADV~%Gp;Po;YrYJGRpfW$Q&S2f-6( z_G9oQ1vkfqr;8yx2_ZrXJe}tQI4|Hyh+a=KXiS&FLaJkQj>Of6N`` zJhdn_w_qG=#>R$Ubvx{sHH0SW`9h^~t}s?N*4MMrVcI;qgBBxreSXu@;y-D(&nMEx zE9id~ixa2%AFt(es{i?s{tSx$bW(WW74$zR$Hz|fKVQeEpZ@3AM5#D+s{i>%|4GJw zI_cAY`2HU&PA1|%o%;WJJs+VZ$dwAYMq@yjo@IBahG9E@8Kp8-08c#K>cZm@eF?X@ z*Wh;4m$0Z)r12l|9K0==h5ur{hflG{u|F~Yztv5LZnNtgKR17;z0%$*eDFYU$EN>F zg|Tzvr~Ci4d`{{AkMyUH|KAhE1IMTTOYq~A{(l{xKK_4;Q>C$qbEowGAN?od|Mx>V z{fF9rl_m=b{=YbJYX9|GK7#(AXLeVT;uq1-7MX%Z?pki~!S0?rrdt)&xDgn$6Kin; zu`O;Hc8?o@Ri)V%v-7&h?Y!FDPR!=EU@kWhbGWsa#|@jhJPPc6a)+CV+2cGE?S1^( z<(6eNVMKO?*w?vDnq3r;YVBb$V@3Zf+>(rkvJEJYUh8XCQt0gu?Pg}LF08a}HplhC zHn$ezJ&l`nrI=RjucjG_X>pdLHMfOjnRiqhuYneBT({KGtb+7iE)1zO11sH{shY5$ zLQi*o)bfhC?&Q`=+p)L&DlTIFq~&h)$=ylKUGI}yGv?xkInwi`u!Z7UnrUNicutL5 zM5Rm(_g5G-v|_KA>sfd|7q6SAGTk;|xWzS~BFXF?Dr)wnwdr7}zaDv-o+?y=E~cl} zs9%ND-SpHJ4QxnVO;7dcj?I)?Rt8L1uG_}mQ_OcKtcSmqUB4P1Hf?o+Jq9+|_eE;m z3a!ud^F|t>Eh{6mO{>L>%I4r3&AB?_y8+*Z-E10Y>7^c5;Sn;gSX=DwuOn==nzY>r zHnXH6-p|F}FN^mTrRQ6iDU`Kflq)>p4UyrK#X=#QC3i$I4AYhgEK#cp!xSnFt2vtq ztU$9I+cwcSESoJibOmFUb9B^<<~<`bXQj6O8OV*pPGqHdZNY?Hn$qwmK@W}MLKB0{ zs6B`ng|IfD)RZ;>L3aOQRLLI=2!8rAUCH0blBnH45ZX9w>NXBnV>S+*NUH%gS4Drr z##0Rv_*Jav2=4-cHUSCYEwo&J-)+&v#4uaGAWP;kr!w)8MWEL;bHlo@ky1nkVxv4z zm<$evW>dI>=9)H;(lv}DR_u}6{j1-xF6{KLK0{~PbSEdVL zF_DK;G>xa5xw&N{%kIF1y-y|()61vTr)EIp#FC4lea+!NedF+pKYRI)pF8~OH>6bv zntAbJR}~0Q4Fq`ZKFdyV2^LupxcwekU{k;q2iQv5J~e8N-E!MCqs&KZbh;ie25?eg zV#95QDeMP-?>kKvfmfu6dvcLj@!a-PsuEr1Gv1h%>Hx7a(7`81>DSA@dGGmm|1GUw zFF*g+&%f~H^q#%^+h2rfv5vj`+qYO+v}2h7dbE_*qrmpJRN?pEd++dzZ$JOpd$L`0 zHmg#f|L`Y=Z+FF*fPt#pS{Z9t#??%}U~z*3WalTay4U1rTJ zLBPlofGN#qymnM3OKw$c3$m?V;UE6bmw)$}K~)Z}Fx1?|3ZK38ursY*g4BeIdb z{xcuixWLTsg)=XdmAL0|qc~AOEu|q7Uq8ykhswC&!~+sI{D&_kXDO_ZVWmDbhU%DD zV~DZ_RLAjHKO-1T4-#t((cFL2sKb%v;MP%jz2>{p|v0*5t*=yW)2FzC2W z_Wh32Mkk2Ugeck6oe1rEp)g}28trOIbMWF!0bkwcnDwD4$`AmkNNz@?`{T=iJ38id=s)Xb0hnHo;~$g@ao=rwilwYEe)lif}+TZG_E{jdEcvM zO`6ehjv(;d&gPzGHrN9aSu4&?;aFK|a9H)+op%4C;+T#VE{Vb@1d5f7W*i&PLs&vF zKhY(bV>q{_YzdGxoO8TtlWMAn2o>4#u4xzq*G^U>Uz8&?R-!cud=;xq!}>egN<78G z#n-7$Zu?#gvtQ&{kd^ctrrjJ#-es*nCBn7of+?mqt2&U5Ydq62pQy}AyS3CK#llz# z4Z^TKjwe;^=wLvbr~WlBMF)jkPppm7xIQPV(K(DRD4Ro6M9px@IAff}8R6vp3Sufa zmopp-Wena*>e{bpGk@o3aKM}W<-flFtG{B+-sbgUgZ!kC4bJNgR&mM%%grZF5ck98 zF4f2o+voIsCWgDzhPWiRY^vroZ%wHO&jRmtmv^^c4>TiR)&s#_i0MyS4~nV{2I=9> zflQ=?JaVx##Zw~dah0vXW$c3j?y~Xh+kf%wo8RNK#g^x0S5QCmp0ClRwDHGKJe zFg@Q()Ouhp_EYmM*2fA{<5Oe0n(w9m7&YI%pXmMnZD8be-7{ePUZiv;d6Tb|B?Qr-~XR19(aZO|M7{!>HYuf_@v+ePZq`|r_L2m@Bjbk zKl=Xva8CcB`@dM6m`wP8E}rWDzLrli{x35|ZiQf=M**g^UOh+0YbO*1SgY>Civk=r z@ zw>kfCVk_9z>u2L!QTZ5~J?l^0o@j3DCi-z(XqT@ZRl9uT2-@XrRhEQy`Q0PLEnyjF zDPi$T=7+~G;RP0A86SI(AD+7(ootqIG2Z8uTRZ1+lg&iN8?lUcZrajjckR{Pqbkk+A%{d!$-}Jz1k5;ha*wyW_Rv5xT#xUjrrV9M91{f@J@Po+ zTHuMMp)Y4O)WyH7r}EhFTyO$u*Yq^ot8R)2F4yDg=&VDiYM!f7cBy?U)>)R;vRSB; zd$BaUeV1;q?x)tQIo^DbRH3WZsmk!uav>1OC8BBbb^S1u zi!CyY$qY!Ssp7s!9apJLC?mvB=n*YYL%>nP(7nL1y}b-WJ=#4uDw73Pi-wx{QA$AS z<~IYWn}69dHiMv3e&dZMY9nvDeo&q&Oce}r+p00G{TuL&+WMEo_p10FL2p_{mMp<{ zmfU6k)-f(jwd-NvbC@iIvK3S&C>BWI26hu>8a4Q9!W@8qZ^1VzIiX5BR^rBk%jnT# zWOSG?UQ9HMj2?)FuL4s5dn!78&05=M5qrwU)p;mu2liFmK4k+nAaN1c1D7FA0bI!3 zTuJ@?-@X0pGd~d-v5QyHSVCkk>G_|1;nRmd`v>WlkmV0!QOOd;alM5v{^HrEKO^$; zuB&_{E#Kjre}DMiubzGL7cam0bCJ6WGZau%D_12Z^}hV=SKj~4TOzN7Dv@z)vOwz2 zF8W;)9@y8(b*pV%XwS6s9%bJAOk>p`M#jkIVcYmzRlPupli9M7%Nb-tvje!D7MP+f zOagYOSyPJ!=;D`!-_JTh_}EUtHxRYJe3;tK@NX|<{LCHAC2r^?!94m94$DR|0p}X zss@Wt$^J=iJQ4O8NZP=caiVB)imXGlj~zaGV36Qb11F)D>+?oVqDm%Z3&o%T&H3Po>7X zHeLyepL_Y)ugFn{eJRHKqG#87L8<6Qn;Qaz&EKK;4i>Z!X_jNF=%VzWU_H0iYCyGl z4x6k%lhHM0?vYWU?ekxG{+oB+fA?3#(%AUKq>*TdXAt?rhV1l)<`NCP`MsC_@S-=eW)T(1t5a15xtyN0e)Ec40rPcx^H?2iV zn$)^M$+88kP{X>5<`@@&8MA;a_NWDHkwYz@{~uex7{NNvQ#AaXRd#DlXnWJ_(3n|< z&$abBLX++$CF=RH_WVS9zRcZ{YRZK&g;kCDHcOfrgCGcHR6KERK7Z%!=U@9A%qV2< zBy+>D5*A=tpa0u?&)@qlFZ}yof0@IpfZf0OvN%?GM1m{{%)Ny7*U+nJXa!+g7cQDv zR9{5PPdOWbxvd=JHoJaI2baX=E~@6(#Yhdmd=EA_?`qQ7vYf4JFpq)hQ>lhNaunkM z=-o?BQF(X&|33Yh!yo=Fe^9=5;h8%SHe!YcV+Cn=u)>J+@&?RF z$D=?9q|6E@r(%c{#eJ{-e_jd8`XdeMT4c>KyHSNpv2~A>5r@wF%ubkla&d z6dSz+$eT^3Pv0_EINUBu<3s|zF!^@5%GF_QJbR=!WT%#_t7}$GTSWm(QG%8;5~{Ec zo~^t6XG^n6vM$^mI9rl_jMZIERaEF# zk>+k4D|-He&m=4AV{Q?cP0F;)yREigz9!UleYfjXVfF;|s9Zh^$`82`2V-dh3aAsq zgIR%PH#p(1X5~I~x8m0J=72lJ{)$#G{skjN%U+LJxXB1piW!(;(9{y!{(DGm-i71@Dl>W4|HLSFDbz{#H(XR)weAX~np)Tb5Fd4RE!xS#G=Ee{zw z+Kze#7dw2{ALxl5wWSg7@jkGUd=G?OfZj9qEY|3iXrqj`ew8*^-SIZp*)O#o8nY#P zu_CnVdpId_`_72X-=fv$jkGLXq!xA2%cPZ(`nX#+=m&Hd(r5&b1BJkhbdU!wJuN!0|C6Zo7McU z%jiCcn0QtrHp?308Cz>R0QkNRm4;ce98OhX$%#hC>$CBy1D#2E(k&8p@i_OgY3NJA z7_B+M5hE-l<0v4+J^%GLUi|j2qvv|ec!3 z*x~JW4&V8?4N);nPqObS-?dY2X7m;sityXP8GnKZzFg zDm*gf!TXr(yB*ptR}!=fzK#dJutUEbK)mpYm%P8hw-}#y44t6S(^5)hOfsSmR^-N3 z`9a!DI)ET~geo@~jw-Fd0@-hLg8;ScCezY)(2&z^my4ye)CSoXkx{e&ODgJZ`eH9j zQpg<`?@6-}w=TF^?9=Sf5KjYQXJ{nZkw^B{cMsqB2hLUu8_=LoT%l1o8;}ac)qgM)&qna~`IPWOKV&0pEq+rE-9=`dV z!ykT0gceJiPmgMFJRs0n=n8;?vWmML=R@E7){A$(A4=y2077cqn8;~`rwB2)SSWJh z%=JHUG~<0+d58*6977i)iNoYd+Qih<#5^4!rqP@aKx{9F+@-rkCO~s|6GN_Nol`J!>~{N!4~fr8Q7jRnAtK>JZu|jd^>Jr-Uqa! zoKY0c2f=#?4JlT;%Yn5;O*iOcwp%@48mIO8dEIPxIrzge+dZ*QRR4QvZ`Jm<{M`Kc zh5A%;()+-sKkRdC{qN$rvB|=z{?}{yoa%pnq(ABU-zSR)j<5e+EEXnD^}k=or;q*b z_~g`7Y5Y|G`;Y#k@Ba_y^dGkW$0o*#=MwS%CQjr3y_QeX{x|e}4IGx;lm?!aT5V!% za;mVN$=m2q7}KN`>;7;9)84V_#XGdn&{i)iT1=EyxhKavyUa>>nkhZ9u*YRAO+DQx zx#_g2?d2NSWsE9R9Gj@o2Ko3{v0j*}6l&^eBC9u#Dgep0>(Hqjlfq7gw#(9#7j2EF z?Y7h6mxn^7R~F9XC;beyeW<#fbGyON#xjUUS(0#+B|6&sGhprN9+o}h!nEipoxs1) zv|CtEYt?H^;h;u}*odf<*_IddN?8|heU*WFelprfU^`7e*+9~yda4k!od&L08KYz8 z)7DypyC$s%DNSec{DFnRPWEGxSLo3ngcYEzyw?LD3FTLN)~! zlFOeQOGdbl8;U%}3j>QITqTPSfVPzK>PMe7k3vxD9#bv~hIUr{=mQf+!x{p;X-&mE zE%bpAweo!+LyRVHJBf9}Yq=b{Io-gpb4?o1%uuxKZ#N{6k5}#bL?Kb3=A`gg)z$0g zrwYmH08eiLKp(5BQt|v`eJoK`%V|J^GI4P!t)5yZb8HiN0|8o`UlY`^_b2< z2J~uxbCAfol_u~H{fuxpXot8e9ARX)Ge0Ud$K$%8Wv+L{Z~((CQPjnjGS8|NMo5X; ziAWAyw^^}M7ULL)wCV-x4atQT!H!*q81WaF$Ne-pOY7Lv!jP>eKA3`Jbb z58P2~3-t2a4&8xGSV?I*9mhsTCMw{DOhbDQ?c}&`@%|u>Fqw%T-N%IM$;5#cqbOrk z$#k{r`H;KgI_$tWwu_v8qe|Tc{FsZ9%6ct%JoUCy@S#2-l4zM8RJS}(MYx;~3f(ER zSaP>T$ge@@Z&0WD?{Nl$dPn@gqX2t=BL&pw2H@3!gB3$c8)dj{#`(6hIX0+@lEz9!_%m7IRJ^`Uvbq?j&~*GDhZx|MaW+TL zi2ZX|*$2Y?q8^>qO5zs=qbbLrzPak`Q5R5+K^85Em zV23^&Ic$>D%{@5@uMw--HFpOo1Vor)0F{}KbBWY=L2I<%ska|pd(NJ-QWe_=M2A+e z8LgU#I3N|g3NgGoIY=BZ)xS9mV09EQ2v1jB|EW$Iuh_4RwmWDjIzu#B!9PB*x((#> zUb*GAU4|I@)sDn*e*i?O)l>}N;@Dg}$Fy!0!){5;p6GzEEM_stj?jl;%_Z3Pfep$Z z9okN(KT^khoFT~{PJ5%{xebr{DTGU8D^HxK1+5kS`-rW0G!?gzqm`B%d+ z(dcqm6BHP(@$s+;{yU`!yIXUD4-0sb*p%VBj*UjBgF7RhYo1+CIU@9-^8FePmH$3c z?E-2y*soWCsu;TFFUM;~YMlMMfvQul0xhWr#j#rEfb=S06l@d=Qeb7E+T%a;L-YQKQ^PT{jdVaq252~N)!G`T7H_++s@W=}?_AV%sBXDC7zvyJK+ff) z37SVc)DBFPH^CAF1o{b_k;wu*kz(D;W)t8P=>m6%;=a0wWL&Q$laI)TQ|G!ECx!Il z5ovE~@l<9z%J|l36S!S(ug~_2+`&Xd0+aYR_!MHyMWlhgV@?%-dvY@-YU$WPio7EV zg@cGj277ha4TiF*lDZWWq40|wIr|+Kh@hlMnq<5h8?0e7DMXVM0tEO@jrNn)4Rd@P z%Hc7*WxEEbC7p$gthSARuO!$!N?ZI&y5m&0@FWvi^$uvHN*sV+q2^qTV+DSvu89{W z3&-s1|IgmLHaC(a2ZEzDJ3Bkw=ey5fF2%|04xjAGRx-*^&Otbob1sy`OOI9?yIL zMD=52ciT17L}p|>JR&?i+&w(pX%5B!`qe?p9O4zac>bu${az1`y)ozb@rc8gcUh<` zyMOIiR^N-v+iMqfs8)#yEWi=dyA>(UCz=iGUT|e$-3WSNwZeHJH8kE`^U65ZBM4Gd5rcTRTV#39p4Fg@;X&h)51SxrBmuJ z_!;>4F32Nw%VQ#!tn&zLUDxVS*K*EQY<1r zLYu%M=1%f5Kmeyqzh7e)yO-m})5mg?+f*+T_6w?0imiFg{-hn_h890cmzkq&Q&#?w z#O~u8n4SiGeBB>KAHE9c(=Xga=&}H$vqI%kMmMuKR;rXHVB-iuTHhwc4dVS$AWS1$ z=k2btqVi3PtiVny1BH0FS*emg9ZVZm-cAa5?k0$O;TbpOEwmOkCAeGozmJmOQs-RA z2G5(vT&<6Rh#!NE2T18za&VbqBsUHE=w{6nVoZySB*&hjeN1~PbLxfcxhC)NM@?ie zj1;s&0rauOh4hf&oKuKWGL&o4@C?wpKqoiOu2ae)1-M1p;am6F{H-dhE!=07`K)@l zmN(<(>W-V>D|K#7Tu*m$>nVNwnVaRCTO50K$)+s}Vyur^tZ(A!BwlHR0fE7S~`iz%yiSm4GGUjx1gR#f*Uj_pnLo`mqd*g7k< z_0U}d+sb8F;DI9-1mI#LhHamr~!^c*eP8-uJ`er-|9o$&-%4!t9 zEszX|gwL|cKu$PR{RaiI?w!ObP$g_IrmgvfZx6OlKe=K1=C4zoXN)}_C^|?rEefX- zYZH_okRru}Qd{BCO3_i9QA>%x6x-0w0Wnf%{$}UZ2@np zaWFqhuDRdz7Q7S5x7?F_i&0ZazTow=u~H<-geHS$p7)u%K9|M#^7{w|_eP;~JnuSh z$be(o{pK0h$<2L}7IzAi?*fc|^uM(Km$7l5zE&GwA^zvBJKx>^{?+{Z&i~7Q)&Gk9 zzkIWF;48%coUbi>=l|tb@vqqb%f0#A_vi1_zVrX`|NDRD`u}!z{~Ojnto1v$bNm1N zorUlA|6j#F5k0ZlL;r?h%$jHT@D3gVim4f}W=Mf610DeLC69}F!XtjJ20T3GQ#Qh% zKt#uE#XQF3F%Rx}#$#o6eYQifE!hPRnaMng?l}sW_n5~YWz_mv%=!ccR7Vx8#?0&R zpq1>nDgr$=-%(1;x)hy~tx&|!m>u7ux?=VLe_zFn;$yyy8P)JQX6N|(1~T5ohexrD zw8@&(>IH!FLPR&EuI~V#6svRt2ID=&Fog$dbsO>y@!>5D7>&eEz>X;z>3e8^if=*+ zjm{`$r}+Ck7U4}_`D`eHimvgfmF!YP+-yb!Q^2T`E6~g%5svhj#_R&>AP6>~85;8^ zF{3geK)X#ng$HW)G+<9@hyV}@i;7u(MJCK>6Tn>0> z)D!qZVOV?cg~sv)e7Pf{q#oa<@TgE<%;TLhkHGT-9u)Sm&!fWjL>SmtJPhnJ9{+V) z1bU5q9_clPdT2VMn1^~j^hJbNpT~gRg!&2Irvcj}6byK{+09sler?B$hT|E`84br{ zcpwCP7~C8bmX^i4faBO35%hI8=0RWg0zTxg#5BDO1dau~$qyoE>>Dw1j{-j5Z&kzN zBaog91F|hFy$@{XDmnKCrRRZL`>e{I1a275&DB187u$h9f<_|3&bOG5roSi@q3Y9!aDr=NjR_< zKm9cPL2r>))O7v*@PG~6Cf3z-n=`IkU24{=Y&bVJa9wxcTwx*1+i@cS+hV6&UUZ;9 zwBI?POsGWc21CE(FLo>aVbrmk4sY1ABQ}7JUSHc0RNkGNi+F|sM)VR{okVn^o2Vh` z#>Qy+b8~WFmT>Hx`u>5=x(Y;6k6|H_Gi{h{c+}fI%ZpdVrF33y>kU0G^h}Qdl9zjKhyOe?wTSSBZ$|8%dyX zRhfNp-RSz7k6oo3ZFyUEZ_W`+&;eS^NgB~b+nyI35Zfw)gbXx(22TB%k z;ax$|0|8FkjR2om6H^es5Z;y>Ew+B(wijEUx;3ZCS7OT%C4eAQPJl>L&{ezTK4<|l z0`w+uKx>+nGp}#kEjFv;=8as;E2kt4J;$?=T!^#ffm)rjNJpSgi3FkJ8Wa8lG2u(K zdbzeJdrjKtBjdLA>{S_7l>sD+SjWM|>l`JL#;DjgBD@_eS`?DRlHkktNO5ZI6N- zSeX7GzC`P+9@G#zb8~TpcFAqPAm;~Fpiu#kYNFRf`286d-Ht;(s3trqiJIGfY}Ci& zWk;@SN$@O3LIl(Xmgb~lxW#xs2qo3`KK;}&fZF#C2(Db0`l6PNXgwI7`2&oStU89J zcwgX&x`V%Z3Ckta3*9`O;-u+C#S1POQazlKSbpTrNLcq~GT9u2 zy`ChK-Js(Sy74l|(NP`tQ%*K}p(KVl!MNLalmWKdUKmF};@y}7ZvgHk?v@V-NA;Hj z(SR@eOXLG5qbIsOboFux9A?zi6}^u{N87xUO=BPit`!YoW8!$-5jfmR+##EFz0e-A zeM?nnF^eix-usz_6;iUO5TCIs{Df7Zga45PaMqCrC^aT0tNcaA2 zSTjh0`apq@hnSlan@I#a9#Da*+-uHVKC|$l67_q)5LlM8*muu4)wy6BFnVWp7@ET* zn=^MYs6cFS7ZhCiTwrv0gNHr zhyX@erHDdTR`oZmxrtWX5t%BWbx_(AIH29R_5_@aA!90uE*KWd_w zGH_v)he95{WQ$23(%K>L*7Bb<-y2oI$>N0Ftf9hb%tkX^IMotc*UY(ayy7bj6M;6(7`( z?PJMV?)f%KAcg=p@BuZt=YUw!&cx~lWJi763!A4ZT-gbkXp{Ym#qhhB;2QK&_YXn( zK%6jaZZu18=?$-qzoLpnGB3@9t#&$*cSEG@`WG<`2E*g^SE>9DRSJD1JTX2|On3uS z;$Dr0lK#aJqYHBlx-Vz(3v;j-pv`5vdv9^8b&DC}Eb8Du@!jkNw{A$){@Fas?SZpb zy@a+wk&}Prd*Kl?#FHc49_3Cik1m{tsZ(J9C%+##@n#QZuLiWP$KpcQ$WIf(O0q0I4GOp$ z9KbOOn<^s?Y=c((I&`hdZrC{rwhHWX3A;@sDggT1?*`baGPJ^AOZD@RsRW71P#9Qd zmAnCKjUKqJ11J$l)?EZvW!de*w6z}!5v^`Kip69IMX|t- zy~xLWmK;=Zmq%bz5uwC^E7vB|%Sd8Tlx^^=+iKxHp2i!#d#6E&hE3!y(`0^CtY=!+ z4W1)gu&>xsEUPXFTj$-VdyH*2%}RE7fO$5i4f)xct8-&cYf5y2&3h0-k{L8ij*40* zI0k$X_QYF*1SN|sBxMc`ke|$M4xfBX1B0Oc+z_70;0^h`S(>dg8mxIc=vA2%$EK zzkDSA24!DS?+0L*@j?WwS3$HWg?89HxrmCVFwZQGof?^W9cBo4^cCp6&ik<2>urWD zz_jBq?&!5gAoYCj+|PqF{FQ^mDH`yQWl!;`0M=I;(B`)R{Z!+2_HDInu-6=EiN7u; zxE#`g{gIW@{Hw76awYMkf_%3gw@X;)Hp$?u#THQ{Y?ZjcCE7~UZSL~Hl5mjLbxf;c zHl;#7+vG9O@;lFH;+!N#x;gwu?3I6f+KqQ*X9VHy5B$g5aL~Cb7`P{6Ly#_r^*~H}DNv3CVbEY>LjPy(YJ> zJR>au@&dBsFrtHi)3l;?7 zb%lEQwTwu^#65^v`~ zQUoVu+})BRu!5ALIEQ4eQ-GprQk-d$vj--ylrj~#l3lzJnpBslR58fomTiIxT^0uJ zS!`RRVY8T4aVb6~HWxsk_?yNihGT~>oNM*8TB^tKYlXw#t`AWq)phBVmzIYee-bz> zay`~|#m>-lyXE;$A~tXZ&abprr7v*Sabb-O&OD$rEjMJduF2-HQRs1I011yk>^l+<5uF)bz6XIVzA zP#>|ZBwQb6RD*qHO5pVd%cvFV=Q%au1slWT15v2VM^=IAu*b$PZ=t7sV^=G8Z#z~# zCtuF6vy{EN zEBkGE+{;AO2cq+WsRIU~h1L@VRu~FAVw@n4U%{vAYug?GSoo}A8suOasFTx!99Ze8 zq>WmD)fY#=7_pq6i5}D8&0_+MhrQ546Ej%5-RL3dPT-AoPXec9z&lx zH_<2l4ee&J+_d!RSnyWGm*|uxm6UvXRF@`lii$;=1XZo;YyDbvfU8P1 zp%y}0l7<;>d4VYB0Pn?f+tNk-j4O6bBZ1Gs^|c@yMi%+ba{%5qr43z2fzd*v*qOHc z&=May!cxn!B!zU4=5c6Rsz)4n{VrkADV8;+bMV%YHILk<%F+;M=Tq5&11#fHTV`7f ze0$#*Jl0~J18%!F%d-HH8HtuAfrCU2A5f=Krw7T$WH_s(Hx;y`MuP>~UN?dmYN#HtTGgS#Zz?2s zV1{yG4knn1Hx9s*I6&y3_T{A|TZjYi5s3%|fQA@p!v1dQz1V&n_Qds|^D}!(I5hPF zrsQ$@=m5BqJzy_dzH3w)(*6QXy;n0aHzx)ZO}5?xWcH3LhgePAPHy6M)Wk{SI^XAJ zTv&qbx?SIX%(>Uc#-X4|HXi3y>od>gmqJb#c#sP9SF&s*V$D3Cn-ld)gSiMKkgeHkI3=+6z+W+byG(V$$+LVH0&1RTLz;AR?n!6npM@6fO~NKm4RUcD+7Irl z*nYmmGomK{sBS@Z{AdCA1Yw5wP+45Urr}o%#zo0xo0?7n2ma7~czq4|ADW!_rr^0J zj0FzCbLiL0n0?6DR=mkB9{hi9FC<W|hv6%!-;=QS+lKdO@loqn-iQGx)fAxTlaz*HG@n z{*9|?d_}p=#n`#y@tqrARknLEcJFw6_X=uCNHKKzonq)4Rtz;JSh9=DJ!jou8>Xq} zU6L|)(~Fy(UD!|C{@?}y(i;q5YK!v#xikdK7Ztv{lC*0Lnq~BD)Cxy1j>Z8T9g_+< zG2|evBGsbwbQMqzMkmmpMKSHwh|8Efc+86m-*IKFQ;3;bH;H)S_Te(rQzekKW!aVP8# z`%0HymnX?g*udP-jCkLhm>DPfEF4DuI?|IhpN|LM6{&lb+HHsDc!me#g*iYr^3S?N zJOpT~9WOw+*;0tmZJq)}86%Tp>3Ycgi#|Fo^^|O0!uwvxsEcYzvf~1cNC7$Y!BuNU z^I`I~mfO;Ii-vS-%(Jh=9KY~S>O=Ll>tY?MMz^3Y;K*2Lr#nM>t?1^mEh?HB7cAR$ zl^D?ypv<~<3;0ISj^-R&InJ>qIY(3@>Xn+k+yqNtc*M}c89=Ug6^njm@A~Wt`Gkvr zG;c%<=H~Kxv{ZGJ>qzo!us~+n2-(#G=#Erah%=~3bgAXSl&JhQa3}&W_I}N)rO0>(z?N6MLSLm zw;Vvta%~CKzqA7CX6ckTnN>!$1c9D4>BT5b1N(|F6ib(0S(#666{rj=pN?j z_72Lg+wLV^UQOR7BIv{|A`YEi?gSk%Ndz4{Dd?~yLI=M&=%|DV9cGr$nZ!guXX!2T z%zhM0K75<=;pR{daK*DewWj(8!qpBJu38D*W)k>)0MqU8%yO8IhuvP>4R&NNqWWye zJU%Cl^aL5Cyd$OU{R3Xzjxk4+uq-BuUUsuH!?PIELkW_g_mn(Ep_uDUsfm*|!4?WY zVX$Kq;`FB;6=6m~wil3A*LvWAxH#kmLif#aawD+!<6&P zz-p-P_;z;Nm3!G4eS#IopIY4rIi{9)L#PM*84sPwgRFR^5u}PI?3g)ac`@6f*U&gV zCY{dFgBk~hvgyC@gO-FdyQ>N%e|9Ss$%X9DBpS-c#fQlcFR9nt!DGNYF^)lczWJ2M z(MVt=1uy#;i>WTnX>(b^U>iZYsXW4+D~v=ub`OEBu~mL(B4ln4k^svn_aLs|FF%A3 z9qz}46Sn~*aj-oC@snz}ioG@n z(C2MSoEBVRIER(+qHf)}Rf7NDyMG7&vlt;FLR7a-yk3N+P0dcX*BSsG@AGOfWSy#~ zRkt>4ckh&L*J|Ev__>3Z3aho+JpAhSG9`RCo4;2rRlm7>R+1dZV-*=}$zpNG$AoYg zqeV!|O0?UGQ*yK*MWxuSEyh0p$!|y$Pzppb2t-2CQ{VRE11^{?aq3{3Uz|GXN~FvM zJV=Dw^+yVDz+i=LSb^DUda*rGehVBC^xjs|si(9Vg(cVgs5$8NW9WFmJg4F-2`+Ka z!9fLvWZi3aQWaj{7U{77WxGUqxbU?DhzsQaLVSx!8Wux@%ngjc1c9<%NuL%Pfci`o z4YWZL9IE%xpc{+l#Hsr*#4Z#emnKhqEs`7lM_^)M4j`dIcxmjSZ)3(khK#R;Q3$J< z_`PKDiC+=6nMkEvYh57Gw!|IGVII!`<19G1jR{xq#KpUSDT6+A-obAaii?9N6ZrZT z8JQ^_4?&irI~qk6eexd1aU2M@%MVI2ftw%Cl_f1GsmIc%sZ7GlusRK8!r$=vIq?A8 zwpLw!^td@MQ1Q4-RDdjd>nGiTOmKv)uYd~&m`p|;Kdu^bJLNo)`{+Cf4$f5e#KV_9s+yf64JsIqZ0 zFvPnS>HAw=&dQSa5g4|qI>|3Z(Ow7DOBKX2G9+u=C`ONWc!F!DE9)rSWT+%111l(S zuB+~UMl5$f4f!BZ+YX&&8VDxLT^@1}?8zx^6KD zd!o$aaM1mL{7Vm5NnuU5-t*64uWcb8DlcV0XK-+NcN%(e?5o?7Q&`lN@54Xt=H}`! zI_>rk$p!i$5*1E*C`TfO9!EW61&$YLcz}d4H)_Z@k_;j1W$)ABXY;Zy-nLviHNSi- zeepyZCgW-zXDE(Yy0nyNaYuMCIiiKDW@naHBtA)Cv$xZf0W8$gPL7gP>s$^Lt6^M4 z+|W+kNUq)EH$+gyZlVnpkf-7asRLDqFR!nE@-PL zp;~8C=AAYl>5oNTgO06m*Vx{)&}r4&R1Nd&ACYwon0Tb=sri-yAS|CEY!H!=yZO`?4(YdB7NOj5&1s4PKY<8t9RO_!7Uoyuwqg79DN72cS9W1|ha zx@WJj8h5z~Fw?EY2{c9A6D(@+wast#e+3Y719B4>y`-R){9s`UmvX7lyKD|if$IWe-Mm9H;1rUFY36;&1vQs>!-EUwP#|_Ms6FEm)O4P$h z+eqp8O|-&95u9NPItEjht%09Q7pH!$ioY7X-wLeX0-Q9j-wLo)bN=4n>mZ92!+s8S znN(bVZr@Eq4TWy~N#`3v$B?_CS1naR{2KOHuWO-~h$t2}NQFo+?4@tLLy5|Qe`YD!W}L_$2=TG6 zgjiKMsb&R0Sy{esru4dfRDAOIQP3A_(hbUIkC;$Cs=8J0F{gNL_#hv)yuoF{MlqLb z_q9HQ>v(vW&y=^7*{de^S~AMt5+%W0==$)KAb**-Ej^V;waHCamNCGU#SbMmC}gbm zzBB0#X{r&@3#A;?4465U>twSl@9Y(MEifvi5s_YxNHhir<)v=xLCI748SR>)a2)aq z9aSK?(Xj@p0qWI^Z;4c|St+%E!6Qb6CJo_dZ=w@d0eqM^VsE=l?o{lcXB zPiXya8W_|ga#CXz`>7yB52D3NTe>BZsBD4!>h~z}vX!|)Jju6-QR6bT{FQ*=MI}f8 zid5(+P+Dp%$k?>k3rFxZj}ol{x=>UyLC;S`%2e6@IS_EHRt7?rvrcF90`=+Qcd2@$ z?bOtbd}Btd<1TkM(8r{9*kmeP{ENPaF|x%v9E;KBdQ{5m!Wu~g(F;4Xnt__@kfPRP zBBfPZ{83VU!fJK*K1~*pg6nqW{dq}2Xwk^URAu?SB(t|=iLCC&=+izKZ12Cpi_Df}P%MX;3Bsms7rp6O zXMol`i}5aUyr&fMS908KTh75j!g~BR_?ooMV`OI~{P7LKFxHpC0)6{QttDVbGFoy^ z>ZZj?>S^2?nk%fU4sM{9h@}+xZI;u3)tfB6{nrP0I?-|MUU~RQ*ZFLoT@`8ZTZy|9 z*jI%>LTh4Y9TYW)K77O#a(SL`gtS}OM`Z?Q+}Xls^Ph%==L@Z|=LDVchXawmUcDXK zvku#!zlZo2X46M&3-idsuNZGDUgUpz8=ZpF$@R{6mbc%q>+1RlK$+?Go$4mejCAu?!In_zz;h)!odG zy70)2EK|0X9dd?m4b!F|=(^dL@%nuHMBMgo+Lv!G;hwha`DFdk` zzV(;(571~(V^Yx?QULVe-{$c26%= z18s~|n_dFO%_n6*jP_~%n1=X@5hl)ZTWH#6_GT#UOFx+Ryt=+EeIRTwd4Ud1DO2~Y z0=fdpcZVhO#lGUit^Ou)>TlSYJ?pcFxx#dAp-LNXs`$W%@&o@s5h^UXn&8DQ9kyck zS7YFI_s;Fg-O{alcPjUuE!1vR?w4u{w<>p+?^f>LEzzH9sajpA&X;PpYUg+FRu-Do z5@wsPER^n7?v(CSYNgtISKq`JK=nCzb|vFTD@R z>-Y}u%EZ<&$5(H-mu|SH8a4)RV)HuRR|jI424bjy(357zE`{IXuezRC*Ap8vYX^{B zyy(n<&K#sCABcuu(eJuQsEdU4b{fvMgfn5Y(+f%CTugyDga+auqi48{O6_)z->B5` zQW7!5EF(;PJt@gp0lkc#;HyYC`)$@F0j8&Yw#b`5syBTUIIKqzsu8V%HA*-2MGpYrU`CI&8Rf52n(M=-0k*mM-?I2lWf`xv(MqZ5W<9R^1I!VMIiSSA2VB zl_=cxHUC8^P>ldH)O|fnm!e@GRm*;>1iN!d7Wh5}_Y8)WBQXS5 z##kO=SV?2TcU=42wHpALWd3yb8@cW`Ogf_So#*nM^I>W@iMz_SrH753A6P?RVqW_8 zj?Pr`zlxqP2XWAx$(>u9S8Mm!&v_@;;VsKxyq1a?kZ}t4_zV_xf`n!hy`9v-Ejf!4%xA-rAt_Lhv{ASkkg0BOOShbp(Ap& z0=PSv4Av#g%p$so(Zn{Eu5>ra9h3vB6d+nor6Apa|yi_3F!%LWRj zZp(u3vgyuLB~^ay?grw{A>9;Sj)mU~fiLIU8_(R_d8KdWp^JjcF&wNpot&OJ?66sAP_Mc~#-a;DcI< zZ`?OjUb)MDn$)%JGrJM8y^O|TN9z$fQh_k*SGdl>edHU0un^r~5GgLIhm^|GuH<+- zGc%NqND<4VDyGPFKVx`Rn(>trr#O|aiq@0{+Z2^yCWonnm<}p>LL3K}hT<_>PuIQa zT;#Zmu8$oRPBn6?i_s5c)r%2I`ZD-xZ$CO9I^VV1PPQqGz}&_N%rX4XSc#EhAOOLx zdwuO0P6RMM^FURR$dS{M^9!GIvSK=6HZD*z!QgMKJR=RYkV&UsrhwTDNR|X~ZwQhx ztojpQN}NNWv@Koqp}yeCi*ALoXp0v;Q^G1FAR~63ZulWygnH(m`@IwheFeffuV+1! z^*{m;1NQz#*tCk8soT`VqFoo*^eXdw6)>XJp#LPjl<5*7p32Gn6 z8jkEkAg7c~_74DO=u2VZMsO0sW(DxQ06gM)B`suSY7EVqKc$I7#rg6xsywrFcaG#^={Jw+*a`^9qWZ3rSigB>ipV9t5KCCO zl#5}nKSC0Ai1t*6vsa&f`XNjXfwoZ`yZm}@o9n$PNQ$9g88B{^N8dXd*IKMe4BfK+ zI!kM5B-WB5uk}7l)7yH3QP{6Djxy&=+9**i7i{3SyC8oJXef~KY_MVQBAd9%7b%D? z>p0mvZV19+4S9Y|_9Wxq1+uqcgM8tOe%B-NI@o2Chl6#aXTEpB8S&TLm($g%9U+zm zzTM#jYe%|Wl8%2VIK92=KG+39)g9}KcT+&xl73f6E6=Ycv)AIIa6_pjkgkI=K}u%d z#hcOtDc9ugOYf5ZSRM*wS6+uEm$v*-e&7=gA9f6foscf42fn;cf05wAmrc=pf=*(0 zky!BoPLDWlpY?6INr_LCh|?ucCL>RSBqaPZ$_6X*k0G%J|SMd@4f zvAydohPH5H#CCzXvp>K@Jg0J-%B%9My)8X5L+&$0?yv}C_xxOb+__~|qJbi-?@N&2 z>nSIF)bMVFln#IiYe8jwYj^#H59d2T-ggtTXUG_m^z(cn{@uV;! zZB(7G{SP`Sz|8LT^-CXGotyhW9!|)`G1i1BhWp|}Wjpmj`>$Qfd*IS|$irbZ<+3ja zp-^D$f2aWQ7%azj)j4qL57GIX3tKZsYSCpW{Nzk$pyTWzdjn(w1(IQbPO=F?!LI99 zsNec%U{@=mckA{C;4F9s#a94IK()Vo$ng~)lvT_nc{qMp#Ev}76?>>Psw?iL6uWR& z-*TJ2JR)uS53HfJwB^Fkt}I>FEvw%2Nvr}sdCSFc^1MIq+$+9^ZzYS`vGk0Z8v7mZ z3BkkOk=hBJz=`6I(6Y|2Aj^Q08}{YF>!W?k8+1Kw%6;IzmOgfQ(RJ5;0|VJj9kIO8 zRY&hmCaAkkMXWTKTkAU%~OLB?p|<ViGiM`G)(U9aoI0Ng9bL9JMTK~PB zl)K!!2h+R9O_jugh|PG1KjXi4YIVzU0$NvCbEP{2KJ{Ank zG+lfMUF>K#B&oAic@QGV$1cW+Q>P;?4(x2IZ0@+8ZBl~)({NER5f zXn2eqJ6h-QgYd3QTJ43AI2?}(^w({vLvHZw!bf3~d#)3S!hzjDIKx5tgcFxddM8(k zVHoQnoE1XD$UVcO`@Z0##X<9uAn=bvhsnqxlITA((lC4nFZeLr=UOq~3eRyk3S7Rf z;^9vW+h5T1d-l29F2sZD#hjHFDjIh+$qNih_Q= z4%w`nUg_o%qbOLKK3S%}OJdgw*3Q^N9qB?p0_i$RB`6ye*H8<}2jd_v&Wpvyh3 zujwkeNrteRL7(7PC%s|R!RxaTURF^jU$v`+f{@XB12MYqg?F*PW?d;^s z6kW;?v~yO{&Q^+cwt#lFBszUGK;gHd0-uuF! zBvi{ZBSgK7_1StpWXNo+1gLCL7_tJS9~(6I24!j`PYPUaU)B zyIGem2*9|-zxR=m>I{=aIhoy1rwc$hAym^{mj{j|}~{G!JRwaU-&)sJ(2sS`9uRHFz_p2JfhA z6FRM*Zf(&X0{fTCrdRXOh?^&9U8N5c)*fhn7BS>>v7j0t&IPH0T-&FUTRzRvkyE9Z zr~3nM-3M=H+=1iJv8z2`P1bO7T*J9}^pP^6${l^I@(t`TclNX|N#?-?1-&v?GGxE0zMU&@AL z3OEM8Gxz|BknuGPP(#nz9!wzK0n-(eg=TV!Qn3gPT?W zt0Rx5gLlV-#q6@&y+6oJ|Bd+cxTxH~7g8!R02iDA82IYM=0YJx&;qo2fT+_Pdt%e{ zUC49wL~Z?^&2QF;D{rT)>9E5bM>sdPX*b!71Q@En%+#|g<2#b2pLq^(irH6k*>NFz z>0=1Wxux~!ql_M%y`x$VbLZF`yi+Ls$Q37C5}JgqyMwto)VC?wCz3M`O!B#AlGWtn zp*A7k_4RX+cEtQjdRql>M!1mKTN%vntyJXiC;5wo3Zt>M-6yco2 z??rye92r7rmH#qMj3uR7?u>2OJl)Hk?Fmf}ovR5KZc6L(7PqpuxQQLAy(=`W)88i2 z!M2h4GXOtUGbD|u5r1nY**rrrHMgo04y)_s4FBvC?zWOF2rVDPkr+HQtNLl-z zI+Au@S&GwTkqRxlWOs|GO*cnvx{BHWkpuk8=o=+DZMzTJl_(DTXvFQcNk3}KLuGx- zb6@Q!sr^9MIvAURND;bomcL)bw;iC|neFRb#VwlRF7(c~M8}WFYI)=`d$acwZBE@{;g`qlS|^CWr+F1gS{jaS929J2!1KRrtBg z;T55|`Aa(FwwP}*jEYwAJJ`a2+~k-K@%1yb4bj(t4ER6ZoSq%VoM_l9oj%@Ey{~NTtr@YB>Qph1m*xQ z&_+v3NAX!7HWv@Jm5z(vF|=-k36UB5y~RLpp7IXSNZOx6k+=Z(wjtw4IGnkSo@lAj}Ejy2pTKt_z`5# zr&za9Gp+KvJzL?U?gA+>dVFS|7Q%8f3`25}V%WST$}2|bJ_sFs+2&2C2w*9e%D+IK z*@yTL_@mOM*Vl_ko?RoMibWBCVxZ1Un8>$et2FE+f5bH?s_}rqm&sx~3gzK< zM$^JtzTf8uV^>so3-~;Ys{=dZ0EUAB)!T^Ad^}eY*M%NUipGim$8e&tm1vCmdjO68H^g^IeHim{}FY#fQ0 zDCJwXF(!%`Go_4U=^UlqGUrR?3yN|yq%UF|Qc^K&1>~gogEkf@APyo58Lnn@St|*E zd~XP$jU0ADI*BH9?gkV=hab3lY+cq|gS=4Vw+#8df^!HPE!U zbiqIk@@i2kZ{V=#lN!FA>{sapy1t$NqDn2BQ^A|rq=68}CNNvCi>KX)YqnQ3pT_sc znH3tK@*qyk*m8#7{Gp2UhlXi&GU+c1T@MfBgs4pTJHcH%uedkX*)Lz84~kus%lLL_ zf96UVk#THp=qN-%D6^3m4xrO6=cPOEeAAjzW7t6zS&$l+fEy79vwbq6^)-0t8=Y5g zw~mej$iW%Yj-XW19e8%{*d$^!)**S6_p_`<_iNRfnG$(ZNA!DDA&(yEZ(=+TWhfz8 zv(jZ5FxI!z(3L#iTUHwHXg}Q@6FX&$MYGvle|KQKJXZ21)<0J&ZXq|i&&Eiose~?v!-}Yp>>Z*TJZ*_*3wmq;jEIU45q0C z?{b=S$k<#mt^-=4PI$ewfwGADoYtig*8`sbOepOyZK25dkg&NP(fOR;tx%&CxM(t1KlGn1MI@ zIGnR66^P76njshD!h{ympge*o&{qX1G`1Xpa%>FcD`ul*AI~4?As1GQ8E8qVpakI$vKJ9%v%t%KQP--d} zup^;N3+;g!bwYsc6-4qt#}LYYj}f-~EZtv0IthEbEVo+909^RVJYO#v!7d-Ov{IeV zNZ4l7shkY`;4AbzHFQRlj)JPvZ5KC{i^M^!&-)3Q92xgG$fDDj0MaUufz;wO&Dapn z9w9pAB%f|ZYKl~(h-W~gb)*m@o-&4mC>F_dbnq6-M}k+KR zQxPIa*Evg}M6wD4&e5bQk>69nq>uz$$bped)Nb+^JGT1#nQ`OAY%n8M9v6VJCiAZB<6OhzO zheFP5SGj=3neRr}Eh7`cr`X5zzKJ=R65&)}xZ==mY7?+NEs=$vK^@Xcc->kuslDRT zB*ch&@>X$>vuWxSp|^^}buTI69#R4G61Eq&(Cc)N$tRaIm{^lFujZsu1KQ74H+BcJu=#3M+S&y;~KS1{+KW6s2q%w zQ?x{)TQC|O+g`!)kjk0mz;+x(wDV3G1U&{3I7R8kMaau7MN_+KV2Y_-HKVKIa!SY8 z8xzkkBQ@qvzRX%EjA2@bmj0~vi!nOmqEchx(4q5zNkU=a$!un2hUwkEKsd~tmJRdE z(~z2g&>19~)IeBvjD7(XUKA=mA3KrHwh-IIaS&`M!vPLBy;r zAEo9gK~7aS=?t;(2yr;Y5n{pNqbNuS``i>jJ&CHBV?#v?v%3o+u~)F=^m2$jCLUo! zOPum#ke{+O4TFRMGsEn4vcU;kY`}x`@*^%39>nRNUO1!N!s+Ydy#{^U@dCfM=|!g- z6bkoTFRcA5%aZy+y?^h-wulQWWP<#;x05?F;aQ8$$e9Z4tp5kC?D-^HipDbf=K==4 zYR*r&XW4CZc9 zpi&quDN9*7*)O9aO^HzB^|0ekD>C7nIgTK5q>pIEhoz3V61hz*Y4NCb3jz*%JRK|1iU!NSO(t9-#U#GBMW@VDKt zpN==q^{E46&0%n6vdmQ+l30Nvl@3{+jf>BoIZG~%PHZmmDUJ;@a;ntx%B7WvQzc%E z_WQE9rBgUPd@sOlz~CNJQCvRDd`KB=0KBCRdN_$cmt3pbjbNup_c@U>{5U_+>1isBGnq(LI9q)9 zFBp&P`ZIYWxriyavI);zPR)}BB%_Lu*$Xm#6%(x2HlFQ#yam>4 z4sbS6^}~Q5&FhUmsqn**kLC9?#2?FJsxcx)MoJg3UjyS%?)aV{amcoq_MV2V0_rlJ zgwiGrV_$R61kxfMtpP(7RZZ}myWy!H)O(3b5ee)#9fd*Ok|d{*;wIew!J?G7fRL`V z*L(gs#yOFeY9hY3v{PG$!5&ESGBBb&h(W3*5TR%vdvRcx_4_@qXGwlbV7%u5Tf<2Lar1)Oa8ZpH!(*nc)6>*np) zZaQeAz1-_}JiBt|wo{427wu!=C<1*bK!;}8RoZlLRAeyM%-Ic&icQUAPGLT1{o`u2 zc6KJ>!qVb#0jbD*XU^Lz) zuszWxusPNyFjr;D5Hmgfnem=r2-qwdSmS#)VI-vV)Fq((=M*FT5&eBne?P>4KUR<2 z3({Mkal0Icy`>Cq0{V++X34N;Ryu)P3qyre)rT-VD<5~;7DizY4RwpL)ID>l_w>S6 zvo|y{XcJa*dMLYyrit5{n3|Qgc3URK=V7KGw|1wRl8(cIAoNVjKJYy)Jr~H%=`4-SjM;EhwDyY<4MfSC z6t^cEA5TC^FGW03C@Kc4fLwI4v}I68Ge$Qcd;!&&j=s!}r!TEB^krB`J9^`3N8u;c zwcJ-VHr)xdqg!CQQc+eZz!+dG8{f+XYsg)7q^m9CoOfnj$f&3eBBMIOk{=2@Vk$?V z()kQ;E9GdPCn*;<5;h}bTyY$knQFq$hIn2yK1(gp{tQIm3E^Ffw*s_ExPB9NdChnh z+;dG~$Qwa96E;37j^T7K9o2*97GTKS^S6?`kJ7@N)uh04X<^O^^gi#qk!R)IO^ThH z7WYTQ1ubwCshocm`4>xh7BW&fHk&Cx*K=w7Qu4k^A!Ioy2;l)j_b~6WEFJhG9SI(h z&no(8VrO9ng{%52FmDfIyexS+-DTl%x66I+>jexkuH$f*o;vO-kE=@JJbJ(AQq*D= znSKQ?)wOb!Q3okTZ-JL?H_iEXi-H(m=I&o#DwjhtnCp|!j+k(zgrOt`<~=~xaaVnN zIb=_55fIH$@gY0P@r7tYB%pu4G{s8a-J4BZx`_x(ws6FBB0S!X)U82)t|t;PUb=bDl5NT;OH#idu9! zN*;yIyL6?bSz2+8Y)hizeIxBaxFJ|v!6D7pxCHne+xp_}v2U*!H={A8m@4Erb!?iw z@NMZ1_Yys7_GN!awW4mannZe0=In;dEAL!qJ$~tXsLH!CjXsY0{yA$L@R3*J z(2#D#L_GNwH7#4N9FLWQ#Vu(25?a;W+%mgK%an%V(!u^+nEkuZ{Tt(*Y{R{jlV$|@ z76Jj@GhRvDOSa`oV3nV=74AXv-6E_nv9T|0Exl72#r@)S_rW?$*@{zt=c0E(>2`SK z025XuHav30X$^GZD#dZU15lw`@0ciw-Ad1p5lm1`(mM)%GA!65FlxjWQ3VM#WNeAu z%wmt6I=t&QC)q6UcQvDn&&{o7JdQoKAI31=t9tf1R>^&gDd!DxQ2;kMqzzv>(1Nb0 zwEgOV#(6WfE*i$+Sv6;U^&>D90no0!5f|t>@NW0a4~MaRh_RA@^2m3>U}IsKW3LyJ zG4^`!xeb%SE4*<3e6P1`zfo%tJhJ9_k(#5htz!5r@Y2WlcI>y%0g_j{-ZD zgG)aO+#{G<^z>X*nbh#$sewbjbXCCNC+;^ze~i5x_SP*|a1N3$y;M8fJEda69pzDh zuaR|Nc+oA4{mxlj;Z8grG1xAi;C{ExH@OuG`E)7P`HB$(3$T0xw?EiIbBj=jC3+pM z7%pa4xThElx3=rrmuy8+ku4QP$+q=we0iX&*~;jSH0IJ8o0zQC53d)G|r+_}nPGcbKsMW#>SzSgg- zrgbZgyo=x+t?AjO;3&&niLsg1Qk+N+EJmVJpm~t_a$lUhJ|mM?eQ*lY6f$q_i&Lz# zkok)~It4llnLqc%$tk{G3r&z}0^gi$Fv(oZmz4M0B`N*TbUIS&l+J@(044K0IXBI) zsS4CgYgKkraV&w4$hmL+P~^V(VWIox3sZ|T@Qbv1av<`boJ^dXrs=Xczhs4D-Ro-BZ++$-|g$XD(u$yXP-gwN0E$bayr(0gQr4oT4yG7XF@=eJ-f0yi0lgXsq8`ufa% zBK$BCTA4BIKlsuMpqGlh#)GO=j5kAfY?3Vr))6zDTcXcCRJ^Fe(x2W~Ok2o)XXOt*jeS;T-haY4Ctt zWa4GD)o=)7{}Jem-b%OjmY;2`E$^<^;qAqGV`qD-v9Y(#qG(Wu?W~8cq$Y}!xsU&ibHhO)Vj3tj#E?aiVLoHkmF!s;fV(U`{RcBolVjzjS7#{!9#Ui z;?@?yQ1lGPhtg3W@Dd82ZOBP+pf2%H#YjID>8F|WHIcrSNq;KRpJvjZi}dH1o*-a| z>`;^`Ut##LYnT-zb+~@w{tY29f#&H<9kLYE}&drL(XEQ)O66udPP{Mng z=Y9xmEAY;by#ZHlTc+yIN0p$oslby4FFK95ZXoKmVQ)R`07#es>?E{;=UnjO0cKx< zh)hwoL^voY9l&0Af}{?2M99|c_|4NUJ%Rwxg_VQ+0KNjV2`oaJ$^)v!t`}VfO#&<) zTq@6T-pK35r3UT!w)I(b8KE&+#NHS0Bvm z36uR1GvlUbM%Tf=SIn=ecI5=-N{A0>AdM-`6)--g3MoX$m5^W3J;RBEZKu);+qSj8 zW#IKe>6gF#`(OU-$DjY+pZ?-!Kl|c`|NQgc`%&FuN|{0u)uiHI{HwqD;`e|2`A`1E z&;RzXzW8^4_Qjw4F0b&v{OAAm=Rf=VFaPCV{_>}P^ot+=xlH`w=fC}zzx*eE`Nbdo z-IssynL#-S7t|M|cD#eet&$+S^ zHS@*9ms|-GH}j>8uep*6NAtzxC&lxY6fx@$^Tg~J(Z*suMIT66QII#Rle7?YOt(6WVi6lXy(925(v}6xE0AN3z&+DE=<^^z*UB&#-hq3$wttR)}q%h_5yI)j#XU zm*q6BC#+pCDkot`&IH)8hxQeRhuoH8%P6|yBPyglu9spr2*}O4k3S&>X{HU>>Pnon>hK&x3G#!ku4@GEhz!mXk1p()q11591eF+Vs2vBl zU>RhWQ*l_hG6>T&__B1VDNq5IH)kVz+ zoC_#`5Sf@glo33m0b_Xw7!a#Vy408s;s&nMGx`6c7B7MQ_Ri(GW zCLPv@Z&8!(Er^dXtk%w@_%!g(yZ%Uh18H<%ElSZ`Bp&0E1rSFBqO@>8_F@h1KAbyj zRlhm7q2Zyi?UXO{GdpzeR6nWt3v34;4%xOlf9n$!$$9k$hfCJyKm23(zg1uT!Oqgr z?A0cQB0cGaFkGvj9XiS9Km4b^{NI1{`S1Sz5#-p;<=BB7fAa@l{NaD392c;fo`vYc z451R7n)RVmUfse8?{kdkx^zDXXckN_CoT~p?}C2|{x(FWxjCDT##P2H+GX*;3>jbO zDcV#@uMhhsS_Mtw0H4pzWzXFSs9LpJa0+`8n+;EFQATp0wh9|zebtb|S|Ytcfm!Tx zSZ8YbH{6F9s{EA?^ZGP^Ih|F9f3KjL$G*M8u7r}mzAsMe4w$;KC7pl25C)obl?8Q2 zJwA^6&xCHN>oN5_fai<-`2#d>fgkr-CYYJUH)XX+TAZ5kMQO2e%|SiiD&vNe7SadU ziI7xU(HU_l(vq=bsJbLXz?xs|*LdT#19yMhx2@6_Kl+O=|Kxw+Qelajz|bY^uwF}e zXp}>C74^G8fH(f~fpKNsy_6xzgX)rvqX?^scRYfQLkgObL7G&C#(|1Dz3o0om!J)N z8!0OvnkcsS)2slhejy*<`7x^^&&h$K24dNLFtE4r?&!00+`NJc0W&OigkxKtz1q%E zu;d68p8IF^xzG5y^(@pZ#{aBkNyiA}*u>(2PuM+%DuOeNh;hK?9VVe@8fek-*rso9 z^9VK$tLa>4;)~#`Qs2($+X%nH2*f@v1qX9L@6FN3Ohq5V6CA>u1#%O>RI37W=U~D4 zdk&O!>goI^UaHGLv5&(GiFFq}w4b}!U*K4^Kq%c8jI#B`AOFu^{NbN+9Ol@2;7H`6 zfsu%X;X-7$+SFbK$H{k0-O~8oD!@S#N|LVh+JGmn;kCH=I=^sf)va6o3%sfiUAV=& zaO=ROb-`Bz-vvt&c)2WSiDaA4;8%vN_084UBsAK1TN#wxDsB_9oN$QE%?8c(P_h8I`mjhaH#QUpO z-P)|pm*y+;x4m1H+a>%DYpj;V+ur?VwN$xPyH&2#?%b=~tCcIa?%XZaD%JbQ_6H9*h1%gfG#PbN_fww_rM^vbTPZ*x$W3Kh6S*19UcPD)Au)8 z@*L#6ys)s)I=MT4*E)bwvxb1g70C&^%%+h%MyJj2wUPVU3)>;bl!UFKfENv#u*Z$0 z_>{1V;xg!249aZezS6N;!VYsEgc=0CZRft>_6{F*3O|&+b1$aeZE6U2vjQH^B;i^~ z%Wiyl@)Fx)F0ln%Vk5!LbKeFOFD*v<_@TQm$jQ%t`qR&U@6SH}^Z)zvfA-r*Lh-iu zXIzs>$7vRSP9*vH#gG5)7eD^9&;Ry^nIckiR3>#MFPc_ysw%$t```Nf_x|nA|Mssw z|NDQU%4>xtFRR~mIj#`?;XnQ2|NQ5woK|V_a{4_v08UVdrIIo3Rk*TAYdw{{zBJd_ za35UkZ;)*@@>VJ9{tKzHB7j0--|&?F`r7*PU;gD6Km1P?#}~oRHg5kBVHblc;d#UL^u9gF*{~EFjY`A4-hC`Ck~U?#1FprR4)90N&ZFa7)Vu zdS4UG+VZoi6h>x^@bdBS*kbCls2|6TcztdaUePK^TMHDDuMblk{fwUPxw)`%;)AsK zQY6?)5A3yCr1E`+mL#w?V2d-rm8xHi(ml37k*=7XPii3!2OgT4;>g(-h}#t3Sd0i@ zg-R2{fOfbZbv`wixFCv>S!STLfFfWT_-DWYd|by1%Z%we3PBfa!!Z`hv%t0nfLcJ3 zh|@wb_tXb*=qd8#mp)t02u8>kLiHAGK$?NQR6JV<#KvOzEyF5cWMt_El;L)DKl@9II5{PHU2ry9 zs&8s)OI94|#yH`nD`46b!pmc{RwS4=iApdS4)J8l9NxKT@{^zc{hxubn_;B?GZNu=3^O&#bS7Ymox5{XC0zTI;qdy_r&oSSQB$&l7X zR5MhKOIQl)ggKREqK1{$7UWRxtcBu13S|o6Gq{H$$Z8WI>qyA(to3gsLz#h0XjDHE zF2V2=!8AV+rh$U#um9kSKm3a?|LX5P|KZ`i}ots7$$A(Tk49;aW(o&;uQ( z_mpI>m2t9H>$rNSdiU-?-iKCYAZ?jJWj|M)g` za?!s)kEd4;{gM}zR-h>UuCXWdzRoV`cay!P-xnZO;O`*|DWAuB^n1)^DZR-)(0hxW zhWP8V2K}C}fYLD}4E}bRNAG9s1(l0fOz*>F@z=liZTxEi&-5A0l^;i+RSqSeRbG8{ zvDaT;eN}lEef>77)#{yFw@UbX_s(tltoT6Jd;jQr-`n(>rR_%Pwe&cK zY2W+r;J+p7@Bi0yRW9%Dz7UUC;*a3J|2~uD*L3>-&X;r=`fe5H2pZuQRnJJtKYe*13e*~ZH9i`B;)d+U`8 zbb?VSTCVQ$^Nr=xH!s7+r@fWsSEs*z>wc*Lg`T~cRLEH4-_GXt-M<_End|@C+5K-= z|26nj&F%jS)w|#A|G$cV+^_%DkeE`Bt>JI%B`1UM%&L5rV$sV_%Fo_hT!ssP7S^ID zct+f=$NI#rdh8H?AN$Pf;6vcC_xKxntcAawnC;;2s?RR)x972l*l3@{`1^)=m&68p z%=kH zF+QAnjG8(0*eWI4J@VNx{(^u$qGXQ| zBn|e6ivt^+tAECYf#=kDHcMGN_JWHJdt4HDjX$%@tK#p4$KFzJ*((B=HSp&h^KMaQ z0Otulyad1(@ZkeI&`{5M>?Qs#!(7}W(FMLd#Rrg>Ht~1c6T-%dns0L8RX*9>SbKAJ z)-var5M*79=QAit0~)g?jb|(?FqO6CmGI=@AZl&r>e&?KmpwjeJ07p+u|SiDV*XBJ zc0xlF^Vv9#`7Er)>>Wv69y`U~S0HZT?^BNvKJIxez~4t6BdO}Wz|VIA9p9)9bJ4lH z^8DoK{Kf0C2`RPwTJ-pdfcUw`s0X8%L;6-g?nko5mf!aVF*!B%!rrY{UVFo=mE&W? zOM4NoEmEcF#Ba5;_V)H^d~TK_YziX2zQQOyVJq!NPZplG-Zb~72*8qY^}%XfNaMp_}QY)1c86ds=Ray1UJ8QU)phZpyDa~X#bU!DC-fx+^_ z4|_Y_lU#vj#24#Xb`&l;y{+!<>)a@uM11nv+0nRtdTYMF{=q1qzD2E9y|>SAUFKRp zkN9%ERF$Ep$dKO&E7!IfcXywhq!5XS)Tw~sFayKx;q8T&C+%{so#X8Iy^qSzs;@@5 zjH{W9FL%82N9Pak=Q55m8Q%@wy}kQ7z04_NN5Hz91)==%(Vn+^kshy(7i|RPwHHBu z^X{9>w{Fz9JUb41<+X78?l9VVoBq6tUrvvo@0`EuroRmQs2>JV_uSt;h9zz)O}_UVjM6Z}y_}XIM@z{1aekqTR>0yDwf8&Gc${Z`I$6o)>=W zzAV2xAA0mnINI@D`_|Gs7tUYzgg+ht;t?+>+&>#GMPJJ@n``yh&s3oEK}(*WxY8{1 zYXA74{@AzS|5xbk&pt=5>Jx)+jgPNr#JqUK=jb*0k{-c`yc&4@x^+D8z0)$jXT0n0 z@c>fC(#wqD6p-8=aWCocIU8fKW`tK%na0JI+D=7HAjkPF?U^?hc*UG=bBd|pnIA=X z${_JdjC@@@trL`gBZsrWjnpmU-x+rm(Dq-)$#m1KI8sVGtT zj=dn@F7OM(!e@sz9o7?XAT%aWH{q_Btn`7dnXlY;umuB{D|hNkPD?ZN2koEG54tVjAdqOFwg$&Fck2#1F9w0|c&Ua*CcA3KxpVX@qFW zld)p@2sN(}-JcxzMq+ni5UE`BMuhi7z^ga+Emjn-XFH+j7U@`;>moi3?=hM8eZ^X! zo|iPe`x_Bn_-N4I3;cV)K18nP+14^{(iYP=AcqRo!Z4f*^<$VacH%~`<+7_RGjKd^ zm#@ty%0>Rm$UdR-=-J4nN^R{%}F67;c z+*Rm{Tx*P9v0;{6OU&YH$6?!%@Z3$7)~o1;#os=$w<3q(cu#PN&CeAR%H|XE1Esy- zk_i$lUybD_lPGuzcP*kk9!H&UL`!6HYdN>#oCe=e3YUp&oZ#Is5^znMprEU^$nC^8 zcvcpOQtVoJ8%@3VX)Y2Y!<+I6_olopyeY4s2PABLEpd;9TV>tHEvC6CpcFlrQ%~S0 z?7w^L^1%HN*;@|VbvMZgA{)tvNBDp~CRfSHBGifIVM3W@OHWD9+(*Lc-LwCfy*K@C zGrx1*dFRdh{N85@!|*h$0tA4ID9|kqSBVlSQ6wdbVmNXGXb=#=ZV27rA`<7w zN$kjuZP|$vZ%P~|apFnhB^k$y?8N81KV?Rws89J9-o3STb)y>qWjXP0#&jZdcWt+B z-K}m_-AdApB}rz+=)knoNt<3T!h{2r3ms)zkO%+Bc?@x9W3+^^c!Rh~Sf;TdnJrDc zpvXiU(>IbHeRy$)#+0ykV=Us(2GnVo`jWD7m6Ne2RJ+E!;D2M;jU*$NVaW+u#u~tz zOBhxU*WRhGURg+jIxkBd1A|FLgn6{}<90sNu z(~C*@C9Hp96ASNUF^;`K0+I|__PE8QQvwF&I@CT5=#v~7Kce%)Qqvw5rn4Aluc+jb zFwi(ZejprEKtYO7{UlVcFtpdeq}RY?R=WW8tJ!1=7n&NlAnTup`m1=zlM4{9@~RSn z8-`c25Pp$M)@?OIZ+XzKLhVBPkd}K7x}_WzifSce341T*j|jfxh9n-*t>=TGqW#m>2bP zeD~p;sjnK^MP~C{#IT;Cuo5@)BGbY^_V8^%i3WNx)QUfHq$rauTbLS0O!-lozekSP zrrH7$DSR%AZ)|2b;g5$)mU+ZO7I%~^ntFNI)nYm7MT&{DKjKwv8@fznK*NhZE6BTi zfre-@u`b`iI<*8^5l_%;D{+(@gipd#W(=lh6YqOyoNvis#ze# zcs9IY9%gZQ6>5y{?z-A!XmQpqxn$~B4UHu*WAh0xHmX(AsG*k0I}VG4HG-mY8s~aA zeZqt3c)J(-uz?r5ZkSwV%LyQgZTK3L+YKn)x?C*9_81D$dP(e<9Y&b5^wi2FGY}JN z)6iUTip*Jr8q;G94BLsp;MlIlAeflvTE&`0% zjbJ>A8MJzp3NQ|SNDW|UusU-3$y~6Gkv4U>rsKA*Vb)>xQR_OS%Lar3-7~l$FH+2j zkQhb%WYHHAw_JtqOD#KaSxu!Mz4#wzI9MP$ zFF5k#*A@PlX%N$DRkdqHS8cQ`>9itPi;^&AE>vE=ZqS^)y0c})NSf!l!8zy|S( zSrms{rT;FLN)DnQYnxZrD=NcnFbOJtpt3cd+9y*{u`6_VwI!N!Ezmp`L`K1^x;`{- zdZoQIj^jN>lA5O%QcXv@SRuS#TzZ5f(2;B##7pUn#>hR_Xn$ zS2Ihh!nD{fK+0(^g?&p@jGT#MJQ*w_TAT9{P?*=(@!Ddt=zVoWc8Suj>){!4Mq$BmF*Tny-nZh5q+z+=vzJ#&h^NYVr0r{%f4;2Ox?CsX2Go- ztFHU-C{ z3Z83I@O)Ik(KZDqymY+BnOlL=-dA?TGuQ{~c7Y0@nUFMAYQ#$^!E8_5IXG)eoq5{J zkuxf|dsPvWlU}|#n>tU$5tn)Iz!UR_io^FgeF|?bTE{yGaIWvf;-m=Q*aY}IJfRd(FGX{;bK7l_II=#H^l71e4w)IU@+YyI!v+Aj3eo7a^hKO|L&z&l;i5F(@eQBUK z73*W5;Wh>8*v$dH3e3e@FQdbjjOzoO`o~}lv2ve4zTa5}0UO%pk-|`BfmcfL@!kiyTvG-YO zkYr;s=g%#io?e26>4#}d%l4;IY&B=0R5Kl%o@EEwVj4~7mq7@ReAz0yWur2WqczI- zBa42ma*zUc8k_>)K~exbOA6R&s1$%9#T)ci9_HviBO*Qbj#OI=)2V1u7&f zG(s6h;D=Gu>UcF4oGL!R3U93@K(sIbrw ze0wctWn#Ao)jzT*QA3SWO&ReN_tze{R_UN_6zR}%i(4yB=0lCiNBnpsvU1~*z zWJWJ2iG@%Stp`YShO!JUQ6T*dTTlV@2x+l}7>RYo;&_VT!8S4 z1Rj@#MTg~`qV9GYX%iT>y1J ziod>lg&+L#`v>p6g?#vvFSJtJSKf47T!zGJ$Llo_tS9hM;m_N*55DljtnQ4H;f1^} zW~EZDI%O7`T2}MIkM{1~_EjTws}L*Rdk6wM6hN)SNxDDPz&lPKb`bY|{N(*-o;%zi z(jxai|LOf-Jn1V!H8k2Myqx#`b;Q}?!{hAJ|Jb|z!QpU5i-h1z7&8jme4oCKm^=IM zO8t+Y-GAe~Lt%~;3BjB&{S?gkK7AcAcS*qiZ{pGa>zx)_@q0vfdG70bpitl#~Ozy-7R!(eEE+DxYwRUe{u!)yxwHCwaS%Z{1EeFV!F zac9pB{HGiL8OMJr@DD}~x~P`LO)3^KhZd5`WY!;%+u`A%M-09xNM70S;LgqcKYo{K z#*x{ny&pfnclYHi%(>zuG9L>(hnd(~9RKa7T1zr4`ivpL?BB= z17fQZB~GQEFC1N$5nX2vExD*3{hDA)1twBjDQG}+EvV>H?$GuSWN`$!jz(`iJ2FzM z8QaOSLx1rWNp^b=hT@-KxHX+i zeWw94_;AMnHAv7WHe@arFhm;u=;)us2R7|@=$v0vYm%xdyTC`zilTEmJ(eS9#mRbT zhFh;3y!DQ}FhO8aVv6F>g^?zv=WlRyIa`MU1#MP8ISUhSnAwXf!YCr0IGYk5NlU7+ z==}kkU^&5zng+AgRM$leHwgJM%wPu9Wf92Wc`)Q9&4oB#bE>>Hh{$2Fj8R#a6 zdn6#PA~|EQlp&K+>#(@fU1#rPS`067#D#FW;<3h$)M>5bbxBF)x7K4ZGRe!O$YxSH z&vDyic0I{gY=NlWf143F3sBAs$o)V5A|`IR2#%k~z^q*5&WkCxSws+r4v7XV%X=a3 zEudYFd)4pX`Q83A82ij;L<>weu$oD6y$XMLTu&(#9K8SW!C!y8_te|_cb?h*$*udZ ze}C_(r}l1s{@@2Mp*1XP^6o3SU}zjFjIAHN7%(X}*Rdsm`|rJV|HY@lFlPHS%GH7L5?|0z<;+y@dZ7Wgf`}{lrBWeEzL{b2%`d|!G|Di!0irBQ>MAQ% zmu?L+hq@=)rV6-z(8%tDE;cmfBbINZo5`N<)_dZZQ7!XrcE{9Zuw&fGrW~Vpo)ApZ z)8m(y91*xNSm{O52NmZhfps&=u6gP|4xW5Dpn}YJV^c*Bz0S;~Zax)()EE=E15{aj zl}EZw*fNiA?M7XiQ!rb0#3D_)0SwG)#%7)7W`$`J(z5D#2x%NQ+qQ39){pI5&Fzax zdPw`~{5_<7wNc+<*M?aEswz9vrctC1K7eF{2#~E(v`|&MyNt>fP@GFxhlkmo=HaqJ z8uX$*dV6)bKwY_tJKpaor=4Km@KJHpOFy&M3o9uehlkbWyQFyJz^E9p)Z+pQa+v>} zK4WE^>)1ABXYEgs_B_rnNy(O6ohbi+lUiz6C>`g}s5a30*$~M0jt* zTuKF36cHx%!|-s(8Y8=tTloS_wpezwnA+p7(1AwQH|})IH`OmlPG>23Z!TLacqT`Z z4lc!z`4AoFvw9ib`(zNk(^opb{b}+u@jJHtx%_E9f(b~wFxv5D@sW|oRK7g&GtgCo zUbt2m@)X6e#u#hN6;y!*YdCZ&C>Sa!jTQs9KN;p^iFVT2Vf~#>anR<>&OnRxB%#dwk%2I zMk`X`S=&&_*Tmh`f<7*GN*}^XVI<-+e3V4>dw^DC9+g~#g`_0&Pdmj1 zR}W7&*E*mZ(YPd??pCDZ`GBhPTzJEzR536y4`kJm7ObOoXx+at4~f=no9fUEltV(DNJbFD*YYSdPw`=mT^rst69x#EFPlD*Hq(hnGaYTF zok&ymAci>e+RP%n`2&|&Ga#`*YB_q{FK6me63YpbSOryLHPzHsV$Gn$Iwuw_=!p4m zPcG;V^2JbKPn$9w$;xS=Np?K-Q)nD|UNc04tg+p1%!tiOn>wQIP7GnST4~^9rPUQt zbBA`4TDeqWS*ckgD(_GivdS9@RkA%6ei{z?>(xAp|M$G>jj~`-!Jd+BEIf%$<*|vw z-caeBbOeXsgTFh0hjiOd=I2d60k;-wwQ;s2PE;Mgo+1ON_t;#ag?40c`nz@o~ugP~WqscWyN<2-PAcTQ!vO5_5 zVpNK%{bjtuPyVpcrbQ{G+#-*$?f z#?WZkkqB$qSwYSg_LfPPd$uI5(@nUx8k|9A9L@A|r2--|qyj?F@>X&!A*6yvm$%)) z>k967I2{lyN4AoTo_2THBcl^+L0P^3c<-rOdvE=I@9Eb@Xy&~iJvSn2x(_{*&FE3NDFiJHg#9~r_g;JF-rc)Aqlc7hp-7p<$pP#8N4MxcUV!qo z{a4@TJ2QXw_Q4N+Cr?(gEAH;U{(ZFw;9>7K-`)TDo&E2=v;Xo(d$&H=fA`lc>EQhj z_g{U1Ay7ib0rgO=j)_|F-tCtjfNc{tCe7I-K3k4JAg{pvH6QM8;4w#e0djVyWP=zy zk|Ok`XMFJ8+l(LNWz6^@Z&;>BE%;X7?2dXl9cJTK=+P41Er0kbPJ3f{kb_G?*v$on z9UH<4-`%q02JZ^;{RF=uuhB*r?Y;l`!L8rz|KfI-JX%znT?(b5iKJs}@FTX`4YBh> zwW0hCJOXL5F9;ZtmZC#)JK>Pw#zlcmLJD-v9MovF+Qo&acssS347E@R{y{Eu$@l4}}jc zAOEQtDpdHOjO!}eN5imwF65?RDfamcf7)R82*>!!Bd!1v+#5pol-2c*dQ;dLd9P z)}|ifh77DGQQ(|-YPW^&FE8Ey{cG&jPSsKjhbgRnL6M^>L0;knc^R!ia?0<-#9fY) z<%&5+-G;&^g;7{>Ky`;tQR<$T7Y`r0xJ%28hfFJh+J+QV5!NU@QCk>s#$nhm&Q0S4 z2S6j&Ib>b|Kqf13Eo}ARyr1+y7d4M#oDtiG8Tpi59DU2}fd4SW<)4+j!1EJ(GaS!BA08zWQya>^2DOeL$A_ADaG@u2)fJz?{=^kG&_@0mG*`S7zn0{@^38Jh=aP z-T)P3JEX4TP3?NlA+x&WkkfkJAtU;xL)J{a?2xjlR~&NH)W;lh#?&VqGGXck+&HAy z9CE?b7aUS`^@|2sa`lo!u9$k&A(vgf4*6Yu-61tozhRImQ|~rN&eT^OGHvQbXxP-p z4U%{DZK%i9HymYFO5C$TX%xkU@xKN#;ak;|dL7sMqe4d17S3Bd9 zofx~WA+cs|$Fe!T`8H--7wM>FS?XC#G?z1Jwuc>lbsHlqvhlX#gvCvzA&f{cYLIK> z9Q6u!4wkE(7I&!wEhM}J@+V%jUpLShkdD&Oog- zNEed%&Aa<|{vdWnuh2WpiL1?$oprllRH5Nl+(bSocA8vh(kL zH4&E8Av+sE)#Hs33+0t%p)NH1!O>An2pAwG+GEz+z3tae?7Hg;+m3BpV0Ra|P$P7#mv^vtMXzeHu6v(6d+&4Ft1i(kHsW&R0|PgY zK(Rq9Al=l^$0h=Be!9tt-+)O{G#b7U;9k5Wa|AOhKqSE?bD3B}Lp^X3F9Ohx!C%<(8^I zZG;^}i3vr}jCFx%bD672rX^i4FXGXtu2%5Kb~{WDa+n7hruRSo4Capvu4evpwW}EB z+pg5tEhl?X1h2O1wLGW^ezaK%0h3PQ;n%i3Vf1r}x^5sMS( zRaREvMKlT2bAi<((}#ycN(Vxj1gf~gs_0I-*jx6kY~P-6Pc!CC^&C zxje=_T0YJV3i#ERf$r{q{?miMzC`J5#mBt7z8EFGHIpc=uf6wPXY!c3yC5U;j$za1 zLl6=IN>f)n{i1d%st=qQ;g|M-ub27y5bO&BQ3$F`gQQcLKEL*m*;U>mJ{cDx$JqIC zF2Q&l7$#PtC75Y>63n$wf*t3&8h+bKFkd5`$S!W^WT@;i9)avS&TE8UMRqZ`+R3gt zGa$S!HwiC>(yvHvg|mCAV|Hh|6&@j{r@sWJ8?88{`R z*aw2oS?r>Ad~PUw?l3;*)7s<5JK%G*kxuxW%aC5m=gy}%~0X&5FiUr+j$MTlK z@-}7pL2j+UZ(El4HPQ*oa~a=9S>AdCEI-KYL-_SiO8~BREPpo0@Bxh20B`-MP+$U+K7} z1k~P1tu{%WS=Hv;PCX|J?7#W;{X0M0d-3}`0tpRvhnr`<`2B;MZ?X-EtWsYjV9)an z^cn4d5sHP8B>7B?@OglVFWCOY#}Z-jqTrl@&2#x1{ON`4pJK6 zXa8q!@85m#-rX1e^X47)oi+ZSH-E-gZ z@aac;FFv>b?%R8}K43>N?tKOV{jLAH`Q-kyzubHNC->idX7AZI55D)8d!M`uwKX|I z*$v?UcY1)kL;W(p7?k<*L76Y;rz3ISe^_xZ17HvP76fVvU)I?(^9xli>KF2Y7u95( zBJ>LpFDc+q%3CD%ud$=lFQpU+^*^sei?w96WLv>nik8naO|!^_3a70r8flTiKuaP3 zD+&kzJ-xvx(CDhaQF_PI!?a234z{@x_o+bTaF2lEnpA?zHF>U1sK^OZDroAJuZ11) z8;<4Za#(D&X5zKetrbgELtsTi@uL7fj;ths4H(HTjddxsG3FwUVU*Ojfkk3aMV!{* zDV*L6nga+iwEzO0NsA>0yGD(Kjx-^k8Nx5sg!^}Xw|DDlVDo@71nE7{7wNy0W6rLd z;;U(^uC^>L0ist4p*CmlXaf!N1!H=PEq}xqpokzf*WQcog9wCN_kZ&wKEz3{3WyQ` zJ^;)%GTo{P)p>d10XMAQb`pHNMoCwSe0n)oq^UF!ZYn9H@09ArV#QP!gW4|s4DyN9 zAa~TO0yjigbz*8Ay#F@SCCxV1OjI9Q;Xs;PEju4vdF!C-d`1Sp$|_vA>I<>X9)U@w zZvWtmM2a44VVGT26hMv|W+(VO>_i9iFkd5`%)_|7&`;-K_9K{wo#6AZ6TW#^7}a?; zhcqlW_;f9a?U)W48C1ftY=8Uw^}XA_0}Ej9_DctU{+K2G?ejO7@C)R@-R3WUBy5Q9 z;&%04dV=Sxqpq(x5~*4qF!kb1&72(Q8yHd>Q*UgoV6c_ z(M1Uh?Lqjf^~OA{U)9 z_b7vy-nux)qeOC$Y5dd^T#UkRWD^c@w&B{i?<=+&Y+-_d%BPp`+a6RAz1?5Yv;yiF zyE{@s%Bajw3^PEViCD9B#=^k|M!v7JksqOKrN&mEc9_4eRKp{WS#{J-V-F_L=u%=V z#5u0zra`sD9P2ZSu5i&|G3z)vaj(XSfiQAXhvZlglZ$GtJcgY>Btw=TlV<^wX#6r` zGaiELOA)wcq3i5sy7Pfs*%`K~e#|07tnKlQqQR>kA!DQX?j#*O+D$D8(;vD*M( z)-AV~`M0!P^H)z@k7MOp-e0yC!Z*r*n+5Bia=#>Qo%OcP>@Do z+@&won;!Taspr9pW!}@k)Tgy8l>|vkowzW&cZbbSd`&kxovgHQ)`~u!In0E^Mq^#x z)XHqgoNINN1u9uv*y1!ta2&a{eS%xt%$wVKiqS%fD7*JM>5CcB7kZSpWnc1k*>ZxN zNvF60iw~?jBJy#|9x3KPtLYQ>lD+r}ok7LyHAWsHAAHKAKCFn(d$)fIay~HE8f8@4 z$#g5+sU3nYmho^CF^({?#S9+)L9;F28E?MQ{q|4yp8ey&Z@<5H>$MistqY2ga)s(( zuogvsI@r^4@Pps&J^PNYvG&uhatnX2xA(lwL@S-j2srs}7B=l;P&8v-?%}bOJv_Eg zR)Ywutx*&(#=j5%Zp zv3$TXH|K$Z?C32^I{58xVD@+a_3tw#WjXPD?X8bs?zZ=rAM<5|&)z!t=s)g1^C_^{ z{?FdJ_t{7LuYB~k&!1ye-TVA?-q7ur_Fw+%{Wm_@yZdsWPMity^#=u)hJs7Sk4wDY z&MFu+fy?!Rhs)k{F*TSIxa=3W>~B_s^s+c~;@^6)|K2CRWAj%4{J}4Ow)g9wGSD(9 zaS2-hI?E3cj7qh%9R`noYzG8917Yy&z4Q))CzFZ-2qb3&M0SAQ+;IF%_MezYjS6^< z3wWAsk%JdL-uvq-_wK&D|LU82Z~x`s{qN(_0SCelL&OqH==S7t(-v{pu7WVm^o7q+ zsUI|*8We!?oeW(EU%avR)Y~fgs%tRnI$@|(Q$kYkwLKZ6zs16yLXu^h3beG)Xzkv2G#LP6!;HP-R+#3RX$0ru4i1kYX(@G+8?R&|)os zzh;>hMDWY++`Ib%zggf%&+WhPTaaBcx&RkJR4#$xegC~zKoISJ7bMmD_da`@&HVT8 z+}gYQ8gKerWqeok~I z>ge2-h=(&Qj99Uzc1A7`dhTrn8qZS@@NiQoSxz)VUgkHV?7jE`a`Wx?St(xC&DZb0 z`8Lojdu6AH<;wWOdapULP#hJY7r5)ma+&8jM!mI>gd}VScwpONXb(ihBp>xaYsr7lqtt5}w6n0!u93P_VATnpuX+?EhyhbCB zBr1LFxGOZ8Purf*Twxs%`*cM|XvWt45G_99Ztz${Vfm`AuPZEPSZ~A&mw_S&1 z+im-#^v#3*-sex=`{ebh@h)wQhVY8F8xI!gE35~?H^O_sGDd(enr#L5;dxTn&uO_p z?+ZT!IuNAFgPT&_MVzP&##7q6^TFQFZni^=8mcJ_J<>K!aRjLaBGA+n*{Rb*!Ls1q z`%gdk(c5iFQ;E_R3cEBHRoEj46gQ*3`&ZVm59N<^ti=>0&8HwtSuk}FHn(~Ji+!P?1RF6dEw?>J%*$rIqqZXK+?PFTo6g9A z�$unLO!W8>E2kvwirWEb!t&4X!{aPI8jCk~2+$@y&_qZyQYbqtbYLI^ zT_i~eyFbHEGIjAdZc50ufKBa#iNr*U(R!=>d1W4OgnLCr%$c_PP&Sx7-YX8k{V| z=D#7fUPM{`3~vb*K$xn8kF0a(ive_XTVw@W!GHy#+%Kq_kR7`y%(O6VEUHg_&6kG zl))qoE~w}mvEqgrM{qYgDi%=|b+1^bOXN%CN}*=rjE9i~uvOhkgd{(B{l$YHNj zD@>){RLAI~MDa(H)=v+CB86It~bRgw>g$da!7E%?J z7A589l|kN8o-cOhsPvDwuh^@z)Z(B&J#(%_s3q5n(H{f9ve6h?bnuGTWt=K#8rRLyg zTddo5dMYyADy%6Km%6dbws6BF@!F?`-pJ#UV~U;p@pTCtp*U76BbqpgMKY!j$&jXCI5L z8*O@0Gef)LU9KF~AFhuxz0LiR1+i1>Wem7mt(p0nQxD8M7hs8nQ>gX^;aD|%bYes~>bQm_$hrx#0vCf_K zw{IVO5gvkcD!k9?{hMJO=K3zv-`ruPDE78yZ#voDh~7xRI;7~^@B2=sgmq{-szb_+ z)uCPDN+2^3)+MDd9YVYeGPVv4m#erLJJ`DiH=o)6;lBsK#T^o#JP%2@_l{lypr$^+ z!&jJ}t{uhbHH?XT#RR(;7nnh(ZY5k}HIb*sQ1IYLuz$%=#MHKgzZH7K z*V1!*E#1bw*$h$m7=BvF_MKH)@gD*bLxtxFGJ}2^<{*2OA?EPDqGITVZm7vqJ9)E+<6ZXi-7qwRxg~y)sd8(I+bU zIJNergsAXt=xiq~B*=#q80-r4LkkT5ZS`TJ&+U|6VW;>?v|v0HppF@j#p>%wY)Cl7 zL!|KFW=Zcrum@_%c0D<)2%&{K6d@9zc2a~-9ea-ld-9JLBodU?vV>1bdk2F(_}@UB zh#+1e;-rsrNnbw~Cn9g8IAO8iq&VTPOq|^CixW=2eFH+Acz5!)6DJbn!%yMyV*5jj z6VYCzIO*exuumvLUx}8}cMqVBuU%kq06P*V5)SbYB~JRdI_%2?d!UwV*ONnv6IQ50 zaUua~BTo9bt=!ie?8!e~oJdewijzJOxUnzTgRi|fxzeR!dqp`zv?zxyl(PBs zsPBYyrbidt$SwsI zlDsZ)-tSL`cZn}4g|(KKt%+y{kpf2S!sz4Cas5HBZpolmBH~rZwtc0wnwdAt8@Tir z*`U~>(8q(>1{}aLf|l?;1{-Xb?2Oqw9@Zqm8*id_4xI{0DdK7-fqMu`|Khr!c zqFflIsXD>~D#VXX&rLb-nlG^r?9TIq&Gx2d7n*f@m}lGm(w=QE{HN{d=A%3+r0-K` z;K#!#2mgVXPoBMh_qQFdU`Xu4L*$XGeol7%z0IR0%0-Ww52B_adsy8rcSG_W+YjFQ zNNJ^0#hzs30p{1qBV6m@fRzhJbe+Fr_>O_tIZ&M^*%gIhZM`nLtn6UguPj?hx zT26TSMTEru=3ad5r#zc1$l9!JSHi3|ZgTVskF3nBOnsUn0r0LJX9k)Q!RGwgrsy@y zXyay_xjeuvf`L?1gLtdEV&n~QMLUoV%h^1(W8Aj%g+!XA%XjOp2*8-lkerRf6=@P& z54ZNh*^sx{%#4g8y}HAE;6?Q53>_voZutShX=rpQ8k zD{trqF0KK@rntU-dFl*$&(>*qq{Sv{>mf;u*2zz(gk_yzp+oIjR`ga4`!>wOX`($z z3%0?OzwF+}f0BX5l%RDU^4NYkFJz{F2z`}Bj3Qs#e(9b4J8yOZ8w>P@;lRd6rlyx- z!#w;=EE|z+abn`E$Op|t=9@WxZsD}7@XjsPWjsX~4<(jPdYKlzWc`D_hBLR?OC^$E z1dALFhKz*a<3F*u9vuOI0TKi4lMoOCK|?2P5qx~ca7)RWUAGEaM9s%Ss(TD{VI120 zHZx3{S@U6-RqTQ66X6D^7*qoP_}El&T9vIz*)rR$@X^{H{NSbVp;hgP$!M2VKX)$n zGw0%@=UhaKA>Cq!8NHe{dW)GKy4J?gp|`9~h0JI;(#2lSK7OA3{#)-Iy!UG*aBB1+ zdnM%j6<08lFZ{*cv%fg_3*PAfjl~-AWVj#k&z?<;OaxJtOS}sI|GxuS{>l6P|K#m;4DL|@ zl~J=dObbolZ~fE%`qoQNX=fjd{8RZ6bn=_Os6UneXN|4X)MIw+(aM3g{GU$w`9G5y z=RNZp>!zMQJ}}UGB9rM$C)0z+`%d%^p6LJQ{^MQKQ==nu zW2dJsO(eGr*R3VP*i|37Fg3EabkUwaS{WT#T>Iy~6J7IAXnLttA%(~PXDi`<;GfF> zUyb%}$N!nWH9|i|2_W7>(nM5N0Znv8^xNv9!nk@XU6d~oiQcOB&L_P zw(VXZh}aM>P{0C?Nx*D?2g@Wb{!Wn^{;ZN5{@5m&#NV^z6#i7m6#i7nH2&1c1==QA zr8SW`{8=ZL@#hk`faS#t{K{aH$eGi4>l=d9|? z@ue+)TG^T3UeDQ;#JGL_c-=X7#h*NeDQme4vm4jS{*;>ORBg*CZg>X86Y@Yjk;4{1Nra`U;zXOmCeD79p0H5dqY506P@R@mG8e}3o z`(i})GvV2FSQD?OeUePP{Q|Fh;;MtXH*dH@?FJ*R*b# zTAZBMr@VcP7BTrgM$ObUOP@}PD^3Hu3aw<#u9!~XtC@7{byF+oCkr&4?heA+P73Q~ zE4I7q|LWAMX6-6Vi#OsVr>|>U5K;g=s4421QK5geA0alCx3kfrWe`r#bUj4z|X)KQU&_Nj}PPqH*< z-+S3CTOeKM=nWWIu%1c9cI0~+-q$y({H-^Y0$k1O21YWZ1DE68=p(5ix3EKKsI$4e#ldV z>RFzRF1y%Oh;JsVb*H55lndDn+-yNdYlH5E2i$BVMf`}K9`89>F|Ad%bTZY`V`|=K zvKi+T-?iBoDseDPKtUT^@Wo{h6VS14o%YG*wS3K~h-^#30o|tqfAEno^^#;AtH9W~ zjH_@~e8}wS(QL79E5O!S7=bvb%7?6G)L+e_LLud%c0^>g=(edNJ^&c10$(`-<2%(P z^w$C-M#EK?y|;e9_w;LfUpxg558nDTCdR&@HX>21)XDK2tdDJDnmvoh;$QjDt8Olw zC3i5i9lfkCyR&Jcl?DR=@*f1(JT&T0$@GKLw6N7VT#Hga8ylbtB`2Z==&%(WDpbrZW?lho}190#1l@2tHqY!FEKNd7$5IC zeLB0oo^_m9T#|lZh}x7YKo$#E>YVJE`Sa(XwHn~DT-*jmC+rHMrj#8YV~O_Ek3LQl z8I*4d=ZRGLrGdJynsn;iE{h9kMqE{tj4HHT-hce3y;~pe|MY`c+yijOQw=mtwgqe`~X=xn@IJmdJs zn|%MmXFtX(Geoy-lbK?GLULV0y3oKPi%V1$^oj{uv22nJebpqF^_ofY`ld;)=mnG9 z&__*jQQtPnRXt~tF&$>|llp>5*7OmRoYR5ZOpwXQuX#Sx3*XS#3x!B;_ zu;xr6pXZh5S><_*J(tokn6Z*&M=Oe;3&nEH47<2xVzLkZB7-iZGhWmMCB%3g&(6H@ z%LlK%*)@CC3rysXs1<&;V!Eg zzu&xhaPv9({Ylx#d9gPR0T7wu4Arv0g=5J-E|g$_~6eU z`#LXI<}Z2sFT}W<<>Qix!Q|gJE^2tg%4*ZiC!K^l^kTUpWO(J{op%ntc;n!$=RDwY zVMv>yplpg8h}j(|tPe9nfKrPMSSRH#y5#Rg*~-~lVdTYQdN4?p7^#MOlh5nl-GAj5 z4B~sAybC1Jg_9+DE2i4+4QLlW;Le_PZIo58pmAu4tDp4crZZI+%~Nk4o5v8n4Uc6m z@}cRCkx7%3HJ2Gc?AlW`1gR*X%!%1lri;&eFtD5Gghae3bxGk;Uc@70 zeYrdqF}=KupO=#*PAknW2t$kS4igPdum+XjlE?#POX%YjM8YxopvSc!Mo)bIws+Z0;8H19RX?o~7i z2%BgA{oY?*>xvyMYuBJ@VF=TmXTSb;vGeUv)HSM7*GLF;UG};;+C*Jfl*DH0T2rXY zbg}zte8Mk5U9Rgj8%13PYmlpW6Dt_OKnzW-nP6nmR^4<4{BhhC7=E}8PHxeSE}QJA7^+p31xYRMs5?H%G1bd zL^#@w7oOg`{U@+u?tk|0T}_H%-3`-dOPy*o;dl(HwL0q}qpG~?S8R^9A+|)Hg73^e z4jHt%R@N?PZan0CjIyO!G`XRW;#IYIDUtXrF6;UXe3*R&HCeC15>+=ul~H3;jYQYP zZ~`$KPN-llssOIK5%R19_(y&4j|%vGm?>fXvjNc!6Oa9$y~(AwN4-R6{+m^b;LSC4nO{CHO;a14rquz%+d`+sy*b|LLdVTwj}!yck4@>f8zo`o_82hZ3a> zqC{Rge>!l(74-`Gz-tDnS?rbqV)v|eLlL$Df>`&t>04eJs-Rvig!qp>7koGZYuCpQ-^M=P{_ed`UN}r2)s=>p*NG}!RLcAv zdpL7xwoAQczO(+xwUXA?iz>a7CBAgu4tavxZ^kP1(Nj*BTs?1Un{LD@r`}xJDD)O` z>M5s7Zbzq_0_T4WXYDl1j}xO8a#v@Ir)$Z)^W`@IKg9W;fz&tWfBrsy-<<#XAN?0` z{^ya(fwuggIvzOxlkV;7{pS47-{UWHBJBK6|B1oOVDC4_fByUb`Og1*CEC9o|EK%< z2Ks{fzc2O8`Jcbb-@jr+7#RJo*u{m(#9(ZQUZa}83qR?3gk^4G_6-&WObm^+16FaO z#CDH?F}zV4su~6Adc}LQx>7?qBfqvvLquf%C1v1$jU@1VZKG^r95uSfI;2*%Fp;xe z*IuVpHJ9p2`nG5{S5mYsH$fLcGO6m;5Pjtv-HxYHLu~QjU(9~|Z^++brVm@B3tqS? z(Ofq$3e1$Zv&D+JHDpxEs}|j%?qu;gPqQ|(YE)?@K6Lnykv8$aAr>PhQ3FH2?qt(6 z7yXn?1O6PlQZ97;OJ)TB8**!g<&^2>I~b6zWTxNg%GYz{d?IJwD4R7cnI0f%lI$hv z_)xxHbD)1!yNm#}>P)tTsNLa^aI|C${Lcq*rXT-fP~e`aEPblLG-^pp0N`6^+uN=g6mgk_ymdUkj-C z8=ZZ&WQ4)CjDXQlBu6JRjifxb6UL@n2{t@w8li+k41;gfU0c$As*7^)zpk5wveBh^ zrMl87WRmz!t46$9C|8D6b9`X1^IFmaSWA+ha^e>Zw&Cy~F;NONtwL~!262r1HFxFe zkQOH6pd3eDAEh{Y6K{mPiIf~6Vj@JO_@vNb#7Il14ktJoP360i22i4F|BtSr(n(hl$Tr*)} zf?}!ls8Y+r->VihcIZ*b8Zj||69~*q-+kyWAd zzrkRA)i!k8U=!ulTDiq!5mSfo9}rIKc=?iaq&1HsQ&YUKGwG zU9839-kdVTJZeGWEdUFL1zKzH{)8K&O%{#QWg3o|u6q3n7e73mXFSv=)fHW0X$R9%_Tc;njW)nH+Klp4LFF+yu~X$yfQAW}AtkM4jJ`Z;!}KoYNxSbNglFSuYh` zQRX%M#A(a-t=-%$iEvR8eQ0Z+Y7ALTSF*i-Chh5rfgJwlE8o`j(rTv2r*91B1rtJU5c8-cGOo_LHt}Z-z&MW&*IZ)Z?{$~O z8NN<(>-c-hB?gTFd;uO#(^$jsQ05_eX_m__snRN-E(+om=nAdu3_Q?y!c&k$`@af> zY0zNPA~~Bv4iB`r1rOJ0(*Qe#v1YXo8ccCC*+HmX3i1X=*SgEFVOXR{<0xB%f>*H! z9Sldd9331NqcYCA`VKn{;AD3ijUlaAx9A*8Gl?7D@m?)bi|?38o>#A72q6uKL1u{W z@TfojA~lreu8rYqlAETvRxq}8nZtQcg4;d6;q4Tj2M#u??ECqPhBKs zU9%sr4BJPKXYF{RBEk%@&7W63YI?WiVpG!ym?EFLp&B&C(}#bGB4UB$!N zN>)-QRgxo{tjdD!(362QtHd)|)orb?TwlRRmH1SJr*3_9cXvCvZdASgtjUJgl1`&w%ul&r*%^#ZRrJX{KOD-2kIjevc~RZZQ`i5h4(_mzkPfK51r@rW<(ju zX=m9;3eZ;?r0DCdhZ-Kb%YlCF?#e7xnW37jne;%Lc1-)u^76lZXJzH+N_0SgUHX*22C7pqmXHfA^oCiJGH_69NJ zP=e;D*;%(Ox8!7H9W~d zSaaNQWBYt@5k@fEJPJ`v+8~^_+jay|D*zE?uq)zurfjvZB3JKF;uaw)p5f$Cuza3YKxl6VRalOvsNyY&Gl?R%8Bp9t`#s$UND`120>HdbPp{X zA-qva<}eB-7LS9dAx1=YMtpf03vZ``@oqQD3WJJ7EgJhFxoHk9iUL9kK)NPrKQt#x z(xMooDW?RXqtW$`sna}+jyG~dG!ZgtWqQ+@!p%N#+7$?6Uk^uNI)fMY=$W2Rr2ja+ z!h!Z9YY5?xKus`I9n{mry;uv=&F8pou3P%Kpl&Ww-CXCodF~PFW*V+qrx3+RO^cP?`4M>v%qnfoAi%uCrLypu6)lwsoB3kFY4$LWNQZfHunU z$FzKczF}QjJkD(v+Sg*xUP1sllcrH8(_~5+S{#~9r+a$nCp_J%#ZPAX8Q3LI`c!DJ z!|~^fT8&vqX}}T7T#OS(<}$4Uz0K(*82O-xH*|IU3Ae5pa)dRF6ywE2tss%~ z^ca*@z`jmr@e#06IjQ$&hV4W;Yja$j=D1k3^wU9HY*1XRa$HP2dXA)BqAYp64nuE- zb0y1e`*_k$@)91hiW0@a2}ChI?9q^qnU1F8$9m&g<&&EF?cSrkk7-4W$J7{Na=}J^ z8lwLyEt8Try!yHNdd{pNr}^QiY9(ae+OQM~y^j@%1!jt>?y)Y`SRNYV95E!H0*!j+ zr)-DR^&>|d=yfT+yIVrmcA)7ZZJHI}+{h8A#S`fvX+kO8?|rQ5XK%rsRB&beIge&M zal#+?q)hN;QzseyO>p{KxAci1{dH6NTj%sQ^;PpC<27}zR?!V^QVxj+ZvdpOz&^no z$1lU_REmBoJC}?~xu8j@T2WD9`&ED##1c%6cXyR&#S0=Z^%W3|a>DbSWsOaE;w!rs zb~o+a&CT7--NnV-#oZe>ZtULJU4_5hQ}DMtZtq^+-P&E-Ns~-tcV3+hfhKo0%NQpb zp7Jo!iCu_g)$q!UamvZ=8OAU}G}E{(bLEAJgqg;9I<_ryl%;sncrrqzB$pj~F_2?3 zya6uxVv8}6FP3G$n=*ef>3BKmB@Uah;dC}bVkuE-k(XLr43y%Kh=Pm3f@{3s+M2K6 zJPiVl2slmS&}RCw_{?ir3)XVkkK$s*wrg0pkX$usTpKNZENu#0Zz-5kv#`_~A~m)g zJ6cZK#iC=^tagSWaBc9!nA}{v8L{aaSAJHGn0t4E!?;j5)kJGc%0;AuEoL z5AT*r!@H+X5AUw85AQn8@a{aYW^V(S_R5uE^i~-9vF{X)fe?Zpe6ZQW@G(-xr^lB6 z4V$Dbey6Z<^ccvX9iJOTdN{O#ezXujsufNS3=XGHq*B?8+3R;@6q<$NYJ3MrdbzNo zA65k-G@oL)u39BApGt;h9D-_X`kJspR{f@nbN*0x9rYKfl&BV%V<-#VGj4CQL3 zvDRpq6~_b|V7xGGbvRaCk7Z-CGxFkyuI&cN={qCY<+5)8m~~ME&2HJYrB?yUBWdiq z(Miq}=Dd(yHkxx%U9W>dQ~+3yrXk;acs|eccl2~B_1Mt@=7r3awwXm<$P5&|)7v=K zrU=dNWS4DH(qjB$EDoPSzOD05>t(C%n(R}^x6}Mn$*$Mvhf?sv>EX;J7o0wXV||2u`F$KrAxh#q$&ZXg>$V% z1;j}#hBjaU2wYavjibHEA!U;;c;DopiVrgaN7P!!4>LT6@?|ExKXpwyM4(5Sd}Y1{ zuuxb1p5$)Nk1RdYAgMrj2$lgkM(4!akEi2q2~Sg4=4RIf-X1Ze(CPNK)0y4f?FNOd zJ#bCcuePp!l~%tB)h}h4R2;@pLZndd0^nq6DU{;3=?sraBh(Iz()${|vRASWxdQ(* zhD7FDB~-XQDOFA7vX~* zV|T;E;)_T11>TDx6z#OcdH6=bBG=%F0Y=vKNsfs{yuIr@m_cS5@PnlO%KawIKlnSi(3!CLK5mE!ho3?#v%8O!o9F zQYY0mu7fSE2xo!fRahtUWR{G^>6JPF&N55_2uvGRq^z2!W&=kyiYfGUYuY@TJ(pp= zVfJRz@yFH~zGn1IC;((cPU$mL3Xu!&K*bq33s0aB$Qk&h157F=CNix^%J{-HDXFs* zPYDwPzo3~%uD}DT4RTo*@Q|XZp9Nd+)b8#Dm;znKxeJ9e4zpZShtKrWWW7>hb3X!u zml`_N({tnk6oZ!wCsV^061?aPbIoukF}b9l!8PTosaH&VxMb>O`Y??b*T932S%x_* zF@*mlFD{H}B@-G00Ldk&0k6K8Hjjd3X_(wpeGDgH@$778Mez=hIoa>f9OMcN`kX$h z&2jJMl{ko4fsQ$G8&YscDQ{~NWQm*uK4PR&w#qIb3#e%E2$G-B>)q3g*f^~%#^Fnx zfH0pp!V>rz*-~7VDClZSaXOD+<|mNdQdb!!a=3X5cwwYgGq$DKv7j#rxqKchkn@l0 z3q$9rG(;BCmzK{bfRixiHK23c3${&LBvaIyxu&>Ab_68X;2$D70x#2QZ@FoF9teCP z3VH_k7L${-wKXwOUEmYdF-uLymksr$ zTYaJPU3piNrMrqKtLc_-I$2Flr;50e)7G`h?ruzR2E`U_Tz0X&8A8qPXu^p+E4&R8 z=bUwQbUYi&S&3XBMsnL%N0;Euj1kkidTKNqGp)pGj=o)-$F~?M)~=3DWMjn|eHOpb z=d!UIr9|GMZ>P`Fx6&GYTUexT?k3)CYU-wba@$%q!Q3#DPPGEm5W5;fl}Nf>c9r5) zS38m>v2(WDMJcy2_4Ihu@rCcbU@|m+jUp z{V;wt&!OhoYB?7Nm{?-4YC1;4MKYZ?A&GauipN)mK!()U z6-&li{HUw!UeMx6{~iTgrH7hP^RrgjLKYF4o_!QcU)*$HAKBt3eme~o3;Tw)3olJg z7haq`v+wiNW?|o%{Q}>OLV-fSImM)_GcMuNF-&H_1k(j>;FZD^OsA85yiM6(JME)O z69V<}H7J_ouaWkaCD8851lna@8SArG8|ybqv!} zkL&%JAxD9*u9qlZ@I6^EszJOhTL=_L!J^P5bs^ol5tFE88bW~I09jZ;U!~GE)KzX? zYAcIM+}&NTo(5qyx1b(s)gsm7FW`IFq)sVIKe-8m16ktPO{&qs^tRbDPy3>tVzuJK z!jJ=&%3ATPs&t1&T%s0>hs&{IlH38W(~`% zjNx&{EqAtNSJCq}Z=wCPGvjI_F2PHaYbLds#_5NhMx4x;`p!n#Dd);C@Y{GRQK0|= zT>%F)5M88!0p4*cw(DfA1`r$C(=~1{7&^jCcB(bASl$||IN1t;=Zz`6(9+gp|9SIM zFbM%J`k{;;?#2kc+kIAK%j>cK@Av%UIgzypA3wwEf96HrY)QOfrmO>R^m82^=$C3k zH^>fj!^GE7*UIJyzF{tW+id7H#ih03zm%@hAj_qm9{9=vyT!zXo#Iv8B~~J?k)sw( z7OV+Y_=)bFN+UsgkihO^oR=qfO!-ZIzvYaH3t(h94~nhT1h@Llc4Sv4R+9 z^N9xD>IGIKU-gS~i*9$fCO6Xzv!85@&8aMmQ(x@_=4vF&NCYa(xd6;%yicl=CY(0- zZ`V)}qwY8Icylm46-@6IkFg_hu_5?gwn}EL?2fo@&C!pfJ$|@is{9!wd<#^EW!6B* zjA4}j)ohI*x~bGLII)hKd1wZigwd>cw}G!1=`uL7WlpOo<9znJ5aD>GaD&H4c>)A@ zEy-;v&A_b$$^vuom~F-%vrXdYrK7g#ZT;BL@+KM6#!#|=oP&Rr-sF9(SMjl)4bns2 z%Rp~sD3K+mJBWkZzpn(4%epC0e5D177x^@bZKY?kBQ#v71*D;-^fo-v@5}(wDx`+n zr06GR_P{5=M5Q6CK5dE_8qSI=xnGY@apXq#n$lp6m`QGJ;@pkYe2wx{{-&gJ2zWYw zqv;+v;ps7Ixcd(4PGW>CU;-1Q2T zeI@d03F}xT{+Kw5L9^ypJUD0gO1+pLnv&35)?ewNbK^(T#9&|ev_hjVa!#T#s_ovX zH4>@x6LB_V!aHc-*0$Q?>D2HODa`T&vNim0$V6Ei|EV;d@IdVL_)?)@SKh%ySa*fXfWLx9nVf9q2o6SMb3myaF0`BU{D{V<=+i1@(E7gQ**>u84->m|3ZK zsU`7gf$1mycFr!CAj|n1EnE$XiX6KQ4>M#p3FB&fak7O57c{noMQ7}~W72>H*;S%^ zs+h)xncXIM_ei#z2@kS`Qo?N{@=((gWKCaQk@ngI*lQDySB54OYwa9nz{Dw;Z3(h1 zJ#G&zDcL6ViO1}tOJqc^YDF>`C*%5AQ`^al&W&&&_)S@XK?rud5g%G&8x^%FeT0;I zdd?-Qb*H42wJDhIa(Z3VH+yI?Trp5r&Ms_Y zy;uz*I*`o6Y+#N8Rqrm6H{ZIx%-d!!1$rM2e!@D%*EM#DYNI=#RH)TEiW!Hxn>7lf zn^UIVOU7Ki&(knxnw2;RHBgJH_VcOk0LfNRSKB(3O`NrG^SGjZebTjj%mnponxLb7 zU%65)=|!rPK|5EHj;Mna6Ur{UQ|4o1E>w|#GRHD;-0xYH6ULmQTp% zC8s=HmiM`)#dmJFICXX0rvP0Xo0osi_O90kTB( z9@GfjE3?aw(67h!RD5TNniEh=GzjncnhiL%tGe5W3v@QbY)o?#N|yLB>#;OQ@D{Zk zg*lV*dPLXVo*n^dBAvozd9Mp&%8byqrp=-|N23X$avgJP74J)p;9Xqf_NK**Ut`te zuV_n7Pzi@tFB^-U%~CmE0(F~*O3s2D%9C}|HSnp}(9I#fSGCOcs&-vWPESlOWbsJG z?rxFdvnCzVS|S}MM^bV%OImssPhm?+L%gQBsZ*yTtBR~Cq@s(nSc@U@7@@6=pIYJAHDRGLegM;s zZgDo^d^lXbb(q~eXy~=!SV`>5G-BD9*uNZ80AY?G$V~SNCpK+gBJ-|dDtA=-QpJe@ znZA5s<$^B<)x;uMq==b{p1H4OumsQ>3f}0Hw5#?HNZO25lPrE%lm`$g^>g78J zT=*`^#XwkCN#>&0&}&Ftcv)BY^3=V+7!wqyJV!GPMD>=bE3>^>CaC63*ON$24b>PQ zv@NrzhXb$)Gh=)6@mL)U684Njb7BKj@O0U6QI&zI1@*fi2XKjM)?7hv)Pw;8YQZ-P zh80inMij=?0kAk z@mZifM(2||r{2p$T-@|2o z4->tkVC4zL+?L`u@z#PCmEl=ipd6~01QpMjMVOAsNn4p} z5p;C2TJmUy6}cazK=pm1is?@b@oOG+P*I>kkf^eC{UqqFI`L^Ov{A~MA+3rEsiIXu zVqgi!sJ-piX<_+oUw*FQ!Yh94lrQguXXWR)XPqFy#~YlyOL|=zQB_^7t&YtOQF|}1 z(GkXDat_U3hF&smieW0RxE^mr_&tl%biWeq39;yB+z?eVN zx@JDQjwk_!zUhV;b9C%uLy#D5%}tp4ab-E48!B`Gyr<`g@PvT6qn~zny-$kDt2aq{ zsEB%U08`yJil^ilSW=`Co@&ulxoZhHUh!JFP5TN2M8bd7 z4ApQgtF{dls*QPdZ@E4Z5Cl$mb&mDo-fvlI%6ce>WJh{>c)I`>eNsUheYo!a|LnbM za~xTA7&vmo#>UQm-u(=uFqlm8R&^Cfg6u*kVZ2C!BnXfI36dBX%&N+&N}?*Wn3)Bj z2vmfmu;sBlV~uvhmRFisX>3a#YgZ%5;aJIfcp~<%c-YhWq`$D|-1p4~0CuxmE2+`Z ztjzn$`#7(A?m6dbjT>DTLuc>$hj#oAJ+BvhnrE-FLtT+eUyXi!zS&i3O4T;kT8e&& zlVRC0p|<1&w#DhNC?NHgs&`AF5(x#wL;vtS(?3t|pVtsJ166*CONT^1luM7b?~Nrm z@YdzB&Cbf1QpF$ns(T!{Q^52P{*M=@>y2q%J%;M+Ii%0({5uA?)Zas+JG;5XNi+Qy zXX3~OK`5*yVu?wey77u~Sj1!2H4&t8YXaE#$CbWjQX%0d!pTZacuFMzMb7Oyh&Xyn z5vkln)syOcN76Q?@CLzYGyoYa;qvJ1YqPU5ZSUH?v=?5~QycY#jP%c>9I1b8DC@!ls>Q+%NWE*y^sb?)am@=` z6TXd$CI4Fx_5?On{v#Jh5b3x9RRk||K5<(M3~YL0^&`IeR5ooOpY4H&BETIaGuT?j zcF$2CVcSd{0JK`1YSLh-#l>6ukUvOlW%Vk4-vP-MZLG z*Ct}FLSwG9U9i+Mq>i!wm3C{MIvmkYRZ>SCgW0TEQYV2kF_Eq4Ov%e|_AA#&;6_*H zwD$N^N3uZ!!&QMAsn>bGnf$GN(bG?fjPt2T=tEm>aMjn2Bl|*DMG&0mh+eS4(2hz& zbyONM!9RasYN&Q|%gRxG+GRi3ZKfxMWcCy zbQ;$G`@0q%oQ4_E7d?K(v1wQ)`9AU8B>-X!4%VPGo`yln4zMgTM0=3No+W!~5o-kb z_$#Ducx3n66N_CL1`jyFs~p@nhqw-jwU0(+qoF!%!&~heDL-ur-KVL|r2g1_Xmiyn zvcZSS=;DxRAbox~WGllb44Fm~nF^Gz2zWJb+kFuN zkkIPdz#?s6OaqG=nbTlEZ-W8aGhHK{{wV1+tYMU-*2K|f(y+y$OKonW8IZRKTk1xK z6fAY42V4R+l+3Pz9&%{g$5CJI9@&{LmZ9+WiqJC$`OdGP!;=T-BlHW{6SK-Vg^shU zW~|undlc%Z=?XQbf{ko%c6L-11edW!_;f$u2UR0JRoEtOx5Y%GqFdzQrdW0}VO>j% z13~o#!ZWq=5R5Iuo0_OYJUY>;-q~3ozDJMX@<(KecJ?-6_fgQ}w|(lPpth`=sdn^` zm(J#N>1?i0I-A>XST&5T!s8NfGr|R7TK-A8;KWR0GIQ$7gY~JHYj~EmP*2MH&Cm6P zq*Na%4B!CsYYX8g{z7>5s*~=8`>F!GNU2NHJqQ&G<(oFW^fG4BAErxB2#h;O?TDV;( zXy@R-q8>FkWFP)PGLov^9{Z+-ZPDLL_n8__mO08P9QJ6nnnUPs-H{U*<7Z0L%$VnZ z1UMaoObMNh_v-r$XEckGw|;)}rkMovK`!in0)lG)sv9~UI~zWw@Y(>_?2|c%(~iCr zgc^2ycDA?gC`;46qhd#R9IiG)%AipB>ZP3Poo;Ndt~IVrh+J_mD-47|M1x1fyRbHLm$?gLje7`^CUq8`^j81*6$CW^|kBNmV}LDm3~ z4gc*>x#KQp&O#?#S{ZQnv0~(YRbL!2<89YW$51|d0p~U?{@Y={`|e)5YCr{1?;I+$ z6b=>p^sw=qHvLSSey&YF-=-rq%FHj9%!obSiOZ)Kovcb`hF3mxC3cDPRO(lTW4p}?Zv~!;nn&aStE}ea)RpYH%gXki{{>9c!0TU#KyG{ znCb8se6zD{#TVNR*b=e9&Cc=%fRPEBPrQ)}ZFEDJ%K_P$t_xeawz~)7)^tr0;)IJD zg3WZy0+<`!PU=L)sIJlvjXj@*DI9Ng)_6 zw%yxp4OP|aC-pgpN=|JlPXv?@BR&!vl+oJCbc!tT1U%6bhg@BC z$i+{ccI47;x-a&|)NgQ8kgnJ6W|eLR0Hne;9fZB@-rnY|xRA}4c_2&jb7~Z|OWZdS!mA&>8bxY`8%lyJDp!bFe( z*>+BM_^$5-7e_)=&pb%&O?~gZ#NqX|*wc(*Jkn2DPVhG04GoG{g39=^sA!Q_0Zdr6 zXULKL#Ao{tDiv50e#{k%9S8r#rC7InaznQxKo9n;e451>9ej4xKG9odNF>`9HJc@E z$MPG+Z6E4w^Z1;iZC-;MU(mMb!;aoI)n4zqN`u3N(@?;aJOH3^5d;mZH)`0eH5NfZ z>sx(}h0+n7+a-IPAX!Tr7GIH?l0Esh3#jQtP%ey?#0%E*1?&5heL2MG>7Cs0iY73# zp}PaSxEGYt?r^eCL2`5A?#SK9gzxRhGj@hAsCeq$PANP)61C*XLV;^RPPNQ6NU^Id zYly-EPe6a}3|*3{Kqpl#om6#nQq_YlYDcqtLPw|#8zLq03`zui0mZFj_!lL#w)-r! z_X5%)9n#yEOs3#WzDv*KyXKkv7-p42qigertK+L}=UZ*SZfqxycPSX#0`Vi7*;9nWVeUI5nD?{Br zjFqTVI=PTcyN)**s9c&Q4cceyYz{M8Kgp%j8kHmnyM3QmoSmIM&Q(@LPFRs=qD(d)YlT^DZ6+U; zmS8QcpWgx-mt42mS)7gIqEX03%_{3y1?`?i6^a^=5!o$m`89oIWOr!Tbq=XL4y4bN zqbNnljHBp7=_vY)If`yeM^Q*P)zKQsd=9ZOF$if2eWpxdODK0A(v9^XzEA;_@nJm* z@XVt`8Fb311M?4Lx<8_yy#U!nT`X6Z6nL--U0Y^!d@EelQVRzx%4M_ki4bLYN`78M z0V7>mX*~=v3|i4~iJ|P|0*l;>dZ-HD^Y$^U1pG*)2NQt2eHv#?9dmeM8{`%FgL*l; zNKg2KW>nYj>lkS`S!oAw=xr5^!W7toGznBE7I@J41P}%IcZWJm>dV*5uT(N|x9JTY z?rvy{3phwj0*A>lq43{mx^bBuhp2{ZC03$B8~15lblK2@4Zv1kZ^#HluvnU-zR}nN zfytwh(S&18kDqr~{$QAnQMZYzYAWqoJaGyKA*5#Il7HgX7EV4{^H~hif+kNuGU@Kw zCs&)!x7Pgqed%pAc9;2~xZ?Oj&YF+qYUCDGUk#6GpL-l|^Wp%@=eh-5Ab`ElA%N!& zvT%GI#xdpV`4uz;G>z-z?P_66T37&5lA2z5Fzlu;bxRlg@<5EG}tAo^lwmq_@! ztZBY3Bg46G#_)WJ#hHEHt7U9>wa5-@&VSI~5C@PuHr7qlb5lQYGCqUCFJ?3A7Xy`; zSIlN!>f5B(HVzZlJT1;C-q|(|A2^D_BkL|a2n;rlmlftNYI<+1yQnnzlo~AZHpHAQ zTKk$C&IP8%IgsSDXc`z(n^hKtCE~WN>@DDVG;~XgXVI4qyx@a~0Dzu&P=h<9XPvi4 z$|6A=4dz|AhZz^{CR(Pbz#~PZgyrN32}`#nVX@#upbvIe-ph zIqF{zr*+)!q|$2yJ4{&3t$xk4_(w5VS{qM&p-5r78Kp#*YB_|FP3#qD^t+B zMsx3YpI)cNOR_6caP=4Wx}?3|*aL}l->?HI+o}x>C;E9fx+`o#;xe0%gV3GVRB9LSXDWkG3Kes216+k>IS7|5T4@YR{d?NL1?41udYKth5X14BtJmUEdkE;sELBlmrdiQT8|iiK_$lsyHvR6d(Gfb`&q>n9yhE%}r3HKUkQ`h(lrby6ZbHc0dB=<)72SiGM4V0VBl#L4yx_Wz#0#weh(FNv$N(qG*5V2 zF7tef^YmkeodM2NsrxDHcAi#umX=k&pp(FpgH8$+7Ay5e-N~E*x+mmhc6O$U0eL{c zTxHWt-6|)68jNf(kvdE6)HP}hRe%nyim#c6X@R|ML3u zInug9>8R)lx4xITlIXG-;fkLxX#a2KO`?oCvEk zN{LrJc(*iyKRVBWRwp#pS<;7k(s2E%UFkU>IsS!W8B*=$QSL7ml@ zWf+8pxgzRcVHHd&HY^wqUBv8oW(2vMXe8Jx5SCJ<>-O^UlB7t{=igi~Dl6kcgeopX z&%LdOD&ZFr%C(OOi^71(uW+@qdI%}hlb%DPinHdiJR`;xU_}`J2|k1Z34^7Up%+{V z(Y08#)5nXrv><3X=u*NU@9lXJaXMK+(F_YfL+^>rXp~tS93Nko=$v)wf4EH_6+XKPqL)xBl@_ky?-q{`Bh80U0?9p6N_qBChqQK-NERYnV!^}>Hg2AVq^$ONfD58PMn+rrR}dd@!t|BZf92PF>~<~ytrM)i{AnpCQn0G zTg27}-%{~vKcKHndR2jT>ONG@1Z@JnEF?XR54wLTY<(;3^S z82UJC8=V7wo%dyJC83=&k)0`vV~=e7L6)zz6kltV^0iLptgvT>drox!>fT3f-?`HE zVZ)GIZC^IrbLYLnJzvZP1A`0l@JyT!CNfy`h;g@0X4!CDg0*$ZSle?9?YV7)_Uv7- zh)3KwQCmBC%>E%+TXgxw5!)~x_A}ALem?iFY^N}HEasqD3J16)8{1xxRb#JG*v&BcnUz-MNWyK;%jqrVP3C6iCBaJ@y3-*$famuO4v%ll~ zc8?ZR{H{&;UAx5I;#^kOD}Y+Y=Xzx@+r|)Tnj7p_!t;`76u0~h+;-~>FT)Fa2=Ixk zNv-AFZ%Db{(Byv2A`aCoXMM%x0XGV`UrE{LW%1N{c1w$!$HHz%lY-2yFdIu(G0Y*2 zU*p*@h#upa%~GD3P@dVjm}hdnz`V%wLDWPBOV>FEAD9TWk>Q=aOYzR(1#<(^K?l~g zh_wz2pq=y8;W>Qub(jg@6fWjH;eV3~3yiaK~r7P9KiL*#CU!uyANM__Ism_Z92u{xx;Kx4t43b`t* zGA0KCIOld~suyqvCA;H_@C17F>o7Xzv9Y~;4!fm7uuKo{2B|_ajShf3o`%cLH5nbU z787;6Ld6VzX$Xx@kdHCmpPlX7XmMb2@>VrE+^7C3JZep6c6Pca!!2l$9yGh<1Uo>z zJJj0T#gGAfja4*~EgEN^!AY4huUbnMwm>WcH|))jIG)ProN4>}OiyDG{+|^T+bYP> z$Tc%S%%0ZiKAX<{N75>1qe5-YXG;=vPEdS4c=O(e*%g3HWHN)`~uX!2No zPn2NY-Df$}l2PQuR2RO6=yQjnk{X6)H7nCJ0rdPnNYWvz0-!ODp{3}IL`&WfFSgQi;@KSov&t3nPa%e_hUF9S{U;CEsTFWvcJyQ*WWdvbb7>ZN*U)@RNS>$Z4z zWORfV0IiRN2k0Hz&lg+{A665Enf&fJ!>W7tpx4@YfI-(9Hs`!4dt%83LTQxRRB^0&3;O}ato;q=}W%2 z-KYy^=fy2}=C-wq%Z)lL5k~9)d?$DDZpgcM9pqiS(|%#qeoU-n)EopUZn<8IU=7*$ z2?L!|BX|h^a2hOZAS`;Bt%`W9^t?nmzcr=%u6&?@jl?r_skp9m48;3_L<`4S3&$lb z#AzRDZPV~iY2TV?>WS^p<{>fypfOY`e56ZITF8ol3Pi68DD=})8}`5rw0GrpHORSL z4TL?Iai1ak?nsPssO+U;++ka+w73hgymTM-fETFF<^nn@h0iCG1G3Ukc5odrtsV=ny|ErnQNVNm7v`xB}yG zLI%m{gbsCm&kI>f=T%FY`G@ZDK&9;BhNq35U*Cak4Zm<(Jd`RnmVDvHWJpzPZuEtlW6@u+t>_E4#e^Wa zP69~?wl0k|S^Hdue4xpY54ff0`+i#Njvg%Hcf8Lv3OWJG{P2CM(o0f0KI!=jJs~-8 zU&FIAyW_jBQ9ybm!g`<^ukXGIZG%Y}#GpuRB(O(mi-c%?cD6h_yKD;yMv*#2t(eLw z7Gz?R(I84Yq$D>Y=O!+kQT{@B{Km&Z&P*VZ7eg^rovry@o7B$o1KBywWY7BidI_41O2N#U% z5phTu8gsi!KfIs6PEHYgWg7dMPfS>)Ne+)2ieVVXl)&qRnHsL|WT-xT@+Ij&UwZI= z;uk0$2SV{k6

RAZ~HBBMafeJq{j+E-JXl3|{?n`^bJABHe!KyE3ApWQYAA1ddKs z+!$)xy!0Wwf>->INQFwX(@Nj@XpYomm}E?a#7acur4Xk>uieHgsHwH^@MH@8qUcWm zKd)eNA22U|)&&XJOhL-nD?1sX-d^&39pjR+pvXg|slyyG0y2n@FRhLkf7!_8xH`10LM{(J@p66-FqmOHChB`XE$i4y+(&V@1Vw|hle~- zycGunFCI4%v+5Ev2OqIQc;xdC`?ekR;WI-~mUS39)8s>;ZgNsFu}pisf-N+TwHaXP zcfBF9U7EV=B|(c$0anInpFC;;R_$iziH&8f`X))m3`<+?uCS|gphqI>M9IXb)@-BE zq#lNr9uwDVCQ+XTrc9$@!@5!X7V1wEKV1)T+O{#oM5CrBt%Yq!c+29!)B%+F0)ki+ zCU~gTu;^nS=H-Q5ffgN$WwKAc^7{QLda)|uamO0pwybZXP5ORev|jP8Il>PH8dgAi zQfUG?JhX5d!=}emz0t6P1`-13HJ}M#N$*qBYaMpuXcV?7tdl8LmcAg)zM5@Nfxi(( zsZEcrcnB4Td~*u^a@oQx-cd!0tk7LTS7E{s8F!ygs2 zCDwz^09!LyHKT~OJ{Okq!?v7Dihe*Kv>qh+4rr|xf&>qL<6Q970>BY9bL)D+l;}O; zm9UB2%2J|GRvo)kTp0o8+^QOU8)!vy-ve*Xn=y$eU-B1d-I68!oVCq9WR_e7GrH91 zFWq)F$kO|^uE6oanguH&AlU1$YbDNSQ=Hz^1krDD^0EUZ1np^iNsl7^v7|oBjD!`I z_@X>)0yL=ry4irpcS12GS>QacfSQgRHO`@GyPSyTrk`W&g|zVVR5XF&{l0fDbu1wt zm+R6ww6l<8WLl-?(o~7Z6K+rO7E|WA%rc$APLwS}@c_P@Pi1Je#vZz#pI;TxOUW7; z#!=X$|47cyGS*PvoKL1{kjIm2siQ?KNB5+ab z&ZV2z>2u-S+vAFd+h(?;dd@j4CI>*BVgTw~Vp03v=UY=YveMdS#R7UQqXm{`eD38e zK+)2cFTa$fNQAtyXc;m6qD52)+$qB94rTaCLB;n1Gh`SAXeCJR9!?ro7=<9ZWVdNd z#u$3&0%=W3w=*YPb{C(?Z08J;u4x};CQ$GBrPMW2JtK5BXmqzB++W$=T4-)oxXJ{Y z+z#HNZD^sUqEEOgiK=b*^YiUSAfMr-l~Y}CZ4GOqr!+T-v2}cSS7>R3Drcw^BY4V% zDy$|;+8*|URHa9E=4 zTGQr!i%rKfKTd;|*Ka&3Q1Ps*w8jhi89McME~80WD$=a2>K(%h(I`b_zShTGUPO0o zSYfg*FR{aV^)Ltz8=LBxtI=Mo6gEGD3-x8&S$*GDp(<|KR3mH@DFE+^MHC?!SX2St z^T)-x3>F^bGS#HqFVq;qW(R<<8-xhc0Uq~}k?;1e+`JlHnY((Oc)n|o{7P_T&iUwC zC9Xs=8gEKBJWQAaq|fTNI(@uR2LD}^=iF9{U$}+=OqcpW7n6J>HkPh|=xAD)9@D}k z6NA3-=^GJPd;FT9QqZ4H)I#mHWfGJj+ufwBr?NkoAcDlARYN6!ITGYr{IL+S=;@)# zAi>f$TycaZPWJU1ZNKYKafg|&!Z0stQkE%hvQods8)X|b{St4v=R($^%+ZZx!%fS` z(Oj5~bQLzkNLG@&k*p#f=`e8a;vZ8`9r<)T%v&d4S1R*sLV?flN$!-@RyYeH*6pb) z&>*(M;bKCVF>*tm5!85V-Hftz%#^Jqd``e6)E>nNRJS;? zG^E-|6moRI+QI%)N}&sq!6e`D;e^Ag!YCbL6jJD^8T=~9ll81(jcSR4{Wz;#e}DC{ zP!eDE3mi(U+Eju>e~L$nptB=jXc_C$h6NYb(m_aLskOZ9ZWfo)peUbL>Ac^}UPgtb z+^@Wk=A8UYW%B=SPTP4CCiAR9k}cVQbw0G~^PX=;5`Juk_YVpla{^LBHDZb~)~f*#-6J`HiNGb6@I@iS_3 zr6q7>l1RyIGN*7uqPX*iX_+0cgpIHv7h zxsHxr`$}?A0ujS7;}hpKwZlO~TLf;`AvfRr^dTcqO_*36F(Bsj2dIT4N<@=uC|SzL zB$c@?dTY2cwW@Uh0Q|&KsCaF*lr-g=o%Ht zc$pAP8~reV+Kk%cSH(cdpp`*MY;^7Yl$V}gthekLDzEKwz2%m%Q7FnO(bcmGdb-#l z3M1&L*#at&=`h#OUAnuq((ab1>bFX|yEQ4F^>g)%A7}uVJ80M_Qb}%HJkuWtI5vtq zyKz2bWKMAyMydVsIrzb_V~Sa(nHWorV1$iMMjqkPqO$+UW(qN*WXC!+9Snw1oO-CF zWhxLx?b=-8EHWXQ!9Db>zW1|APnWie=W&aYL`99w2J$d$OzhS~Z7P|#s*Ub0+tY{{ zN!uE+GH0u#fuW?!6gy~8QAv*R#_o0BUUo3t#8Q9Q^X%#kr?GxuIXvC#SDr&XtdJ$sjA_1?eX@$P2 zLuIa29onLy*9y`xlVWK{CG$MD*}{f-V~y%Hqal%87t?Lb0~cbLIz>q@>|N6j_S4uZ znDYWXm#EBzt;c-VMm1hSdsCmPO|eqPTwBW&DmcL{z9~`)Dumt+$OA7Lc`g+(HnY>j7Zb$Oz!!6NpJe+$ zH5BSq-?JD={OI;UjyHOQ+%$kNHsD);?K_DJ!f z+Cm5}Rk#*Q#}uc^YKDLI#V7db`#V~D8%;~;UuxyOYghXdL@kBce z!Di?qRmDum8{dA7{=Xnt4M!G%+%?0=C__De?D&yF&>)F}H5EbpHKP#$~0F`R7oymoZF6%Fyj0DnM$ze}*} z$7*_|V}U#$;CuJ@uic#BZkXE$$|P#S2*&@FWg-QgvfmbGV+kEGy~cvNnjKP))t zZK|}bzSr`5Q6E2aY!2$--9}pu3tqZ8--L0=P$6qZJB&XO=a$=zL#;P{(uxCmTXkbi z&BHSErSV9oqU+`v{EMQK<~v*+2a7ZsHkS0$|5eRIU^jp0zEgPCx@mlqFT`t-z)a9j zFen%pD+Av$kCp)_q1KjIo3*}wATrYsm}H(;-N?S1TTrW)aebAtUMHUuro>FL2;n3 zsBXL8VnRn}LJjy|WntamjKW!BL_H@?DhZu3nxNPu&=z&FOh*B<2TLyJcHJ{F)kv8X(2dX03*B0A^EdoPkQAU3&5G3?=eie2QyHhH;u1ELHz0eq^0v`!sQM zVM=Tk`aWY2+CGGyDk;i{hi!7Cd})vQW~C3L&6ne+*n>CxQtZ)Zeu_7MUy{6{%equa z1|C0r;Z2MI0cR8^Q*c$dpJDq6x?0hk%gvY{Cxhrb7xbJtA7wB z=EI4m;t~^mX}2&Ya~F2kAT8(G)8~Y3vCcJ8ywgSMBJ2W2%1EJL5(-Y#*VK3uAK=bQ zqxiB&XKYfeI$9lYgz3sjP@q$24Il=~{h1|U;)IXs=tNI57hj9+vO;2e&+&@aESB-5 zJHjQ;`wf+b1dGhVN0ZAUYq7Y;&JrpuF^7)|up!dn=tT)+uv5G{lPTXz{7M8UT!<`+ z((}pIX3^|gLEke=fTfCvuc5?0p<7EGD^wRKLD4!;Hpw$3vzMQ$8KJ0(qaivZTRF+$ zIj8+)=w103{s>0rXq+A-hr#fm)$?122aV|(j{-qUOvK6RlH$z#n-!)CDL6367p3GI zV*^zfBw(aYZ9>?Ddnm?HofkdZ&8iDZ=nC>awDLOdqtM{_UzF#_m-{IF=9I8ExK-nZ zW~>WnFW(Q^eq}VQP)I3A;tPC1e{~(+2WyxHNIj=zZ#V?#EK2*ro&F+C({58t3}F|5 z5Y6sf2|wJdE71J#6VKZI(&qkBUH^ium#jd)Xa#`49r*=GUl@cwf5Ro+k3L}cFSQC^ zsO^$Lc%kGROrdwXNaN4>6&fq7n!GMiTxMBNLhUGJmF)_5DT@=@W2Fi=b*wv;xht*8 z2|HrHjOINfM86&-Y0w#Svva5h{24f*pC0>E@6J`##p0Tp2D;Uz^MH;MUKa|Cd?jza z&}EHHV_)|=C#1?&qr+l$%=PFQ0pd7D?1~FE3I$?Wrmo;jXaQEh!KK^1$$5)}sw?&C z4MT!Lo}Uq+CM2aX|Hu|uCb8P-2yHM|V^MJ_xBNwtuQs_k%YC(DyleD}^_}+YmDzzl zHPkEFaP7h@gWKML>y}?|#lEcHy=_*?R5V%p~Zn2}??1A}tHEWYhAF;p% z09>R4$=EjtYgqHd(>#KuCZ|-o%^7n|3~iWk2Hr#6xTA~uYHz0q^x(?eN7wj4DEbC4 z%q%u!I@g!O*z**a)(TNq?PP@^oMw-fX`7B!$VdB{QtiEA0l30uMTs%}?m^itStsBp$qmQs=Vp zNEi7|iKAbq+Q%AxXwTI$2fvr`7RxttEo-}RvFlh2BbmQ(jw_)y(qad#xm#Cmo#&&4 zEnGjxPiyYhh2Cp=6GeV_QDN|=xe_nPo9Fu%!gvAWnz8#lwb^a$JanZ5->KAn?b|PL z!wz7L`*m>`a38NIATQ~^_YnzBDS22E(CAln$X7~^3B?fiHjPE6&K~M>wcO#uT&-pt z)A&{G4dBJUdZUJs;rtU+UZ1XqQ(iK)1mnTqWims5x5$A0J|PkP^+-a0o1{&DTcl5a z+oVN*F%lmBcF1$uUYBgs-yVz~e*+TG-`Au|e-Ftx#os!!##HTtjPQcL5pHduD}65A;2fU$2??KUcqgbN(AsCm(dAC4c|G@&8(FYJi$1w|;Ye{@TZLbJy$D`mI~n=H_nQ zy7A2$H>V!1FE2e=xwrmot$Kpjn^cR?)m?hLzI6EFX|#Q{zr3`2_|5AdPi;e`hcC{n zq~ZA29Q$wSPsjf+hx=cI|JQ0auHDS?{~On@f6f2Dia%!Fa@t18d6yhg`P(N{z&;?y zOe{ZO(mNI2ACNBotdf9j9k%f25gFmnbFzv*_sM{Y`92xaZG}&~9zNV7%^KY&_@qgj zd`vp{vq`)sRB$KW5&pg)%{lyiN>a9+IKiK1dt;STZWZUMF@bl2if zI&n0xsvpt7(Us25`s$0ppbda*E#OTIBVS#W0wZD8-_)~UU8pY|&^ zq##XZb@6gqieq{ z?nV>$jj_wN_H*}+wcf9|ya-PeJpV4X35h`hio(9SKn-=jbc-%i*b)1qsuzx-_?uA> ztM(@tIDOZq$}KPD_gb88Y_6^~u1(maSaihA=%no8yV$pzzJ5`o6}KuaS&7}NtKKv7 zI9;DO#2*A{(%7?F7<=6!P?{I)LTkxty0uWcW1AFc99ceVRU zGGCQt@Lt;k+gT9zp7CUMF9^R`#+Av?)zoJC5u0J6w_tm&xV%f=eB#MZ!*i9FpO7Zr zpmX_ptoTIU>okKsB}c9YT|+dj3TWXsEidFp4)`Dj;Z;+#SWDH&y=u}6I_U#{?D9)F z1ctb$tZZXo59m>VLrw)GXWPMR<+Hamo_hJ48z&@tClQp&CLzq>85K3KX?PPgq-kwP zeBR_}@oqXfs>l}LgSWQwfw=%?q(f&8^BI_VDqBs3xEQtYVi=F1L=#EG3WMxrg|j1t zja`MffrgTY92tiS5qlWU>DV2i@!y^$KLtpDpMsyDjZq=sQ}83*Hi#uYg_-R-L?}#e{+9V3^&>n4zO9$4qn+RI$8P#}6W6WMg!^Yag4at_vVdB`qTVL)9 zGJoig6MM_4!iPpJ-?kr;9hgDe-l@{V*aTI%^nx?5VmW7LW-`5pfhN;ie}FVuNXj4X z?d-c-@Q*_t;-wl6IdLD@9%(ZROK(!)tNv=g!NHvH49%u@1of6!uM1XBTQY4SwbhfC zEO^3{Ruc8;Y4O#9C7zJGcq5LuZR|*1Ez$XWldpq}WNNE69$JU;E{Kx}*@HA@u}YrU zrvOhFEqqDbu$!$Q#ud`LBg7@Scn0mU}6%F}5ZRtp~1n;7$72n0PgTS}(Y9=cBuXQ?j z^$R(*YTq)Wvm|(liCuS;$!GP(gfP?PqJq_Vt!NDgKk&;=eq!k%f`}X8O(gN8g6DU` zSJGoB+831b9y5(AvG|lmo!t2?n(+I(atB)!uER;C>9xE5lzObRq7id=6zB9<^tM(a z?*=GL6<7!=-F6`F9Vm|T@W6HZoJpbUyUSYIphvjucI=SlaK6h9fg+Yh2c8yR%#rtW z5#BFrUgEEZo1?TblTBF(;uhdptoDV=-Hc0(r}^D9S;^IiEx}5;E6s9&E6qsrbLq;K zL^c~}pgemHb4BP!hvXO*x)UQ^;Aq=&kRl%)_-E{t1ItTvg(p*AHswpw$_TrR}z2Jp{O?wXzpXHEg(r7-fs8Ou&W$1u?o zG4Hsk6%Bi58MZjhBK*w{HxVHj;zF0KOVSxFf|iJcFm+{uNvLnbVbz(uwm*eye`Y`U^X$*!09JcDHL6xX>+U7Ob$Qft;BJf=Pa53O!!3 z{R9Go(zLSm92I-ROYWGR=?i9YNN16|(&Lw~5%}m8&Bj(=_HI6OL6^yHhq~;hNk|!| zac;b#U0RRgU8G4(sgh~>hG>;_Po|RGOo1e4NN9O)>5h_$y^Ji^)V-2dawb^IITI{X zNHfqh$a>ixW}S0~1)^(D?-9>|jvedCnwxrCPuKn0x48(6)WrC}dV#2!4`VK~#$2rA z??%5^gKOuMfSzyZfev2L9Y}s#@E!s{PXJKX`jnfDcCxWHS{wkiMK_JS!%BK(E*tW< zsYz2QlcxISC1BYor0Laat>Bu4qF|&l=4g7Z@#>xoB8*JT4Bx?082d6y>E6qbvR{1- zP1``99O4No5{o^YT#YXzld6D~<!IhSSP~$|(l6T+A&&T{1DZf=kBS(yD8T**+FEq{?z^ zrkV-ntjt_F`NS?Oj9bZI+_J>D%Z3;m77urL$>9!*hC7^O#z0*gjWLWefY7a^S@L3z zZ1mnEJ1idA;U!0QSTwT3iDB7ZL|t3lBAb@;65HI@0A6M`G!fs15aw&XhssU&+v3pW z+xuoZ69F)hL}AchR_v@|i*A zf<&<@tsJ2p8}*V>(OX+JGL`N5h0W$JsL4;CCRlSX_gte}nZD-2N#NQ=sAskO|tGnx7;H#b~|LvjVmEJaXlLPiBcnaAi|d;(4}TQ<4{u^4^8cDyI8+^ zTp+`5muPF#GMRjo*@pCsHvwic$FbslL91+EFg|~8bb&A*UAP-aOBMAi@=$4S;M*q- z*{1&<;=lWlhlkeVuWeC2uq{f;j=Jo;L5koPZ*4~%OyWzB(4#o|tebWeTd=5uNh~i< zBRA;_JB4J=^c<2svSUhowxaOq5Cix%cz3K!GCO0&#EC;TF0v$9;$*ehjwJP=>5Rb# zHS`FDp+`iLew5NEZM14%8Ga=03%u6eZTr;`9tYuKQDccCY_;Qd@W*qX!oN-TH8u92 z(AyHn{Vli4tudxu4stDNbFkQIcq|u&xkAk_v=v*{JPJo@v4Chk;|HG>T2KW1yLN|t zV3D=m=u{(lE~*SdEPf-++1Ziywus_6?}^wNHh2OcD;?k7ko5N52t(v9MBG}XK?l6T z?IsL^6f-MpbF%HWqzlp5ZDnjf7GUQ(G695)9imdX4FXFOW*IG5<|-aT`29A6N9@Zw z{gPR%Gdl~j!+}{a`~2LO0mFsxbtvF#VZ3OcR1sm^G5z76KaqoX*WpFjzVK{- zkuhIFQCxi6ZL*}Zv$0I$U1?~IZu99|av#`K6%+_KQ<|V@=69OS(WEcgvEqW7f>(90 zxF|b79LeD+=h0lzAgrL?v=V?E26bktL2?J)Q7l`>Z4azkm6|({9v`?M#6x@4w)$Sv z@1wQE6Wmnw(b7biM=as-)T%x7MeYg9Wkzirx6#G|*eAEJNQJH0?5s4lz#OtCb8KfH z{$pkqC#4)fC+-QofC{NIB^5~{@w0qEr~iyHup^5*w#857EJtGuisoPm5jHBrB1#DT z@`W~ojM7s^RYvYl3dlVp@6x`HQF+6>?}bTv6OHuFuvt3Fklnow@hCY=ko2Q@Ag7i{ z$EWKO6odsJL!S{46^=LVh^#0pGPHZKm08b7Qpc{qGjhsSE||C@PA?a@F%}9(XJ@u( zIB>``Z+dn%0BX<$nL&Q5hNf&L9SscgwvhvHouQ)+LE|fpqSHQ4I50I8$h_vmmHSS% zav~XD934nRhDt(6ki>VyR_Ii~rwM}r0^Xt9H)!w>ts4@7%gllXwh<5PA%TqHq=JFf zUolfJ`gX+20*W7L)?PzlG&5t;iV2`1!+K9(J;2!1F{Qp@)nUfFT=KhJTRa9LJ@AHh zn^{_s#Hmq2?3SXiMyL{oCuCAY*<$Aka(A({7 zZ(GpYiPJcC?U5S_qNl7}N6Ijli$EhLIETRTks!B&BT0@^ogDWl93f3U7WDJ@lC+3z zhTcc^6vT*8p-568jr^o0t&u?G+d^Q|P^7Of4vvnFFLJ1L?L?<)sgyO%dA0jR)7j5W zXJ1Swt^>+)+_8FtIYA74=Ap3cbH54t>-}ADL^l0@??zrfsVqOATixC`N$SPLYhU~S z{=NUx@&C)={ukl@-1YgJ^I86XWA4V+{Qs-?;_nLCW#NOG^W2S?`v2|_uPz*dZ!kaN8RiH~Jlae2Cb~v@01vnDVV!u? zXLxv?uP`4d?dHH|j?NEBi-sWbna^?8XCv;w9*@q0Y_AKbR9T5{HXq(OKGK`pp%IfZ z-(B^0ljxw+*9+1qJoK6K^>L=ca`*22YY)0F^$K({n!INxnF=d64zJG**WT(CXv-_v zf|X}0{dO7sE zXvXnOrOTL`ko-s`W5G&~3Ro8P{Sg@~t%9*VA>&S_MjE8lIDR!(A;xn?$k|Xi2~*uc z;&LugwzQ>vfd&?i~-FUG=E+g#VU1UqS4C-?^Av3v* zwx6^tyg4MB5&OqpjJ;Ve8rH+|VLj1TU_>wM*n}ih+SItyq1BH(JOQrgOLUJHs*6JY zV3>~S#ZB1kYo0wvjAh4Yp^*F71P8UgyO|7Bb20xh$b6p39Qtt(;h0}$GWhh}&yIs; z@+o|fZ7hzu=<5hseyYtbOBG9%sw{rzJR~M6k?C0>%}dSZ`K8Dc5@j;lFm$RFpOAi< z>2K^0V7gJ^Hz8rBo=2AvyBnvOm7w_&AcAbG3M|@bDOj}999WFf3=ZRBPNB-H@2Awf z{2(n^)?s?ivJTS1)jKMi!EPo)_J9U|h60`Jh%*y5&Q6$`kzV5uX&b3jYH?lS-PGZy19#mq9yX93{O;{L z-WxPU5;E>(q39F#xi-Q4EC zZTLOKVN;hg3!^uv%e&wev*_^vAYgKMz}AN_WPC;y#`PLuT{!dr|%b9e7(Chcd z>g#w#$1**cJLN|f?w36eF|{LNqfvgN4m)=B5QWuX;$BZUh>3*?q!TVINMY&xbQHXVl&oWr&~lt*w)_aRGK zb#Jd$sjaK)Mp7-`oShxY351*`J>{A>#;kq%D?fhsr~moWAO4G<|Ms7~`@#2q{#*Z) zcm0Py{_Ll}{n?Mc_p?9yLp6_T@{W8vy419(xfV@)7e>eK7ggWIPCRm z3y(MAfqhCTW5as)>;LA{|L42xJIagm)z5$QlXpM)t<$dO8nlZ>{-^($*Vf1E zt1lD&=l}7K-hKC1#ni`Mc-XK${a63nyB~aC zrKQxl;j=&br>f+{*^Bni&LXmv|4ut!-INF8=g6!7ZmgvcKhyI zM6n_KNR+1W<_F%1T_bo@+3DCrVzs?7@G_82u`<@FqIHOu+SUvBt88pkR#yQ!?qhbe zSW;l>-o3_PU^zhD#mD;mWW7U;Anb_vGDghQ(1m?}(}i9As=MNoHMi%J2QF;tkKAJ) z17X0ne&C+?YduHCgjkw`%{aV z5A*$7!>Z2>Pw16;{@zVkDI0%WpyN}Ye)l&dxty{{rS3TUSn3ulb!*>+ehg*k5w>6C zy8`R8pZqtVKCJTJ{N?vP`{`eO_Gfak^#UUG@F@jfA3>9gpb)kb(W?EdeEp( zs9}%n)drvXIScl;#?)hs|HHm7Kiu!C>A!Pl^=7SBI{mlg?AL3oSMbZ`|6l$1v+w^P z+bz+VE$D$A*wZzKwCo)QS1Wel+fN*l0zxCmwRl-7rKgmG61gFx+uauKZRE7Rac|$V zSvkNr336-3V4sT;<)FqQ&yZVWk3~H)X7MSX#p@Pq*hvVjG2@gvEduZ~!%{VoOk~pf zZ_KF|lt-?3cH$?rs%E&w`OB1b6+gqIHs`7}#h8Ev-@^XxL-$|!cnfiT?v`T$!ny*4 zhne26eQN<2T!4id7}Pl!)EO8^F@?i`8qE`*ST>GA$WP4h1;*mAQ4lWwE*j9VNxR%6 zr@WK&;!6Z|r(re(GF8p2de0nf5KsPe>U?1#M$%++vyni8zln zy1i>m-4;Ne*km{wwaOt5K@h+-zN6Q0*z$_DpCiWXLIJiV(XxBjEb@}!laLiDG8yP4 z*BI93ej)NX4G^U*f|Db@`9K?+pVDUrm1#;_;A$PVkHJsEvRvoObDcth5xB~d_lF?& z_$UkN32Ri}8?iBZ_9{7rVPp7_hIU#(3vj8Dw%n7k(LJimA&n2|+^#X`Y6Pp&V($ z>R!0kI*bZ&sRTxU_UHeKj`coLGclofe~lRMxtP*Pi)CmNr==?+Z)&CQ!3QwnXkAD1 z$AxGf+X{QMii?3pWdQjU>pcdoF`yKhI8~JA4?p{>-&MPl^9M0QPP?Ntg}U|mqMo0COa&C6QJ=fan*RJx{^H&L`7bYA z)2Bsiibz~E6?}^{ms?Y;;1*GN&mM>m=*J-?v|x?{4g6ACVCq{9azON!sUoqoQNDoi zOB*%ywdIYXuC=sHF%x{VXzF{F zUI;HsS$NUtXA#KoAzd>-MyrF^GYFuEf<@|ai6-YDOu=YBECK-;`t*mt^Zp3LHcLfw z!MCv2jk#dOFGL=y$1epO-ZK0$aOB$lazNzgj%Drf-Pha=*&aPDC~uFe^h+D$Oc8!% z2Y>k8^BR=r5T#9WIehM>>LWj`_%bm?-vRQy-lfn)6JLu!gHQMk12nWc0!?aP6ixK$ zppt_s%l6Yldfl}tf@tkgN2`aaJM`_hAOr4M2Y8G#^w&}1rXk&rD!1m_*RFr;LpS!N zztO{#Svt~r`XKH_rvDF}rL8FHZ+M~CWtW$)-mOI^qhwHRC7;tE_`Usq;NfQdYy01? z{i>Q7luckWbfSqoLF2B-`H45(|v9v4TO?amGF=Jn40 zDM-*29^DcI=cC?&2<10-eRwjdO4Is+n%+s}SX^pe8=fqPn8s5WRclh!JS6g!MZ8lo>^fS0rRk@zB?}kmr3f^5O+t%T=+#L4Yn5h{rqO`+R=HT@zld=J zD^wdjcMCd8Uys?q=WDeErj`9gzlHxT7~Ru5z^-M;eEgRDHtN9gpXP0Q9sF-Wb)?Rw zm}z0zBB5?M-+&g15^eImxYEU_3Q)_gU%S!vyW|(^UfrAX;ddSo(03Mi0O=~aFhWG`8>SMs!1^wIwW{SeF?pAU^U%TEA0lyFx z^k$|o;#>ht^SNVZcG;QDwUI`t*S`P~v`XBfAYTE!5-M~e8+70crLc5OjV+Ca4LY^b z@ufoP>c!3Hs!VAKRDDuD%QOdF6!T-dXV#A9Fg5viJKC}yHUT5 zsFM2KC>~duvF{z?eQf33M=!Jm{HFo{3n?iz4H*qWyb^Qhd#Qb$)H|_50f~~KB4CzZ zM6f@XF#ckP{?*zU3U^B*aDu$>HZMid3b3ve^fLs(geHA3h3J)8Tyy2Kph&3}_Z_+f zrm~T9-=QL2o+tXk(da9o`}6u2FC@nG3pu!IjQR9jevT*`#zX9q4)8DD$bXR&-j?6* zzdzBtfke+}L0WK?He-MW$Xz=5XKh@LY*MO4Ej%vhF)tdQGc>=0r~8wyw}5{ef2RI7 z=LY{lzVZI?pKgAw|NW}|zSjT#gZ?Yl|9)6H@Hyf?eXalfivEiAzaQVccJ0>O{MY*5 zzxRJS{(m{#|9t!h+`l<@?M62K)BOC+ulfI1@yDDk4-SrCt95WN@P@1YFmdgG4BSj2 zX}k8I+BrMJf89Nc1I>vjD4<;~|T##iAeANU)u9|gzeG;EA9GXWraxjgUBtrUH8dE@%Ejp02bOV<1NPUlvC zJ~O!wskF5l#fMElZ1pN{mtHRKzSu~OCbET=-!30tyMOnA+0?3^1l`b&=~Avn@%Bcg z*UMm$R^sDfyw)3xLbH*j(Cd%Wpp{g{H=8eSbzTn&#`$#1JGy&x=i`E`cCVU7%e~A@ zqDl{FYh`);&h}udf8E4JUUxYd)vsS~u4aI=)eHP1Is|~5r{Q)uu4TYVFR|Qy+Kale zm+2lV9TRLIe)4*=vupOR#d0e*PJ;T@-E`XoJ8%3NAoBj(>+3J)OfZnGuDpKuc6WR? z0|vSHnkp+7FECSS<*4#;_+lrs=sUhQ*m_V|S${Kk(me`|dMVG_e!Knj&P$ViihAwC z;p@k5J7%NnVsTce+O@L0c@%BePE6d&6}ow6<@)@|txW%b_UUkc{nq{G{d;$E{6{OZ zve|pt>2>QF=%zNDPey$|sT}kM%~A5&WVU*N<+pclG>=-DCJK!kmgjD)tsa;YevuV~ zMwuhjN1r^pxz#;toAi)ntu%L6j%&eY4#V!yyHl619R;_Z^xtHWi{~Ei-bpvQOSCdN z)VQ|;dY3}r-fr&&`_95QZFzZ^h>DfEB0xyBMS0pIVco(3ZHkVP*%!~wAT$XG!S z3kFrM-S*p$qPCwnUN!Z)JSdU%aQ)E(Yj)PdvE#ehquJTm_MC}RjaV;iF)DH@KZe)z zB%@{;`#wBQoCPuOQ=y2@1l`4J+jz8*sERjzcHk<(sSJI3$8Db^JK?9 zb)rF#T8&I%0yA2PXl;uG6FCdGq8LRgs`^6#EBK7ju*BrxOf7VL8q%uTgE5f+Z^g2m zg^#XW`R0|We|$9ZKT3KYd~fRLTJ=VC?mDE}EoW-3R=-uD(K5nx>c!|Jn6lT7ywI6? z5sjw2us!u`N= zKprAo`PF3D4^n!ew3F_I`)*xcI_KUF@p`^=!pdVD&IySkcW<8@hb-3Fgp3CCX}V5e z3Vpos1x;>N08W=uyN0(;)a&)xStGg5lJWLFBYn=f4clbAMZ`$_#FQ(Kp0 zH%NQ7GLg3&j0MSu3zP;VL2|d{+CE8D$eqyjw-l1YY!<&~hTIFY8FZJ0p?&DQ$xFnvBJ86U>Cn#Dk9_FO+1UyHIJ2|(TTOd@ zD4uC#EQbsS`6)^xaSI`Zf#HBpj6)uc={6Tn)AIe1Q&`w;sAiv+#5 z9(j*|nxV5GzE$AavaSx%x@#R6fKTca*nDz7asdl)s>XQJ7XE(#Ku>=$EefB709niQ zO5P3hG!sem#c!J>7=(#5_LeOY4)kXDD+Wj;BRU`FBBa7LM8C5$n_?m4Vo0jhvHMZw zJI5b&$=%c)!E7N4#6I;Qv{%RIpPTY|tvj#`Pw{@U6%TLwavZV@wZF4$?=4kc?mN)N zy*Ss#@|akanYx9QA4bQrGHEBWEFvRCX7dAAefedFJeS26Uq#`}X&YB|@~UCMB&4yx zm}x!%LBf#cL4rBeYL%iXk|w-yDRgTK;U~gpc_F-d)$w6YY+7zY1_Rn02}$fu*RdjR zwe5F;&=-hG%bYUB$%x*ziG0qNcW>flKF7eQ=yS}kD+)1?+{3Z4XcWfo05?r-b!AVG z06YR2S@av}guFIuP_SKmM6Y{-rv?-)tVi#c>b5x(=_HhrcH000-L}gSwcC%HUVo<- zB%fF#Yf-&6c!9+4cW4$qh{R(cbkQ+QW{(Y{IjyP2sm}0+xNOMl#*_-k5%DWqOIC*$ zi(OnM65;q`XYuVA{%t^uF};~Ja&*}T_(}x)CfIi)_-DdrC!P3x;A$ALkr5+Glf;Et zK4(Pt0IV16#p%6&V((x1j)RbW2a+20-apyD>U=~F5$r$u&J}pQviO}V-}&gHt{Nfq z$~VR(8kZ<>2q*#g!1&_5F%9;%<^f|5Sun{^jW^__?u-F2L`VSODuBZ=5UMH=#ewbY zyYXIKyr5J-QYBpU=#1mUrmkfzh@m^F>o17OV2g2?1;Ex|4WKvJiK}jW)ta&#IjfIk zLqY_g*Y9E#Rz9TV#hj$P&wSpWjX~H#lNI#kBJ{fM4v?^x{XVZ+=@kGa2Ji=ZqzKrGNe~tUw zn~$oD*#;e?EzJR$bNJ)eE4^Ec6!Wzd$dtLI>=AS8%nAXM#cSPp#T$T2xjXQ z)9XSPY?QS008i{r=EV?5p7W~VPjs0WpaBCG!-nBYkc$yrI_Uz9RWUUlFf@rrxR0|_ zWFDs}P*Uh?3i#~<%_a2xfW=V8D;3#$1USbY{Z0p1O@cBoeQ))BFB}b30zl-Jhqg`_ z=a!}b|9~O5os>+&p0tx9reQjjMP0j-Rw+UyK=$P`H0L!3Izq*jA z%O|R^GSAa$Pk;j?qd`IML1Hy}pC3*ALykst<8uy2REP155WVtiA@(8wmKda#uVW{r zpCPHO0vbbFIGIeS`s3E&L>={K}ZNaKB1F!{pd_z7)_Cm{YOKbXpz==!iqLd4 zk>x3sttt8JP>QA|m{Kfg1Hoa{=Ys8v#KqQkeeV!>6sN}98EVWysuKz#+{Tky)@hZn z;{|;q5o6LO?hW=5`X{N8%ll=dCAc0SlEfmFk3oZ51w{B=dORQl}v zb+KIR-L1^uATJWsBe2FG`vndM!)>t?)-&K_-7f4HVCyys@epEcHarJ_qc?nQ4ul%* z{npvp%z*b7$qz+aqeflI(tNUQ4#0dwbL6Padvw5CS`ec0s*kiVTv+uvsR~6j_-vDs zf^G6P@N$@a_obL7oD2ReK zWAJTWfX}HYH=!7{G5{*|I3j+a1m>B6L`IlIU##RU5JgXYh7o=MHDEizW6<+dXy{+lc7$rU6Qx2=7m>gTJh1A4MIoTf* z%HpsgpSH5_I9d=5!y_g^AIlp?uqkg_LK6aMs?dwac#mO%FrbDT1EIuz;JAR!$I(Rp2NS5Y(EUqH8C^T<1n)MoI`Ir-jb>p`!cmIs)7}}_W||`k71S4G z4hWRoN>&djZmC}G47jd?BhBn}q_5(oKJOrKNO-E>guybE$ul|6y?_-m(|ZAd&jrc$ z_7mKu^7iI#!6>HLRKSNse_;1Xr^}JYP!fmdB<-cO02+V>$D9WH8Udytx*$NpM^rmKZ5Ir)kA-*iCTA+R_c@^H0-%~yNwX%f$kf{r(l|Wf%$5PbYvnm<&&V5pp=$@ z4Fi_70cNmmHm(Ojx&GwhHB30Y;){qB~qj^>Soa48@d*0i=v~fU1nJD_TTl zS#lWQp(RK-Uh9x=>!w%nZlDxM?kcXL(K$hpnAS1VFuKU>;#VH`dtG<&fOXdIP zV|$ftG*KX=__L_N>#D};v}XWI{0|roc==zFA9HeLXjtp3&Nd!f)QQv58DNc*!lNNNzr`0** zRSJ3E;PKp!W1824PQmyjd#p*q95WFs0nsRSW4V^_-B>8ExhTa)HqH2eVk9A_o&w7; zKCP+%Vlr=L-IIIS4R1&eVwbBu@u2HzUw<0}+M9O7lCsk)w&{Vdu5xiNcGtZs>;dR0 z)7crkB=~}IA6k=mLx&!prfUw7wTML0)xeF@5yWatO7&W?eqgraYvpmR{l&Cl15Bp`&xT^WR1_fJA-mWxN4JRv@?W-~2_8gEf){tGkrr(H3BQ7|uqMl6Rnc5W%yp)Luw7O?Y_rP!G*@LERfy&F zX!bds4%-jCBrUIxxuxI6%nQCRiqdjiHlx1NXRH1AeG%kjpc~ffK&@JaRXp{U6p_=_E~;V z$?Z2p!DgEmWZCrlvhWl1?7yG}EhDNWy`Ym8^pmDzPm^fi7Y`nDN=MI)zKVy)GT9*0 z=n0}{;qj8a>2BpE`Dzx%U)d(WF}(!1wb>seXB(r7fAmpyy-`yMtIE^%HZ6w?Nyb!N6N zS?*Rc#iZm%Yyyo*q^{!H$hXq6fXFS^0q+ZQg`;jsAJrKDw|i|%XD!TJhj+h0xJb9_ zIPp8GCUHW;VCsU(f4ip}^GX^|*k=<+Ik$bR2ZXlMFiM%d9K}UnHyG4|QEUy8L48=w zi5R=_H1o^q8cPus#vr6eZ~udLQ+Y2mlAJkpke?=Mq$=0L&CPO92J1o zeE~^`V&ZCmB>O51(EIj*QvWosF6xn4mGmCRRHE9iFZ^St%EO576& z}v%G%sK&Vore_%xxv(ZonSj?uuN zL~$KPXFxMiZNzRz(u8$k}uR^Mvz-BP>$=2ei( zWD_c_U^A6h{7ZP0HV*MGj5EN+8LO(dD5i>Ux!5d)cOF($cYE1dPBSZ-2QI>wjmFv0 zPR2q`<(9||#4J+T;?>TObDDaX44R6q$u>843M^60OGG;^l6&yBd{3<}g*&y|yoINA zANo9BW+$5NJo2Kqrj_})=6>mWR+_(LUAg}u)24Y~u>JXl;9-Wi1-FADvD6P_KQ~6_L+WHFEXy zy(&x@Tb)R~pC)YEuRi7Y^){-HDKZb~082o$zgJyz&>FK2bAt1U+_p*4I85aNH!6wq z5^!C!xZg6oVxqaiZf|bV!7ugxq}hXik{ZP&9Y^vdHIIwIPm`z_93|1DFUR@OmtSmejfgoi{;&pp34U@o1|}YF-akt39t`I}%7g%Zi9f%H<{;X1z2)Bt z7cehNU$Xfw_o#h;G99S#Bxo2PY;>c{3CIQl@R2lY8!fwSIn4s7*K@>btxlq`O)aa` z2Yt;dzFIayq7g7MVY|E99PC4^GrPkE3)AlG13GQBDwn+*wCT8uR!d?h^!TQ%yn z8u&_1#0lYc^U6}e$^j;-!QQ`* zKtyPH% zc^XrcEcx1P|VZf<&$t4A=3^DX;Pn|_|Vo4W(H zvI;7QfZJe(iPKm0{@1gV0|ZbaN^Nb}oHbr6c_m&PKV#KtF0-!6yNXyQk74)C zS53X5ON&60>DBu*S*u61S(jFZX4$L9k1QVm%3ga!W(JwF{iW}DqOGB=gObIIJR>+A za~R#MKF6+dTDHVIPWp!D!depz_myQ+)IyIS(<7d>Vz35v?tHbYwdq5V>3bNhy3Od$ zyB2bVt0t#*H43Lm19F-df51Q)W$Gw#WeiY}CzE`ewZ^yVnbaEO ztzY;3l%dv}s%ri5w)dDz9Buhl`#J$$Qo)CsNlClku!dn17}gX!yAbK>#<7_verZteNJrL=jG0gUm-ZdjVwh;iNw(5Dd@;*}2%O%`?WWa5v zPYBJL#jl#ynUqD;hJP$cH6ib;)Yr=0%Q%C?+kLUrB8MUz1f>zY1F9llY+%~)bpxMU z{sEN(HG`UFKIGnX{oTt7{sAiU0TkE_#F2I-Z8;#a%k$f5ORCyvNS>}iG9-htV&XM! z7m)28^tC>&WQD75!n(|dHreul)C|P`(9nLyPO#rfyW4l|4?EgG7kB!{>Qp?Qbc2(eDFxFQKALA~& zNDErFVr}b8J7BOt6A`0M*S3A&xgWP6zY1||(kG^-7ka9$z70~&|D=|AD;}8+&zO)g zsqbU3KEeMtjx60DRh z?nj0|(cUF|SZ@5ycJYw9S&lQ~3L<8#Eh9Y7i2H920dG;X*M!`#WX=M`y^m$>X#4oTj1wJ|e&bF5Vs z#-8h=u_A&!*T+O#YE{rM!MtAkN_h@frt?SEXf~ftvouGHlZx2NY!(sUT#T|PSJvBv z*vVu*#;!RmuZSH6a|nL8AZ~Y;7^1;$L}%h|XjEhJyFEB4f?d@OBm4>zI%gf^Ox<8O zQ@HhsoStSaNLjh7&n~LL^j{dsv7nzK_zYgAvqkWKFvTvo!6ZXZPrae~=m~cyRHjkr ztain_o<6s03F=*~3BaUQUik<;royj&<;h?|1L}p*ZU8N+MlsDGjVA+q27y}WX%@*> z@v&>s^oa$eXTS($o)O0G(z5o)RXq27o$#$peW!-A3u-Z#xmT&??s7@rxGMnbRtB&e zr|L5171$nhUu;KZU;2URC*tSQK;3N&zdLm=ot$gmNOilpd24;tei{Y0#!F4j#rlr; z^2U7(z7~)}UZRm*xl*vvWWDxXJbmnJhNf^5e96P;ORjm5(^#*|2K}2`j~Zg_=3DH) zK*R+t0kDbr_+>m7hbE5t@bGYhn5u(I0fFF5U4g`@=;Cg8bpdyjD!5d4VHhs1H#e`X z%Jr-@?^1RZ52-56;g{UVPN$ojcTn$g+3+H|Q?rZyU3{sqL=7#`6@3BYziO?~*>Wl7 zfV!_%lm_`qaLNK|C}*dTwaq=J!GvKc2~I(`N2h9r2_hu03+{6ss%sj(xR&|W5W5B! z%z0LJ8Ba_?0POeLTG&|6xrL#w=0&7c~bLIDunnMsb40d;2U3PxUR z8z;y)jP!GLFFw{(pMRz19OdG?h=%yWdjgn?@0I5)bHHX7F#)czTzv^=k~WJhOn*?s z#lwTPSye%yS>1<+?eJCHc+4`}XKUh<5dZV%ig8VdZ)rTks-DNgU{vvTzj9$Ps%Ovm z#bI^Pek+~w$Jt6U%&fSnqP01I*of%@SN&$nflu6OR6?S*+V-E)T0N@17eN~f z<2op%&z>@)a4iVNc5!9O*lXWY4PH-~Ybhn<6e0qm>0B?BDwnGPYD#uvv0ggc{oDaY zPJmI$VT-;D^9%h!_eHO}CV9APq>?IMWg*Jeus6Foc}@jhsTWT@jtyKDE2o|(bs>ph+2f7~?9 ztd3s1AOQGWZf1$W~)N6^cNDtPh#7`S-4D3*VSw_t6(1?Mr*iz9q)?!5C-nyvKZ z+{{)=vxN%7^nxa0qDG;Z(`XCuIyU~~Sc6;}%Frt2l3l#Jt%m&Cg;>NG%kkwYNSlJF zt4bvv4S(A8rkkiS?Z#RfH%y*}k~RmAlub;VLqOCwh+P3f0{Lyo9JUKh!3|XmH)*gzI`SXd0iBJx(4T_NP|ULV$pvXiUuxG|ptoHX<&@bK~6Y zy0|_!#WRqHr$%)z3xgOEj(2IE6|Y{s>e3vXHrV=kJkBA^CrBpbC4OtAaReVUarLYl z6-3ZmZrKR}=i1xCb7u5HIgO#RFmiMPJb-zq~vF}r!D4`bARQI_AHO_Y93t6KZxb5#q-f zktJn#K}1&7Aa)QWLcDm->2D>PTKj&yt-jDHPBUs|+v-u4n36AGf_dv4RxiI8BSE@x zvs1)his4~((J#E=75?zd&5uM21nw3t_WtfPWcE0h_>Nd^juvDgaqzqwfndfnw@H8D zSuw6lHwcP#mmFB+Af**UQyxy4B~1Q}jNj5m!!%#9s@bTR=_o_K47w#JN`kD?T3z)^tR|HYbFqabUi~6&h9w~iTGOwY z(OEBa5J;j9<`OhAf{8Qqv=A5*g_l?%z=A$X^>XSJGRH)Pjz*ZMa*~Sl(AlsPhd(XT z{5sDnSZ;(Kf#}qN)w(&b3=LM1phR$OhnUb1la~gp`iTSBIxdHa42>ub-I+M2GHtl6 zmDe>Iq+_c;frBFUJJDJb>cEdg4{T)X@16 zD1TlM&XQ=uQC3@;73yhiqCGMar0FP{tJx?@)$D2%U8^!$P)~{bh-Ru4**dyW5}5Ri z=+kJbZ+6Qm1`*vZm+?s5#|_~{=3i=F`$c_|%(8t#WW;%ML}?-pi7ig=sq6V@*OcrM z1gZz>iTv}OZqhp4u~d^ z+lU`bXzcaXuu9z^2^_o9C(3+pQHJ2xPfgc`D@o;t3 zcLxDGHn#y@?Gi-lNeIf|I8Zp+3aYY^wWkXP~DXGp;Xm2_XxnHq8yw59Y=z z52N35R%l$R5mBI4tPwQp4TjWWkga?XHZHzuw&B&%73{Z=|-hbM20PaHg*0 z`wXT~QmHDNmbaM(ki~ooW146or4g~<){4$UDTzZVwGs%32yz^xL?h)dHDNlr1-3(H zVH?T}HbTgh0lcG$`K4o+wrVg5Rf0G#dbG0`j>7~h*Y5DY0a^Ld27|kCC*w4(hc_4f zDwy(sW@=+&x(Wm!;}wtZw_M%ar?BvgF1wzJ^*1B`SU9v?(K|FDbW(%akcfRSV( zi=kw?vI#T*GX7e*$(y*z8|m4Y(`=|TW|I;BFJ=<1X%enq zjx6&Efl4qMUW*{57xuZCTEsIhZc?{}3@|`;uFtO~$UL1Wks~0Z0d>255>1qzi;;gM z&1p7MV%n?*p632m-K)EBacdAo8YICqq~aPs#!F1ywvf%ez1Ili&Zy!a1^5Y+8veOQ zGdFW^Fx$GE%!tLEZCw*l)$rm=WGcHBm`%tQ!)uIqQxOu} z_L+!f=|AL8XOb!6M=9Mx(qW(|bD%uo@c2)pxlY)|v9}f*Jo2J>WwC-wZm&IuKME=n0^LnO&tNBa0i2N^Ur3hc|;z4q)~&m9U$V zbp=Yi9?7*46TO?ppQdiN-6U-j!fb2Qd}T2-9N*`S(-?9j$^okh>8L90`4fuj(3@3$ z*q#T73kbEA{jl=atP7z*P1PC^y=Fm2=Zr4z3V3lBEK5)(f`#5Gee|p)RQWPpr|#P; zT`)bBubzusY^9#V^XWyLD-g^a5(*NDT!}BHG`m+g`h1clx7qF$HT%1ZYjMfxaZu`^ zw5b;)XkkePgj>(+_tXk@Ki}v;`TBk$kAC44Cy`bkSe`G{qz2+@S<+^k7^+X1d*B+B zXU4*2zln$Yx_^pwUKi^3DOfbZJvwtiGMm8``gK_%D3O{$A6=v0rlc$++W#Jg9PK0A zzjwKFO_!^WcEJuzh(nPwc^^}dNr89C@2@_3^!**_sJ!K0qlNW{ej7`5IBf;0=O8R+ zRhGHUgwQKxLF+m=M8q|F3*n|Z=^B>gby8<%RZ`TsB&Lw_<@YJ9lICznU(}PbsS)wn zZnw`3BwjH9#8~b6c1s+7*2_3y z`Q4U!JHa8Cux5zybRFvY=bNMvCzf-YCfI=*w4$?*D z;61(0ScgM5WQa!6t;7PDFh;EkUBW$|%PUV}wAEk`RJp@zv4Sl@<%&{N2XNs}IPZOkmmkYMqC^2J)!EEO0EIS#iSwu?+>?RO&xeuvAVEVHl>PVfUhcCY<|y2Km9kmV|!U zk6-O;z<3$MKmBzOMhC8h3m~^xtWT}Faq%mZES<6{A!rutU{^vw?4s*bAj*G%P>kpm?@NW5VuEH`J=07SgtSe z^PW95JrAdLkRdTGq=Qqa;cN>(OXQnk@;AzsZ6B4fxC-QH9S`}30P>k!^e&z~d)9g0 zQQKrrENdg>)B5m!rlMq_k*u>Q29(p1^zpU z%K>Xea<$0mG^}9Wn!%+OG6EtX;~HrsL4_V=1AV<%Msh_oJKGA+XT%&gnYsp#Ks59$ zPUVI9G`fjj?PP?*Zy529HgvK)x`-z{%}-NUJN%giqcEHgM;C$tXLL(boSFhBZ+*)( z7bD|v0hO$b_$F@pH(P&#v>ei}evzY(e?^#!@EF`it-3umOw|0M6*=(+xwVLFo5pYP zi_YFWJWLJJzqOXF1P`Y0KE4^u5I-XwPA;N3{!E-*Z5a}P+=pyadJjRpXKi;MhWA@G z5*lJ%-^*_Es@;TWF@{+cKEks>_b6=j{0>$6`or-Z;WL?n3{!9yYHQ4!LBP!=t;Q1C z`BQ{3(Qha1x8Vvcj&yd!JPo%9%VP~FGw4rXUhuq&Qn5QEI`6)raTi+X^c3fqx^0Iq zeVY0j^w&0OqNl`1z-rDVm8V?F#xadEg29LG(h=*% z`AX(iG~+rTec9LfQSJN=55VsR3hN47{E!(P^syH zkVp%2BgS|HARoG7!U-3C??Ybf+3vcqj6X~44ht6E5v3+e z$HWcFEfWbY52Mm-MU@+U`aUNC4fGNsI_kz<-C;5!221Rik5Z&}daTj`xe8 zb2UYzfQGMend{;?UGGC2r_0y)m^zT_su|PGk+~W9vciwzJOOy4 zo^`U3j{B$Qb{==^QBZf(aX0Ryd6lsdeBJR(T)M5(IiCmRT#1`6Y?Dnyow12h0?_K* zJ}=T%XI}eW%#uaUwgiiJI_HV_7<&Rlc%axbUXov$p|xi(8Rhp%U>EWaRzu z@TYImTO(=+dzWYv6CznhV61A6XQku&jbe6MR+&V*3w0Re6~6{c+s@-{q6RE&JAaw> z=g?*i=NEAkOa`ODQ4(<6);T;A%vFTHYHmUuH%A4fg*&4_;@Yn;w28bTlb-e5(sJ8a(es zKYTB_5OQ%D%n_gFa>V(`VM^8w6`HZ$`t-aW2RVm>Ce8=nCQN{w>qsa(iRE+5bzp-c zCRL3YOd{Av$-Z?K`Id9ZqGkR!$NT+}mqvvHhv7*WFmv1vCxZxT{!aEVWV8iyV3 z1XcpFDq{a$(Zt!Pg)MpF;%USosh(yNzL0|&MEp}IyQB

ZzyDDk^V^~%zf;)cp@%DRzvN9f@+*LS#5PfJc#$&HUV zrRP)H5+j}RSt?#jN*E!5U}vqx8?g%5Q-ip(Bu(*uCc#6p5u~3O7*T-Vie2og_ z_#ikGVns@=yUA5Df(g$q64phD0@%Rn!k!V#bcsfUg(*4{_tTCupfJH!XEwHasAH4N zBsR&Du8AK4T8o2`Jn5OD(iVLmS;>tl`py*PRAjA`vI)pPd*b2yahD@}^69E4k|wQO zt<~dLq#VL&9Cz2a{F23trj*tQmr^SI&`Sh=IF z#TUcK{jq$q2Ra-U%IpFO*#(X6TpyMJSlY|WQC%N#dR@*bm&Pf=+nVOmf7ng9K_UPW z`=e;bW9`F~XSHIs*u8m;+2?>w8uG0F^@5?P@*%8L0J?l>bB{ft^}m8w? z0q#B63PcU5TF2E?BSR`}5aC~g_|b!??XJ*!wFcz~Pefc0;gVbEns5=1+Fa!3s{H19 za5EzgFxFcB5p^R(=&7|F=+60`bweA*y#8{_n+FEdh`~gHHg!VhWi8EUmpsAqF2>^{ z-}4n@`$sNhz?vrst<5k!&nbg_H|a)S#(50y~(Patz+uw zwQ`JKw67f37k$8~!~>PmxV;w3o$2#>i27amV(z|}(?`^=F@JMlK89!( zD8YV2DgAg)?h5r&yrFx^)oQ6$+iF}X%i^~IhmQG`gTef?4EvfUTiKSC`luF}bv1?M zxZsz$z2^utAU=%iEn57`-N%0|dBpYVSnF^y4r+`N_7NW?CZwB2mT3-jfIaH>q`w+j z^J`UepAY;p%HxyX#PK#In5PVc`*lUbu)x7^}{OV)yt6kABdb7Ft zF=#LGhoBt8gyKVxHtHI``B=^HyI;X1{6kQT)fB&bU3H7!k6pEc-=i)JuRjC_U3G=u zPno*I@0(oh;&-R34p{4Z#LU~#inrEB7?Uxyr%W5_YhsDQ{!wcR9$!Qnv09jADrkau ziGG#q_*d0h-=)oW^X-pbg8PsyONZ)R!2quBFGKa(NWrPp-<9(9sIJ4%I6M zdHfNlCb;5<2jo7|Pnn8nKQ#BD;(*M9$7dUI$$z+Ia&N~Z_BKxFc!4WtoO^PVv(%i) zWf7P|3gIuR_*JF7Ne!nEW$l6$1KY`XMX(ZAPFUaB>V$*4F?#}PfT@_)YYABp=sIJB z2(P858OOV{VhHio=F-e=q5hhXLNriL0hPzEsB5|Pvi3v~InY72C4SNshORFyTBH7$ zvXy?RR;Uy{ls!8(wqIFH^RGEkY=jymKyy^VvcnXYO0#%E`Q>^+qlFY7Wz{%4ZD6E5 zCQ1tgAM8FYT}Z5dt^>Ap>mhLN)XU%b8p_|1^2=7h+wR z3feP9`X5pS7Mw2(HT2G-k?7i|sG_UJ(nECV6GU`t$Q1iTlSFiHXd?T_kUp-irhTZu z5kGjd@S1fb6jy8Aw&@)zSRr`Obw}CIz&z51?t}t7?^+U&=19|kzxsXydu?%SGsp%u z`m~b_HGx)CdkHzTue$mwYT>mfO}LC2UwQp&Te$wN3>a4{`r9JExzy80o4u*yy-9Mx z?SA1*3*Z<_`<+Pgix_`hbz^6RW~v#4UqH$>rSiR>i^o| z*@VFPF=mcqiO?UH>CH237+Z*eb)N|M3}(#h;{?EYmW-qM6eel(ktsW=67RFhoJK%( zkK$5#&DX}if z8G;a`soI3Mm^ceFgnp)Fix-#kJdJ+pK6|Dv?JPx=x)c;!qO@odB5^8NV!nsCBUhKs z^B7NbEVk%FkAt-fEI721YuN;gfy~o+NCTVK8PV#r6H;4QGAqt8{5dl-^DX`ujT9z8 zaCe*|z>+Q`HjZCs_Gr$ocD9av#__8eCh%F?D}fS60?j#EyDnv2>0BaTo7rJgYV&?N z2y&00g+?eZi;-1?6%i77jzcP)Ew*$GpK0TtbRPF>At+YooAQS=H+EPQeW#}<{914 zjw=g&5@Lq!>@CeXNWcKTfS|la+Xj48_Xm7gFo^d_&+yt6`h5uP?rlwKzGe2bo=@64 z^WxXMxzSS*SS@K9pH%&mDjxY!+U8zJH+q&Xozde+wlbBrqb2;FSzLUhjqssNw;i!@ z4sM*<%3iu!mCn`MdUwSD8)$&727GgckC_+0(0q%fV(?>lcsQ}d{z)}BskY26wjYVA zL8ZGR7J9_-Y>hH16itkSBbafmmU+#rj>%*jf|Nwu-Jg^;F%q~4C~?=qcUt>O-u#Z@ z;E*c9)FY5oVA`{pjzcvYLwWUh!lVp=jMA}N=`Y=N zGMc7!0msX0LU(;Fczz>{Vmd@{L)zD>OFu$nPawP$*7S_~JI2@BBLqVn6?zDD)kvNcm zzU0+5Oa%jWo0XU}n?E|$u+jEuH&_q`F37|qfuTl!Q)VjLi4gR>*0s2t4Ww^cxA7l6<_nTPC5J{d5kph@sD5*LK= zoDvPVmY$n#r$*DCiA%Z0uBakG=jlyfPM3;IvehZp7|DA%y{AVy;^Yoi@l_q<&;hgBQs7Jgz9s~C+74rJMCRXph<76wQ8%eF3S#?~{IS1l95me_+ZSQr33 zN6D~@5{cwOCmQ{}tt8o3z>)fF6dap^@f#U4rsGEr*kbT|H`4CiO!O<0WnJ{G?x3^e zxlv0dI5FCUIjFi;oWpsX!elZwy7=L>nq9=ylT?C2^bx%a#M%tWT}(`_S-9*&lGGR) z*2s`-fsy3Q;U1s`KZu56Z%{6EA?jUw3$VU{S+-t}It+KEwSFb6DtW>0IR(!@fq&Yp z*aBgHCk+_)CD;o`nUgE}>XLa&UBYimh*W;tlwb3%QZO)}wx}=Hw1dCmXRu)9?DaAd z-s&2@`ZF=5Lr)RKSlwdgWe_)R5cf2Q`P8B8sM3? zw?hjSskI~+#lu9^R%WuYa3gR!&W**_jAovOdgHKkF%^?JoBps7iO%ZQxg0fL&Gl0v z@~7hYc#VKlr#F3}5h*o|!_1GY->!h|)Je4ZtNi4<1PgGR1HFZ{m*_v)%@ zsm5ckMb9o7N>~R;rB)aK$IX$XYUON!tyg)Wqec@fBW728*k#dj<;2=~8_y;&buUMP z!&0146U6km9j`@n!YDv9vYC=Wxco-3R9F58*2FXcA}>;(D@JD!*2FBVS*oAAkB*$b zAw(*h`U}nPQf0l> z?=OUitdv-0mBu6}2b-D163w)Fv@bRHr3STAv&mZy%uXGXn6Xhn_sy1uGR1uYNF>A` znT`dzXTZ89A5m9K$$g|!3v+@wg=s_rYfCL|v7}N>y!d?+PkZiYXnKCWi(X4@gRer+bAZtakfF>z`TQw3x?=6F1Sg(5^rk)=?Xs?;YzIo&223&&1|B zNW^k3X11 zCk9;Z0)9@@(W>$~i}uaCJSqa%gpNyF%K&GZZFUEjk5|RR9|oVl@i4&C?;jq}LJy%T z4{eOC$6g>P2$xrxkt)6$6nQ|CLrZZ2LrJQ+M~t-E*b8aS%;?Nh^A^1$wFMH?iI`H| zT1auLEddpSJ%sbB>BriX%5-`Z;k%eCozS1Yt4Al$iM`p}90l)`*9f85`_@PG7YF)_ z6Yq-;53r0wy~uuOsqR{@4^gO2bP$(|_$F{4*OS?*L4!Vx14LFjF_D$_mZmYFyLaNA zf(fLN?DfNU@im|vP>*T^;_v#E01q{`yVjU#=yBz-9b6n8^~2M%cOid@i@BU%|K51y&#hLiI8Le)jjAi-R6ToSkk;2IQ-^(Dtg*85m2-EE#unLfauefPyTnAXiD+DC;X^$E>V3`_~9dJT-vBDD??Eti0jz$jj z_ph?yL3Tj{q0OeN^2@rKE;4nsfdch4IE?AGEPlZ;{@Q6ysr`8o9AYDc%e9esDpq62 z_T(gss~&CJF~Q0Sy)e$U{3rz_#}lHi(1Oxu`MW&j`r9T@0AZ!<;o*)|YdXfyq1P9s zo6c;bYn{`YSy+ahWbAz>i_)ZCqw<86AObnpN8imLwP6C zsq^@q^*zT!(XKYV#&<25_BjBx(R@bDr|vcYdffJ-_D^hJLf zXfRpZeDvjm)h|0zlqds-+7S!XNfv3{S0jKP%BH;HL%Pf!=rzv){09wHDYh!_a0pxo zxabr1mMP6UUI<#!c@-RU;KblAi^nTKElPiZ>bs1(zXxEt64;?@D(U#<0XRZTlSHA= z>ARH|MOM8%J9^ImXzz?vooYa0u2-Jxc=sB$f*Yrm!U=UaI3h%{8Yc1(3yH}kWJ0(y zq&*rPO3SZ+ZDdvmnzH{6RJ?(+$-QvVogjUA-5_kd~ z;~R}9%rkt2Ui{wFshfMSePA(gg?e%!^{fE+1dH{k?Dv2mwSSPkJh8g}gUkWv9<8~c zv%HDZY`6z!w1&EhtA5Zl5X&|X4|{x+S?gr(++Id6QEdXwV**#q2F@vu%-HkD3|e$>L|x91w~ zewKTy*Xzi1kLL6^G*?HN$9J9M0NV?3Vx6;|fD4X!txiCK5^|N#yu-P*^5U_P3!ks? z?u0mx@h&h9{SqEPR&ko*bN{52k0ijV{DGxA7?0(tITrWyfq1Nc-(jr#-=?t)86@aV zD4&{R>|Fuob@WC&FmX6J(q0=Urt3J7fXv7FOtI|s0ZasiK-NiSEL-S=q{o(M8w3@d zkW6Y;AoTfar$rVIFR1h?YYzrg&8uyo1mZ4$w6I%Nx-|$M!&I^%?9rg;TQ_6A!8;h_Zy7@@%d;^f^}bEr zqUb4ql0X0cZ;!VVrxlNPdms;h5+pv(3ClLw6+j!!B3mHb#(F>@9MLeO!ECVy|C?;a z;7#-Nj;fCl*r}pIRQpX?B5PwN{K3nW~83_fv96W4>g@JTWi z?x{~Uker-JmSCVKObNxrSbg~0MKB>PPQ36^pv^d(EOyn^92;p)+d~URqLWN3I}exz zFyvPe-;ZhCSbJE6i!@^$7MM=e=PxEm)f0u&6Y6NzMClH34& z(;=1OOzO`;qvEJev%$B%LcOO#gKD2o*upxX=je5VCyEbWIU;WW9F5cmuDSrYZ6BWSzo*)Gv9BLX` zb_}})`|OtDL?-$K+wE<`s~=+oci(1-Ql>HXyvbeU4J)qrhm*Vl8>QvlN9w|cSmNks zV!FvF^b`~m(K0sZWZ~9CmdViJlR^X%vIZjWD6qQ`l1!%ifT#N#LojNcT+9{ zBKXIA2R|*zBJebVl>QxR2NJw=P=I zs=`k|+Zkh4Cs3dw&X;k|JCzepsyE-@_6USMV&udpba^6({%|r8M9K7sU*%CEV_$Y6 zGzY=f*sv615jie?A_^nT9uW=sm|p4wAQr#=5A`8e_;EmU&0>|)jH+uiL%Rk{KQUh^ zup-wO-0|OUbs{qFvxf&7{ZU%rR3Juj^Vpj-!M}c+=s1Z_h+-}YeE0C{L76=N|J7=w@>XD{CpKudcEI4c>^;L21yeMLUm3@$g`&e){%kMQmVFqZ^k!Q>xXbjQ+dankSpnF*B2pz>{6(EI^U*haq@>6yP|qJnI4VGtjqOOhT&ygW z=(MWi0>VN4g2mVtnYKDGxr1&&)fXftgNK-V@Iyx*e;O^ga|;*cO_RJ@|o zeL&MPDk4nLHqm6<=BT)M6f|jysV^pBK^nhp24#<04Wm-x)>Fr4soS=PG%K`g4_{$s zpFj6!E_-&U(COia=0e@1eDSP%p{8k!3jp#>N>KdpKtCS0K~ODAgEZ0_rbnP!rs)cj zW(tMoaQormkdXW=#Z(QlEHKr3ArWuXD+oD>5ORo0*2k)yX)B!zZ8vGj`sq710BGqS zYlm9)f4E5}6MgXHk)WJf_vd0E9?HQ7!mOSlA?g6l3mlo%oVd1xevdhR(^E{iX0C~uGuE*?135a2mAvv$HvvBxP5 zAk>M>7ni_BT=5**Bpro(bMxGUC#DZkRcQ+0e1yIQtF@$~bTG!#6XqF+2hcf-p19Wk za++RJ%n-${v{^`_^|-OK!`p)30F8^G7;|(C@}s#*Rhc3 z`FB5l|M0N!4r(a~(U~SS;+0REo4?aUEx$287PMJ`o<=Tp7OY^VJ&hW6*Wln>wp!Pb^~DxQ%qWzBBA~84%`bqKS77j!#;~P1C|4w^FdMCeS8iMsFtH(RZISN zD)L#jD&ksP&8IYf%uhz|6mf~P^EK{AC+TSdIRY(tBIm_{FqQOk$PniHnevM4_jr*&2dU=WSIUq4;y!qfVDM!5IN! zZ(>deR(Afvz_YSGHtzK=kDYqq+H%rf)h~EE7CfwF!1plY`ZOYVXUWfTytlczha@uA z261prz!mK+8M`!@b5i@+TN)?By=0DgbMvpjT+1#oY3!q^eV{Xvq7hd`P$xtrQi=Hd zCPSQ<3}h5B)S)C%<(?p_XBIDJ(G=_c7cDyx09Ew6nl{mUH62AeNRs%5lO!IgBKk#@ zQD)qQ7CPd|&o+yf@m6FN|ITa{ed-23`wr^OB#`ZFgjdbdkHiz(EQ74n0^-ZZg}9h> zXtc4PY>=xYMNz_vt1O;>gp^;rcA5x!6du`{H5 z#UQZBlfUaJ8{PJMLY$e+SNq*}$|bM;w;w^a=leni8?+h{$BbYKJ$qdNhC=T9M&s~@ z7t~<>h9UiI1F(nsZHs!ZX>phdR6fO?~U52s$a%L z%hoN!weOHDJCOEbVw~_Bs+uiMc&osQ+k0h0$O0NFk^`cq*4v$xYxTOEdDXU5yU>2z zS62MF=N|l(y>@^E36_O8MhJ$l^IS>xK_iiLfiW?WNdH{Dob-6KYoYx}^YNysd$f?sZ&Pds=#1mm)&jER%pTM1rU zkG>L#l=S#m{g?%L&kK+9d)r%ngiWP=FubtV*-Ch@(cHxEa$z>ic04k3vdE{?EX{QQ z!7Q6arMjNb8b`S*;X<4&<3iQi)tTcP!_yF%6*o8GnADk-b<=Ti1n+X5k(@Ci&7L1l zu!j=}3HB~IFhZvN$Kma>UWn++R#4)*)$&oibgr~G&0!ilBcigNPcylmCG%P?U9i5J zhVTPCSmI!&B(8p0uxBl9L`KFQAF&zNp!S5zGx{!(0#qMqpUlx3E+d`?9xTW0qtnWN zEzn7l5VjowH-j1CL)Kf#d<+}l!}5w-25UWY2wJ87eGlKH+dPfuEcK?#c#c5n#-rk^ z_zTw7;{Fa-a()-qKH7Wxslb-Ph;2@4j;4|S=*>se< zlILQ50G|om9@#A3+3*hpinRrQa5QBq<7mo{l)tT3%H7r<$qFGpgi(X#Dk!?lOCK3^wIKPA(8d$u-{bqR00~XbI3e?^(UP9 z2XlTxAdL*H$(966_A0)=4?_V6vfIi}Z#;<8-OZGy0%=v5*kF3&Y9#bzy+Uac0L<AcH(Ms_)vE0E68iRSPpHGe{ZSGK}z) z*BNbTr_2EjtQXYSWNa_K8!k%Yen_O`xDIw{8zPCe7|XD{|1`d50%LP?x)FQZZnqUf z{4P{pKB>!PeA?f&uz$JvU*d?mD^a%3Ip51&eBBG~5NBPTA~)8ly3^t%>xNn&?gv8E z>k7itavgQb&tmx*$d57elK1G;mtUfvo%n|nO`f)jFyK@51_^i7y$O=g;~bw%TzWQk zh4{w>%vy@sr{*)ozVqQ>QfrUefTOL7J9QzknOs!XrMm8|dB5o(Po7&FeQD!-#DQ-% z;1I@}=071jyoK!VUR^Ld;l(6!$a2=EGqlZr<;iJX7#9vOtaO!D#&9R9ogfYzLgn~A z;gX~o^UPt=-K5&c**T{Mp1jh$&SbN@-TS`vc;clQs|FuI)Fg=evaylKV=p{H^Unh# z7$VJtMo5O&7woW(-~=0l*+z(ZU^=oiPSrxu&^G0O&>J)^;t{n+9pSz02#(VU5MVV( z{N8SJhjn{hFC0m_O7Hnwr;(^6y614|X(bwHXKLNpULmh6e_px~=QKod(rgF;BIErq z*CQ^l-|UFjyL!_Lv~!|03lfK0i`G*rl zy;2C%#;$YdeVwiFjMrm`)z8Y6;^r%gi)N*GH09#SD&vV1E~?Cp%lpS>C4|m3^_Q_N zVQB6a)yO1&*U(Q4GA8bh>#%Ha75Qm=NaZo zhZ|?k@ZsUD{76{BMIMc)F>3lxYV?{=P_c|82AL-&io*@5nE#K9+?)mSr|MoPpx-RP8)!_e|ZNbnjWcMY>* zgYP5R*}cf-<7wf+L@=Qj*0a|}YjZ{qv$099xrE$EC#Z1|7aPk-w_@O$cvslFK4iCO zLT`7OB$9)>9%!Zo@~A|JH}pRv4RIrt4~`gfj2Lr_Eaqt7NR3zr`M@x6>+1N|`xHlSYdMGGUCLcVLG2J=X6TSx*PUiQvI7@iTZH^B|I zxehBts2JgbpLYk{AEM*|f7*zp9Ug2A%dDNuKFVo@V`hxW0ms{`>Y-szYVWXdW~n?6 zCwsYeH#Y%be(qflSNmypZAF`RSNTR#nQt-$EY8;diJ5zC+XDp3P9$oCu8pkD@yUb; zQfB$)YNC_;LU1;B8{tcdbM{GxaND-aO&gq*!fs|QasPdf4`od_*}%bpNti`bByS=3 zrg!IU*23Sc#t+gv{;zaIn*sCSkr1w5Z7MFSgbE|ZsxUIl@G;Y2lY!eOe12J!pEN zQ;N;kf<^-4$f~O(DA>lDP+v6V*0>@?vZ&c==^q!K9FfMa2|1e6l(*z^j*h(d{v#Wmy4)wLtPcOWn_N9}Dgx(=I)YtS1|6aH^CN(tAmVHH>Ae9Ig zn~1_IfhXj>gtGUd*Ci|l;kZKiz(ugmRl<3zC7T}fL>*HNjamVd7e9Jl*hrQVx~Pxo zvTS%exe|No2rXvGNMn^FctY$F_*!cm_!?#kQkyM$AZ5Rwvb~^|i1I^`tV%6l{x zD_>?6ef0|Uz(gbAHDJT_9c68qZ`DfV!DkOuVS(|HA!q19M6*Ykmdze9??IL2#h@@U4Z7P_MrE#93SVcAu;SK@NN%k5N^ZRj!zj2KX+}hTuBP&8w4Ra~#=3C`S2G<(ywI1~7;hni4rz(D3i3k*oildpB((NgbcfBkQY7rR>fGg2$4H}c7E2q1f76($e4DUP#q$so)8EYZ1XFK8Brlih zI2C>VjRam^slxcrlOtcYg+8l(Y~S+|+<^mh?>y~k@sifn(T9FUu7g(i8DA8=GoiAxg>1C_1-yd+`B8W#{@a|Ge3XZqtS_V z#xmANGNucJ(tO)BgW(+v`iG6Fs?bkRUB+2E;acT@=M2M)H2eHI#{wqtaSlB{A;O^v zj#%6YxM2=E^!X__(t@Sfxf^+ke6FX<(ggvpVgwePHD1Xb{EG^wKsu*%jlQqBq=!X$R^ zNiWpLhe_Qk`Bo*_s?Pf6NB-tqvmT)@UmKV!Zrw{^^Cf;3MZYPa6JYG!O!=!mlyx{$DGKGYR zBY;NoLs)@x0AJ$96ZiUZk*}iW?pO&BB3hViI&Y0G?q2M9?s89d`o1u*1~B=Y1kcUR zC$L)$)^4p@hz9NrG(>Mmz7t6;{MDF|AVuLdfVL}pUZX671b3Mfl{ma{eSK(kH*nGo-N~ z=H_bNlvRx;(PJWBo*W&V{jY!h>jjR9Y^0~|bsB5_?~YFQ^6q74PBgk)Z#C5%7OiJg zffbCe(-3o?jpEm-#?Bu~YW+Uw+#(doLdp{VtPUQ1-u=&ietZGXRsM7IzN5|xGp4j0 z@d{y719Ky&{(Tys6`Px9#a1hjWzcD|DZw{KQ92@Iqk~|&(4mlfqDZ_-05GE5p`E7V zWcs9m$>I$0TVIRPP>Cf%ygl1-G;Kt3+Xk`9HX7b)Z;wxdogT!Bjb~&1SgixDRjfUt z&n@yA%8zLMkrfL|IKB*-4m&K!skWv>ljpOE_3kNgSb!1-++{(>Jntx1moWL#4--oB zWIu`m%n=UGcJNTYmM-JdRYw#x+Iz_6(~J$(;%2!TX4KX4<62QYFZ!rt@3!}?Sfr8$ z+ZCuU^Q^i*E)*eyjA$MX)>lW5=#^Exh8n&@k{sQ0NCL2Pde&jg%lRYIiOP>)s}R&QPBl^Ht^ zZ{nAPHQ0NiIAp8&&%%D)+J>}uS7rt(5G|b;CWFME2byx%lKLOkga{FJUc?6`&BvJWi^Yg#HBmTy04d?m8~*NX|mpg6hG32JXmRjU8~mG;L=B$;PQ40 zOSLoBDxTk`G+&|m$c92ei#&-tXWq1%gd;mNuu%P3#UFbC(4P=?4b;CxVw`o=0aNdw zcGHRp67Zg|e%1t#VKs@8EFk%V0tBqETrbtral{6HSw#)C6b9UdmZ`g+Chs{ zZ5bl%iy;#Kd>F%~X}Axgqx}mVIGZ4YQzxy&Cl^Q}5WMXeQI>_0aup!*_>ZTHV)>kl zpCiZUAM4POv?_^l$scWCsasW=@{cvRnZ)@cKr@0fLknW&alHyIscxK$btl(g=ekwN zbg~6XvsM-ABvfcO@o^6cWTx>A@#Mqp3w0}3-3zrSi`UNYIJ#G5Ay;?iY8RQKCKFCF zT^8aqxNEN5!%Jma+Tp=QXE7SnH&^kUy>ajC#HXKU`4z_X?nomiASye~BetnOR-=%&R=nzay?J3;3GF6m|t(^J%D; zjc03Ru1wg6_dM()uF}8O)3>%LgWy`vWHX`;LH&SVrAyru?YCUW1G662dOc{Sc4ema zX{K&zqTx$H!`r3dsrMIdK=#uZ{{dA;;t28bW0!Q8USdX;>wsq8*j{;K$G*{qX1s8O z;ZU7Rsk8J%1jjw&CS(F_Yc3Rm!rY|Z)kkf`2G|%usQ8-qW_Z!JI1#0WE*&-soWAqe zn|O@o=lhnV;=0f_Y1(Vz#edh&UW6@N&eBX^hWB~haHc-u+fF>XQoXG7jXfJT26}Hkae521Pl(rVnbP|*r|pQuKjz`T{tW>MEqmhq9ey0HC!Vg(w3^MOd7b?B?&cr~{{ShO_N z_}A~<5+q@!Gon@5$Q1IrrMhO1qnS1s5QE5P&*raUq*TBlye@Nt^OyRGs-JZsiKL}p zZ`?DLU-8t@+%$b`S7!m10BVRT9p4a2ErvH29Qxh3^r$B=ON^_4PN?%@`34^7e?aPO zOY{-`2V|9hrkv|q)$;g#X+c2 zY&7m~<-c0#ZP-_O-jtoeb|j>HUFqXdVdk-)8yv#?#2YURS6xmV&oa1D)!SJ+!KeXvja|*5@)Wf)0T& zv^!!lY4)TiAGd{i1Z)FUP*mJCFxvqP@Vdw zyzBUBwL$W;?;0$s7ruSsk{I;ieu4yWtQa?}1QwwnHn}tCbfT4K$UxH&3+TtA$-?M! zsgurVZQtL1v|wA`LPxBDNJxiR_TEzEBxTtKz0qo*q6RjajTqieK{aRwLABBu#hqcz zvi(FDRdTe_6Gm}Tr%MNd={us&`!P)XA%qJvKu^rBix#W1*9>@8Bbh^$4L^=A;tjK! z?|XLbXx%>d;uo~1|Ib4|{O|f-x&^w5UbyePeMG@81`VayUz!{~UG4I)Q8N9(Dd7KP z^A|t6B`jYI+U@%p;S;;j5^L$$iKvG5{OcE9DxPr?RH|lqjt#nHzM#`rC+*4!DzE0* zEJ99CuLlle3FIOQm3{+_IJaOutPN_`4+-$T$JQD_@hqkRJxj3e#2cP*97j4rKKBG$ z7AAci3+>mor#2kv?@IzuPUeKokq;`fv{6SdCp#3F^_F$EmX^PyF;PPQ+Q_G~vTBl| zdFrj@I5BG}rb$F_6+XqJpI4q$OJQb_t^hJ^r_Sfk>C22y%pt(44?m7v74M4m)D!?6~3ml=_m(_}6>vwnYW8x4%mK8f~RP z@vY=4xx1$_zDWic;%JBp8iTZun|0An`C^fQ}i`1Nq`PG@Zsah zPapSAPmxT;Gr;OR_fZ$S@Sl7*J2*LphxWL2y6T(*pf@9vOl1|3$NSi=NOee01OP@)~H)%44|EsfZTwewvqvh4OFjHVMt@dS`b9PMK zhL?9un`mO+K;Y#JHH(-RJ+hyz#Q=^o7!DX+NLtj0A2|Pxf&+NKmYcO(Sgpb+u9C-k zU&8*uqYZnGVS0o)kS89A*Q)*)ucF_OA-_vK>L$xOo=WtWo`cKQG$B9WyHvPV`i1u+ zyxcXeq5YVIiC(A5!;M4Y3NGSbdj|8kSSffl{?wB^?mXUFjbOCEXc@=NUs9MrzQVON zn`$uRmz0lo_%c0wAet_}%^r4>87+Jk`bWaAEH4m;PwK-LjiceWz!Fi$jw0^I$vfee}GFq<>4GNiZZ^u8uWz4g3 zeb=+eAGvPrHk^>V;1*d2-W@gh$@xEC@IQByH>1;W5iK;UlVN94NnuIcbwNct< zqJxC3s_!dm53Jur0KYZ5Nvhp4rHvbGBUyE~+hqxiEAC41_DXRteD$9n*2-RXd*7)q zCX1w2&bi?JacLA*%tUK0X29%I1w+`z+2anFh{Yz4#Et)a6u<=4+t4}0gh-}JFKu-O zo|At!u)obs zeb86z1=G01C44@ctZLNgi{Z}m-!C+OjuFC>hse1BcZq2nZCe&e2byZ0x!59?xD}ml zCG&Tk(1JfVM55vuU5(jel^IQ;{Zte@oep8M<-aJKj9Z0FZPn#R_?hQ);I=%$Zqe~dn4~~AK z+I;!T!JEAkbF;g1{L9X%xjQ{M|G2y7K4+*)J(uD!x6zGcj`a>c9{X`e)SLG0pPmGO zmAF?#od50}lOlfSQc(-)OsOV`147n733%9xfA0laQs8ujKUL*?*3l-!qGj9}6W5)L zjY^Wg>gyvzu^(>f6D@-L+ys#H{WjGG>x4H9X56m=lM-a3Z2Op7`$~z-%b$CA_~Yrq z7+ZC2JIm*I^al${i@N|eukgz$v~eh0bO=$GaBtQ$ma~-oxd($0 z*o*?#96bBHFea8fnBY_McqEKCG$A8IFqv;zaa}y$x?}d}%qwwn3?rT-8qErGN5emf z-@Ha7$<57cEv_Y0Goll95ZsVtoKvK9= ztfmYFLe zuCAL1jcAGAd%9P2vifCbd_cXo$2-;8x|9hrgbkr~KVX227r z-uWM=$&|oG#N@oO*9gaerk@x5!vP+tRl23WOi>e3^k{T^Om>X5#CIDeU)wN$*kdGk zx~P`3uj=Wdq@VB)l|QYMx;Qk`UiztgNqAlJEz7d1l+C{0{>R4q*1Jym^1Nv#m7-)17_~kp+Q#o8M&#`AL6I&x|m1&P!HojCCCl3-1fG<+v?(848wc>ezKR&lPWEroF>pcRt;^>qp~WS47FNj z;7`O1{PS2Z#AyvW`SUo=)LW}f^W+l6$X83!3F{oCwa@V8ruN!#rt(JlYPfr_14F6) zL|Y_wTvJs2@bu((!<*CPZSw!J_U8R<8&{+7|Myc+sH*_sh@zaODHxbnmSsn^wOEv# zG&CbaBq$;#0Tuv7$t0f7e$O%+K+0{O_s!1{fx%!f%b7E0KUpJ=JFY6hPLP*8CfaiN z*HNCp++^U;O+$7|y9vt{)MN7v&y8c)lyOWk!mQwvOIKlsC|I&@CQ=>Qf(1En%+U+% z<_mOOhGe$3*fgY5gi%iHh3$Tw>TSq=A4Xt;e`yf!hv;fm$Y7 zLzQf#m4)-Lp9}XMTs3_eAn6yNA$PX*&! zZkeyKg~E0SO{~{D8y_4kalNX7-TXz|IiW}&oV zpiMu+uw=p_v?P z)Y5z9Z30|PEbQ2EA-b61kv>`goVp`1#z0e}{&;{$;uq%sdq@Uplb57*ldHg~%3l!b zGaj7_4592H!;1JvEz&gjjAlneDLzDOWrdmftOB;KO665R1d-+&6A|4Q|IFp9lOU^4Ed@oLkW#odnMk-X-lP_1R#+qVkh^r!83qfqMwREn zp?%19-NTJWJ#iASQBNFKaXp~2G@~Fig6Qwu2ae8n0m$oZh{MfRcn!rod;|((fwxo%R^SU47(! zmQpiDQ-t6JpFw+Idga1H+h=V%)-T2smibgp_h~7Pf)yOynSee66h2FnLz*Q&8e6{k zV^1(R`1j%&dI}c)qj;7tZu23~#t|*;N=qkC131G+0tx{PSoK3Ptd|(g#+CTg^$5y58aj7G-`7()n@jPcnvHLqMuSs#ffLQ02uHylmwu2 z?GlKV;Hq$s;3Ln01Hv7a6?^2L7;T?UauERPxk%BkZ(Xp0Z|7_qzVd! z0R0}IRnI<(4-x)v)s&qCxBWxn6XAq9!4nh?t`j)+;2?$mBQXicZ@i>uT&H^?RcsS@ z(+31}BJfIzJnZ>|(%{6a$L2Y)k)T<+9<+1lb+9|ZDF)SN1Ws*~r8$yr#I1)@d=*ao{$qRlrVlewm74*4nUNj<{~ z9B`w~0ghp<1QECZ(y0sylH=S%TN8?kp0s3+8tc-%X>l*b1Z`PihY&`+LGGyWmz$fj z^PAqutK*yF*RPL{u6j2&!0VXMq*?+dvxXSX*F-_mGu^S+9BoIC1!!p&cM~6#RzPnF z6r;r>izW)myjDxc-yXOcsPh1+jA8eZh<^?N&H)YpA@wk-9S|#j-oau5l%wZ11|4?e z?q>{@QozZbdp?jq8C`i?4Jn0869OTjv`mY#?v@m?e=&{bS%gQKBkq@h%~472bl+)4 zaPW_jM))-{H6)?Mc10VCZfRtv@(WM8rk6edrhKvN@35J&hZG9!- z4~F@CFTtD~0|#dab}!qj$ZSAiiyq=!Z9J)(zfe5~wznAzP*p&V7wF5t!TYCt?oEUH zB?V$6)4Z{gl_X<~-mZN>Nc*964`^!V;2}Tke$$8fmKxuD5=DhG_{MignDE4yKjGB$ zCs{NOoM)?P=?tE&7Ud_eDy12jDXY0I%bCRwwD8-*uKjiYZYfZAGHQi6bPG;{IR?c= zPwq6&&$IY0CL5Vii+V6ch#b|VDm;4tdP4VM5{09R=oK|7+M11H8sId$aCFa+q8M$_ zs4!fPu{WC!BRtC;I+o@xwn&G&`{_DC9c!aV8xpb`N4*}JUN&&@J`o}*ZR6?~cxEl| zqlWHj-tJ=zEYEEm^T$?0@^8TpAOTF1icu|z7z{!oZTj(i!n2kef@#5=ODyKSV4-g` z@|cbY)do$OR0ySDQuYT8QGr$H54@aE?t+Yzm`aF7X#VA*(`uNfCPN4knoMDkNU|g4 zL-&hlFH2#)vc~=Qtsh(8{qKcD+`0GJHivqH@4ncPX*NYe>$PKfWs=Ysib%bxZffBm zOqf1fK!%|uqWqL_872>rv7D8d2@nh!L~sQMgHy<**xkh*syjvIUg$tG%#&KO!D-bB zXEP#vVHD$-xv4`E=P&`LyPFaUL3thA=@9x#(koqON)f8i5^+wymIPw_@4g;+^gZrL z7dt@asGZu!KHLTSf7!nikIP_x7k-!U`*9yG9^fK?-yRlp;o?`Q`LPKXM^N(DA6~-M zGrD>OSJ!uVbqrTGSoUKZuHInT->}{}mi_Gru70C3eB%nsom z@X5A*g}Q%z{?dB;3VPDC?mtkC@2rx?C6@eXmE1tdC+Gh|4=?P|Qg?OT2mIQ1uk6}~ zPtVO}Z+rCqYy15Z`+aIm*Iv@tDzzWtuXy|+A13cdkzD#CrEy>wP^OoXEJ3P761)Vz z&-TS7i}Q3RPM2&Y?7`3C8L_iFl~csB}Z#bb(>qp z&p>Lv1LCjSMG{u5#xSE7VKMQuX50)hh%H=lXbL|?U#5xwfM=ei0zO;Tvq(|(Zsf`< z4@nfQJi;cMNZ;W^G?S)FlE?`;t6BSHT**#TkOZ(|Woes#7u82zm%`Z6SoKKZ1N)Yq zG|9Oma^IGL~wXipc|(#^Mb+R}1s4vJcC zilUyI;$SmT3d)dQ!W^Q67{$b%sjIV47>BQACAwCKJVH0*CHCR2pu2*iS!3LN^JX}% zzexeeWNMorP|Hg~-FuilB{)ZGZ#>3KU<6hiMr`9~ed66xPfk?vDS(}>5Dl5u>_oLd z2zJUes7zd&b(;oRKhtnC=&+4jtEKGNS`KArB1x3vC?_M6^^uZK;>5F(IkT0_NIOFF zA#l$-+!U2?`(FG4MjG$!S$Q^$Wv)UAv2v?}zBZ9nQ)y^I>w(0RZ-8bXTmWS(C6k`q z49h+sLdFrRl92=;MU6`E9l0Ga$R}A7pwKvCxlJ^}Kn$`$mJ7$jE!-s9+NE<$BseD> z97=~A{EHSTC;%42>Gsz!_;ldlUwjc}bP41tSw7+gmMxOcNxCFnh1;^6(3Dnphk*Wl zW=wXT;xlgn3&DCu0V!mv%px*E4kIIh=H)uM)*m02P5&dDM*TlN?hl&##`7z}Lq{7v z1N`EO?>h|3c>NcdQevNSN?b0D-1QeY-~Q*{tio%jV#Jvg(P*6#RR z!|r;_>O!)LILw#nf})rnO=BpzL}4UviYJ~a|JmDU65hvY2FIGZWCN4Twtd=j*%WHM zMzv{*(jILIwMR$593Ls~fTNt~VwWwmBy$BUYC4_%Mz2?j{%J4ymu1Caz=@{pjAGf5 zM3^H%@+FCp<)GBUMj?M8G_YB(n{~H{G+sWQpu9jI=Kd1^h8`(ZV~ZkybxJWDIs4xQ zQODtdy_6G*BY1M{h^6mVCgci(R{I-8v;s0|3Eyl?EiQzvu{qJ!v6NSkyE}v*p4fAvPRJnXY{j{#voQgDN$h*wCmhgtZHv-*9zjCfFoTtdD?TQOQcYMmZl9sP2Ad2@Dtb@TfC`s`J3FaSoi zUW)FNQS0pR!^xXNbl$pobA5RE>gMwJ)yd`Y(N*vqUV8HB#>(xNkPh7|$tq&vZ6I`~D0=4Gpg+azttuOiMDq9`-xBbCI@ZS*@})X)K*K)7 zWU>JAiv-p`p{#A}@R#YVs<>9oK=V*tm@lC0fNQhzmAlZ@%hNPIyeNxdjF*)iB`+O5BqXv zx*6VQUfrAdpG!O7z;JBeJqQNl86xn&KNTGmjR z*GY*#)y7b%IIx*^RIFqBrHWHL8u^7l{~yxPX&WfP>fXe9^>>|5fjqJ)Ji7MX3xkYv z0dmYJPW+NndWOw1l1*EQrYoX-)OT6_Amp?`(dfEU7f|X}bS+kB7~m&B-=Vpg=Z`mV zCcHiNd^pd?-nNFS=Ynhx4f*{sw&#*Ucyn-+#zLaaan*)1R0+dHj;3g05yUP}G1=Zw z=u_26Y>TFL=&AJSb?w-~GqGa6qx9$_7XA-CQoNAt9wrNi>Dk@}i>c+=8wW39*UHP$ zA5`K-AoBNYO+t)HK$D$8p(?wqpA$COq3{t72l}`~l>tQ!LFe+&8YiX|f!bg0(6C=G zvO6_-yw>Zfhu+0QObbF&_&a*gB;+BO3+1V~VrLPwl9APLa)&%83+V^7kO~t^({q$A zrlaH=zzaOF4Ly>E^K6xuP9vnNkPf!e`Q&L`+3k|kb*nI$i%y9CUpS~>A!>6HjO2Uc zz#ZA68`0>L1a)^8MmM%b=aQaDp_f$UB)e6kX$MS`^YQ1jc zS#TWXWLK%)AERZ+FnBRZVL@^ak*7SF{6uGLlf#|{k=+ns^z98zF-vr+a@FZrsRXct z^8bHD6ps8KOJWkI>5#lE*wT2IPMgj?Fw?qtlIE6C;qP`ZE)t7yKunQGVKzj0DTzQ* zG!wK~HOd?k`3iU{jh0-&rF%(7!u7|kOR)e74Q<7234g;Eo zX$;4~cXz)oy0!F2BhPD2{eLW7uNgI2*;w=!UB})$z_j4LfA0HBc{A><{d z>YEC!k$(FgwR}@i!5E#`T`2EEv7k?w z-Y>=)FxWZQvpTrYU27hc80iti=$h`yEuld(6Ut5>SHRB|xcH$pe+7Ye6p$!i%P0uw zMH28qCYw%b>x!ch%7VihF-)0s3d63X7v3`w3{PZ?`MW6D|Hv8RbLvl#G%S5CrW(;P54j`z}xrMdC`j;zEj$K;lRlD?y6*&|mBQlTy(C z<_mOr8QmQ}@OP~HjS9!fDEO{g=NSF+vD$Gy4Cm1q2ERi&sJW#+zmLc{Ly4dsqzk}9 zCi^NFTRKiDvWH;5AMSlQ-21(~_xGEDrJ+e5jM7Qc!F_flrSpE@(WS`)m+x%qE;o3d zlLS5%!`^ zb`ZLYENCvr0RU@02_)IP1MyE?x#cG7w52J$xM(WsyI3D)y8ar zR^w44MDKTGnByXEq7XvC#~UER1{jK{!`do<&AJZqfoCLRG82`e`#Mm@G%jrw`B zIS}-fj~`LhwNsx(*qcEj2qw(7FrvbI8RI#KHWz>a8+u832>VXg9a2vswjulUsB#tXs9)e)hoOgr;5>-uKNHC2UQ1Xo`1t>7uJH znsAZt?bUl@w@jb(obIB(FqQy3f!#&0up{pRnNjZ;K&8KcC3*S-SmVFzE$=aIP-nAs zooi-#7+f4VBrSi~+%U8ot`j&w;v87{#t^_ioi+F&S9UtKpm-Tp?2H(>`!Z-5C;#NKUjvnfJ`J4}+@9QVfE6&m#JjVy}hM&AllSO_+ zvn3BB>gPtWRr}X?IvS$M!rl+Msw%UpU21F*>^;{NO{rD&Tq2KP?%WTwu_pECVi7$)2=^ui;PPp&&LPP#6E9k=770Z2hw91 zjFps?LtH)iieA}e@VH)&#yFrQV4>02TXEku0G@cOPtR7%@{?qkj<%2^>R9^vnyr0l)j|+WXe~+4BTcaC=QsKjM<~Q##r(rMI7{nio*k0|7Lb<;xD0y(IIvAC0Z4OqXzxr8#u=i6DeHW978T=PwGYR^ zL1+B4I%7Lyvk&LNqCciw2x1b%{V`hNjrtRccs2;&9GkQZ40q?20iQot$KS|Ou44Z< z%KV=Zz3Zdh{{?dJ-^ffkjDL*yOSMZ3DJt zvoT9Y@fgnE-1}s(0)4XkXexCUX{SBrJst8Riqohv;6I2{erLW3+b!-aQxER>8OBXj6GeF=y1~ zn6incq+w@T`5lbmaX%UCDQ&!pBiYD0NwcZrCDhYHbhlcyYLwqJ2M!vRADHZ5x`Cq7 zK=G9Z4t!|BvgZBAU3HXNzuuzBq8Md#rN*CG<4^13j{}&si~>!NMF=WL)EvY#2T(#< zt?)K?)$4nzO$xV49Lg9cPrXSoD90GlUY6_~7-y+k6BY|Ju7rc@M+QxHYAb=cN{YUb zQx1M^OTu=D#H^-YnQRp{b!#aV77ukRbf9yP`6sO-rB5XBQ6>NyVM@{KnFEyhg-NlE zIvX_m_Ay?dl`DG(!yDmS$|^hvc8XdNTBxdq_%x{oMj1V9Q8y}^yCTTO?ht7(OLXCm zG)10~dEKbuFE@9I4R;K6C9%#hNn>U}hMmOdsL5fJ$zER-@_MqLsUQ)xRBxr67P%Z$Ob( z&VxC0PVMPlmlSTH+w)34jBU~Rh>*j-?og7iNpl65oiQhxV4Kg83NScCXAGmW!#F}? zvrBZ`BrN4gF-6iUL4C+HgCFAnMfw|KY& zCSvjc8#3`aR-f9q%WA#C6&{hd0-DrT9twMV4=ET8oeXmzw`ddH(`HSipXNw`4K2%D zt%Mxq&RkL;Y?!lQ$SI5*eT2f{U|#9U1aajY86D|tGbe`@6@0cAyx8fr6glLn>`HSP z^RcTg>MvRE0E+dD07%hKy87)}Wy}LPw@e);Ci}|BwyT^4a*Jc^9J@0LPo_1>wH@l%UK zI%YXRYNobKiHcbx`TAurXIf&}0W7^pC{(4pl+7c@w3JJyTngAng)#C>d8tga8;Xo+ zIe!2mv@A^m))Cr8jqI3hsC`#9p+OrH-%K$Hq-PnzuA>2&dGq%ulV|JO8@VE=HE{GG$Ur-n*3gtFyV|Cl#`Wj~S*gv=fexQUn=C zh31Y!Ld(#f6!sJ4HTrDRo@c^J4F#pvos4!pCN-O;RU(bY(E3kki!k&EqWn>B(1eF$ z$MvE@Ca$U#Rt@m*RqIH0%r&`fbR+AG=EUm0RkXXiQesf6v@E}_cq;@(z{nky!D@xk zN-VR~@k7-tRiM)dO$acCM4Xy#vU7F%U1=*vw(6q=a3`sy(O$5zI}$-^KF8W#>VE9v z*Pa{vHAF2O6fbZE;HVq*jSucXo|M77`{d_ja~_^WB9=p!_6tOZEgxLvi>nL_)PaPi z{F^T*dyZg>S1|ebKG~ct-kMUVEX;ZGP_cb2`m}t?=D@ZJAt%RytCm+q=m=xaaHaQg zv^4n!v@xsIkN>$r2%(9EwQj;xf0hjBrW%bBCT5>`sUnbDm(f>t8%Hk=( zB3O@{Gi$@C8Ry3s-&x*=aQfWGcWBa)TW8^273&zuMHeL>zH$eVvKJYs^06EO1EWo303k@cBVit56uMkqonj?KRte=SkORpAm60d%b5 zVP&PlBnlz8tNtw>2-c7QgfaAwH5lQr-R&Nh%k<}t2d7O-EvDdB>a!r5I<6O2+M}0h zty*uCp6w@~Pl>{WW4U{nLdm&jxmqMUYK7CTfjV-^^7xih=cpFjiI`lJ~me^i|$7IA0Ec-SZ04hmym9^ z=yntFu!+VH6FWdH1CtQbI^-x^ln9I}Zbu^W09(nTwB@AOk*Z(H~;uj2&Cr z*71hTrlt=tLr(RSHjV~-55&ap)EBNLOVR62K>0tHX#D#-jt5iFDI$s@(8+Je2$lV* zq>pm>%S7HlxP6LXI}rV`n2X7PQhSra!g#uj2EmZzR{%xuYzY70;2Kjci{ViT*(-&> z7F&0i{l;d8%qw;&ykp;@!~wpEz1o=r;WkO#g_vVR<*_GKD-~y^5qo86j(8Tln4xb` zDOMDa6V9bH2Y9Nuo(fy#ySv*2{mH5 zVgL(?Va|0R4~q&)kP1>kx_S@6gLUQ{RajA!=kuP;1)0A_EI)dWcF^nf+a*UaQNkEm zJRKvVRW{{FPNy#IZ~V#mXBuRY`$i^99tLlc&ijlU5G!#AkO-xM%Lv;F6$vVvMv<0s z{I6FFoLQJG!l`2dXXjIk;{Oo5aGd4`)UH;_PnKx^T`78qt$CljA4DN}!uKN4sdN5y z=YH_I?)!iazlxv+9zeyQKp|Qb!0Rm~2Xd20ytf%&sH!GE+TKzJgOMul25tlzwm4QJ z#^v{E^<0)i!k|AWIhb`C;a3$;y7i-`?xP$&9-)zx(IG~k>ge9 z;QLZx^;=+=P8Kwsu>&1`Lc9e$VGN$#vzCexqoEo;e^FgQ)zEYR<^uHJoCX8tG<^5{2;h@+<%sZsX=guD;b{8)N86YmMHEwBumb`v;J>7 z9k)ylhee&r7imx<_YB$bLh*;e$ko?M^ohWwC-OZ_-Vgvyhsx?C`t5}axW?i&xqpZ8N|!kswF^yM#|AYPEQ=4RA2<#`gHN^QK_JDiA!p$Tu4<06 zvj(9Q@2tX|D0bjC0EPG39sf6e+tc*h8XYsyjo=Y8wTj(w6 zdY<{%nf%xj4VKj=VdO9y_IR&}D|%wULmY60x%#&w{*St`IkIz9F`U_oGLJ3rs%4%{ zU?7I7Jl*UN>x`_~s;J`rEN;}Qw)(;T0R%}@{YHE{wps^9Vc-PDBt zn?+|VcScz5?5SW)UiZwe?d&uAlzvG&29WY9mwhnFpQ7N!iObH z@-Sa?V#7gvh3C~=3la%LBvXOYg0n?u#4xhSCyD8ij>0o&L)lSc%r{Nt%0UsY`C;ki zFxzI>?^_fFz!T_|>WNeA&$(*xm_X)ud*HA2lYn&WZ+WYJkh+r4IOg-NHX_;u0I{nr zAiMqhhAKdrm>d=oiqa04;~Sug>MnKn4v;)3t(mK*h`~rUx)FXy-jciRD;OWml-jW{ zRjcFRc)fm2Gf0&%7rSm_XUm*ebGGpbf1~IB@lsT#!Wyq9UUKJ-D?=}(-Q6=5Qe5!Q zrGfQVdh5SIUR(q5g+v5e-lEh_KwxX+06_j?XwZUR_3!?$G4VDZ@vC9$FL!q(9Pc1j z*r4h>zY-dW5DS72rMd0n?Vu9t=Z(dpT*5xS40b*U*wo@#9lN=G>^TZ9KXl(CnNeW; zWT;1RpYZa76(%4uxK5{#sz`T`JqS2h?)z-L-Wh5+)@GIuwS#v6+x<#A*Fsor3fpHiFZE9sn~UHq!<|^NLx}q+%9Sw5&VipSaS!Wi2$*_C=36wN1oc??;*~ zxk0D$FM`4?;K0AY=J0fk1ulUCNg4nE_%WpklxMj7KuBej`HJe~bgwdCUC>>eUq<2R z(f?JboRk1^AB@!0t>|BG9{`XxAF@`XCm%kMu#(k;!904xZT-ntg~RVY>Cb*$uwewA z9Xr44JxC`rV7K3qk32$?a~0-Bf(nuZl&Dja{?)j1+^-n*#oG)T1&8Z*pO!6!D98DI@=h^E_|?nhGmP_00oE zhBKmm)No?HYK&H>!UO+=o-ehq_ob)8yR$TpxG)SqnP74Q7wx(#F9`VTIfi@3@bjLy zqKv&zW)fEgFhH42#6M0;&MyKuCb-I7XOS>uk8J;~cd@Mp-BA+J9l)WSdXt8kEil|l z^{M>?YTgvpqE?*0B+vDFEBb*6d!4j{JaHZB?MvzIZn}7GEWm04GV294n6V1rGl;Ib zMX*_jxmk)p<}Jta-QA?^sv9&6ly>hAG;vCbE^Z3C*POp6vNn|f9=IhR{Xb*MOQC0K zGsv(wexNEO_TpHmur2RHETn+{80UY-)HiYZl}c!`FjjOJM^*-s#j85Fuh+7q3iS*^ z1Z`-RP_#MnCSKpfboia+{Fpsy^g1NcglA!;S_rN1PJYGXs=yOPx#gvL zon|xmUig*F-m1!aP0e!VE0hnyYS8jgo@+Q@W|)H9;iWNIvTxWfjPVQY4t3*J5@f5- z3>;MFzT^R3U%u;6*t!c25A2Ely9e>-gZSk^ynYx2eFx8ZJd&Wv2xF;0!M_#}ObK4) zaG>XK!aBb|ttiW#0coZ3#1o$xE?dx(S(krY&$lDq_dn(z7q5?BzyA1e*d8?3wfkrL zchw{;zr$OPzD(YtM_vNJUMh>a$iS}LCNE$YEA~{S=d%JF?^v@4Z&T{mV?Y4UdD1mdYvnM;+*P=|eihtB7^p}j z)M<8A;_$>Fbs-O8{BX4=`9Ppb*OH~DZLLtYyCu`+1huRwTFYGR4bPiSO92x|CK}n^ zUgJfZ>}5GTvOu6rYbTO2&FxrUR$6KuKcDHgD2}Snk`U+7bWD%%LKSjEGAiav zpI;Pl8{tkLIDqlGAyRW=NIYmC@D$ z(g7MMgc^B5C?~}P-|-*-+A0+ZCisS%w5+}Fp^?Fd2c|z-!%3JOrK9MuaNCHQe*EG2 z-~LWp&qU{@H-6RK!CWq@$l&y#O5xa~7#y@#MVmwjp5h%M;pB@s$}DvhJ9Bz?BGJbU z2WE4v_9^<^b+we!(Jm0s->?gI-`{A+tT2x_<6{XsB43|^6cbbv;IJ3!adfnP2saJIR4Qtc$bJiD@mXlL^v zH5TQic=KQk)>cs<4;ttsg=u09FEsZ;;Z1^GjYVNj_l)Vk@f=oOa(2iwqz_e&rKA+U z8Tz4W9RnqdwAJC;k=Ffs&ft}-ZHyADMm5OD6G?{@H~Ry~IZpZoPGVuD!4q#E6v2+# z9QzcHJaw*I$4T)MHOW*Y%(p250RI-{b?2ak|9264?TPWKyN%kz4Nw z*l5dJLYdIX10fP|zde9I2Lm_`#6QjF$o)c3yOh9pXnmrGXoyoJNzINYgG*TJQHtN0 zX!zyo^c_+YGJgmV4gXMweI-SgKI;2>Hy`tXi_y+Ljs~9larvzWr@LmvD9EqnHe96F zKm^HVnkj6>CVHfB*VNk@%uoMzH=zHvCa zb1916#v;%eOIZT6j55YE2j>!HBHAyDwtN?vK0k9c7LwsR0FDK0)}*I$V`lnGk;*J^ zMm!d9aIfPe#*8mPGs4K9R;1^&XP8;^?UA&$CeQ&laDE9zT03eN%VRa>byb{}K{W?$ zw7tWm%W|oKuh2_lqWHhizDFG<;!K2tjH9hPMT*HYaM|$vf5ZZcNwy+HkZv zO#vP=lItU&RxA|Cfi$y^+#0+9J5#L{P<+M7Y!;1TG8pKp>~3x^+Y*OY2KsS?yUNoA zs$5cm9FreY=xR*5)&tf&#`6SynN?JZj7{xb47yIb4TQf;M?-*bq0Q?lw+=mUzC{T3 z+)v>{oaX=c*^0tbWFb(Yz~YpjM1O!$2wy&qViDi&*jA%RFrao25f&h|eH-L#N19Jp8;~=WQ|UN$CRb2wVk% zUhz4w#6Ch^vdF_v01TwW#eawXI|}&yR}a;yC>rt4u~16TuJD@oou5%N?9JjI;{!&K z{-4<9#Wai)S#TeslhABlJj#NL2Pu9wY^nc_%ZM}e|KuBHPW@l-PV%SzYrHX}{vD1E z#R2{yK0tQgKfx;$1Nf)h4n^{NPtWCI`8NfR3O{{DO;-76|0Nb)!9t998sDns5nTPt z2O+VW|4S?Y58p-pbSPkLFkZ-)2Z7wO|M`L9%*4tM1@{qf_Z$R7QFJ+NQ&;dJW%ljceu@BZ%(qITjg z$5sj6v1r#iCH=51##t17iB1XW1AEnjPjgp$QQ;SOdjzEI6pwfR9}gl)p{)dq85JuC z=?V1h15UzE+k&|}zn4J=rCH1?xri}>Qg0p&(aw4rhe<)bPq9Trj&hpf9)c&Kr^FBO z<_fS6N!#P~DqIs61D+5dMbbeI>i^ z+hbu5bUhr5mrl?{k-@RlDiACxm9^;_pk^AK=_b^jm;$hzS+&i*7^c(3EU_QjC4#2g zS_{E5=YMY50A^3BCvdiXJ?}zJku`y2{4WpSc5q{}mcurZnSJN@^77{8;j5eT3sQZ5 zIDB_~41`~`7z>Wh&(3rfHs|>2-ptddH4GA=IZ?X=JfFFpW0%f zC!f7KzJS|9*^K+*9qdreD8$Y~JS`9)C4q$VuD9}u^aQXwsBt~hAz2}~9=>{Lh zDw3AvgC_&L6znRYR}hz9XF3nylQ4}tfEDAe(|4L_(-}BE@SGf*F6lZg%1Y*2-v6Us zZFFiR0>-_d22Y;_$`es+tNWz2llyeMommo{SweO>}A)vD5=~NGgTZ6#)kvNv)%aSfQ8dRr)zfa#Wp<&>s-^MPgAR zyhMuG3}YtlHN*6|Wl>u0eSbDEhN37?HZ4=4ZOozpXWn|f5SRrSzl5o1TJlq{HQc0y zWSJK4;|)`{cm;EhU)-PrP^_aS>$L`fC#Fkw!pv90DCaETb|j#vmzR`8rM1Yn@MmI zPyy-NGD+t(xJs?*L~ceytU^)O=o;}WH3x$w6}tTi@HtWuiU=0o^Q~xl3wI(29#BN` z7?2zexUHRg-Sos#ORi{A^oE}aXH`0$Hw&`v%%9oYUQB{ozQ~EELt~oFh>}Oxr#L(+ zRm8U(u>B=OEy(mf68x*z^{UJeN6b9&sE;lU?>%wkt#;fhsQ2IlJ);mUq`<%p*;x@# z73>9Z1X#!qWi=dQ6Z;{C@H(>H+RGSGDJ;lN+jn(K(8N8=Enr}LMoBVMSZ$2ZHx8<} zdfXE#$jt`^aDRc={~k_;4>IUHYO-?{`S`2l!`1)b)!&dH`|I;ITsQHm zjaMJrjuy$v<)~Bj8feDr8rv3K7Yhu<>yhb59>Y*i+?mBi>u(?eL{F@2?>vxCy zcd>v~vJ0Pok818`)v_b1D5{0HM3{)eiZa!-D_Xshoict7cX? zFl*s7{t}_1zs}%6uj)rKn+IxpyK#`xy7_TH+psG6fc2rg-|846(^UW)e8Ut2e;J#c zp|u=&p;Y`>dO_gFQ8U;C8s60HI=F-P4!UsV%eN-!ib;#h1mV!_=Aw{Zjq9vO(03y+5U7R*sh?#yt z(W_KbOCO1zzSETyao8nE9{c|HP^|e95I4ZJ>|35=?78~qCOx6so%IAYao3}WL6jdd z_xsMIDCU?&8-7qNgJK5wxkd9K;ViN#{3UapK`HJQ!G_hs)(}l%G1Ok299SmJY*Qay zxL!+%{B19|qu>sU;d)KRI>X{D;g10?OS+p1;W*))5@P2LsZKEgd*EIVa zWp7B_)*Ax%$h}3|3tQc}QB4mBYqsJUlB4|Tydvgaa*t)2I+J4)$OB+EaXS6|eJq}0 zsy0VF^b|G2Vnug5f4jQ5q1Cy$k>PqmwbIAQy??jBf*y2!6lNpq!EJBLgI}Xt9v;?s z>M&){fpwOS870e|mp8Dg(ju}h^R zl+Y#GgA-`BYLE%-O9HN{JGD648Ahx)1B@?O2d459ughCqQjnQQ3^t~;cRzmbFeT~7 zttzpojAQXcwd`YYI7P*0y2=$Rp|ZRGaoJpd?331a0Ebw_{5V>g%KYp=_+r%J0uEc? z?(q1vtbThNp%k}SD4B!Hww;PC+bK5Rilwy@*{;B!Ex{5eB!sZ?V(BU7q^(ImZZ=7w zisZYa0Z-djwzasYu4HV67$_rtVR|V)9|k)iFBr2QDSFMwaD1Vdc7ftGf_FEC%d(WbHEaDz1 za&1297V9-}h!okxL!$dLQ~qcnOeXvp$A%&y7fS1Tk4X_eNMRk*_L=N~o5CrXwkk z$8bGLVu7Y0ngYU&88ZUi|1(D+#f!ZI!E~7tN^?kkUM8j4p#YaIE2Q;WqSCywxW-^6 z?bbPNGNyr*Q(|wajI>A z(1^jF-pXV?>@AneJzS+dILeuV9(B-u4NV^w#dr@mGE^1Yq^`8qqzncOURxJxYumJP zs&8;E6igvvt)3#nGp{Bda`T`f*(lI!%K9qjz6!HqpT0)q2z^H@{^=u8a?f^-c-2zN z-!Z3B-dz!WlLMaiixP9g0xbftGu_%tleT=lPP^{cXPUs4rV{ox!BknA^_sj!t>SL}}HPB(D%{G4w3j_zs!`8Rlol zWmck&SVM4KsSt5<0atZ34>R#ZLsfe2 zVMI=SSi%}HMen!8L!OO~()4r8S~usn-sS7PtMj+VXAV7M-Mf6YXFr{V4-zom@lglZ z3(8)mqeoK!K^1fgjrxwKs|v!SgmI^7#3?b{kj{ zX(NDXA7*LduMUU9XkPdm@G2Upe%_y>N<%ch-TxNQyBx$xXA5b+B@D?L2!)jP_v!Hli|=J&|?5cw#e#`j=sN#Ov+a#b~_0=j|>8j*^zJTD?? z1or9weDXFWwxPF5gV5keAJbbwLB6=<HGiqBQV$KLWIeG%Mzk1_a(DN< zhly-t-cko}-`7f zv*orz;&p0^p>}tTJ>0f&SkZPu`#o|UA{QbONk_h|*+OL{5bd|2=j+x@L&p^&eTx?w z%<;b4|KnpWI>DgneaydgyMBN_{XawpO1$p=ouuU1!2k##vPlI3ALE?^YXJ0Z|G7o5 z^XD!IDE9y8@7?HRx_^9H|Bu-D%d@}ke)Da!g{~y5OjB+s*3^xrwjGC}Z;cp4+uK=U zMY2*xSr`*hu>3m}#9yaQGmAbk#(gB^GDi(%N3*}D)PGj=nu@e>>)IND;fA%XtL)U1 zntg3%hqUQcvkoAh(Z)Ki15gPBWnUt&oY7dATUz}LHt18v_E|Gg46?0^1!l&Uy}@>< z%r)02H`UcL%G-Gn5aO>$`cQ->i5hfdhiRlA7W64r5g*$*x%N^I1ME1gt2t_N;SS*=n9TwslukXZbF|g)NYzwK*f;EtLN>^^m1{P2 zLq0^jp@7c0Ao?wIukg42n*JoyNXn*)HVY?##sm;zX!%huILC!nJTWF(DBX*tcj3kx z%HNjPLowEk&(Y%^0bK*-{;(o>e;6$KZNMk)G{`Z~Y}d3u^}795t2Gr1#5jImUEtpm zrXBr0IMtjon?!5y!t$xXSuF1u+SsBOAql3$id}4gL8?qR_ge)rVIL6iCB}^k(>xN4 zs3O3-sa{)4vTwK?vy)QnV-?-l6$#lwV_P*_L~0tf_$_O$*PIx1ZFJ;7@c=sb??$fe zt25rPFm5ZLH65&@sWFgam%GH%KH@tErF37f@Xxfl*;NXYEt*{(GKq{`3q8a=N?2pM zmO*hRHy_VL#{P8)iBM#GM=DEkH8f#t;E2>9+JxQnW1NRnO6x5IYs<38ba2DrPUVV3 zpF2B62EqrqiB)DN9ss&51a!*ksd~blRp3-6KtL1ER%fc%&!H}~;jh#k;Otq83BX`V z$zSajo!x}&LSkfXrJC@f3nyBv35r;eE1etwAl7j-@l}zaK{~gH^G?nHG~!aKZBZIo z)#2Dq;>r{VM?f`8Hr?B@6gJuN)Er2$RimRd?&M_XQI zYML0yYSmKAK!FrMO4(p!OW_c0;!|rsW&zDr|JisD=!=!Ams6gfmcCU&1f$}vq8SC} zglRemw(GSE6c&na6%>2_A?hGdFFWUj{ycDf6{gehpzjpB#|*5#*gHmH6Otvuxkk?F zkT@%NTw%usXaKok*zKO%&X%sW*|a>%0u*HyaJZ10P*D)p(TCWpWkn@6N8=2yr8KK5O)*4w-EwiB`qx3}KNsvpg zYCxlD%V0=Yfo`EJo%wL0q&k%tJK3I%v{m&#!Vm=M$IOsavJ@W z!gHkoi7)<&*?Q@yb>qN|sl*e*z3}Iv8pB{txeN9d+LEcuK2PZ)8M)(cq3io|PfNY@ zRGszeX}_>0v2-2Hya0z{jgdUeg9Vy|q6iYZMl_T1q>9lUnnel`p7}%V64>WkO`6!P z!pIkUNoVvT$hsl%Mn25JP{c5ku;q=T7WoVE2LuuurO5V1NNZBQgA|jTrrtn#&4<#a zJorxGz)UAZP6GgxAKCz7Z4FN_AF)L`#jR-yIBFzv9q|E8hZdZRJO)f!VA^ih^iP9u zFB8K6nNpas@aG`!WI?F;%N4ifhk{?r(OTgy!vF_)H{4f9OWZzUI57YkbaWYNLju?| z@hn8Mmf`L$kiLtqo9~6*{!qO~$y)qa)cfh@OazI3(RD`YGQoa0zO$Iq#Q^b52a~Io zd(i%YYr+LU zkgHm?+JsAOTd=#k@6Pf#S_Abof~odYG99~83k9NdYGVvX=}jcAykRwe&wdTSa96e>UyffExQWAagR-aFa2aG?w}##mVp{4w-DB$@c5|>PXS4 z1<}`hToD4bN#tO($^s ze{=@ldX5QmTXaX!=A5G`BHNG2P=eSmA_CMZJB~0oZ||oczmI+pm_E8JeF+KPN)E}` zJK7DxW6M6}oh5I?Q#0KAGGM7<^W$D?@U8FB$*BO<76|`YHEk)nR^XqH`|#SceKe|U zLhqV|6%**=ev=;9z8&|PL}3iO^R8z{M?*Cm(0(K%>&ox?I^-6ig-W=k^P-FPI8o_a z^!iCA4O=->hd$I-)^ubfOrs3PPdK&=rFhFOTcC7_Ts)b&GjV6erJ$4O={GP!jI5xf zU)_a{5woyD{g|#P&9}CWUU=J#w}(d3>`Dwjqy|{JrJt#G?Humd>n%+#Hk-7g@P+!l z|84*3ecSSpoIA{h>6|dk9tz1PMjVoCOY=%CzFK5cA02a%1%V6pA{S^T1sEKU*t!u% zxJ73j%IpV5HICJ!+EiajXKyG#I{66rF4*i)KbIw_FC{k_xL2knp|JJIa9dNshIPGb zDAMGb4&)0(6kyRSd0bnChFgZi)nZ1dG0S(jncH?nV+mLW!vGOKup*#J zYHOU;S1zruZLCKR@Bn4hA;6k5K5;}pTcv&IIT4zOM?F)=(43o$^f`gSU&p0NWFzi7kZ-%j8C0()Fy zL^5Z7-3xx{?e6OBWwe)eKx^BS)u=#j+lq0<07oRd(GI&3x>GIVv86DyxhnWE-^6~? zZP;&E1}PtRn9k1}{Fs3YQ~1FmPj+`FE{c6D48|Tu`W6$^FK$t8b{io!N?YiJ6;81l zU&&lwweTh3Eg($?Pct0!J#it`IqlrH(s`7?xcOwNirU~qxTgX2iTjp3TreUZUs-wP zTt08_s|0^fPb+G74=ME;O`($7!l4OJb>G_h6hnpI1dP*m02k(H`+!z5j=Uzrs_wlp zgo0QXu9=Q_(xz&7GalWy7UI7ig?e` zNeL99WJKeHj_QKo(Z@U72#*wTd^ib{yU329yaPz*p0>&(o>6F0`aPD&Pze;cbjLNL z1q_TcY26xw6g0H+9nL!rb7w>okI-T42*HW{b=vD2!Z)g>WQSIO01=6MPmNnPN%87U zlSgV7-ULUxyGPnz{mA&MpG5^A=+6!59)1szVg`K7@Z#uKQ5j_l}2(2i6T}u~r)KJozso=cb(XG%hBqav3 zX?@pz0$5W`^O3k|ZEfh~!T;LKm&CU!E$7C0#1bx4w8-`3eI_@dA!$Ob)-fOs zeFLCq)!&7+IYQc`O24G78rg~dviJ4EU}tBuA`%0rspGv3({^7qr%Wril!w&qkHusV zd_i7-5!GfGCndIW&_xq1P48PQOTyPjVotA5@m((dY7?Wc)>B(v%rTu-1&d>9V8+&z zCpGB7$;=yVr?+1BD1QaHLSLjZ0xK-8-=iVn&U39T_SMtkiL=7hhlSk8-Gz&(T~y)5 zSoHCs1Jf^q)1FI*0LJwGXdIL0^)fbxK=aiu@J=l40GGc=)CeiEkR0CA6Cx`zmD z{*)?QL;jFYwiIprcG@C$O{&nLEdt=ZI`pN1YY6b$$>h2%?20#0cj3!bUTn6o&Umz~hS?fN;K6llX4(E0A{CX_$TXpCewo z7zaOo|LsBh`S+M5LC1#sM_dM<#dQw@mMZa3B(zkhprEBsJn?|>ESek%B<3KOBjLc(4+HUKRJ2H@tj< zD7fcPP*yxfcVamt^1H6lSlxHKV^~~$ESS|mX$*?fXKHWUvpD@xNa|o&kp}m)g#2im zh~{Qflyg-ThTm!t!QqBP{UmVma2Dg6t<}~rdOVk0;qt0z`+jVbo zPOFkQpSKR3ldpr%J-5FUz1`g&h7Up_s7FI{y#cUXGIf#!)OW$_-QCwV5rm08aN;Y4 zE`l~Ct|L+a7X1ncKH!Cr-$Ltqc}YG*(84o$n9kip@klYK+k7}ikwREoq4iNPe)1(o z^28TyPORjuj|f^{f+*6+ii`j&vQCsu9@d%IFPOBL5e|b@|96o`gWzWDJ`=#i0rPQ% z?8;+s4D=G$gi}>uEjV;7C_X{1^dvaxI#MtA_4nAMH?*fn=7I z;S(?#Av%BnmGfJV`L`VH-KPPDA_uB$L6A2^0?o+H7NnAZ*Mht&d+5I*KNM(Zs1BoH z*?DYhS-|}wSW%dfMNd3TGXHgt&jFt-Qy<$Er_K9?P_m1dqIDrQelnQ|uB6E&qE>xX zKKX)F=$_Tz(_8V9>=!=mHP9C4lXy!+do|*M$63EP^AS7o8Damww6S8gU*ID zMkh&9l9iRoI1>i#P6!-z*lYqVOUXry!+VOMC*Jm0#gqp3i@uu&#`~>$cn-+uP-@(& zF%3UmC!Z5I^BZzvkRZ&vyIK@SI2F<$CCES z1s5xGcmN_)zkO0)ezuyHjZaONoU#t!;HlB1joZgYF^L)g{&AKjlnBFDwZePYxk#9k z2?Er*UBuH7TKKTDQ_>A3+`l;Jej+d0hW~#)HACvm<=8UD_tEL@a_oi}7R7f${D7XG z97tC7xvi4b&ebH!BBrCE%N*BFzg47-QJj;jF?CL2D)>&m8N%1{7m;Gcc7QwOP}(B= ztEbEoa{qo|$3~kfNk~=vS-EXdw@=0rT|^@n70^lu8r5W=7O-fst7ZIgHnud3^m(Ca z7OmZi3`&nktr(1_DsM{EsvCB|!M4>wTT4$Psf+Vu&@w|L{nzm&I1IwF-7>R_*(Ugk zLL10yK|xJ8iRP9Fcs%m#tAmqTw+!pmXULe&i>%xh6>!#u8`ygCLzqE9ktZl2*Ej!B z4@`go>v~I{>W@5cBfT<3DPtggXa~+g>)^ZAb4Og{Oc<$*iH>w%gDaV6QCRW`m7WnU zNPcHgOzI@KAcy>Iu?Ja5ffHujp4!R)Wb*sF_b0EPUKC{W>5-d;92!cmXodkx zPN(%Ck}A3nClsl4>JyruBJ!NZpQGtxBWwVasYXx|j8gkg9$Ojd75b+gn`kq|U@dsB z%;CHxT&S&Ju_M2m*vVVsUtw4v8BvO8r&Q*!((Z!jBOt;6<6cQ0Jtb+wC|F_|AeN=^;4H=~w$+#K|JU<2c#Yq&oRc)0>Uy7=({uu)9NyCV=Z`) z_hrdq4_Qk_H#H$#q#T}NklJ;sL8&&>Ow7oqh%Wxyv=XW3rzUfRChI6=!|-!g{#y38 zt>(vs4R9TYqtD2yz{&0?f{u$k6GJ(!wV+!eizBYy41k(x#-1a#Oi2JI+ z0U%)8*yF#5AcVGu*qSpCD1?=s)Ny|@Kl<;?lhR<11of{$WanXDY<4X9eG#GS}O9cXXO4v?CZ8JN@8(a zABXG+@>$d8l8SXezfxbx$a=uuIbw3|0TC0~&IdhV=mf7++^G z9l)FTyD&M4|3lkl)$kZI@)pXDx!AO0{*MB4NfHgVkWDvUS?OLd3Uk$Z)pnIvhZzhk zqcchg;0u8Yk%aXi{LCX>sq{SQ0@yVCk!6O-h*P;3FiVdBgqQW6G@2YuAZ0Lr)hw7CzY?gdbga z7u$1qU=|EcfvVj05srhcw#c!!N`OQ$L^s6~vEO6JPGwHotaUS$hA_FEavUAc&65g^ zu5$U>6@V7UAS`xKV22F1zfE~EIm)dPZrMZ0DT|@eDCOuXmzPp*km}76N_mWJe3{kk zgh~+tJ?w6Pl{B6LG_EcKDV*UNBGC^JX_uSQ9$H*uh9%e3Dk)7rijCTLfsH^NgiPIj*7~# znk}?u^Ogh{U#pO&YMIfua-=KWaf}Y2WM_4IE2?Ub(yFW?V{raVX@#p2P^}6gjj&(6 zb|rN}5nzs91|z>HZ6Csa*fGltuC&6Kadc@C<|THtv-kBROFH`oO4;oETI2w#l1>bR z)sf^i{eyN}^niLBPJoWP(81de+6O2)!Y~?0j~}$3i{mhxKK=qU1U~&voPYM8!@EZm z3T6b?2ZBRr(9LskmL}5wrJxw=CkbLjlI_9s@8G#apK$j*YCyCs5DI<}zzLwX&=mbB zP7%LK2dC%lzlp;snUh~eENzCepTx=RI>*cBvif`)9ZgaIq}=}wdV7juk&6iWfo?R@ zM<_fHuhB&xuAYlt6rID?&)^0-Se(jmjd1_HfQBzHG9%plAg+@|PUSy}s|W+RhuNd{ z@cIrqr$1tq{v=+8Bc=8E9=da#s3?!n*7rE&;p2?n`PTwo!m{TWX8$%GjR35^=U#Dt zzlSby01zlT#B-kN|9~}kA;=;8d*~r`8y5pLuq{!?6#XP%-p8>2@aFI0 z4H~6=f6x|e(_r2`Kp=?#sz$_ky(V286hFs-pasHJn!dyIT2S_#05S&eQ-G)M4`4ox z1??#&l_b-I?+<IQUTz#p^ScZV ze>42&KURisJ@^UVJPnga*@+y={w^xE=C9ENF*g(R9rjWyMPSk2MR(!!lCU=Us01Hjj>(JhbnBu7gZ{|8u20L~;L z42WGHonzl&G0(|l65*fEEu+Js=CFmvjcc*Aftt@We zMgf$+2CnyQ)S|N_xXSRl5=$qzNKF~>|H1D)EQxb23ubM+{cwEQJ2^iK9_X$}mGMk) zoa^?v!JEi57}P(9k6>h51l5}BBF+pB{!Tt}8NcLDll4?N$QCM_#uQBtG)|h-S>_sDg^d3ZLOV6sa-_%1E(Y!qsa@PHSor(~%7pn^X@A#V4?mtciPnQ4w zRG8KAqf22=iCQl%(})+@x{Ro9fz>MA>X8 zQY9(buA}|i&&=RLkfPnRYwhoxhtsTO5raE07z_r38To>eD~uOKCWRFHT`IFwoo+P`9i~$nfIpJo`AQwhHaMz6Mt#nCIDgF54}goNnEX2A)2B!3 z3BEtvcnRwG=QiZtCc!d5&Wdg;A|)lX?l37Csp5Bv+;rw?Kn8WyU5k9%Uv16uzyDjS zpkys{rQoEE(ai75GFKBs3y2_C`fn&(uxiF(KC~&!95@nd7N(+c z(U~b4S{`vV8heL9{DN$nDV)IdA42-I6344|eiE!4jpq5apfw znS8rIem+61VfIw{w2`Ot3{_ls?^PU(5|^@%bdYnm9cEv)N6+P`;9=^KU14`TvF>PE z;S4uP(7T}X7adG1CmMHWpZ!%^=assb8ZJkQGooR6<&gz~cJhO(%x z#-*rMPNDwsvDV1Pl9SbyVQ@Nnv)|T{TbulDDQL& z&l%#eOIlS@yNU(nv{IkIqX<9us+K2_H^1rFeezozO@2d5J5N|hInuE-ufwM6f;ou? z43*O33$ z=O+0l#xmyPoif&AW;FI%<@*kcdGAzO^rLc=nYLB373nKmkrrEwW%qI(vN);iwK65P z7;D=>u7EUFPn^^PCm%+GbjkQ)&hN)x1B@AhNin3;oFrFNQwn6UWMzg4J`;w9!Ia8N zH^?$JJ!|B_ka=4k4P}U@jc8FAD#9_|-+vU068BNd!8#0s=po*xNu*3Pqc-m}Nr;9v zLVIOIhPqZa7rN1;j9db6HO=x$de++C&sR_@H@7^}p$^@u?&NK2iP~pE*Lk?xxcre0 z%F!HuG}5T>)mXKzU_VjHE>W@xyC+wSkGYh1i*NP_kV$RF%*-WV373fq^Td&ma-xsi zB$xCuZB&=O61MqxizF2tGuOVC`Y4g#Q`C|uC3)&*BR(8Xfj;q_VG~a^dqGQBEwZBB4@;Do2nlP7pDNO0K!*@r9u?v~z~^^W4@& zz`4-<#9KYpY@IpR1lv*p>E>rbe8+vg)-68 ztzIX;7OUvgPLgjbB(fI_?-qj;N@D0rbb`AYGBD!Wn4OV6Ek4BhF7sMaoJ6s%AA zrcP5M2TbISyG(vxzK zqx4M-Z8aGd@Az_jf0Kns=qL)fAveG&dqfYfu^z2=9d7Q(N5k$}@jtQ|Jbex|HG$P{7=dx)9>D>R6TBzOr34I_fPTV`P(h!M+0&tF&iTr}-6ct`8 zYv%}Bss@cu>z9!KlaPbCm9Tx#f)lFbF2v1b-V_0t2kHH-;X|k!AfhuHzgJwLp}se& zEhZiH^|3COSI%)y!P+R6Wd`|WV${6!@QUkbh7uf;G@ZOuhPJtV)!pN;UdS|wz4^=o z!lQD+h9dZ1yV!g=Oa*gLi&=3>X^u^07k1ZjMy}sS=4dLB8CBGX2JdEvai!&}k2546 z3!lGLiwhr1l@_*H%zjT+ITNZo?9R(zgPgAM!UVabt00_X+jziBJQ#q4#I0zPWj)Oj zpR-Sv^U#>Qcyn^f6Q3x@au2kyNLK8fMMl0B3R9;c`OfS;dSLo0EvEdf8LTX!dJ^xM z1+k_orYG-}`M?P6??2p~zd87oWcxvaU)Uf@6SG6yD@YzDCpI77&LPE9>>PTMMo$p8 zVODfv9!Q<2}jRJ=LSg2xGF)>`}kjI}(MOOu&73N#42*qNxB=mp@*^f8c z(MuYjF?mmM>n}J7vWC%9rR*O`HZ*n<6m8rm(u>vWLVO3HA8T5gJh)ZiWG$DGZDLpF zxP*ioPnPjDAk7PbP&LOQ?J6<%jmJA zty!5~6vc+X9Yk&m#%JgBj^mU~q6VCR^0RrAI};TCPBif-x&y>g1J|Db`;^T6q+!(K zy3ydglggXap|z+YWow+?>A2Lg2l+8@9iN!E^bg3#jQu6Eq{N0n%#zIy&QTA1C^j+8uoaR)#E0O5JNE=j2MLZ zNU;?AnDB<8O&AJ^=92R?NO*G^{E5$e;0z%q3}7f()0|bL^#Gsh;r&C}Z~=i5#S`MEF}FS#}a^6{gJBUHgyI-k1T z%-gENS9MO2BNuo}Jk*bRcnHgvKaHjCmy{Q#wk-J}8>2G5*E23=qjL<8Xcahw%FcU@ ze7r0m_YI-+Ev!OctcTe31&T3sM++O{JD{*=c;`(N-qIkJVB%(nc_jgTkZ*Bn1-`N2 zBzJe?WbBq~K|{4p!g!@l%wT!}Oo-`Z^@MShbK-i$5CQP5IP)|Ip=mDh7CL)3jt@m= zqUcB#oooqX-rjeNlTV@)D@=yc0zhL`7oZ_Z&&|>I>~E4VogaRYNCRg>HU{ z6*#(gN^(K#OBD1D0e476|BjE1L$)Vmi_kYpzIQ{p#}Aatl3cUaqlw`B9Ovz{Q+EMM zFZW>TKU+2X{gq!sDcRJUeMGyGn*dnCnwQo{Ki|iv=`Csdsi?Kh$6I8jxt0^a6=R_> ztdM4$-JH8g6{?BjOM$1vt*gv3RCl3Olj%r&g_>NfHbtwnu<&bk4K3#*h2l3zkE94) z5Gr&T0%r?T1Pfj%h*Vg@7lN6UNNHv@MJB7VVP91YAx;V!)iOj*qxqFJ+>scF*&NbQ znWPrzV2isi#Tyw?JlGj;T^^zfSo9={nMO%y;m9BT|4Ddw5>T+%lwM6yjDbhPT&AE6 zu`U|o6Hy-R{6M%PJSUO55|-??hbJ&A=XST^Vzs&?rka2bKd%i{qMAtU7^a0vOBF;* z-m-12dno&E(cB7cXgc3(@zts7O@{#R%WU(GjTU0`5J7l6lOK7F`AhA(YS zS#j4+3fnW8r=Z0I2Ma|X;X7Zwnjs3E_kL?@PpfpZ6asaJN{8&F%?;%ck$|K^TM1bXBW|tnV0<$gBIvBO(m-A(}?-z(- zSKh|2k%&*4hP!_qB#V^Z=<)=WjJ1L5T6Nb+}BIe&@iFcG`N$SKvPQEK} ziDW=av7lM2NX zp-eUM9n&WghYzK8mrlS{GIG71KqTf4)pPqy+H8#WY_h{7z1}-oU`QUS@%C2$(3>q? zVt?=?n){?hD0<9ZjD_`aM)(SK|bkaPdXlaTv1EvUFD17a{S6D0;K zI9t9eW{A-TwtNj8p*jfqFGM{Dr6A?Qx`f37pD)HTag4?y^lVMKTS)v!=6{LPHA1P= ziLKQF8M9BH^1#WM{0AxkshhB4yCBc}w{)eH=2)f5lwL%;vWPaRk^a*b?7>aK*F$Anj0?IOXxzZ|WB*MuQ^Y zkuFiPKeQq@A_L*4!4$1As0MA>^CHFN;|GONA4CgsC!-QX7YFq4d03#)%{_{!-ehDf zieLkGjxx(3>JTb37XeqbLPOQKr67tawXB!?&7u;2@i|qx>RJ<3(`c(Gv@gmVhAw22 z36P?|LPgdfH{6L0B^Iu_k8!aKN%&TJduphvbhrsV|_`%W|aV?8^stG z7qLRCi_aP9=@F)tt5z|Oingu)qA5BF!DdN1a6i2u3yuXyu?M*vx&qJq zsJS&ll|Nvx3Q$D}FuC?%_?yMQyXT?lrhmTY5v!)0i#GR0$dnRWN=|_H@UlBayF)sZ zlMdhkVT8~RvgEW3$Kafnah6MI!*`}em`sidBTs^DfJ=paKYnd^q;Z4(n8X|GZu z!NQHXa7iR^uXERL>K`};We+L8QK=Tf`}qR#CY z(35>4<-G5J;4<=Bmsj*n;f_GVHb7_M$|Txre&B6A3++52RYWeERTh0kXc()dc^?w?%@pi2ZX_-sJA=2l?^j(}9STGoKe!e*m(jUHqJ-M_wK2lGk@`~Qs zx;L1AxIw5rhF?qq$rp#?#KmV4mU^3!n*bXn=qVnb4dmQRyu;1J;{AbqF!E~bu6^?k zU8#DRcpi9n^5(@$rvnrN-FO^6}a zBWI`=xL&F8xucy24f;yu`ow*onRomO$d*??u;VQjsV^=fRPaZhYAfvua>qrw344!g z;Ri6GK~dc#kRGp6cdfiVsSeE1L&(Bi)9U0RFwv%!Y}3`&UC{L)UX#M|AJYXhmyWSJ zy@AVO>o<^*-SRuuMHmAgxw6=`9=;OS#PW{l77qyOM(7$tTKNE4=Adt6vW=wMr>+}DH19ui;ooXT zI&!ZPJ@g_FzON{>#~*W$IGUQqR8n@g2!Yx-+}07Kr5IeHYq>txZX`uH`7&JZEW@>U z=w$(l;qjYL_&c&#uBp!Ytks9+5xC$(3*RN~YCap4TFB^MfTx0i;#DZ+u8QO+1A3vS z;>J>T67eR#TDbD;eZ7{Z*K-!>IqhTDYk5PUzdrri|F=2u*6`J$?&cDhR}0+-dQ5VY znEX>d$K3$6pGjJ#c-il4KJ$>c(L$m2HMi3qp+hnCzKJe2wqHwg(1o+yHMjPj3j(30 z3`dZjzVlS$pddLGsy4e@Efc16#HIxNojr`6`cft69)+MS`xq3OW7FR4m>la*7eW>h7=6bfoeJf{q-IymRRKj*pfv%1RuaZ#>~znZM7>Le+r<*%-* z6KA|#l1Ccx9DXj<-8z?e&q%(+-l5w5Pnl?>z1ZslVPkqZ+VBxOC&$a;`QtbkT<~x z_&EfdBmvh~z8@O=Fn1^V&R=NlR!3JG>OgqM`{qa=g??3M?*Vf=xYE4u&Du3QG?{`g zY+u_-O!_swVnOQ-WIE|4`_{ijcSl?h^owu4wrQEo-qB|GjsOxvec{D5b-v-`iJlbx zIi{rM9ai=>f$6^)hpEIgnDO_-sbGB`U`FB;*T@!=yp31u)%__}6{Y4il~X5ZKqh&) zTxGXAamsx9*bhT;JyK3FOIObCc>5GYt98ady>%8JM`#W8bmh(bQ0fb)7R!Uk9#3`kcaEx7L5Ycp3)jO!Yna>p~RN+$~Qrh zt-bKCiiIT%>n%FLRdfpb3Ucd9}X@4o2ky4rb z``X)ZC?M47TA?dfrhUs5T@pal-?{6AeDcM;0GvKee{=yT-ylC94q`u~FXALe+ncpj zfpa_#|F4wf$nTOiygUhwCoGZiS}u{(HMGlOKhPrjg~C9tzR6deoXEnOJBnrgZ)^r! zp>*fLek@95kK$b2^cPAjRv+D(O8yRopQN9hAHi~)AEOA)qZErq9{er)Fm~^P%wMMG zK&8)DaW_ii2I zJ^nzx_!=_B*9Jo+f%oxy9{U{Csxs_0rHVf~#+#1@BNo>&#`t5fB}^i)+9&v9uv?1y znPSlI8eb@m!I8U$AOmfCmLm{kuxsD{C?m#Kah#&DO$3T+cH)+KtvhkI)QGu112VG{ z$-zA5E3&i)t%m=2<0Rzd$%`ivdMkl1cpBvK%js(0eB&&a4*H3vmOb}j<@-1qCmcC6KFsLd(L;;E>p zaw<{;)e3w;!y<~5V}xbM2r5NJ$XsKf4(sGrWSj92&!?g&qm0_JS3tPPzQa`<;x1sE z>IjmcO}n>R^u#zt9z#SMFQYmCJlnEA7l7Q5-hoqYRf!z4u;XtlVmXrhv4+C^$^;@x zOlVaKfrE&qRnZ)M@9hz99`%Sm&v??GOYN1(d3z~2eTwAY?{+}uP#vI5G9>bylshGJ ziB3@-{w;F+Sn8(Cz`IH5>GKT9jzu)!Z7}=jC$BjHB3Z;M)DSw#Tw@QWZt92ITuGC7 zEA)o}o9fnJ?-HGK8SHJoe|{J50l6mrcG8GOY3y1!}PUtqsOw#**&8`Bs9+9486j(AqPH5x+$o6%f`Mho!2F zD}VM84U9=7K^-I7fMg2cinEAM2*T?#RhnO@giI(4HsVMtoDBIznb^;G{Bg7a_RTT) zZ)KA2(2gai^3*=Pco_eqQu9T0oMd9`PRVz-f3xzUxnsPf-&%s3%q$K=ZvFz=jCu6o2GnkC-4_P*IrMj6+r-&EDi`whVYI$u|5|W@sDn z=EVNvfTDaB78nU<@52rB(Pc11qz_R5W#JbOr#S#RB+gZUv2;)qlRD-|a1Y+_JSq*( zT8TK-n8EW?oXA^7s)ukAcrfEjA7j2~(ALM7V*_0K)id7xMg#xYOIk??Ek*DT! zUxu`U`PV$tTOJU(=E74fkCT35dE}OfqLDiz{GtOW$ZX7en+ivbbsxbMgbz9r9?LIm3-KCS;-}08SdF{Im>t1o@$S zT)k6gAu_946X#iy3E;TY&|AGE%?6X|ii%|Vk??0H65#7UC#U7oPCC#J)K;Fr2?P>s z&tQ+d0tDnIuRQsT>sgXv^(pM+A8YE3FXyzJyM^GhR2~G^IJ8FEoYVzm!IQMQY?uYa zDu0f2z!Tf9$mbOHAup2U@l;!SkN~5`FA$xhOuBkKLQTg6IA!?Y$`|;>jVR$4H6DJ2 za~1qVrxKSuc7YEN@go7q%?iK#r5A+wfj01u(0u+4{J>$A*NmPOE5>;b;oE%Fkoyd( z62f5N1ZI!}5|nayHx)V7D;zFJ?%foV6E9GaVd`hs=y!|P(AdqnmE&_yX8_l{kXMSm zy00q$4VLSrsOP@s15~DhX8g@oLsR+e@|Kje0#_K5K-oQSyeJfP(OMuiYgLH{<34MvgKIf^H$}OWv4#kKsC}RuMYKbS(J3$K- z9fb0~e@7-u?*KhH0r>u14PE)v1ORCRJ;^|dOCLbXam``cQ~m)}u#fg%-d=L{@SQ*N z1O4_=34c)XT@vL8j#9{9g#21cu7Lkknkglj*7h^5H{bcD!TC<^vi@$1S6M>Rz^}xL zP4irXbD2h|c`05}hHm=l)4OEiq4OS|?2O%ckxVq(x(Qpm-v0hm+xWuDVFawdAF3a1_b#xL5ez$>F8Y|;-TGA%#xQ_ z^E5r4PoLBkn%))YZ=wn>>I!&f%*8G&5WguYwdPkCduwT^^nr;GrBCkZYk+&40e!ti2 z^sL9NUf1ppI^BbVx@ktwo;_>zt;fB?gTn(X#$wTLEg~K=LrT@IO4U*W!xObIQN@U2M?SZCN)z!nmm)iVCo==!Zv^gH z@Ry8H3I}(czSQIRVc{BI8bkh?qYv?aSr$&>MU5`Vxry^O2T9+#@H#BMJxNjcr0V zw!F?iZP!4Cxj}9~8dn}}A1@ODoTaG%MX_s~pU=>@#_#F5vUYK9XmlgcOfT|x@xV97 zOH-SKR@)M98``9~QE4nq3A@pAkrpJ`w~|2@z|B9_esastJ8=RB9P@(I(17oKnQYH4 z$Xh+TgHvCcjgfZZPE)97$j6@i(q=_Pxp)%AXD8kkQ$}#|1z_@|(y5=z1TGhqq$OCu-5sT(3e7jw(EM6nTvY$ow)vId@Ue&)__*HC^98!b|BhtA0knRD{g zO6rl$1QI}|ZLUWLC?7B^moOa-NzMSbtLs*pl@oHgc;>EY$GUPCK%LYIoy_YkM&3R> z(c@xq$P|dNu*leK8B2c>)|XS2tY~ZG+sw@EX_*yzb0tHjI{;x8MZT!dOa50>ROKN} zYmV0GDE~yO(vMnVm-%ra_>j%4L8gBU_RH8cAhgbcumN-d&Zx6pRl2WjuA&V`^yDLu zu7CdKKxt<~6G_8{yGG`%GVsOR1%2`h{$IwL>QY+JWQVgp%+ULIlz%$aT!6_vRRYIS z;Bd5&EZfmBAphDoM#d^3Ur^su(D4<(a17Xf@FOqfCwoW7n^+TbH4|ET_;@Q5N_36LXHaIM5Y(;#-r zu!b>mzB#k>W)uTUiz-t5%iYnkConGIAOc#9?sQyVx!*oP+%2$} zaNrM!&*3h>^O?#YpZv>PqcEs$$9 z&DZK9ek4lu)e8wkw?do;pXHBZ!y?7_nzW)}mO@Q=v!@&(N%<1mp(?J!M=fO-Kqx8e z<_j_FB*ICdw<;=&_^`KJN->QKQi}V96v!(p%T5$O1{Mo%PhE#n8m zUEkMSxN#3YeePp_eD1pRR+{u7e-T!3tW8Dox(V7{d3dIAnc<^y5rNOZB~4E_ zlLN$^&R`Tkd-)Zk19QQ4>gM`sLe^8#Z4)vfZN$*+!GmW?e89j>0Vy79w-jqD>@;a^ zYI&yImJOfBdh6LVZk`z)i$!FX`mA9b;LejK8hGA*4zPy2Uqq zV%Vq*W+w6k@ZdQM(kebh8+*}CDjUsFNk2Uj$sfghIOvCvjvk2Y?>~y=!YZ{V*ed&0 zy5S{6-ZIhAa8Y}T2qcy4K(O6r=938tw%Z-lciZ-`0{h(_Nt`|`$V7`I-I*ZWnt!$( z$j`RC2q6=w9~MBN@x7x7v<>zG3X=iq{2`4f19E0PM(tE_>vt+HD#yh=pvr{we+F*c zV_(x#A)@@h>^M#rBa;Lh@=}~uNR1tQ-_6(P-2Hl??40BD?eG5?n7QF|3o5I-EhKU; zRGH+9u`J)#s#MeikLbXb5vnt>*-O##RY(hH8f$)|3!X)*?T<+Ea}_GvbyuNgyY3fa z`q)*-?H);q8$)?&L^bWAU7x~BTxCuJ-w|bX^fsg$uKL9u z0Wg7ab`tgAHPH;#KrA!?h47FJPv8r|(-D&AC=m1t=i~FWd9inwZO`}as0Z5%+0is` zFSuYGPMx{O3A~Xm2;H|A%6-?9Uz<-Z8Olw*w_W3WdbE5VztKhaUQc4Wd_oJ0myTa| z#9XF`FC>_b4%Z@u?TMPl3GTfPONL#IUtKy6xRGd1r{qx_w^Hq zYKrGZBeeMA01@0VLky>?8MmAHumDP(s=l`s*^AwvQf8iWr$`cYb{rZE#|{`J=4~Yl zN6UE`=7XH}a@K1Z>W*q41T%2)-=TL)Iz4Hyt=-j{@sB-FWjkAlQSr#&QK1{^z1=@0knMnTTOmGi|;wfxT_wBL}4Zo+8 zz(14>Bat`+(hya!$n=}sseo&hxns)x6t=~o+|3)(ko&WF4|!0X*M;FjJ4rAJ8vi33 zyz041@TnU^YgQ0vpI@W|(q=$7x}Hh2RIwk&7>h-m`r5))C)5t<4n9U z0Sc@%8ia84%+f9y?l6NBqJ=tHl{0g{T`;b}#=Q@(nMO!BXufqtm2Bb8M{}hr_@%ffEoQ*Gnr&mc3eH4N3{7X-ZdxNcl?@K^}cT|A*dKu00 z!0F11v=9n^MQ}__DFpJIJgj9F0Fi&0eywU%-y$^+tJ3wLNU?yO;74vNpoNz_{x>oZ zue=#v7r~e4U7W10JQ}dfo6?iM7TODsuMza;F*?e?k2qdBeOkva3LkS?^3&2&btEWS_eFFtLL0{J)I%8I#aybH1w#?tah7~WDz#70; zvPOmAXoKd`W}(3@VLSV8z7ZO1g8{tWBE44F;xG(!dw~z|VavP4Fzv1eOE#`F#n0ZX zU9Olw#C|N)R&sh7Zwf+uVFFM^UwcKD&b8cQD!qKo6ZUozDpyX0gkfB~CDB!BfD_it z+~3AKMe_4ovA|8il)n`8b3B zpW_(lY(9+d*K%7!=_2!3s$PKeqq@(MI7VCNe6x2V(gAZ4aTyusYJ>ryz4I;=1i`tE z8E>`YFGK$$E?x#BeNVE`q*e-&)}Gj z{2F!nUD{|^OfM&iO65EaHlm+#bwos0{&vOh0srl{#jk+3 z+I{}flQpzE@}noq?((vG0}1Ge%0wX@k+&-w+mZ>}JYlybB04gnC6!mSRkNq#JD{&b zURWl8vw+xy!xLSlP|rblt$S6!47FIG7BB^WWQ`WPnwdDshpmxcZ#($ewc)en_?B*H z=}k}SD-KP-3Y>RqfB$FyX{R%^lKkG6&)c*1r%&+lGkkT>ACghi?(^RFavwv(Mg!Pq z%)h_?2+}|84nBSQ*?-ou+w|Kzpi25Z3LV%KIy|t3sy;#`#Iw4YJ?-!BW6NB()8?h( zm);HRt*DtAZ3zVXSz?&)D0JPKR=f)6bh-K9&f)$(6bAW*Q#f6I9LhPT5o}RpG42qs z6RRZ9wqjAd)W?MniC_O2E9)H$N&H@?^*HFtUb@`_i`T%5GoCy)>M$S~!>P6E1_a-% zX@0v0BcJNm2IFl$r;lK^XikfhNrPKh(MLLx0mhwrcqK$OBl15Je0Su#gu)Dv)P|5= z@u^p)vcP#NW!M?uHK{eY9x$>t@TN9H*B?t*02}!KEs}BwpCX2j-I$%b0ekNzZ0ep4 zH)=|++-pciUA~i(1A}<=2ngC>e;=sTl+9t3V0Z`C9qfD4x(nMRtdnbt8T$tO|JQ|K z@gLZW&DkiO%o?pT$F{6TZUYU9-MV-f<1<=G1Yq$)u~cRO7w#oskQ$cSRT*;t49f=5 z9G2_>apYMLt^J{-XQI>JYu#-tVh5Y!anA(&!qK3`V$tx27XHtKxxf1KiAI8!Efyle zfP+AArtou%;{}xq(HUCMuAy~2Xc^k;6YZ5xg%JA0*lL{sQu=u5zzEjsXD*fb_*TaB zAf`ZfZP}meBH>3eyg`8@Ph4L+w=SQFDbC%LJ^6LPTJD-u(lL zrg5IJH6VJ*P61sfXN(-0%+WP?fJVY#hi!LoeXmJoedo%h ztiyX0^FGa?k@!DYjnw31G>`LsoX`)fXYjmA>>Gbd1Qe}7r8)0`vML?BBpo2?j0Ty1My@7Ir) zQpbD%IJE}8s^t)FR)MD9u-K)ngnsj!zAoGt(0t{R4Xsh& z(8OH1H+a;C>984c$hUwR%cse(USC==BPY7V&M)e4(sF?)Zd`A&JcDMBU4MA}^a?Uu zTP9Fth=M^}O|H-Cu#D=b4y>qC_GF(HD1t3yAk1)bivu}UTaMzudGoT3lXoHS=W(y> zX)4d?Z{W0KA`5LXYHXV|+PDuTS^+<61GQU3wBb215C2M_!!Xa3JTGNAE4xbsBu-%H zlKk!*UFb!1H#2>--kcDdt4sAY>r^LNuO#Qo4Q6uOyzM!3fnTx>sxSJ>9$&v3@f&R% zb!OrpuKYlAqI9U?IenQDnnr+WuOcgDs2g`i9s8aeL{ET9Zbe3zGrSMR1D(+Q6ZThy znj-`9Y{|ML5^wOPx?Cn&Zmp;~!n)w_B!rQ_NaZ^;U?UX&8zekxtURcg^^EU1yP4fC(Pq?ihqlJ&&qIB{txlN!eh6PsO0OvEb!gH5XTs&p~Xmz zzDkJhYeMw0c2W5PMvu*O-J=&e=ZX`}9>D}peC-7Fyr*CKQKT4DQtHEN;{AQX1oY6b zu_E_fPrso5>ps6s=uUyxYf$$`5njqkMIVRD+G{+8(4~!7b|!HBUkeu-z+NLtquYk_ z8Cr)pH(0s>$P#wP;G9;9erxuR3gQ&L(J%2~W{<=e*OsPW4gY!|QGXBUmVyH`d3 zP}>Vmbf3G&!$-^BGoIF<^N$Awui zSLU(Bpc-ve%5I%(9V~QsA$6svE*wzKi>P*9w*NQI7bcu!hgB$Ip3BCZPtwg9G^4oQ zrWQ=O#J(fIzV|TAN3c8{{FtJ*GxX8)D6;16#ogZBe7nt8lDEH)Y8Z0R;hd%IdPB&d zbTBrPjixLwZhmT_Jhsv3j;Uw!wz%UvUv4qsg?v9bnX|>2Mo?c>Df{@^dnMYo^=q72 zQM;Tj-4k((E^ol;DmcFp&Tr)>;$j&0OPLtAh%>1}@UMk_;m+Soz)@GbJ*OSTV`8UC z#ZD74QnPkuj5`e{N6Bk(1vz)^tvq1Bh9X}o@lJl?e*1uZT*%p&Oxe+y`y3${H5Kd( zfMxTK7_6-|KC%+}5-wSgFKlyH+GDw8am(AN{q9ygz`M>b_a{I+Y2mNfo$6(&WZ7vx z7@{W)!@9Q`z{7JF*u+zdy?0;bS_0?PL*=Gy>hhOLFBA2wMZ6SY>aoxgqC`XHTUf?o z>C6g75@x35%!Wru*HA;=z2nn!q|Oh=?s)*D?LDlM;W262Tx+TT?6|1&{--U6-eNlW zALr!%1XYh#q^Wm;US{Fjo6q5!K}FE>@#N&pVi)KBVgcDvM-5ibXY_V!JW8k#AZ!{At^US0zI_}5~8|0mB3RlQ`V1K3+nXxG2;i#nFb zQIpD}B8IR*aD7U=WR{+aX`Q&yRuad1S;^0Jt*2BHN2L1)qowNHUO4%;K>v?$TIn|1 z&A!3@@Z5`ZZ>^9vpd%%f!c{uYqb95t{EXRzZYz0?Bxc5tI2F__*~$ zJRmS*6NfV|iZQG2e-x#L-0I_H9L|lioRY1Dh3-FlP);>J=JgLbLgBV5TAodK_h@up;0;&MellttU{G#pG?i z={GX#Y0Gc)QOc-la!vm!{Ct_!GY9@!PqaEjdFXe}cOkMmj_GGE-_y=)suL}}6NOuu zKkqr?DQ+-V1u&Wh?vyBbFN^~Uq4NTN_!|X&DGJmS3KhWN)YHw+u#=r`?&xvOqK&ZG?6s7u>I1=j~Z zmQ*M15{GaO?T$P~(;}A`&hqdS!;uPW=Z9=1i}GDn2FqNPpGB_9%U?seYFc?t*izM; z3kdi2X|vQdlX`4DX%EF?zt}=BM00i>%M0D45lj0ko|`^v_D10ov)ur&<-iw~K(C(p zt!dzzStGKZgm?`bjrxvV5SeF~AZqxSpshzl7=gGoreYe zh)q~hDT$sU`xw=6{v%>_q6U3GZ9VCad}tKZ{n+e3j)2DoSbGHL;HT{;ojeW#(1FK& z9Sc5`wT&4)lN6Hzi&jkvt#2_Y^qtXVPV7c#_yjdF^Re~Z=qU_m^w@0GGiXm8M(%M2 z)6#w%HN0WsLibYUF~8n=(!q(8VVpww-M~0y`QAwchQ$K2f)YkeXa9Q`g|?yqEm(+8 zqFj4qJL!`o$1R0d0-JV`CjBdBZ-6y^@-|LxjPXl z5R9Oqn%bEp9%5?erK!E0<+lc;ne0x0dT|YV^kanY1ah-BeY~qFL}w32N2O>bm_VO= zsMx6;YAKA0U`H9g&cOt&Qi4`FLA#WottS}iCE8Y9&riX&7Ib34=<1TopL+U54?W=f z-yI)}{EA6_iIRD2-H59;u)-QbBQ~FhQr3pi>yipg5F4aVUerP}cR# zFtS0#$bQ=^^A~(T3OhC8YH@(HcT6i?VF^<|LCnrC`83xsHN{zx`y}ngQ)>k*79Z_w z!D2D}{eG`Ku&rm$2C!!QeOfbgi#c=eJQTztyXY8SEbPT#u>g)f$^u~S$>=D8J%2hs z0Pr7c4}!M6SWqem0&k9*ea06D0Gf9J_~$wiMNy)uA6!znx9H7h5DwBmcwGy>JA(|< zkc_fIsrVm-BRJ*Fy>J=hAp;XmXLF38e3*&~hTtZML$n|L#lUM%{WiXmh_53Ed%d>T z2BvwsPQu&kIL313{&X-nK$F39KNE%Z7KgqEmE!DXiN8)|S8M{tvpDo<2!V(xNY+z@v10d0e7hmT8>C zgl_l`U{`UPsR9Rlt?Q^X_h8}J97bi$4{(A+p9VJm!=S{dfo;n$KZhbx%@+&A2+?hs z=_-!=+c_FZ<)nc?nHtsp;k1iZjpl(DaqO7Qd$Zn*!!EHGUBp4?Yp{vYEdq`R6(ccs zVf+07WC*FFXWYN3u5uvjrh-=mR1G&Mp>&k2vl~eL_^( z7qjNg=L5elV2UD$j&dMhvc6c@1XBa8h6-lXo3~{&ALMxl|0x)6+oA0PqU6AxglZh8 z{aH)E)RKZ8*tn!PG+$F~d)oFCTp{z|VCF9rSX)yE2ZO=kVKKtrjmUgI6h%$#Sr^@1 zP(X(8xp-M%>2{&nc3`bJ&1l>g7P}A80vgy)5ia{r+r@ z9pzCPg~f^GKt-meUayD4%E4SJgEP=Mu;)_=)&{XNn|2ObDncVo2h;w*_f^aaH~oC+ zEP(XNIGqr62lEz^Ngl&VkysE1t=^!AR+HzEhK#Ml*5QFo#*{PEz*zFlc7ap44ASg2 ziBq}X`q!jxmVuGyY-;6D?S2?IgG%{_hwhC^Bn_(e=80 z8~q04WWXOxdj|wcul$g6H!#EKl_2n=2v?hD_uB0_0>TYZP#6UG2Y>?E@L^!|CD8YJ zL_7;r1V#dBS%H8^SR@IAhv>VrW`OAes77GA{XrWyj*Q6d9KLY}$b6#6wxGx;f+fEi zUjVDHm=Dm0)6zr7RJieUy3qH+3WV94LpH?*0P?kQG_J3FG^G!#3s}jHu*oksqJ^~4 zzl=XheiwJ00@cIWoyh35l{Rq3pl=cu8Ea(DW;lpj-TWM(dk^O^|*I71Lth@vwo_=9}S zbUlTtOj068L%nFm>~d;22L81iD(p&@n#zxjg_ikHGzl;?mFp3ZWiCo6npbpEmDL$n z>E`~TLW}&9%d!Ig6=GQ*WLbe;R%odEkyR3BF%An}iM+Xvq|-^^N}3rST|1-Zorm&m zH{76Ip}Y8_Amq&iuas|AK-rO zxyGdN>#y~*(XYSG>tBux_Dccu_}5>XGR99_!yGwE>ibKN8P+F+SQ7Lzf?!;xAr$iK zuj!{>e>KnQrpn?c5Ia(4rCKjwY=$C?J}&&!CKG(mC5Yrp)2VEbN}NxZ)>W zl?9MX&vC_bq&!DhC>`1WT$3ufe}scAiJwFg^P%>Ejqck>JTc~rV-6*Cn|)w6N}PC2vq-Q`rh z%tWs0ar^MF4&2k@-rxX!r;l5$E=-x<*(v<>8n`M%z=zA0&xTwm93}CxnFhmc`r6 zlBz7Rn~E&4mL=WI(rmZd{l`(gdC=;$A19FDpxu5P?~KV&xVrN0n`W<=Jl#oNGC1ZT zbDUX!XGxXO<|K~d*;NuR@1LY^(hxP;)qw{$`(iPeX0w}rIui>@EGMyEBr?Gc2E_Pr zrw@k&_)1|9%bFV?=3*==>frS9^J`*JVMX%VP%p3mO&`-GY+QtH2s$i|H)&$PPMovT z5i?jV=Zb+T{!$Oe5C$&aA%^pB<0AB1;Sb?&_|Wi&_YMDjHT^ro zA3iYr;XfJv@PBdm{hP`)THLVF68>#9qM`|k-7?F={b0$)7e-w!SpUYXEO79dW&PB< zr zp}+&&zo?+L`xaG2g;50;!gd*$Y2~tkvroPNvf={RT@SQq@VN9h^8B48^VUz$&$f5z zmj;}7UICP@Rjxs&bvIXJBN9TI;kgD&rp;8QO;GmN^HONueKWM%$lASa+y3>GsMeN< zk7{`(d}>J&SDLMVqqx5_k@VVB8yacU*3)DHg;V}erFhyZS^(#xilpH_GD*%vTpMs+ zNE4yJB=A%7dS(ryr-^PjTqq?aljsb1OH}9q#u62Ir~%MAPx%4JO;VORS3!KZ^x@GX zI7qqKXrvuegyz-@f0(<( zoV@YXcOov)SD;njCF#YgW?>xp6~_n^ar&lc={?P)8)82yV0CFPZP`*iT|WaTAzL%7 zyBQrR;M4WdMrCL*UUyWgZJPXp>hv=wRHx~L+N>b1hq#6|Rsee3FsW%4X~)Zhdu7(k zhqF!}$U1#6Yvn+oy34;g>W$AH^(=_8&)}I_w&0nT!HsFqrJAwB-lbRG-2dB!k*?Wp zVRJwAAhVbG|ISFH`ADREB+~mv;y+Sd{j;mXThY4Q=9H#O%Ub)Frdjz0*;w1Mp4kkt`9c79o0b)<-9~T5Pu`B3`r9goj(TI3> zacah}3=<$~kAph=pdMrlt1AI&MvzNjx&Sf-$dp5-za78lVaW%lE^q40~9%~7$Co!%J*mp$iWjS_=u~QK{EysFj6QD%q zlfawbM`>1ZcXtHbaACMA5GvFVimxg_5L{*%U{!1AJS8@J1%J$G_PPsf}CWltEApcg@c_H$@EhxT%4rwZ7uOxmx6 z4svKWhaM_mM+4?b+ZyyhL0cMhu+^R{#;aT{26a(T7vmBp7MMAhSd_KWtB+ic7%Mqq zOk&))xzQeqB;LKnNduB!sAKD`C>@V_y5Eq!!iy-e`NXvOWU%?5Ud-d9?(@wjgUtsO zYY0M{PX?P0s?!i;y3O$_cQ3Ij)G*vV(4!{IGq(>EwFG?Xw!8KS8Z|0KG$QYD=9GYS z0#>B1SEZ&R7;f!J9hZ`mJi{M6r!-tHP0jn@tu%8tkek~c1S+hZ#nFt-u+YV5gmaNL zk`+{s8^?OXA@yYC@Q?3%*a&*ymeBh)IE_NW*=Bz~dDi6MCYmXG+O+#TWW!D@-a00* z@Lr+mDYIY{HLV_T-keR{-=krI&qlUatp4G0ze|cf?*F;rnh3YT#3eN`K(^Ut7(cUz zF+TAVPkLafB;;%ra6Cp@251zp7#&^V`@uZ0aZ(jP!WMBF2waZ%N<3m9WV2xd9;Q!W zDu!uYRu_uuW`*iPmWsOQiz>n|t(IBtGrpN!`P?rho=1#Pv`40l6e(Tvs^;#RKayn& z_!4x2t*~NTiZL!IwTq79FoyH7@kOhP{|t*kf@m_wyK)kKDLRf0nj-!jCWN(1s+g~O z?RNWx!Nj9#gdFv{t+A%i`s!#fZuOO}{Hvp*7f?;E@AvBH#d8D*E$1Wq#bK+JE6?}Z z<8JFnJppNT#_e%iJ#uLc4((U3AhmePjcS^&dfk`LHKo#ncKhXvmaY^1@_GB!VPC1< z_vt@l%f=p>ZMzB4JW#uf2}ex?ut$_pl}8~cPv{B6dr(7jO1Aj8^P0Wfdo}*ek~kC=^s}w&ONw8J zIX!W*$IXKtD?oobMQ925`fC8TaBtMKjJ!Q%{p=1sXnlwRG<1%irxJ znci~D(Yi|D)AelTrz!Pl)@^wL{G*pdz+90=v^U2(2>dch2rUWMdn)ypsdmh5rop`fT3crGctoGHZe;NC= z5654z+PioPoFl-#@xzS|M~e5Dm#0Xe;0PUG+^S)`uvg1W%{OmykBg>?CW->9477-<{^_)a3ml|c)k z*%Bcd2U`SQLg39h8wbhEF=p#&Fl$WxKLgkk?WR3oHM==zn4Ol_XB-@?D0Uazvcom~Ac<+AxtMK;LvoSrI{GPbB!)Wr57WNLQ% zrRplP9YFIMkgrd@qJEb1eZLN(-G(0YctiPch``RMROfAXpn=rCHt!yH_VUh~2kR7$ z3X0Zsb6Uqg-^Q!;>L?-CE&U<|b)Ycrnnd6O4^R$*w0w00Ah$B)(Oi-4H-vfjHa2XSv- zH|Y5YBHE=0VXp}_EO+5DQ@0o?!M!3FApIibXhyDg=97aW9J?+H8SF*@^l%rd3TxSC z1yT(iUZ@T$7%zXJIxHi2hZm~D1-Q)AEk@o021vgM5p7(k4hwkR;f3n376s75U8pKd zb(jzhx_hO%DM&9Y=Kg*{jxANkBpg78 zC6FpZb?N!#iI;w)=Lkt=(z8FqQrdoIv)e0)s>CqmZ=ek25ijH%JGZOlqih8atY|Pq z*_1-}B>pqEH-ZG{VC89x1NsDLJMf7Rya$0ZyZ`h+B#+!KBpy)c075;?+NB_>U-t8o zT3rYqsDdJQfaUPGHXP?$7{%oRz*YOtu#l&yT-n74sKJa;aDP|WA^m5q{-_1D6gE!a z#*+d7xc8_4zym-506R3yRji*mX4;uUW41D#*D96@wdO2aoF7SGe-S0TSaXMPue>yK zF81!=&_N{y+w_7JQ=SBKnecJ@;iRX??aQ>Ca$420=g8p4_12b!d*tbnBgvbH z+$LJ?Gl6ryhHhoKDEk=irskKIJgs&C&eqz25fG3>FY>U~axzr9Xu}Vz-46V~V(r2Y zEYBYNz`E9BHD zC@@ozrK;?%7PnKC-L@oQdF|cCz;J(z&4_gsbgT?MQiQD{UWWb#3Y(YVr>i(4H32zB zvf+ZnQu0<2nD>Wb`G+o8Pvf!+XFls$KVJ9MuQ9 z-~n(+^C*}+iMF|4(&93p-qLLr78$$GV)6}+mm?Y|FH9{Nc|t^@hHFQi80o2x3;~6s zH$O7`(`)|tM~1)gfn-IOEr9lSplyKmcAy=4H&ljpE6eSP_e|PYVC(WQv@a>9fc~J1 zbq%80m|z=s;BA2SD&QS}cPrpsfOjh3J!*-LiB|ckegt;wC znU^NtCxE+VJONrAp?yjZi1Edln@qejd|rEpmNiSx!J$HNNQuz29sa%B7S1pEKS<=G zA6rI;w|kCl>%E81ZSy_6x?MpDO*ysr&~=YqEC>9Dc1YcKmhQ(5Hf_9Dcm9lW^=wr;+eNc|qP@TznGrBVJnJR@Gh*>LKFDpP*{N&<3jP`_!nhO?(G z{Hf!&oYb|h4{JsZx}ZAMaXyt3MsFClk|&_}Jclbrm}?rGNfnYy?}d-US+AmM%xKJ&#j zzC_QINzBCJHVr2U5JmVyo%f~1Y4LbWlzi!DT6tXdmFo6(l@Fg-SK6%1hKxg;YjDQ( zx}`O05}#4KcDl3@r5vX(qxBMSej56!W0;Hg1-_^|_?s_gjVxSh9HDZ5j52Q^<4as? z6GMWHGbH4+7PF$&_ZMj{B|zm9Cq}>8ReTED z&8IPRZ_xtF6&glSEiUqTdD3!I?@K54Bt2Ok`P!TMp&nHlVXDYaXGyR^gw<0PhlL8G zO&5j9G94Wrd4g{sk~4QAj>HXe0`k-C{j>dCKdbM|^zrPW=3#81{CpUw9dKGB2e$dN z3F*!K(hEuElCK9@vpzu9EZbR8^a<+vMU7XuVcP$;WUWt{`kU2 zLu9lHjVO-D=$0U})lskF**e{g5<~aao6kw$mIQy|0EJGgeQzA}ZNOF>`V$R=xTI7- z1ydjz4}Q}rK>{FyCocrxONoVbs|##fl2w2C^iN!^Mjwe%((Vjl=auUZW+#fI^=7#DrQ15~eC4;8LnSF`*WMgrQ0ZC>E+uOsIt*;Y=k2G@GeUOsIt* z;W|$UDcAWoCe?$GcCMv`#B(ixspt7Ly^U8$8u^8$f#`t$Kd8b6x+N9a#$Gb7nAryY zfy1>Bp6?XVw~QS{&mCN}A1a~!PzgQ6dWjVG7h)go7E(sv7m0TbI}mYVvY!p!y1!mm zjSi*woQ2gmq@;gmW$~uH_7{1}KDS|?->!e!tPiXIr>GEb)C;uvmp7~Nx$SCve#07n zn?8Ee^bM>$G@49vX#`Q~XSJ41?ZQ$Tl|>Eob*PuZkyRAa_~+%kk5{r3n>Xhl6!JXI zvUr)7)A;Lxws__iGTRNmRS+iHmCP@2ePGbJzls13!3!MmZRp!$w{FU=-QcmFt6nY| z+ILz}pOi1JVsgCna)U~)k`2A4lZ?-jeu^3=Sd4)Ch9miWhPQxMPKNJRhWf)&E z>08z@U}EBMovHG!WqH>ydo)otqnSF6=%8Rr_Q^m`L?!K*JKe`9x^^U=Mb3pylsnrZ z-d${SuTlQvemsTx_GkjvzXmxsX|58UGCl*RGXkkogPCA7*%3JF3)JF@@ z(B-OLR(0wWU-bg0JfeV5T1^T10$aUB7g=`&GwCl~nO;xdlRy}MKFExHV1HRxH5 z0~q`wT4<44O6!7MpbIYZs}u6L27l6R?Fjd)E&pTJ_)vxzo^5NdYv@X}hCbOSUAE zx(d;XY(S{pScE<_&4Xc!P<8;+1K)`QMG$R+bXR^7WV~=yTe9>*LvNT(!*Yb>RIhEPywwItu0G8m(I>0O~ZKh(LnEt zD>;q@LZZ@VC~z@~>!UzrcT%FOHbfJ2EM8nz7Q5Z!_~bcv1w=vVE{&EZdmzNnL>Rl; z<2oT^v5@OXk;OVmF^$+r3{HYMalD{9Yv`wOS9xK3k3N^tJWS`zORvrgthx^`A> zsxCTK0~^Iz!$aL}K+i4C?R81{;Z9q-QBDPtEouE+Ovqv1Z|Lni+Zt!A#sQCAqsAc@ zk{6i7XU=_vv3n>}j?Ap4A!~K&Eu$*_BSqnV>_(uu9A9z?TF`yL4UlxG2@4BiNGc3H zee=w377N(6otBQkhKOs#6j`%VRudrjM&|Kq!&@LRqJfTraeX?p(cJJ$Q%%SAUs=gx zSfcH1-1lfw^a!i`NuslRmY%sk1n9!^($C%|{vx;;hv;G|fHLy%Z0nBKsEs3v3{B!Y zdB7BpMFQa=lDwZfo(M@#o+3z&J0}KkO62=+&B15*mS{6G=j8Bo0^K*9+dWg1VQtUk z<<{b#*9q7w!ZWQ>@fH~BUg*wNd3h_gAion=(EYqMz%+|n%BbM_Gcs2eAo)h#zb!2$ zve3huTk-`j+5mWR?QSx3b&8Ldf%HWg4)iTLfRE6M$+a3VIquLjNCy}v zpSdhIav;a2r^ny@>-7Be{n7j5)Az^Yr^ebK&$u@;wCI~V$ji+9<(duZSgJ)G`n5nQh69vS&@U)?HW2v(tkXc$ktGt}h32Ua)&`XkKKJs1Pcqa$4` zlYXYq&OF_d4PI_Y?Gnf2)W>N2g7)0pa^()RL>DO1aWlhvdUPHeSS+3dXSh8iE~QZ} z+80T@L^}aMGWa5i7>X?g>{Qh%DTIbL2)sENHjyzN6mqDrGl6eY)I`F(D3y z?df9=b%3NtX+E}}J{iXL0+GMuoEjGhb|Ua`2?KkZP||>9mwd(h%KQ-svyqom_DW3%M~3VABnD zM~;ZqK=I?b(He9Oc|{(&VD@LXng@sY4M&HT-ER)i<%tHzr+lq;v)>;;4Wx9* zpc>qQp^mv@oV42Qrrqh*`iIbr?(4%rvt=LFx^}bOKAbgL%~p2+e>;PIqum^|@o&4` zYjm5v!w&zxYV=yoZWq%Y^!qit(ScC6Ip`cxs&4}yy}qbr)9OKqovW5TXm$^J zKXB-us)I)_pAW|E`xv5`p{6jjcu^)7W{472hh46{B1P{ z{R3!;P5prHZ(zU=+SJ}&mzryL4`6_Jni{2f-G{y$iYhkH^n=6p)nONagC96hHFlkZ z<`>8^ddb(q?sNn1H7ci@2En?gW1S8RTFt(KsXOd+yl$;4#4+%-IqWn$-EIzS{2%t- z{V{E0c^v({e+Aa{$Cc_e0Xr{?s$9n*joUa*W12@PhX{~^5|9*0U=zdterIN%62eK( zIiGv)58rbdBdzwmv$Hd^^MI%4RKiRBZQlnFdGxq;Tt9wrG#|cIZ+Qhy*YYVKW^%@19*DQOPPrWhi{rqti&vRaQGD)IEIcL!xTQ6;7=2>)DIsL zcsx9$j7|6pAai*CIpr>W#~h6V`Yx+`h>-BD+8%tPwg+Ef55TH7k7~`v1K0}yM)gOJ z9@GFZ9yJk~j~~?lULPI+P#is=$LEKQ`h(_!5qv#7dJGjks2|hTIHpYZ4=|JesD1=n ziT~yE13Tdm_sKB_`3IB3hxMc8;}LYQ@d$65>W2@Yi}wKz>G47R{^0_^a@2s-<3}); z^&{#G{yBWaA16lun2#Trl{`Y|zkk>i&yef*=*^=C_2#H?fED399Nj-U!1^CjQ~Xi% zXK{pZdJL`BA2uHyz&t#HeR0@)hy#F`h31YmU_CwUG&&C@VD&flE36Mpa1H7_JU*Pl z1Ob!-;5_j88sWSEf@$JbJv^EM^c+2cOt6DTxP>1dV~_6xC?jHm4-byybF)#u-*|l5 zJbGMz+&HWu<~pnazN#M+1ORm0Z_v{t*e8#h0>_JNi0vQO8^@1elmPa}53sss^RY-h z0JQkv_*lLnpx`<`u0MjV0G0uK0ciE`F?OzTxNS1nUAP}7=HY`H$AymGr_Y{k90rK# z0puP&xX&^DQN8)_5l8fZmG2*OJP(h}2U|AygZjf`1geKe&l@iwBjEW*4cJJ>u=dCJ zIJsXxe*EAK<)Fl`dPVX7NE&tK^;fSumz|g0&eOr!`N^-H!PCyOlRsZ{kt^Utz;N;J z69LK*fi$CtDl8<@O3R3N)Ak`;dxD&hB9NgW{uKmAqQD|LF$q3rzp+UObZ9Mg{}EQ@b=MmnnV-wKxqeIago*Rdc z;S4!E1Vl$2sv(ht$INj94-`1+`SYX+=g5OYs_T%~b%aRysPR}lL$>D80?vcRBZ={H zMX+m--@qHg#RlHEJUf5&qNCA9x9Ew)kiq2gek9ULTB)H~rNx+`dGFvGZbW){-@WA} z#BF4{tyI-*1zt4e>w^&O>pwtE@|v$6%n~%a|0u3qcnjj1I#u4bg1Tv-&%)V9RfUDO zcb2$ZD5Mo{OV?}JiK{q#p~Emn%4zjIjUPuEm`2DO)~ zPyh4kFVDHKx3la0mfKbbKU!=nER_Arptl&{pBE^1y_GeSBu=#c@dGP1=te)m(ay?) z1n??%406^VKYE}LbFYg=meQHdtN&w`@n18`;5gu9SKOg#=_o%P837T$V?_B7e|JB^ zQOU-Jjyxu;9rlXLA<#dxYn=#)swPOpCFV(t~CbRZzwVLg)XcXqXzq7qq6*{CJWnWPYX>}do0UE64*wvkaAPcj!}!syn?oq3kE_Qq`C zz1p<_1z3~^=m?_~c+(}}g-Ti^kr!ZnRgmLf*^Ya~uI0z28CuiAL<@G=a&K>0CrGS5 zXq3BB`^Nsyg+mZc+iDGpJ$>n)MW7>5gsy$8CQ&vRhU*^J5Cgj>Y5 z^R+kv0NGwMk&@>4DkT2oB0is@GXc62kkyJxc?piM8JD0<;N0n=m$JP*q;yJIjfwaG z8BhD@MO(;QslaCK;`oBxS!%1ywj)RdK4vb5eYKt0z-_}px+<3-Ekvf_LS9yVjlIvI z{R;NMa$yh~dc)?W4Xee-zuacmVLqAwTD~3mK=pR%mfof37h#rLt`yqaUt_sdzs6ho zCdS#K;+Me`ftlCUO@pA2aO!6xe~ee9;ycpq>h&BT7-m2FXU#LUB7%Bb^TYjv?3Y-U zLZuW9mp;Sd*oH(IcaUSK`kd_=mZLZJa#7xGfoMiZjj#2!1JYySzTx*^X#si7wJKf* z_Vt1-0-IwV$6@D11MHiHcBcFnWiE)VSF2pz5o zy;crg`^1ZlObTtEY@qEEvA}Sg71}dfE8H<|X)!M5QngO22a5*9TiT23NYS1}8ITL4 zKXn#8nFGaa3}d)4jG=09jzI_YVla%bmti&R3Z4>xd$hU>QkHl;Mj)HqG11WJE|9Z9 zt}T`BnDL6--!=8KQV-af?8AvCyYa0KP$qKwRgv>V=R9%kGv3uwX4TWPjh>#Vo-XbJ zN+6AZwU5qyK!hAeksijKFuJYGcg@?f5`-2Mk^Ni>*_F^FiOg2R01Pq(ILD53>l#aMvWinI_A9 z?g8|gZEj(pT{kw13#1jPc8;0GC_sdgz${@~5STk+<}op60=itlQd{Oj$wk zr-)XWxzO$VdlZz!(|sn=+w zKj)GiOvyK~)Mv_pFKr2%_Xt=z<=jAPL%bGxzuI{`YJlV$ahp;P^+8%`UF%us1RjbD zl#dD6CUtb2x}^9(Of_V9`8ZwhrN@g1pq-hALTLn9}DV(0eu?) zM`TQxsGmpwCaOR@nZCuNqmv_# z!NMz7!7diwV4*5vFL&{^xXi4(@b3NV@9ZZvBKz;_ednIx-b_32XOV|PEb15r#2RbF zn`^|Iy8%j;w4;3^j#Pby=*sRc)U2lMl}a@98eyPXGEKm-4eL0a76MTR~w!)u4xvoH!__XLgz9>6_O5rjZLZep(hW|b9REULhY z^7pMZyNs&_SzYLv$cW|M`&H$hrqtJN2&1@a?2_oJSp3>mUJVdqAem#q4fp(LwmxO9Y?KYFaJee1kZ3~*O4?_&5=D=_U zGJLtkgpwJcMvWi7riHi!fRJ`Dc%NmpQjdn%4%}ND9TcP8?0fIgd4D^t7e~5p)edSF z><-*forR5fIi;;9b{-)@o}x&b)k-Doz{^frm-E77E(n%aqP5mdENXDtcI@%T()^9p z+`!PrjN`Dz8d+13zYqdIX9qkoMt@bG;xMwUTAbD_$8lrN4!sdZA|Rk52(;Qa7#-dX zF@>*y)rQHENgRb(s};TRjU?Zm#PKxnBl-GFOZto+!96JG&F~^Z*Mu zuocJSp_{|Gn&lXx9JVZP3M?81~tN_D>RYehqA?uXM zZq7Y>q+(mNt)B?yoc$4Bw+}!3RQsUjKxXtBY`$J^9*!?^^YWrWw3&2~Isa7EJxEH21@EO>Ew5M?eH&L@QVm zy{PfQA^5}yK8~n@kAKYrDXBG=%ZETjcPILj2ly`qY~?V6&b}<%L5W3e-H9P11+Rg^ zi)#3(8b9=aMyMfqZ*R+}KftK*^nvYIllcSNsa=QDX$>`05mo{KfuJ^;r)X~kzgZs7 zYO3;@>_NTuMx}-5sP~5t`@daYysUGnd3eRCT1SK+EX@vm5};*Z`gu~q^cc?!SPxEv zR$hytWG_RFkBwj+uw&!ixLPVas8qhTdkOC}{dA9G(ow`0DHI0~Q8ZmQyI6I1S zJa1Q=A9GH8&{jmRdDFuC8tqsVcT(I}AsZR%^dvLsHb55%I_I+Q{mX9e03E#R-1|6V zRt-)kB0pak^qz)WIKljAX^|x-`?M{KB0CvB;^k?Cg`RIKG!!S8C?gIuEb?qy5pmfg zO7Jra3w+*IKoFCn06#&oz}KyVrE^*2r$ZXEzuA^uFgzkZUkxnqwyFRi6f5LJaIr^B z0fk#ZAU?sX0NjD>&uz74B>LwFXnRElku3^ft(qkKbyxA3rAUZY5yOygT0fiu)d~1P z)VgC{mFJT*z9#>SxD&}2nQisnh>ckbFrn|(-pBLWY>uvAx$LRtE7VoAp_BY-Ul(UGxWfFg_R7nFuJopcqNN*DnrSS|ZiuKJ^u&9#~j3Y@%HQJFYF19OH zA(i3914Bb&2b)v^2fic@iwhI~`~Tv^TaFP%eWxkIm!Z%kC`#-^-%(!2Sra&Nuv5Ut z#DJ&c^2vh&5t=287#k@viSr$JotlTyoJeORzzVbh>^T&^#ggyYX<+?S@XoqqT(nsG zNQK;2A-a&%V1fR2m5i>q`aT@8YI$)i7R%dWk*k(8BY>SG)_*Lv)HntpiQ>FQ%nm@s zlK2v!sH}ZotU1tO2^_1z_#lilWtAZX1qZ7yxr7fGx*F&s!P`bA)P6DVaB)%z_Vliy zDRId4ZE@!YZFlqP`VwGW~-g-8tKO&dfE1!>?SntNaFw`BKp z*9)xwU*-J#RMffox%iEdnxf0x`R1^5)Q8$Ka@qbrANp^*I_hRomP(Gv%Nt3slA&mS zXUE>LZFIN+eNz`Op_V`F?S+bMbBJ4^(|h9MKKs^O$;K#hEZKPywou-hg6fHPO zEUBe{1lYR^5o0=j{F)^rWuO%76Ef3uSZ>?i0%H1L8*qnbQ z@xc9yGm}InWvsSyfJViN0}5{PU~BYh+$bcClk1*WGOOy; zgG|%FA1~zw`B8uun|u5WmMafu_S|XP;&zgM+p?6rJ1lE&S;=_q{`0;NS0JSb)m1#Ju7VfRl}O1#btRvxE8&H7B|_RzUB}nzI;f1ULP{X2>v&UL z2QQ>6QBsNOTHaRIB1#z(E5s1hwfsp?(03?;yp}q=S}2lB1>Z8I4}nMc+^lfTa6$2e z;j4m?PWjZj4My{`*?h0RW_;M2-)`5a!%l;7@rK7Xt`s8UI7w~e0H{<7;DU8|rfk=zcnRjLbGHr!s<7~#&YGQOn-`^*Mmu%`$AwYi?5Arx z0m4@o;5jis$~uX3GNi#P7yNj??&1rvRNgy!);xjtUVp?>#`~S#1^eNB;a>$w?)^ot zM4EWN(epeHr_h`{>I})1aX*Q76D$z)KFeeBf^OzC)lN+CiBR!?MQtJ5cZF1cy>1mE zN~<-__3Q`VxyCu4JjVu5cBG#47Z2Vz81njS8WMv8^yr8!zMWW^sgh z^mC+U_mACzOj_P8iQxr8nfYWPoQ$FZT>1c>KlJ^Utns-z+!qbgofjM}^2#4`CJR{sv29!uq25zoW;8)9lp*0BQ zG*A^*Iy!*2j{w96RFx&FRKqLfm)|L{2(6WX$l)AV(lAo#*bv_=yg*lZD5F|MDf)C0 zg)?7IHA4w<&_OH3rvZ8o{36b2>H&Bh;y}$Z)3E9JH5Wp`Q0z@KAO^}Z!@|7HRzCv$} z-{=(Tl0M}Vdsk=TVK`M=1+{1S7I=Dbj%d@kE@NkNI0?n*WK|o6d_{PxSXf!ogbta; zxApc#^VQsxk$+w}pK|I7cXE6N3n&7?K~JbmgUNUu#{k|lnu*f&A%3nveC1qmv@QJ{ zw=NkVZ~HjKeZB1?Y8zL4>&ElZBqO)QrH^Zt4y3{1+1z2bV(ANmhGmXRxp2G5#i%M$ z4YgZ=+;qToJ{PXkOdBm@m80^yrbe9BsLfA!ZU;=K?b=O6Vv3%l^jUen(80jWrX)B= z5^htDUgFZA!m9C*0XkPMl3hYOK-8+vv=ckqU#&vCHpx5~ZlU$k;Cy4`A!48#`*VsW zfk5h^uUOmx8LWuh5#&p3k=e=)kDK(}%Au?cGqCjm4nW#}v0m>*)3(AZIKrZSA1$8O z=Zm^k+~M+rlUe5k8MM*Qq6qo5YRlVnz=Bx_M`F*KsSRK;{z3pR zUl73IIadG;FSK>7e%W+4xJhCP4a;qKwc&0p_7;J*9ERm+h_D z+##@~N6fDDL)-JU)6IQ1QZ-i)YguS(}xc3KMk#AzD!T!XycAXfS zdVWzmBquD9W}AHH*2gX*{XWL{g93fk+YoOc zVrgS(3;QTmeTVQ&ia!=C{%UiyPtc zk+>sI4x=?p1OGEOs})K3HRC?D`PX%bH?LD=H*XtIpgmcJwwkdG2bSEirUf`9M<2oC zoOc)sN4#)lXJvIRERN`j$Uv4Wg~9s+TdE=ye&nqEq#RRDy>X&VBBXuV2vU2~{bs*4 z+S{A%aLouDH#&Q~dpIzlcVY#k)i~tw$*)2$LT|BbIw;uQ0#FDqMGfhqs9~PsT+k!x z5;@~Kce%_1?TJz{k-OiJ%BnrnHB=21^~e*3np;A>yw*_G1V^uEuq7Qz$~m%l7RwQ{ z;Fl%qq z`V6YIO^u>>#qXMZs_xKMBe!ogK(lM$KEurIbc7RZ{og|jQF?C@OrdC2I}o?EmOS52 zN7DHPhl-*RPvKKN#0Z&q>7x|*wEn0Tl}dZ8wOjSERA;HVfq&>N)S!+^9;D0urA>8t#|;4fJSajp!Ea(VA9%NKDQJSL zq9<7FJpStP$Q2gDKpY3N44y9FAwIB-UH=yzwy?Y3=u2D4dc28SZ(>i_Pz0V77YZvs zLExf?52sEv7|T6N1?aGpnpuRiFdsP4KA#Uj0Z^^hOMGKk7Q9|)c{KK{ZCUb04_}$3 z`6W4#mLuHJ6;lmsh3tFkq&~~4=tb8sDPTtIV=Ka|8a`|^BZO6l>8t98&R5_3>g8tm z^wViUA3wXh8R}!Xc1$c*IxdKUxAtVUlFi`o`nvqpK&uD|xiMKn&ysl_jEMsgH=|r< zh-{B}oo^7hZKJn%m#~=u2g((9_V#Fa}DIt zQ)SwH<0gKZ1<$52tZv$C9`;wOw0FPH8V*~Qz8%rG2l!3W4YwHYX$hnV*RmLR_ked! z!<)ZtyZ)2&w{wq8RNB#4rRv=@8<oxX>Z#3=0@|`Y$^;*e|Q!_$^ zLHE?xW$Ei*c7y80F@6Nl=>2`L-*4M(_wVsff7e&e-{XCF=>?rW{{rtT=UzRF=jkY5 zZ$krev^lOP09rt$zqhFnp0rBuV#R@KG-+Q}&Sl)|jN*a~ix(ka!0AQIzPwY{IwQ(#i+kibin-@DJH~W zhV3o-9JoTg_+nxnCy?;~KzKw&F>c~&6&BtaEY%shaiYJ|OM~5F)hLdP6AZYZM)`7K zqL83xp*IN)pl$2Df3~*)cZ{NonaHc+e`x;RZEfcAd(#_51-_7R6&y zMT3oX+{Vxfg>LJ5hJ(_UWJqcO1XBY_!`Dj@aEyW8ahf2*2Y7blz{nju=<+_A8DL|} z0mTq`9enKEB)0W0js89k08iTO9l&ut|KRKY^!IlWb%yZg8nD~HY?!`Pir8kwLmRGW zf9&34yHm89A^SYxqi+By={(BAne^Is1#pV_Ep{{TaQ|!UcR45m{qD36^N&C1;R2v{ zIjHABhAhqA-p;jn$sJ|qA}CN8fA1>gQ2+-}7@k||iPuZtDVK+4KbG|AYPGFqFtgkU zH)jO%n1naM6!*{m%|H~0nXW9YGl@)Df5nU`poL-$AvZ{AD#2V?ltLe=)9 zebt-vz1jZ4;ra2!I@em{9=9Y0dy| zFUaS##l%;55TR17KHD962TgV{@DABv0<9cLCoA7BPo=OD(@LpC!Z*+mo%y^n=zORhWQ8z^wB9*&fgCyekj4L5 zn#QzzDe26F%%>IWd9>P@iDMM|69MvJmXI>LnHTRXA!h8}+#+M1HKd*o4o zLL$;OO`^QKHI8eMNk28Y`<^(<+i2E@4=z4%Iu8{nTGx{hC?FIZsDeA>cB$}wY`BeH zw*UltVie30BKp3%CP7eJMm-DQT1`RV^sCG$G}$a_CnjO$-=LG)5)oY{(4k5eaq_w9 z6ZQbZH6(GGn;%f5yWaY>qLUWqIgDInWhc8wScW(zx&bbB8V^)*uqKwV&M)iFVPX+j zPA@N;r<8*BiK`WIT#$XW@(U5shDinQNrba9J;B=ejYoSK)#b=&i6EP6r19kv7ZOEc zdrNIFCbJ@V)pc<8$1Kp}3=^|nbFgrG#78lUV?+xnOOq8eif&~o>mt(k*6;i(2~jy= zIzZoz7VK$x3;(l+mUNIHm1#Y}88bVRnYeD0ojE@@xL%0nt6?Kzl}4{ksS0gWgb723 zIQ9JYVC3$Kb9(B9h9?ytW81G*ndVSDZ+Fz5wnqnqqIy;6HU@OzPTQDwFlvw7(Y}q( z7lU@G5M>of0)Jx?6yK-R!GN1}ApaI~vCFH0R~dVq6e=jvvkt(m0w#+X%o<9U$$v2@ zrF03E_Zn*V6mBnJJ{vF_!$e$KiIs&T6ONvky&7nA1P5P5KC%QOM0PC@*`x}QNpaF> z;-@D$&;dXa{F%T>bLwY-?d%6)Hk(chaY{T##Tg|-FcAuw*2^W|+gqaH63{j6?!X<4 z9O0PH3;xMg!X|@q6T)Jj1ef|kf?Qxp)9X*jy~}CEJJoRC1K>zmPeB~h@PtOMqnLWD z?EBXn1KAxp1*G+SQ6f{e9M=)|qKKk-(~%&*n>h{W#Nz^q!ik4uE?z%Lgp1Dr8(rX) zVGR@}YsrJAAVXNK)PZxS2xMJQqWLJnxsD!y0Bc2%iC5igExH-I6p085RqO34Bq= zoKzx1*%EIb72yD#y@;RE9d^WY( z7QuLLFDSBGw0lVpV&;H7{)TN;#l_&oviuaabzjZj|<2ReXcl$4|63i_8V2 zPRvxn8iXhW%j%pjL`7~9MX0YKqA7mKMgzAt86 z$c%Co#jI4#&&WYlPQo(Imc<@B)gX9v2l$QmlRKg9YJXx}@?PtzXH>Mcwc=}j_FU=` z3trhBja}+nChYhx9{Kmcv57~X?-%-t@6^;UJWD&`)_L00ZJ^bGHGV|$g`*~+9+-3P zO)3fIf>~Q@rq~~UnrHbtyl%vXe&+3wIFQysN(#|OD>{^?WtX{s@nm-Xz8~`Tei6M# z>XxS*c*PtCA_rDn>Ow^&@dixLmjjVgAP7fS_MRLt#mUcNHf?(IgiuqCg+r#u`im#> z(i?PwPt`ASVQ4Ho4F_Z9IgoiaJ5X6qTn1$RPLzDlH_PKbmkpJB z!24yW>H9^=Urz}`07{W@#496H16Vk7QyO2n{;bV3qF&fX-`*$z(H0Y>OH}=JpWS=>GC6SC5f@~iA$eNQ=0k3oYd4s@pEv;MZ#JBJ%|_!Vltt_8n9T6% zST{Ei%>@8C9<~j?OiVQ#<^U=@wb@tu(Lhqq7UlB^xO=oH%wX3=0vD}Ty#|9?eMG$! zqU55Gj=roUbwU1Lx#AHlTr2dnE8`G%jDGcT&t%B{i`!7(Sm$1YjlG>68_49W?oY4% zW%gwGMxYv`yMRBKB9W1JfM2Ih3x*$}FBkar(XTMSWc2ZpG3R#|NM#{{MphY8Cqy@) zjZh{}wAD&JzDHu6DE>?zcd)%C3@I2sML?tZfa>q{rfL7)#}JT*3%G=(u7%<2fMNqE z2~iX98lQZ8x|AJSuq6^DuwyQ%#?{K6drd%f3z%-yC%SglM&ocgofaEEm5t{^-gxc; zo?}DTB7=_sZ@h`2Zm4m18p_5`*?@JO7Hqd+vGG%Ad;pDio%Nh-r;5`jPPZ9DgDULA ziKnK7oq4K2gmhDE}dsA;t7+%FB z&zL>ZU@$Vk04Qi6-e_dxn@8;V8Q)oOTqpcV@pDrqrKG3g-~#yE7x@noxg%#{6H*Nm zLD}dkgn$+6;E%i#LBJ$RimwTUsW`r7{Mf;>V@4Q~-5R&9Zs87=nHR8b@jjN7?qD}> zWtnlC>=y2p0R(S3`fy_@2n?CINN~zZRN;H!IUf+R!rc9$dWB}yzCccsf z5nXk_&Z z%QI$4V#9wZHw7qOD?rA;VOPeH`7{@%-?e<>0td}hwZyd9*N)1~RF`D48)Xm8vWJyr z5Bq4Sk#3efGRq!SmObjDEpyB*zl))SC{#-9Wf)O}2+)#YxUi-6-k~l~&h))VNYRzy z{BH}xdy(MaC6jn&UqdPR7b$hJsol{k!7MZ^0bc)qJiY36h1A7U@0X{jIfeVpWbV`+ z^i%sCQf4%K7_>5LqKLJX31R(R}5B zX_1Q!v;djz$72JPDcLUngqP7z;u}YqWU3^as|#~Hjx!)mHYFQY&MBPlj3ne6m}nrR-E<^wAm!wQc(I zb5qk`>HyPVypNogjV3;GIPP&?VCceh9tBGA6dfyYV|6m$&83m>TsA!>W8=u1!+^(Y zxv}VKzJcz4C5)iw;dqz@>4L<0+&mjCBcMn`9bJpk&$SIZpH7S!xG|Xk*wesI#RV-2 z#0`6PpZjVD8ptPyZCugT7E$2DOh#xD9aF$yebdW_8BqW-8Eu$LvK1ID8%y&p7qUQbFTrn2x}E^vq(ca@F^=_2x&FG zgM$`creSrxs85WqQumRp_d%1S`JV?Ou9=Q+_jTZX9*}v^DA2=rV_2dUMg#EhDWjc4 zR%Aaniq~kVmn-(&kV>U(Kmb5GIquhi2sr{s8`!dg12r{l$C#V9-_gFT{%3{J@j4rh zK3&b9ra>N-D^wLYzZ@7?w##=F5@RZBKzD8#JM=b0; z8?*|k+~2PErj!gA#(P^zoA{acXDJO@^nM*^IC-}T?ua11M%5ty`yI=gi@q5a8M=;K zOtRqt-(1>4T{xJ61HSz(Nr6?^n{A+4o9PU9g)FV-<9d5Zk4b0cF^s-g#(&i)$6 zDzadDWmbUi{O@LAOl5B(@9ko>`fIRSNsmvqWpvqc07;$3-kYe-t)*7tx3PB`w@+i0()FdcNW<6Rg!SS&P>{)~i=YDn)U-!UoYv1z#^_N{g(+YT?>_`>ZbdwX3n0uY`7&}s>L=~7<} ze6uh%gCI)Op(^d7Y$Nt5&~PTX!UyRaBRa{J(MW<1j4HdR3Ngv1M)I#g#0~$A?<3*;1h+ZW`21&dL0#`o0i1i*U@(f);NVj>$R_INI6iz#4XF4Y_vN_`;L(W5DFqg*UE($v3Gq0FtqBaI=Wc zTxl2;8^!n->_fLiaxsEya)Ab~fB$Q@{?mTjc6xvJZ`Uid&7NFf>{+>7^?d+h7K(KJKnrB-#G8vUB~}Q|3+JtIPZOI|f+#8LhmJ*#ic`c@W*+=5WaTH@-kw0Pg&KQ7f8&Tf07kFr zHW-hDybCB`Fqp<;Kbs6D_-8J$C2UVNsDPe}3vW;c7d@wV)n#-IuP=8m z||@lM0+s%*ou;rxUY;3P@`vePL^?gTx=yU zH>iP&pC$!aCrwUtv9-c8Iy=IDk&4vFSg_pRJ7(+3xKXOk|8HhuHQW1B?Y>)(81z>w?}5)cl&uNSRCB2LvC z1MR-OD9f7;P*hjZHvpiYQxZUNTT+Hn5-tDU)NTH=L~PNFUoS}eNtg^xJ#CSBWoJ7cFb6xfzqdF2B`XnzBQF$)jm^blPQt9w-rihp zmyzcIXFm75KwP`Q@UKv+m_XC-F4{Q%zxj$oM7vPEGx#IPsP^~AKW8mz^&RxaeKrFk zXH0$;Qip3&`0W(+3fLINiIy{NYERexG!3EME zkeC{YoCSl#*hst#a+GkGDMUIzqHiV=4TI7FBax=z$^7bynwoYC9ry9-9Fk9@kVN4P z89byj^-hxZNx}u?7grmilTp{)#pteA`e5bWS8LO*OIKaPWgfhsjl6@_7wQf5-|&qq z49IW%P4DjjFv5^V!E($(kK}H)mUUf`?ao$qfYE}z{fb;Gh^O2^AIezCZx=58{vm!N zSt$zh_XD<{i;RK#z+(z~B6=HMG0lQ3^FLCOc;sc$F0VAyWg*YLW%18>*X2g!zhCf# zm?uoVbnpEIp}p10&b%)dq`eOCyd#cPK!wvX4)g^MB*bT8y;i#N{<>hkLTAF50Prxy zszh9^R#9078ryu%d*{=Fc4Wh~&Q!`hJWoF7PKE8uyjywnNc>J3vBk3|=*9cOn5}3O zcouqvD}D`+<8D#UB{wMLGjs{jPF?6o%A%sA;HtWFo97~Qv%ygBPj*rqTzEvba@(h4 z61B9U@B4kvR}_|^1dw#i5L0x7bT`@CS=XC%>oNf2S=LeRE!kOer;}LrvFn{B+Rx{v zaI@EU?Z8uVOa{Y0qn%MiJ$&&zn@dph*f^4mV0P<7gWir3LA$ zl#d#L$Ay%XCTdu~Z9=ob6l0zzMr~B}-X6&=J#W~hD!x?6*Oy)I%Z7aYni4A);Qhio zH=OP1$fmMOYujm4^5kDABFhY#*Qy5BfHZn8la9cQ)O z#;VsmIPU*F{>j-jJVsu03oOz}vVyIr=klat3agZD>gY=ZaS+Z%MH5;NK^{DS_U zga2uIsAd)X%*AKj&A#bg{ARjXG2mBudn?!JbvZa;YO=NZg#J}gLP6xc=P z4kE8e)Xfl9c_J4P9*pp7u?xHJkwTyD~s#BoeJKywQN z?~d1G^NNWYcf9%biMr}ki^E18*ap;~0rPBxNjVC-MuvE7y5r4ZRKDg3<;0G?X={x7 zWK{2!mkCJ~?2Psm`2umr9eMk8uh`s^C`zAHnVc1hy2Yqn%pTAT(%I=pIe+KzpxFVU9phz=1b z7Z+x^myUBg)8ecJaMlYCHF`|QA}ggC%;Z9vB^7t$z$*kjXr1nO1Jrpns^C8Q%Sk!n zBC(TCi#z$$yaqT`C(xjnYhdOYa66uzrt>YS+>8^iBY9&ji6_fp9hE4QNFnW&O1nhV z#!l3`pwb}iQ&OfGlTHPB_}s`Q8bHD?Dbt6alPXg1Yf?$@O*-D2)b5U8VD$7Vbl69F z>Ok6pr@_;Zo=)KDRpgz=EhqwqV?=_W?#Ncos5pUXHv#T&xx{l7D2$4h}hmF)vU_f zWRq1H`4s?hU4a?@OuXyunDM3(Gjj4G!`~cHqu+j;xUXsQZNw5DM3&PE$B9 zS1TfG+NH}5SBOogDupAjXm8mOrjQAu@;?(L4HyirQ;au=S6GOK$NFnt`bwC2wVLpX zW?s@3fsSm)4cNk)v~%^YykHeO9@{gv5I9rG{IA+}F<0r#atgZp@zAs`FWVgi(tIPA z4?BV%z~9_yo9fc82?E1{BiPGMfwbX`N~4a`j(5w0iVC$Hp~l}6uZjg$(LiMio5fKG z8<1lLR3Dd-Q^f~vJNB9!IOve5Aw{$J9oDfm=+4}wvJm;Fk*(?s`2WEJ&8oF=c;l$9cU@6+Z_Fy^_G(3KXzbz3jtsV$-!fPf|$jHsRRPWTf zHxVWo1xph5bnTUWkzViZeO=hs4v-Qr=Jo=}hzXDofXJ8L(*e9+qR}w>G_X(E0uH0S zy(RF0kiNt?R$2tkcqFx;z5!JZqAr|B9;Q!(zFZ>?t4f`FMntwN^btYR$`YKAD?|(L z@V6x<4ff8cI5o)Dh!hbR5Sl8@o+!+U8NG|dmS246wq2TZH24LGd@-;Ka}N^)sjy26 zE5O7r@=7*_51MXP24AM-u9mWS37c+oGQsXS>z0fT_)+}MDc+#No~704yMb6F3AJz- z{BnEM&Hzhd6uvuy_sM{dxN4-&Cm~4HT7fr217j7&Q9OA;WOKhr%;GL$EF6`PVkYO*$7{ZJq?qa~| zQyKj=SRYVgO+OwaX@Fu1Gl_!me&FR#2&H{b4Wd9dzB4mNkslaE05&?u^H8ezxY;r# z8ySn06&v1`ckSMyS4^_)La&*_|M%8|KWz-_gZNPFKToR}&uX-l>uTmVCS4S*SmffW zRrKoa)!f>@t&6?U#O9J$Ea8@QI}bf^FYEmh>Gg<2Q4ECrfepS*zI;25_1XM?Gmf=S zjirJdiZwYD@<_ct|H=4I4>*p#Hw8+ei6p#;dXJ$d#6gZO=qPP(Z^X@kjllL8N*EXu z5&9QF{i>yo6X8vfbrI>BR+T^nblX=OMvv-fktNsaJR!r$!r>;*vtGg`eJ>l>V>U-W zGc>4$$9q7>dt3av<<7IEr^0l1f$^49g|^%7dfFd$x2_KrJZi5!yUT{M{E3+3Gj0&A zB6#DT;zfoIJNGgfu~-p5XXFNl5Aw`mLW_m`B+@#gZx+nfzRP}giRYd>vDI*%wCpF| ztUR12#&Di+zHCOv;t57w;>51bePZN3kvtv_83upp6$0;e)s047g`w9s5^W>ZJvY3G z^ekxf3Shq@Hh_42L0h1&6rqNyp%bs$8}?iH$2)`6iLIwhMsj8+Y6SKfdV6NwHHH$w z{*wHIPo#}tDW=GS90<3)OP2IGm9TRusSIODMA+!Py~zf#wLy0*%faD&L9Oft&^2F1 ziWhJ*kQ2&*QEChA*w9T$h*p3l(57ACQv(2H19(;|BS}mTdejp28x0nM)DHy*bYg_4 zKV#zn5J9{o^mY+F2lB%`2NvmEVKDcJd?g&_K7-e;!d&hrzT4I^6XG`a1qQ6&^+Y$> zE3YTqSWIQOYTcO zr|Qr#A^F_w6FPhCMG>xna$sAv3X+5rdJ!?Z6$s2VP?^(uZJhn~Yp)dF9!{CF^#Q6@ zLRUYmS?_hn%20nKd0=y{oW?B*I%<=xjU9PdJ+JH2ee&*7ll+YD+J&>=cp(r+gW7x zjlJ7p(l5aRw(93#i3ERwWGhkBm7)ruYZO;LknEfQNXe34uZwM$CiCex0aJtEZ)5uf z>scc|$uR_bAI`+rQQI2NNgUQvs{dUw_(_wuN&>#*q* zG0wY@hggd66pF7r#OKD9lSG>#Gxw9?M&Vq)u?L+} z`MdJ#T`aZ)W}X5--q`00HWEPu=Y0Y~!$_`E@&OsO$)TY~(Jfr#83Xn;`HSiA;CCbT z8%`28b|LSAiie>oOPxZbsE!hxijvBI+1iU(SPoE+H*<)Yd7z_E2RibavgOPRdp=#u z;h=h1UpiEbF{(N5%m<|B56DCmml(+VZ2du-z4D5lW!~*9$S3hQb8lgN>wm)Q>-_ZMhF^Of9cs?D!4uD=uU6Vn$&FCN#j`iSxA{yW|;H8w& z%8N2F!`PZnIicx>KpPVJ;M;viI(4yy`V9$Y@l;``qZ9HsJlfU-GOs$E;CwbmkgOP7 zQ&Hc6_nMEgVgy)Yby?n43*F~7VNL= z3CaiJj~o(GbXnw+gjUMEV_<|Zp(XJ)&>dlcxZrwMRrNARCwZ^(Ay^WclYK%<_syPS z0Ax82`s|+?bcD1MKr^znk{I)vl>P!g)zh{;)uD;2s*|LIs5Eli7-)gtXFw9iaJW&p zMdVVHG#TOi#BfJ7m5NE?5Rg|tzhLwp(H-*gcA^{Ap4pW9FK*{AX&E94oK`L(3vf^2 zRuSAS@*2wK^v3M&r;yLIzUF7%Evl{qBPU`t%fkTrK&(pe& z>ZL*@uvGSz_lu>fqs<2*L3#q!g(3{81;BfX-u&<$z8J{T{1{0n|y)+YNu9 z1+Q|sOa}~85!%{v-Gl33_&LlE_}mWtRC!SmReT|RGEoh@M9R6_Nyq!J7#*M@=>bN3 z3sNBHP|3U=#n(0-?zm9z6EpbdSx4JMl!BWY@jreM*IZ_Vj@4?rYdtd@%if+@mVn@w zVoL_t7~b4F=6Z`$Xa`Z@j!=?tTJIB}ch$xOV)mr79_-$}PhhBj<>)gLSMqnDnD~+w zudHJa7awSAoP*XNv_JfS@sw)Vr`qigwVDLt1UySHKc17C_7ptO;VSfh3T z>G3+baateHvCR+b4@lk=Ke^3~rO*Cqj4Ny@@}4?E8ON|e9Lfv=p( ziO4hsPz|)t0;jy9U*-$nZ7WJuK6x3NKz^k(FLBmok`7xZ9S8=vQIgma2vOFg#7-P=Yz+*B z-KS=e0$(|r5G4$R9L6@mmpQS0hNkDDSZ~3WeQ&~g*X*+I%~}Jl#$UA><=$ZECp4#P zCn+Fv7qv-}^uR+EPlhi7`7BH7^gQ*lAP`t8&sjDGGP^Wh-6bZ#q@e0Vf40wDhstX}(8H`0qFirSrDLL0=700mLKuSx&F|Y+n9#Jx-2_4 zu80eQY4tHs7i;I{-Etf~Ls!G}^kh2cIRsA%3pKa;?B%zG(}I_^Fv=zziEi&%*57hi zMFYHX=4V2gv{6F8WS+c|@;I1XO5l>%g|ki55czLM5+=a1^h!^dje(=|#53?<#Ue?wm<4 zlMvB&E-jACz-M(*d*%OI6?^`RNMP-6{f-NZM6Q-_fnBq$28ti8%57+ z3Y?J!i-M#oXIbf;s}gXQon0d*=H8lJL-;Oeb@73fD(3T0sGDsjc>z+(ocI~TB>q(m zpK(-LN2;lu%FG0QEw+)5k_#F6h1o_%N=8M+ITsScItQdxBYG-5T$!W6+om-FPh{lT zjbIF205~D=#k4u$(ebH2<+5M#{!fYw>7Me>h+2~cNeAVr+6A_dMpJTe`=HjLsjx!% z%3yDvSlkJc*lrXRSX>&I9jn{~Qe00G^5XIfzwfOb(ajC?A)zbG_@0gNQXY?OnW_;- zH@=)8C+v`Qc`;Opyay=i^{{QNqUvW<&`%QJAuFWL$O5B zSuU&$bO+933JI`*>}A!+(&?HBSA`U}XI)ShxT^|5PTk$Rrg=ffkqGHG(L7LNCHyN9 z#U_1%X7zmDTEQmXMn0&bG6Tt(xNp6Q=9Re@$yczt6bbXHTr+N2j#j8OIz#S6>ic2BD`dm$$L~;V+|D8E+ZJFLkPj9q&+EJXTiYdd1Bx-sK zqzFno*tQT%43YAc_pBlzJuS@q##S%}PNn&nor2ocT|muIy6mn=4;p<~7gW2pRA(|I zMtzE!EY*jM2-XCEq>M&NU||Se3wANcsaAA=vo_zHdCc~2fE8j&V{#2#Ni-nQe9)7p z(yD+}^w8wFinyq_szJF4LdTM@*@E`Ps3{{A$Ay%F(~7=uzEbu=fu1BoWGNFQL?h;^ zwj&+Vc_aTO5IF8ernb0(FYqE81?e2#td3FR{t&48gVH#DK5P`wir!p}^(L@4Dgd@2 zK}OJ9#m%~n3^U;iz4lOSq5^PirXWhh)Nwqc6*8YUvbH2cy|ay!Y*qFUFgixQ(!_%~ zDhWUv(73G)0i;E)x2J%RTkrJPeBD4AB;p`NJ^EqmuTBqj3=;{P1U zy=c0m1;tD$%N^OJjrsTn`l~m&Ze6i)?i5Cfxa?pX5fE$;0YQNXfW8x7&C0{(4R9SX zad<-(0Z&ga&M_A$ITrb6ddN%=sesxe{jk&D$8F`6)TJQ(h8|wY^a=vT#`m~x$7&q+qA84v$ZFKO<{dbJ zU0s3L&Ue8SOmh(-z*O%FBPYJ+NpL5CCa$tm2>C+k>nUHXoH9$vcmf63CCwGeHsz_L zqu`DhqHcFiKf2_Y-b)3qn6O$%n6RF5Cnhw{n9u-TFjCrgmxXKF>va+CibBXsA%r}Y zxk$Nkz?Cn}nUGai&d8!v+pK*mq{e*GNuouD(7rKCm9|YNCRL-{SSvAWYC`hz9Dmn* za=m_E=+}>wAwViYie6W%vjyp7lhSj6egO=pC%rh?hRXCshYj=v4xe7SyyoW_@EJ%= zyx_XAOxQBeO_-3j^BA0n2e(NF^$Es-U0o{?oJ)#=F3SxVNUJFV5~9bgyltl#Ws_ek z1G;bUc(NG5&~alg+9IH$8#xxYZo?ii!k^;^Q561Gz`d!R2W7 z12|<{w<=ZEbQl0q#;VdWli#GKnS59JW-_6u_O>ys2;FS6GU-d|48kpg-pf(xjz9^} zMZzf{w~vZGX>H*}Q|?Q|{?j)>OH^S=vQFusmrz2$Hm)wvRX7}kF&%YC*b(PZ6n3H6 zt0DQ?99v0DhwK6>f|dHh5y&l%EiFQ^G9GhPL}6X872&{@TcoP@tz@zaokxc%)P$KR zI`5-};O6|=n+51rqJoeuSt;Z{HG?C?-BzrO)|w(&G7d5-3^Edfpc+rKZ?*vt$HWHcH)=x`A2=8_bmcY)X}Qw}qRAN-3|rPuY4SQSnK8Y1o#7H~B4@ z`8UPc&2mfI#qZzK;NiFB=il4lq1j-?orY|w;!~-KstXahV1zom*D>r1jPa|i0=V_1 z(iwct0lpZcXX6sL7xee`F#1y)zssA{VEv)*N@;IZ8DLoGZ_Yg;6eSB1&5-U2psoy3 zPxwm--zYcsA<_;PSGGl9W1)`AK@$(W$`90Hnf)!bTHTPxz3eHO9A5LNGZp_tQ((=Ip$P;x%CO8{@3StlRx=S20cNg)2e0-9|@(l3yjTwGUU74 z=J!0h?SiQOKN&JfAgh`+KVXZd2=PA|=hg=4+E2De)Nz3u<`SN5l0Ve9{0^xPZYI-A z6EK4zfhOYW4kWbo6ME3AGTjPFrdvV31!Pb}M9;_+pI7NAz@barfu?3T$0@1V+{;q? ztie$zup0$>aFeu8i^P0Pc9gB^At7dt<&Z+}mW-x_n8s4FfeMV%4vSZ|lH)d#+{!HF zBcpUN;gqD_Wi0~}x3_I-0h3hobPpd^2@_NT3;(ekL8m!xxzVZtaoknakBZ#-Cd~QA zU8|_h$mA47GX?C)CvQ`@W9a5YXjf7)S(#lKE$_o=dCzMY%O)s>Nm{F$Uvw#F6cSS$=g{-417@-a#%e9bP)Q_PRpnDr5DfFc5zR0JC=sHp^$D^ zT6BwgyWBMyn|e3)Xvl7bs|9W|SkPWYG81XRbPMxsD)ft6f->Kxgh$&>gmp=VVv0IV zam$l{%+XmdW79tA;O*w#?UR$!-!EUCoOayDtb6jrJ!YMk-Og*bdBiTCpFQik&4=u? z`}&33e8f&(!0Tgndh+UW@Z#e1clWTtI+v&J;UmVYI67pnPJZnS{(R*g9r4Gf7jIv> zNB5!Dr|!`M_Wa@;vOQ!kI#Bh|BZfI2v)5<8K8M$1M!D}d*h}}(WA@5@++gRO?umQc zWN*)24u0>vAMmWlhwStM`uVOqIPbjt(>*?7&zf$tdB~m}!VkP1!Owm6>^}TFV9y@F z&qMa?A^bdI&mO_gWA^MZ{2a4q$8HnG@vPZ^-zNM*J%>+kYDk@* zQNt0y+VysdD&HBu{2TxZ*Reetd#Y>^T z6f40l6r8AS^_Nz%Nh)~8B#%xwZ`^=cnh8jo75at(8tn6yJopK-(D2YJTaa^OH0bkd zT19)xMr7Kp+xDw5?GL-;FjuQz|luc#Znt{g5~GM^3BTf%~E-qz@hBd z$%b2s1>G5vNj3>oET?q>+aS!v7qNbVVNxvuG$yF>!gHOVQR0xin+~}VdM1zdppKB} zNLxrx)xdpw@}04cR6|(Hm7F~eG?#G24QsKOU@ZeRTLr``44qNtcv!bC<(NNPItyq#+6HZ0V z1f^uS!yL-v{LX6SaXze51#72*)Vh`g(&b&W&OdE$qpC73RUml&liHyoS@$N@^Y2tF zYatJJVndxuU@w3!J5h7L{5|zL2D7Or4Jn4C&YN)qP%!bU4ix_gEZF6(^N7^SBg7Igi}g> z==iGa246JVWQgX96IvR~)k3jDg90CPYSM!2dB^+Uk48a~*Z2>h*fb0HTb9A5t@*;+ z(sUYMUqL~jyMjQxlR^zS0~jrT@TX8LTSlWA+Q%c-!~je5Li>!KlBvI}38$5{Q81k* z{ul{Pdd|?zDn3szxGPN653)O>Nig~xjB9j4hT<1(for1}i$(NX8*v||@)wR0^0Qhq zuC0E+=agrT(=q&{ahAm-1+Sqv3I7Cn2vv&%9e)F0k3#&%$%YzE>GLG+S-z4})oqs-1>-{>E_vf39mYKMJn`NSQ%Tk+Pyr@fT8Sf#)0i$H~ANo#j8! z-;ZekQb4W0JWpz3>@%2Z_$PD09ESia@Bk}Tqg6F#CW@m`Py;$K`XNqWUFNeI9F}>2 zLDf*-aS~5qifZUY0Dsbe)+P1FVaywaqj?-h(ZXk4Kfj^tV=BROu4s~S{A2R~EM}Oc8)|Djy(ge7*EFIxL zAQ$k5W)^-CuxVGr-yo?G-uNM#&tO8AH2_?qpKAgt@-T;f(juY!-A`(SBWir!uW517 z7sdzntb?uxPvc}+gP(a`6NsQz#vHXx{8CKfCZsimRDP(z|L6^-874sBotnU6HICM5 z64BKJ?t|aU`4C9Snz#(96>fQIsLVcxEi@9}o(`wSt6IRG>j~37k!_imsCdy6Sd#ll3+vn zCqSWa?NQR#4y9l}pu}$p%*ALr9|xHo!-v9q z9i8J`OLwOf@s&gXz`@7lm^u}%)7}a3Y2ZLdlj$6t(5({mMVGIVELp+Cgs?F8R}Y=W3gfwlezeNd zAYRRrzw7o84?QRWhuddowTQ?56;^ufCt})wMhaFpV}ErMhEcqd(-s2hwn98JjN{L% zES!G67s?RvtJS^cVdK&2;Zd`(didbDu{!#g#jE@IBw0OZ^qL0``{C-*qeixR+&Iiu ze(l;{`9puS@<%pwC5~6wfwS^M`*XZXBY2wH{$e%qVblAYYioA)x~h(M#)>gww1>RLjxJD6Q#hRBS)*))QuIKG{^Q?uvK?9nVN&7zV; zQ#eKH#Pvu zz`^>nZWnsp?TzdiGOx2P@L;po zqgl_p@n=0BM%IJ*pY;N>JDXU+tQVQp+RTR1-q5V?04U&TWDaYy=@6bm)9=o{RNyHZ zZqK|2vsJgZ+Lc)l!Mx2LU;_GH+@1A@_oh9&v}V`U(sXvcv00hH3h`#weS2eeZ3FgQ z2j=hPTf)!l(1Zz{0<;IN@685X=$l%kqw9+ zn_XzCG`pO+vuh|3VVM{|PfZwp+=T^Tn>FYFpgffKYwMMn57S+m*|XnYK?306%l|}eYJhbx{V?UTm>?IxsAdm_Or6?aD0NgnoqXmk5A5+L?ucdE?C< z0QM2B$H+CWJ(v`L@N5(zS-63zh-h90Hqw&`R2TKWU|x?deGLssDB zLm*}ztiTOGO?VxdK=L6&XMF~%BL;X!f!U9tL3o;o)^2Q?x#%9>M;%xO zHM6=6s0B}(CX54UVs#r{pwmDS03YM-(4NJ;Yxsjs$gpKN>A)K<(Ks+CL+BTzMdnro z5DX9z&=B75>>Z%tu!1|V!lBpJY#K+fI_l8K%@!aLu&Dm*#y7)js2Pa+7qbos4rm0} zW5Yz>jNlzf{_JKt?9OiP%niVzb)1IH>I@nK&~H~~+v~IKjoA+L`F5R_IXrls?(B8| zP`LHY>(XotU<68hI|4#_3rC09-4=A>-i_?peJ~AyCdI+*A@*#wNu(XddYVu43aev` zLd9PVLj9s@fnR!z-a1C%bF5u)g=mVI_}>!2f5t3q(`C$u*UlrJksEa3W<)pFh7fi? z-uT|_L2%bFHtQw$x4pB8{~6r179RK;M&BJqo-ybCr$TSsXz^;`csfw^^cu$L_IABg z+t?h~TN_<_qg*Xj?49yXsaD>wt2KDoEN`A{RBNTpje5COs@1me<7TbA*|pd0dbw(E zmA7rkM7i;S*R)yQfSRh6@_K2jT&Gvaur*xYfTE@P_GY=-g@o-=bqn6tclhr{wY*iT z@Ju$(v<|gyl-HplEDsgc>EXec&)d@Fn)J8h;R~FCfLri-F!;Bxm6Ldcr1TACv0f+d zhO%_#nQvd~MsqM<1~6J!6dgGSL1N@Z-ypFYP4E%TeUgd&GF6qXsG#ATUHJ|eMdkli zO6YS5X+s}9u53DHE=k?HT!ksxOCABu8X^F^HxOrso32e_8-x7>#|e1DVcEcZ8|=7C zae+_Wi5AqpBlTPVR6qxG%N%}D7n6JFN*hjN(MLZXnBq`l?Zpl8h$HB_Xo1uC>2)&f zhp8|k_k=ytMWFo2s+3(Gle^f%t!bdNDzTQz-tKB{rS-d4(tL-CrWg(h$gKD#V4tnMbZpIQ{rT^h=|#GMVJK3=5ZBfI?r+ zFMd0Djn07+`qN3`k$-zQMf4_&Xq)mU%x`}I8>0yt+Lgt4S+LOMIhRIbSl`H_driMH z#hCU324D@j#nre>QZqc;@h3;5SS%3kq%TfS9FLm!9hmzVFYtWy%Mrz7ipK+~KZ;lz zVuCmjA_qY4=ixb&@NB7RW9b=fC&$Dg&XL}IiVjAXT^z8=930BP@g(K}px`JPwPY5% z+AgC@wsXIOk~tXMJeS)Q7EFH1&M;6|8YI*F+v~qznqdzjaLW1tRD^otUIM64Azqk< z0#Jn5Lg9j^qu;03x~71_ZbGQ>Y_YH2d<> z4u*C8h#ymp=D<~T?yseiFJfOH`Z^y(|MG*G$%Gc2h3QYDQ78W?1CVA3Uzyr>(ZOd~YM zfG}lGscFnDazrg9N_vGqBkBZf%*8#@jf0m!^XF{9r0+Uqwx}@zfRqwYUzwjaLfd&SFa-c}k@OV%VGhX+wt}YSBv>C`dMC}7{K>8QuslAN5 zy`u%7&wfZ0Yc!Ah-CXRe6YhoT8kjAUl&NDNDvUPaFtQkYlv~D`2+H(Px5p!3xisHjl^r_zKe8QIXmsxzWy)bE zDb~d!-L}Z=8cKx$nZmDcR(jsGwM*HSex6gq|K)!NR0D=i?KYREFR4gs;?KMT6jtBOPRQ(rhZlxhnxw)OHIaZfpyZN5HcmLrupUr7T%_lfb%J6UGG%4Dv?_9*_4k=l24def;udf>{ zz3d?M^(Ow`U>MVAN+y0hz+V!Gt(lxj5y_J}89LKWSGe2tQ(2stb^S~pbRa##V$9~GXP{=88Ey&H$ypcB4_K;S;Atx;)p$RIULdD2LH%-WGkcUjSLxNjK9Dl z8a51Iaq##qi8rY%h#+%li#hcSa$3DGo~Hg>2d-ox?G5nK573Dx6mN zzmCTx65V3<4tx+f80B~ISW)8Vx~MdoMsaC?`ay1~#R*=S@&oN^o0d>=D_?27?Q7f1 z6Vb!M0c91tPHvl%(RIYuW{HTc+sv+#GzGmi8t+xWurghm;Mjvu_}|x31ZGm1Q+Hx; zXOG6Y7pv}Qa#b&BU|()6P~_GBntP28{_NNq$MCd0t8UKLYZg2n4&Bkj>skDGw)%?S z`;4v3P&J{GNO_de1mO(*y>x>@&Tbv`rp#r=WSoLXz)ybwyYYAN?il%p*0`Or+6gu3 zaZ{IX$;&8mP$}aa(bV$W1TQzyvzga2{^x(>yFqc?#(W2sRI$|tItZJEPw_`tw2OW> zvEO49T=5CqT>;L=bWuPH=RWj6)`NAzBT13RNLNM9GSQJ^;uI#ftX-_$+T{*2QU2;< zbFjYVu&+V=_m9{j#xWh2#bt>w1(NT$rbJfq!O53YG@mPoX=3oEz~1a}+sbde9Zh6T zhjqX*Sm`mVQMgVx%wu@sR#i<+0h!3`p<$M61-g)Q)Kr%#1%xPxFYjra-d-!SrDiwO zb|amEw!rHq*&Fd|v15`plbgoK;N~-p{IypIon!fA8Ge=BAg4|`&1mGqId(jw!Vf=A z!Gk>Y;-fY+9zcm)OlVwuCrwn(IZ?fnwDeX!+^2}@{eKiuy?s7W4gUW_RD&E*4gP&Z z_3j^{`n5#${40p6|IaPN^UsE#f+x;~l;Q8Vg*bdt*s%KlK@rR9-z{PRmLyn}+d1D0 ze)Nh?vp`>9rKQiaz7q(CIViBBV_pOgxJkpAH7N6Kn!!o4EH`R4#TRX>W<5xdUa9&H zpg{mTKZeF6S>wQ&XtKr;yrtA(*o|YNYBvMC4cSNt{Z>{+l9W;UK&DqjLUIKoKk3&l zuGpFL2;s3o)^GPtcQ*+sxwh7WnYedw6Ggb*I>WukQFNh1 z#e%mnRbxYDpv)MZG(98JTm%JX=g47BEq3St+jEW_=B0Cp9Oec5Bpl|QX`o$>!Eka> z-MwM>qicDdCZi{#!ACY{eGG0#Gu7w`yX@8t19X$K+qeh5t#xM(Lm?_~(MxE)0dmX(R(w>}lkT%tpI;!uF z;~pN@4g8Wl*Z=5cnPz9y#MBH|?sPf{o9X_uAw7RXx_nAldHFX8D}R|5%JbX|{Y%!+ zj~?S6CEkB{tt?7+f0?E+4*l%Em%4)ZHH&rF+WY3yN|=>WrBtod>h$N^lrVR;w@VxK z?XF!#-OGx-zG+uC%T@ec--P##9aMX)qLyWK%U-YJ!&bGt?$+y-a($yDexN56d#7Gr zx2x+|QGI=@yj7Jy^p!qPp2N-BPHB4`wJxEmdTFaxLOsu|T5&HT@mQj_?&c1vik7PD zo8=n(MfJ{|o$`7KI8gGP5Cxr*AKm7OwV z*{s3e+9v$jT!(t0608*e@<*&1(y?FACF;Tk)Um#qQUjIrKCcOyP=)9ps_=ZOaPbsX zxcD2W!b!1|KUzi=q7+ql-%O~&Nk(LR!f_}e3@3RpGdB2oF<~%JFm8v_A-clEkW@jh zXRyCgXL}3Q!}caDo6YiiwOg*#U?r9-b$BROw#${x;l@^Za|hOSd8*dM@{o8~L zZn;{6e@G9iSZ=e6f7fj)zF||Qos$h%;XCU{5{9+yEnJ@4s=B(Y3v*_x+oI}fyt+EC zZhL5BXG+wsu1y`nPaC|!Ey$!-gGr?d@2Ld zM%C6uwcA*Pdbqhm{p^a5>!NJ}3YJcd)uyrX!PNL*Fw@RSeWQ*oA?SDq?WBX^KUpIc zD6X}}YpwH4+c>9NypLVpv%2UU6tFWR_HG|3a;0n=)mz^%!b&HBTr ziY7Er&sM#>T^r!C8`_*~^VJDNeQTq%zFjX@>!tPe?eg|^w}5JFXO?IsM>NYs@nG3+ z4DCA7fD-+6%eAs9fool>gXs4S&$I>qD3Me86VWm3BkvXPYPbz`c{PIWZrZs`rjqOm zKq;$oE7})$==sU_Q!Dn z$>z$KZ#eQ`mLjr!`iz6~UqGODU|B+O;Al2ZHtTq7-GH<0_TlCZo*Osnc&^++s=Bqq zf2%cEUEA={Ua#Ugd1C`k*1!Yp*fm^0_6C|V!XH?&>oxrYK1P%uh%*+YpyRO02g$%qomAM!$*kYigE6Wzq$?&cPqv0=x7=CI0bvVjDa zd;`zT_;=IYhPJl_H9#3bsWOEAY})W=BlWRDB7}$2jjamoPxSY2qXu*w4JrXd*uo~9 z@UeCHh0#`_-E{)fHX3nmls6A+RU8KvtHKt*3(-@x7V*3&C@5iGUhJ?26Ux3MV=dW2TM89=|AM{`Q+{FDe`vqcaXPY zk>GS0d7Gxl+o?|8G9tlqLZO5u{g@XDs?mR#>UQ7K%vd;c`^mdkwx*BM2%&I;@g*_r zYT~umC0)$K@&6|e#^hS;lL(1+ZMt~JG@C&l=(Ia$P}TAHv-D+shWSx<;&DC~Zfm?q zbwyY^;{;$_hdB9ijJ614cbLg>bEbL!{gW<(xc@`Es_u#yG&i0pjEqsq#`IyJ=r>U~ z>O`wQOps1a7{kL6-XxIf-M)lINV!NZxf}g4O({N)jrvFgFHCeV_vn%6_+7i4VKPz>xN^0d@XZ{*K9729F>VY^pedXydY{- zB`GTJz>Vy_=k+>n_X}hj!&2g?`v8cH@N~Y9XETqzYdW6tciNRfQP2xRoJ5O$gsp)5 zW0PH!A?%|1LsC3j;|5ZOmVtkf3d|VPbdLF?fg5DS97lf0{m8;G3B9+CfvbR(cJvqY zD2#{HAJqURJhKm&i!AUKX2BfXoDZ^4R$NgLeCLT&1$Qf*{*Kj&50rA{1}_Y`NZwQaTLJZIzm+= zzykQ&=2!A3O(*VXoKREhdc5xoujH_Hn~u@qYI=h6X&dZ^rXvGtBnl$zO*5xk$L>4M z4^5?E$G$+pX2z90duTfM9ljz(tI3l5Me0YbtOlgSc#1zO&gW+0432Y}CrzPE7dt8a zl9h_kFZt5sZN%slUFqu?YvF5q;{YHb90=4`C}96b({~)BriHZrJw_yM2BrAT(rVnt1UHFZCy`cLiak%wEr;AV?Ivnm*0NaniC9#ITY=2C*V&qDI5VLlfv=jFXE#9kSic@pCs*h5MFvPiaG23*|oqe z)AhnKq6ByBA~rylY*BE`CH6q{L~1X|xEOp)IFR3T4f?}mA`B6p<0n*?|4K-Ee;JtX z&$Qm{)BY*?Xe5Av3he3$#h{=S~olf?C&;%f`TCl$#|f6tK=Y zXrjGaSCAKs@Fx`YhT@mjeIOSiUwD3c;jvT}nz}2rc%Y~BM~N!*UG+LegyvStt3J-d zRGdtE&UkE^=F|!EvoX!h#+1)Sh(2gri2`RYQPQq(mGa2EGmV~qW601)WA1=osOxnF zB*t8EvyJ+CJ#tuK_Sr=CaUl9Qa{6m)eH1OtX8Op9Zmm8}=2z^-S#gPkyPELhwbH?Tw=KRlJ`uxiJ}N&4(yMgCV=4{B)~7)aR!=902N)sp z$8Rfkr@4@$a~TVzILLR*oYZ3oW6BUnm znoUIqQf^48unt5_%uh{nx|IwKeO{lFzNRrjZ`eH?2`(m43%M3g%K!Mx@`YVYo@|UZ zaN{lLv-T)5%26cklFPlA74;*Wj_LS1&y6E_67}Y*951bs3AY_QUQTj=VLvxX+VqC| z2Ylo9fC3uq(SQSH<1xTM5%GPa0!Jr0b0$euL{qPxE&LwC;bclz^QCEfitB%!kWRX) z2B6Tnc8rllLLMT_?nB3}FeGytAzODHGXwy@Fql^!1^R+H^?PU-kl-xNbpSIGX0TRE z*YK9lxf*RUH5dK}rbC|T8V2`KB4%9FBQf{!$x?Jp>-}{Ay6;2Lrtt=6ZF8E)2c_R^)i-&p}I7fj#RM_w4Hdmel&yAS)UZbZwgJ z8Y;_w<3gk^{kf&cMYl-q3On{~%TdaMzhOaUhlIqj?mRv~e;SWI2A<*Ws~Y1v3#M`M z9@e2j%w~7|Ya^YvH%RC05460n9Y^uA3t+6%OlCLJlSuU>M6Ao?9%gcPRqp8|lQqa> z^;K5#%AC$T&1CM?ftxz-q$O2_>d2IcSQRLc#$G?of(l$fCBFhF|U$74}D zazlw2cT$&EOnf3pBj@!HCnMzQIRJDX{fnpiDDiV0f6w^7^a8m=7C9|rfYFCwxO*ao zBl$9824}*Jw$xq_G8pcsap-)1wbodD9AZQqcYbA+KLhh^8Z5a-Z(Xg?iMKHhVv}wg zlyuL+f%8~j$8-gQu?20fjE^7j`iHJDFsH%KHmwK>JRFm;Tn*)FdssaJ@+$SAfj4>h zsU^d!wR!)NT)6yoM-&+t>fj0yT!9nrU42h@bCo*iWk;8YIIW@~6Az93;4PfFJs;+$ zf(Wq~PMM0aIAo`)SZR&l?u}Bf!|O~#6w49dD;tyuG45%J^P+BO14?)6o4Y}O@*PPf z()9Sexi7%_3Tcp7@`A388`5J83^(QpguIvAP4Yr?(HP|N0G}tC74YpA|(O~;2 zqrrBFh{P3i6E(JAzZ z-|p7QW@Ot&6WeWj`^>Yy~LCb4fxGiK%boNKA@y_~U-?9Fj5mhh#jTWZXVQGH(9{lJQ$H z$rvpo8KV@*xYtO=ZyAzt40x2K6~7hJio3NNP(i8{( zL$lEBas^#n!0BpTH{Gss({0UKvZ}2As#y!pj0JvGnb~jWjS{)BeZum-YUj;3rP=G|HkQs?^k*zN3mpYirA1`1!KAdQ+*xFFU)7B6 z3yfZQ4|SkPv(KSuytdFwKs9lBuv2JiTFn`ARx{?W-1;?bEG&#)(?+Hni7oCkjrpIm zFtwyU4VmjeICi?gm~Ep}3x#87b-BsoY;Fxm3deN>Tz>k`lP5Qk$J^>0}9ABvqO-7H)6H>p*BsIB@B;X^AB0KX}dC>%a}J*Z2WT zp3H3h93~HE$#G4d%t~odk362COz2?*_15bZI3FSd$p3QIVP~_HO9UqMxx%3QLhk&t zke|Ph-#@jG-~SB@`EBt+?kro#ozz0U)E4sF@Ci|nT+VNcm-EGc@N$M#w+Uy%XDw4D zS9x;T6Pd8r>-BG3&P5WM{5rn>XB|I(9lw5R9l!n?*72{!>-c`zI=)Y>;|pya|C$Ya zqvSJk0sop`z+74VON@r-pQlm$Yop5e({HGL{17{>3ahl)VaItyjiSaQ=|e}2meEDq zn(okOGgS5H4};srmzV|pe&ofCmR-kUF`yPq9#4CSq2m-gNUH?xe4wV{N{Z`AnD*};zb}?&=&;jMQTWKxU8B5 zkcHlyWq{kEdKLk9L3e0|GW{7YtEad-9KzWi(a1WBd$h1a2iXZDP%o@$S)VdDezy73ug^M5<#{Psn ze2imhyVk5fX}OlOQ!7b3CSkfnCX}kzbDM%#UtzbsvfpFecX`a4H-uYpubdmpoVCaT zPJDQl1zekZM8Mu1*HhLva-VygD+}I`Tkz%r1*A=w^$-Dil!5OmsgMC=G%O=&eC38{ z$sPq1x8$i<2tg7^0AnC%ox=SK`}K9P53m#;3p~+q@K*~xpjmMbOzTltG@m{j#}*kL zJ^~hBUwa|PS~B94s)21w8eab5M?P+lORX~5&9ib*j8o)gpha$3&Mil|f~)Y`?wUIYBBu(KAlpqAex|owTdp39 zje=?9y+(!8BM*Hj{81j0pMA<&u8IsNsQS4e13FF!CR3sWF0e6SLn?$a3X&Aui)nm6 z{sBD{6@z#M_yJ{etGFVQTl`{xK{pJ8TS0BK3FjupQ2Vi>YMm*td_J06@yUu*l?4QE zDP$)&uIC|8Fiy{?fT5hzf%v6zaABTLAC+WMdryO&(Lm~~0*tc941InKAf#L+_s-m} za|==KxQg!-Nw54xU1E( z91X*+r_G0Et1C9AsUxadZljy2$i$GtVj_k(5i3&fyaw8gkBX?@O_p%pYc%s#qq^sUL>v}Vmdh;7zd%*lj&qEs zua=P=6|tQ(DtO9_N~R>E!bo%4s3@qk7!?Ll$&HF?*GGl9!ZTGZQmgk-;TBGmBrSzk_4VH_scrva#S9PqQ?&E2tyT=O`qn}OmVILUI9Tp>~PxH z)TU;VEUQC4%T28lYN{#QMRvYc|RJinK=IdF8JMR zcAVIsE8h)sZhU@L7yd8gQcMTDVZfn{|6(pj`Iphc298=>&ZE01$)1Q zgAobY851#qvhysGmvAOD3r##+?Z<~?Z}S9D^bNZuGYN-K`p!*wxKA>U_wu0jL@C+C zS8z+JSA=y0*!&`;WF`3|ELqH^7{U0%g!~5zBr$Xr<~`2N!V)-lNp?Whcl8AlpSwZw zp`t#(K#uBM6qFnl&Pf27c1GoC`{ACq_paW!8%T49yMe_#Ek5Q!U3t%Zq{$;Iokr}` zS>otrA3vNAd^od@o$4l&?mV>g(Ah4U)1U~2eB5u`WwI>&yuMUkrHLAr$fR^(%W}Ds zCUICSThepd=J;i7@roA+k+MUw%@B&Tmq?5B2B3Og`y!c81`w+DWx}6q1FHUF5l^NG zXrR4RkW&C437=w#O%_Dd!hAO^fhIY)Ybnuk1dN#5irS99Cy*Q8EY1=W_MKC__msTn zs6MykS~%@b*bhJ} z63>&sh6VUU?lhzuNy#$7lXO0*k(M|0gtX-Ss65lNMa9}{mCNPNbq0U`Z~?62?r;h; z9O`aB=e1W&PxDM8YCqJAGkvM^bDwmo2HnMAn2zR+lWbMnjd*Ph$!&mf5q%U*NpiWi zwlW4jFesx_%p+EBDU&Iz5ate|@epRzi-FQ&$dj8N1UYPU}{Sn4y&9bYO#UjTHzpvKz z!k4v171CTbg!LQSZX=S$dA%^0pvf}zNi5rl6{N~OG=>_Gf6>t*V-mkCA{_oBVaLIBW5}XGa7$mX*=`1^Q&{VI)hBNY zK?=vwm=ys-4ZB|il*VN4z(i-KHq3USaCF7}z^J3)tJ=#znNN<%PLxb5xwHcanm8$O zrnh0ZohTSiF(|n(&#f}$fRy`(1x9<-3>k3!3?PQ?jf}NM#{;s62G+@{!|Ao!tc7;0 zis`mt^DJo{VA+qjF)htNJENYXoh%C~7>RCYjdwhr+~f>jj=c3%eke zK&Gi-zDvmh`0B>bmJyJ_yr^4p;xnD#w~+;D-`KO0C97vZ-qtzi{jt8#ojns5j;HP& z7sHHY3Xk0#%2!}A1NE$X9q|RX)#QRxV*0{h_=87aixT$rT*e&-ZdDB6{1jMK@eKU$ zR5qZqh&}BRj9l(>2{{)np(8}#;+F7$Zk7jR=+m(EC?W%l+&f#|f6bXp^~WgHjQp-K zZv(@wKtmEZe~6BJ3@MGn>EzRscX-d9IMxA3rSp7Jr9e+M{+PEGF?du^6hOlJzV55q zd~#R9i@(X;m5>3$Jb8C9mvu{r$e3~jKo>4ed7s!VOy0-Pod7&S&;4TKyTOvK@p(_5 zP3UAIrV)5!*ejm1-ijCIK@o9SxH1+jBDG-j%_IxT)ClNU2oN=Ww%W0hs0Cv3F@J(Z zp$B9h9TuT@zq1O*JL#GiuY3WAzi=oG=FUWO;zJd#KKAEVMZL*Dc%ZtVDE(8|LTj3z zS{8(tz-Tpa9KDI5%a8fv79OnBnbEj$Lvv}1mXSVI8b;K2i?===I!9h#E2POXo1 zb7--1^kkOtV1}aBP~f6Qdkfq&8B|Cf{FbG|izpdvY z2#|g-QC&~njKxYa8er6*wY7jWuSn-8K!u|KFH~2a`WoX`Esy&3m_Zy+_D-r?&D}>qQo(J(O10=> zfzbNbr*)(DpHPnaGBz(eCXVm#90=E25$ZJybXq)^A=p{CYD*whTl^xQ@ovGLUZnYv zAF?nA;-<>q<$mRrvLN8xRK}#B7?M)$g8@HH@SwC?ew z1BW_Dn7W{cWByf!4LMj_JAlt{xEVR8YIz+v1L)jT`N3BT1F|Ovdn?t(sl^UvGcz=e zY5GQx!52uqNvk>V#Hn@_D3StoX^1J-eAh45-RUpX-JvV(-eRpCZTk9It)0`H*3RiN zt)2TcS_smfyHhDADJJfh(F$K#8`AE+SISC}Ew<{2 z{p&LwNecYOTde6E#pWq{jbB1ve6K(5g;-aTr{`G_@9Y0rweUPx^{sm)P?)Ghn zK1jnWU?`hyW#n=?XNMZWVh1`!T}H6X14q!TWX&Gr*6e|bO~Ni=eXxUsS9s&x3pJn5 zDTAUnX!A4fnJ1!8tE?%9{-$-(<_iq3DxonGR^5R}NxHF`>&7Z%2dvi1S9$qnLHTB` zd=s)Gg@?uUSzc6~z={KscYusAAo$_XM~;_AIazf`d_&SAx?L86qutL+S(}j5`rd)H zrzMIL3#RX_mLUuJi99I}2-EC?GQWz3-r%9GVQM8WimK}*)uD>Fsxe!W#k4}Sm~vHd zk&NgVX5l>})K99b z@TG2whp{aQq@N3G|?~&yL&92Pn)$gwl@KnR4-AaIpw| z%arodo4k_L=k!JFNr86Ns@!Lq4lJ!GG+_l6g#Rhp7R3^?l9hUptkeTi}Vh@h|l-!`RPmYo>1*p5kbVgTdHD>e%3)tWEQ8f|;^Q?=c zmQo4uj8qf*d1QN=MmEvK2iWV1wE8?Rr&KmdD@9&rbp}s;Q)*~t&YVmvQx{3a-0q3StLi)ZiV@kz=U+SizCF4n9P4bNu9w32{xn{LP&ST zB`&uZmWEOoiTbT8O}b`OO8o!8oM1a#^rQTTmut*eD^m^JrMK`uF8zb^?zgU>LOY@w zOOuRHGE@22^C?}f!m`j5#1g)_6uw8ck7j< zY1`mFh<|JJcfGy0V#b_9kZCpkvfj4vV61v_Jk%6|ytOq{<2L?Jzuz~sXYCa_XVx}0 zSV{h|v@dS@6Dw8mD-9NCD-8_Bwby{Lu#)w-KCi$q%CQ&4l!L0#aWCoSkD4+iM>t>? zxF9MPqAdP4&WU)#xdTKCL&yQ1_lRbgiD(DLA6t8I;}|!fy)piV)?P?^PSX6T4kfPK zTlRriy@TYvO2ZQu5I=LNi8Kx)?Kq(Yo6#&*_B9C>gfd)VYs|v7GYI8tzg+-=_3k*-0nkSxcFn9KMBlCE#BN!yQ)&a=cFx!#$2G2NfE`p7=t7XC~ zu}q!=%+oVv4yxT^6P3VEHP&thTd+AHrF@h0jkrRQ{8=O)s0%;w;Ux7zFS3e*^3B#8 z_L;4+6MnPRgfs5Y)Oy^69$RzdRqoj>I~Sl7#}*2ne&LV2U>cj}z!WH1&8@}mCEIk5 z$4e+TCjOibBa)5li2C*&v*W~BF+?ZFZ*|IidA%Oe#=N@^?2*f{4b2bs)1TMYG6^DP z`XTCdoBJqM=?S}{LqPO%2PaL8hEgxFyBprRoDdD=b~||dR^li?OExm$q=-SlxCr3P zLv5?~oyZGeBmB;oqM+iQBBZ20nKuBea=_mZU$GJy=l#%3q+1tG2U0r9qLjP|prC=% zaHt!mNoTN z?!h9{KJAcCX1fR$W+~;99~~L8CIcOOo)^%2PbKEjOu!iXC`@gq1Z0PQonLm#jFhbU%K z#6XheRSqIg!BkjULa(oNi_p^4p(b{x%JB5*)^K{Km~$NTybB=Oz{6D$yOlH)CU5BU z=(d3)FZ^6fs(bvd`QgHVNq)F7<#!8Srzn}i3njk;YYXew-*Hj$+6@c8CCD;OC_w;j|LWKU59U4iisu(+4EW+isXV(LO^%HrX5fGaRU@gx?{)o8y zgaK|r5@?(p%Qw2ol?H|5L&TWITNf{q#;16bi5lgU02pyNpyNB=$1n??+#Womd%lOWsQ^ApeLDQ%?@;|Y7eEU zyp_AxP{N(@+!onwL?FDnxHC^eE>+}giR4QIXJtimNP~0r}(-Qk1HLam}7z# zB86&4*(<^R(7VH+^E!m%=Rr9BhTv!mAgqV5qpBKq$d)zr%m8RCBiT&gpsZw|N2G$n zejk_A|JEAht}&CQq<<<-9+XZ3_FER!FH?R|agH=1 zl3o%-FR3|=k_%BM-L7h*a464ecOlwlmre?z!+l*ENL#f|JXEMXTiS?U=`ww3r^}}W zju`NeD$j%Oi37zF3WZoKqx_{V4TP_Jc?0kqiX$|UaV>o0a*leX!fpou%5xYB)rGhE zhOX7yB`p&DLgIMjUaNMq;@$LR1&1^D;FhcaQX}RG$konETA3>*)gu5-K(W8XMTeo? z7B#Mb_tq{9w6Ll3%sm3d8=YS<;PS$AZwUN`>~S1SyMuk9zB+Qp{$x57PFOT04BkEd zBJNSdwJ+*&GJ{iXUH3Yp>CvKyo8x)urK0cn3$J9t+Abo!C{!Xd&GR|7;6nAhX+vCa z5R8q4m}}c0;oIPbG}CDXN%AN3jXAQ)*q6BZD0Oknu4hQmM!v$Rxr7W^G>lBWda-Q& zMY_1UA!N}t7|QK*>d8<_6Q{N-{p$jZ=b}D6qm=W(y6uwH6Aehq@dCBo@ro>i-vSAA z=0IvEH~wW9|KiL{jyOn)FV(fb4!WsY{@)Jfr(rtKml&_{8^BC=d!NS%vs%O(${p5d zcnAc!yT%lAoXD=3*gjyRniYw(e$c^CNhe~^-%46M%oTGi))Td-K0c#`OqN%*e2cj( z(A-g%H&>`!rr*d3$Z|FDJY_qQLa7Baa&WCw4U(+Mm8?`a3nGVmIA%N|wkwVke0SC~ z{z+NSe((sU(Ur6|OOJsLpGAGA5A!_0lGEzKQoKurAfxY# z0j7Ok3NGI3qL-y0;)o7t(BEH(qB+z~Ol+RmP`(X8mtyBvwOc+-ua_~S1HBny)ZJBuHZ&iD~;_F2eMFzThmatWN0wqbB1 zeQ*Ue(m`?IHi};tl7Z#L{z^EA8kX)IOECQ4x)55=Do8-Q*0G6mJjKJ|d^`eEW(Nlpwe1Wuqo`&KQNE@X}i=~S_#&kHW{Hz7S z`wRSI6*_*B@}e+f5g#P7ZeN#kxSHTQN}ptgj9)A^`T64qwVYh2 zfJYS)^E{bOnRD{px<6yiFFQSC)^Ex}-lRZ=deZ@EZGb_dKc?w-bpJ z$#ArFKvrfXV>#a^{pka)Wx(``Gv~I3l4uAmdkxYkz-FNW%}_C0(g-|WI7#EgJiWVb zhKLsUw?lt#>F*u=y`jH8{rz={4bim<1{1It3G~}|)4x#XBCjifwsRfl!_>6cLHB7J zY!0Khcn>J=!w>hN>=Rl`T2DgFdO6@KOPzSkuNH;g66zoS;+xiPvs0c%7$)k*?|J4$ z%zfcCeh~Ua_ySv`ywP^0MJsQTvPRNKXz0L(65aZ!@g7-^U7!rc5S1y627DS`8@uGT zW0yWeBX>BY578*~VdVFE=qVUqN>aU*KFfRR{Tp*7Vgi@S0#L^B_{N1JPYEGby274a zl?|y=Mg$`i+L*$$M@z9+ABUJcOs8_Qogi(t!&<_bf183Ti~bF+Fy$Zi<389gIAVu*XBcjM<`Xx>SHZo-QoR!#%LeR3LrYa-dZo@dCff>WSD z|5JwixbGg53MSn}9glfWGv@q+wOzF?_=+H9`}2L%MSFX`D2~Yh&qV9`{lG-Oj+#05 zs+{*#M^lVb#%`+#%eC5=h^Ys5KQRfo!AGGTNz3OxiEef)!#$kKheOe}FvVISry=Z7 z2={3RxMbq(IQF=vFHTp40Sn$vP2kZIQ$y+cS{g#@M%gM6@N?+i?`7U+vy}?jj!7G8 z^hnTQt68o08>yfy+Um$vWbjZg#-7NLQ!c+1>8r4fi%J?=T1rJvsetdN2@|2(s?cR? zI*IFEu3&a1l*o>xqS!~=Sl&9yIMZ_;iE=fNY%!u$4J1^IQzxZD_TXIdBD0yW zIqjUyq*>Z0=K-MWwI14N8YDL(VZ#S3O)nuUM26mKgz7Dls3s;f(V#{SXKk&Sd|t)R ztI7K(eE%fh6ZTTTQhq3qqo1v0b zO@=;DGD8>-oLOiTkTP{WN=)lll`64}9D}>5>6K~j*Vb^OpngCtid!?T=)hVE!qOET zQ%ju7Ds|oQzv5EIE4igBjy&QE?!0h@i_K$;y$SKJRGqz%P%oqwsA^>jhD#&0row_E zgL-%_QxR12?F^^k*A>MQPDn^c^bF<=_G^4zYS6Fz3IH)Va&Vh(7H#td zru=G~-@S(!O>ClJA&SK)W@T5SZyyt#-nU$X)$@eD>Vwo`{e*pqJVzk&o+0Rf0W@%; zY>Y$Yvgjev)D>ti>@r&W;x**T))@CGMh0UyY9CfqGe zlKoUH>~qgSD9ACKHSWw{pK02hD=TL#Z|5wvI#*;#X;kqGz!)uWes;cpWB$zU6%Dg^ z$UHCSMt*9-l5!39oB12UYl4Q=uoWqw3t*G=^9V4hb?&iW+7{o~W6y!oLcuy)1(o26 z+HaGBMC@SJKT5wNrJobAPZGhxn*&Lp>c{)MDq)+TCQ%!ETF>+6b`n$;j)5Mxq88R!#*YBL*r3Xyt~}bn8cPZFTLnA=uJ`0`I*rc z)wXK^QzU`OUX1dK=7l;Ak`)`Vsk}G?)t~C-E5Jr!gsEz^ zdXNb=#(@F+t?|dy4M{I*;u!a2*fqfXSi{IsN2w!9xPcRg`FroNKDS!-tM(o~S6OXt z?WxxazP2vn-=p@6YCj;k0R7OEII@MfFo)c=fLQ=_m`&u{aj4F z2!B6%y)l2f7)(QYcoX{gbM6ApASC5Jdr1Kf0gzDHkM0-%33VYS_(oSVd%YH;>QU` zkX5H;;9MH$;J`p6Fv6bEW=-ehzpIss*yN3t@$%%Qx!N+DcCBK;7vMctTTS{~XkoqsuAf{!niU;c@q!9*CqIkmY0Ru1u%4b?=Y6KnXp@Wg8ee_)g&K8-EFU^m8cC`VA zxSj;>OVh@5C}6#UuSfRV##?xXBSZy;;ie!cNZHAEmAxc{#0h3?XmC4$a+88`kEe|u z!_Z~74LJ8Uy6mViU;{WBkKm|`2k8+y$o#p{pSMl`S0o;Amj-cg65M*>p&Q{~LXTYj znXe*P%Lc-GTG&y=^PtN_hZf}pMi;qgUACQDf_KL;R{iM6z5g6>ifpibr>L$H?Mve> z()>4YAT?K@Ybz(M4)D2fdPq+({@PO}5li@q{9p_gpTR=x@>S%54t-6nq&7PQVapBD|+#)pvN6l5*C7}t=Ydfn@ zr{~S(f?HdIYRpUL2=EKW2=AA8MA6qP;qyMgN3|g_je6E}v{8yv{@!XM3GBjGfOEo{ zWng2XAq z!=F30Dgqb53x5JOg!=hLXK=JBg!LK&R{(W6$FT5t-TYvKwu8A5N0x@LM+w5B6OK=-uYB%u18RTd=y)VtkK9nXx0Xb< zx2;;+t^%E44-Vks778PPOqQc*hu3IU*t$Are%wns0DsCKE)JnkwatL(ecM`x!d3XK zx3Rvrtqt+JNxu)4)lj9MUuZth4}GrY)*t)iD`AzMoDAF46cgXu<-hk%6OByYz@+>} zv|Av^DNjG;q+28&@kDA;rhSxY_bTZJp7aB7S&>Q{i3*7eOOhj=%-x4;xg(if@pBO$ z)ao?$hfx8me?UWyenGyh=p?|04Ma=OYLrBdc$ssw%S~`v(L?7Hpt`G9K+d<{_W{Sh zbAap|eaFG+jeC^Ht0U{NOnqwcH?8k7HSt2+P_4LBs*UeXiQWJtu2Z?$Q38rGI`-P5 z6@Auj@b*0P)Wl0p$NpUbh_r2yiqNSD+O%E)t4#$($`bHib5SLqD>24G&|(1~4J#|y z*)!Od&wv9sC{GZNJq->&1C8Znx*mMLHID!V_S8~?w|mIJe%4k}k_IqI)@j#kbUgva zu7#4xOd|?<*hn03G>2ng*_}*=_eq)&0ow`e2Zm0>|F;{h+B)0V!GAkDJ8d8paFF-R zF^Y&_X|hjZ(S1ZYgIHfETM(;?!RZ}NDzWjB!yU$Fz}QggWy#y3xBRxKG-;^uc%E5h z6$?(+=6aP?s}|i3m3o;Rkw;l7@bFURnKlrhzlaX-4Ffn8JOj{OKGGT3@XSx&{j&Gz z|T`qE{KD{YT~<_EuRnx14*+ydFr?+HvBo_CE-A76DwNdO-9mASZFXeMK}!TD-ibW z!udm7hHVdZXRkDdDNQFtE9@9GNcp%$Ri9>_*R45QOl(S z1*)T#FLLOxIkJE$1H}Ib`*3$ppx;aL%og}*HO_D^#LvCi41XqktOA22e~!dtfYbE} z);uz0=7YsPiLHl8j^LBXr@Zq^eE(F&ljiYQ>@JoCxryHcTFFYT zs?@2Ly2Z!zpfDG(E#x>Da8w&3D!nB>MG`lS_P=btc+dy?iF(1eglQ)Z-R`n^!^59l zeIaKIb%NaVr17IC?I1@P8b;>|jw_rMQ0Lat5uguAZE(%aW}{txj%fmW_Z$4rK0URM zjxK)qp)rCpUi8VJ{pf*^mZnuOH?VvJkD`Wtigf*`EJY|t+K>3DH$vJD&tB;Fd~d{l zNyY&iRM%tg*=sfWG5OZ_T3rtS3#fYr7yujNdrS~pN(Hjdku+=7Xd72#dftCmYcMS} z+yMFM{%7YLhMR4dL-DmU_P(pu_Kf{gAXm*(6%O+K-s|-FuJKwOhd!z!?LA9ZtGd$F zTZFd6F7{sNJpA^XA&Il##?Amo<-TbPI#1K0GUDhk7pv2_c#c!``P0j)NIFdwfpj6~ zke`$9znF5G&IdslvrjN{xPQoudQSNANqjj{$~r=uxWKUcrDr%5S|?k0cra zac5_oX^=Y~WG&9i`!92^C%KdCk=MfSZT{(pY=1nlgBw5mkQxb`Nc3?G)W?{p2Tx3; z%D%cP@n@{%fxKbUm;KxqS<@)ntq1vGlj z>=XF{B1Lr2^W+BfK&KXVWga$mHBK&FlPve{_I z!+olm+OJnnc9Wx#SmKS?Q8Q`{)GADBY+h^xZUVL?$ zBsrfk&J`?mgZXU+Pw?QAw7IFSeG774c4QFdxK5r;mBZvc3TC?(M7SJ zTPLk*yPfoVHPi8HbCsR6Ds6T`ZtCagGu^w30m+I5hbQG#e>O8wWcvnVec(H)X|U5d zloUvI439!W?CisH`lx@R2_oQ3{{p4o(CFAf88bYcM9u~LOpxoN;3(#Phx3B(_nrNY zyp$7{&L0Bja^HNvZ?WT!GL&rCqu_x@^cn=i9@+#eZS5+V)Hl)}g;_)1g#cyN^4b`# zid4k`N-D&L;lTlC68RB@^ZMZH=D^Y(!XHnjv1Wru=`76Lhv%y>@46l9b+640;boJ=sUwF-MNrv3K8=u^2rn;7JIPTDhOfB2!&0r#6F)D3x3PGEA8St=Z@;c^cUs^+WlWQ5vC15+C2tiy|xS5{aWr zsG(QtLqw#4`}QfV+4P|7yWJ>{gTjfcsI?e5m1&Ox4`Qe^C*is@*h9VoL*Y?JjP+K4 z`A>{CSTx${6Jz)WTLMK6ED$j4$$F z>PXft)&#N)u-Yr=ca78~UpW5)oVEXjlV9MAB{4DsTuof|6=jy*&4$8}f;v(;4k;Hjl8XR^P_)??L9bl2y!HiQlex`)B)@n zIS*UVAfJS4p}VmR+6ptGZ||eQy8>W(p?8Pqd}+lW;aR~Yk1n$JMzP-{Mfxk>M&N~; zH5h0(f+bW)^q2ZtOOfI?{t!SED{B0tUp3|2KrpgP`L^d$iK}$1Iv zMBLXX099(UTGF2)Um5ecvJCFJQP;)uB?T@FQ^4JM1_+n$RM|$TA~HbEh4dECDR8bd zPnM9rO!K~8(V?WunWB<8_pKV4;jZceH&(ow=uw(i(|v7L^aZ8nSF#a_|IBLfv6f2$ zBzOBnUP%li4|PZFTR$Gy!n|S)yRNzFR1{eU6U&it{p(lyculXw(!w?HRbFFduWh#v ze71`DsJX36QRi=V7^}?Bb<+Ngo^BPSld1{;oqYU;{(e2IpXWAL#b(b!yVxc0*J6i4 zRgRtux|i7&3j3PQGuP%%M+X6R*=9b&w*qNRn@(XlDV?08cYE04CpV^*1cuj8AcYhN z>mnb_&*MFo$i@n&$-IahaZ;LPSu&nmIVt34iwx65F=`83_?8we;6i`YNfA6;)3#%W zXau;V1n+ebaI&5v?EDdQiIg+H2PFPVWf#Q){JwArvi}nPqch*}bVX=GHcP}-XLDw@ z-hxai%$bNR2|h2ui1Z|V1%l)UF1ZYCv#CA}9|~<)ROCf!b1H_Op5@QLJyp+h<{($# zuO(p$1lq2#00K#l=K>%*6s?Bm3|&rhw%k{sTV|731fWGZif}K#jIciiWGQ`UR14;1 z!73{Di=3hxi?+z6Csmm-N2RVb7bs$%(pR_mTyWr)NrLy|Y~KPm>Q8&pFPx9(ZS2q5 zpHZ>`9T9Q_n8QvyPWfI+4y5O&iG$v(YaQ+6C(pEVUN4C>mezanc0s-H;puu)$H0XQ zR}#K0*M>M$=1XF*GxV#>3Tnk~+C?8Wp>?&W85?~ZU(FY`B+x}2ww~zme4d`RKM4l9 z;rYK0hV#?E3=F)-B-OuI;wC>+CLPZM?Wldn9NXcGnqA$rUe&U$`h_j^-I#=DAGl=s zV}vQc>IAy_@1z^(MZcTmhvWyvgykgajx}o4FeQj ze~rUCGSpX|#f0#nk{4F}JauC)9HV>Cf|ZMQ+8K7!t}ge}Pc<)=AZa{Lt}hqZ5DG$c zhu!&XBu*Iky`G1{I|^d$5Am+o5Q#j48`;qmw+bT>o@m0nef%V(Ct|(nQffNUb0DI( z870!i*u$C}QAz#NsfCkJQl(_((@*|q!-VVYoWAoG2=FzOC)FxBo`pjsFEzVoGdbRgQs@;OxO?Y zoRE>trR3aPy+wrpxD_{>1*=B$vj6+8_zr}YM9PfC2H9Kzd0SpN!#*{JM+C@*%wtzaW>?b3UZEly+sxAg*qn2>h->K zTe|uf&ACfaH;Qgy=G3z19rd3R-6SX?kS?`ETT;>5!eBCNf9VFP6fVTtKc9BHUKADS zrKHt4mW8^?ZkdEe^Ui6yA3=Szs=dTal;nCBoAD2~6Y37F?Yp=kD2cP2#Pc z|MI85%!K5_rBqo$#3b(vZ(E*@%eUxqtUxAPqhLjeOQQolRIsKMmtxU%HiZ@4qc@cQZOvxG z%wUS09OGW-^bQWeFRB&mRkf0uv~{bnDc}kcS0&jdfOP6oo)>Adx}c$A{nKX+>HPF7 z2USTAO7kqNU`%xu_fFS-)tS;~6acU-)}_stU!;bd+FTc?PZkkaOZbI7ed=gf;Br~+ zMyR-q+(IC@1$i`1_=R9W%rRWa!CY|9i%z~V?R*(>ja-u01JzNk_>@?5z7VH;<;XqF zplN|cvz%Aar-(DjMW4uG6UHi7wlx|p9z6P<*Tci$Rf>_yDT9Uc?6Jm3cnyv83s2m1 zf^*S(;m3$nny2tm>=4OQj#Kx|*kY&jw@H8Z>F+W9{dEd|(V6H;hrEgY6r0xC z8uuet?g4Q{4>#)J_0Yo~_L=Jq`hD|R`IFF|6qF8Af@l-951SZ-tv(t{|zD#xmfCskSEAwd^?h?4vAu?qjB;%3~t}L6UV6XXzjQa zk9&v6pqooux4tg3`rHY6_o;%O#rm!z1G@7K3H^X(iiF+>h_hB?PL3uoT!*WChUL?s z=MFUo;P1!*qPIs`cmOOHdTljwWs>N%NBf%1pDXr(ldfaXS|7W^Q#XWz_G|ZPkKrd| zza_lPhbqjwa1_PenR1_BbA~>|PxsG$y1bMQ^q084fAC>vPf7r+PP)=3bm*WvdpNH0 z)}@5c6V_yw@E}TVr1e%$x=DzHPo8P;>_IvWY!WC5j=nUWu*46?6LKc=) zs}kHns=1sS>%{kLARXKVMZRN$obT8`d7VWEp0C~KI_-p<)yLktfAeUVt(6aB+9iyis= z4ecqh_Br8j%ViuI$t;Nk0pT_?d#q2@dvR;R25i)JdaQNB?%8eI>F=5oB-gXq0DkUg zvl0B_8jy$EE1B1(911^ z9$yHsLZz?a8^*DwagW@mh{JHph}JjG=gycNB1bxz+Z?`E?9vgwV83|x5v(bow7qE; z7JP^8W4I48W>tl2D`?1sKbCcowZU@JQ7?zHyX&KUk!tb@*fEq39^ zAQ8WrqIX|Yx&5QV6i zGjn*oYW4xdIZD&d0k|ldxj-@6gU;& z%9a&Fo;Ua|6s{z9gO@KYty6GxqJHIuG!eBNN3X}gI)sQtYG7E3j|HA+IQXj|KIZ&H zbKq12@%+5+kXJG|3z!;Pu|F^+6YM$7fpRH1cnLhqRoKOiqYuY&%zU}}_<4VRiNSlX z1TzsunN3FKeco1+TzkBO8X&MDo=)ttRJjGJaJ;9`)XHyK} z(3@Xb%2tYi50*+*W3-W$` z?|gq>=7bOHu@p}AF*u3L32niYz?>s_x2ttvNTXN?U84O|*T;;UmHrEMXJq1nhfGpFzt5zmL8>;Bf+5i@B_M zOYY0g4eqzoWH)y%JX1Vc<-U)$FlLYMaj5$5%sEg&URi|3rpe|v&24=>ZNKZo96LeLemBjL<0)Uw0n_W6Q}BUFYNBDPZmjeG z@3RoQojW7xZQr?pw$d{P6MwHwkcfZ65$!Mqr^~r>&%>7CdG`X}I*z#ED;~3;oPC`< z?)A;|r~sYWy~XZ{tVNb=IpK>gXEW$X(!=xrhrM_2YTQZ|NB=*c0=)Qf_lw-B+O=Pj3}hx}?mw9|X)I~KckSBs zE}^KWZC?4qX2;uxojCMoRsLp{_s~iSaVQsd1~iPi(qs+zWjKTBmcCczSoD?FomcvC z{H?-Q z;()fy8=$F$Hs@oYOAJsOq6#-%q)c|cBI%R1zBZ^0VD(hqAa@QU7@bUk z)f|tjfQSz{Uaq36AxCR~s&PJBgiPUA_olCN>-Spc2SvK=a+dAfLip6&hr(9y} zPk*?+tVd0BgK?U~GoXfA2_^UvBfP<+`Oc@f3p}L#JU9`QrdOqH|B`;Vue7?B?2*k-p*l+dk+pf6-V54HW$-u z9hLMcb0oHf#1D;Yo$^f5^mCcMt_O5P)d0QU0=Ch57O;3jSUE|49vbrVhhp;c*d{*@ zHTikCfc*R+Lw@dWTjb{tfU^IiBq$tm0s<`?8B6>?N0@(88aHk^jxOU!N$)xC6T>eR(PDQEh1hc2;S(Z6=n$9R^zQ|yR9HzX1t?6%ezDIo} zxkUguQB-`ZBnPi|%h{zA3{QNr0oMC8uOI<_BTK3HXh2p^@T85AHdF@^UICpAe}Oy4 z#CD;55GTk<+VX*crXo2)7Lt(@jCOm8st3KW&UYsN&=Q(d0zDs%k4~kM#P4z)6!CbI z%ifib|2#ciUJ3jm>E15I5>lztCOQdThqte2jJZ6OuOLJV1uqK;(WH3!GA#AM;_NLomuG^vFX8V{Y(YupX<>@i??sE0+ta`gZ3lfPea=1JsJ>+U<1Yj|jp$cK3n`Pe0q_n#+`PYj8?&m{86mKPI= zyf0u_$CR3-+=vy4yuW}%UdfQiE3YMykDLWQ$}aGcB$2N*i7aQb^4LdR>nliRaXH0r zQ%kRQ?kRd>rnqESU+*;Np_>EGR7r%?X-3cm@+7F*cfuknMM8U7XZv#Yq|bYDoh91O zUO~6JlzU54?y&YSFjVj!kwo%5x8A#woG%;tWZV{y4qYx0fqKlQFB>sML2|!uf64v+ zG`XKda1S!QwZjh(O?HYjnFij^+ZO$0Eq7{?$nfw*M0AH#7pJI*t~Hatr_BUrMJuZ- zz=P3DtBW(18WsCui_Ntg>MOeC$Cu$%)98_5vH=E*ZvcVYdy-_0NO8lh$+E^7-!f-} z7456O?SJ1c6fT~j)}xaZE?!IF;<*woUR%P&-Mp--+(30}CnH=$mD?@}7tb@o#a&yt zD5u~E7th4G5iXv)!o{NQVZoFvaSkm04A+TYs6nw{aatf)Jaedl-TB;~D&lMx5HAWA zXQ(0`aUNW58E8t;r&sshKBs$}ECMhkdsNAXSt!|i4)To1I#O($y1X(_=}J&il~;49 zpyScy4$nM?MWdRz`u7i)>w3W~?= ziVUMLJM}{0E*ur}P&1k0Rq{Ch2aNbO6i?T*|s^rT6O@fVH-$Y>cw5D19~nJc&n>QDZ2EFu2xN z7WeE*DqPvi-XXr7(N(n{c%@@m^cbN25Ve*ipnq(~D`Jl^g#&wQ4PYI9^D1a;S2lrT zF=`Rn-+$@yP7x3Cqs3-9;F8&U|g@uWZ$v`998ra|oE6 zN@d`cU2QTA4A9Qk-G$0%lE6Q$C>XfTtvT1dIo{6S+GHfmNYOPDmd+5*p?T=|SqFry zLx2k6rw#qd&0Ay2i)}JC78D_yEw0x#YR}7WZ&gUFJm4OZoarXj>zi^`(~- znNub?>0qMFDX)~i5eNS3UlN8tS;Fw+w*OxIzU|k=Z>|5*j9)jonS!3Mj~Jo$HZmPxi>shYwpK@zlIur8DpZ* zer-UWw4Z}3|6AJ|Bg3u_m9pEolj3t&A><$>>{Zj=}E^U@W4f zaHTCci+6|tfm~K5sBnUnY%-|AYGawPQboe^4W$e0+M`xN;)yM0AVCD_`3z3bc^D^7q@`qv5GLB0n>S0auI- zOZ&To_myP{!P_TCmiUD{6x1D_1fIOWB3~jaeJCV`Ks$1j zA`TdwI!&+Rr&)D<#}b5hq%zCH%P*#SluVDSaL3mYKs6(OT}}Hz#4pA5ob9qvwqft> zL`|?eSA!7BicgsqYlY8<_-Y{GNPaLRiNhU>;b&SUGoAYbapwPuQj^P4M_tbH%GDR} zH#Vn-x|X&4m0aIl|4z+gSM9g=iFD(;{!J@NyA@tjxJ^Ix(bllP>srp+FhQy+VG<$1G( z2{bz>U6PIk_ug;YQb&XU1dIV|hEQyo8oLmE{{OLUIjRscAkVRfHjoIP+hR8rKW_u% zUbOvEN&Eu;YLtJ__5o-9+Rm#i#E)(9JAO52Ca4>yrk}UpyfFhv>*OMZ7pSd1_}rG4 zDvZEq3S47hyWB(DM$h)mZI`;hQszzW=H8d1@09oZG zLBbeC+N^%z({FG{%3Bd=>(Du4_JB3!3U>`ybz&f+19xIm+2Z`x)~sIFpKRR&x-|SP zP@8Z8OSZC;XC_JX(6O1wtA_j~_zRM(SB>9MlaB6r3;5O^5Q%z?@bYtzt9%95nc_~n zS*BFi<&kBo^IK|xe-l@#Z@ZSZz6gx<1t~gXNwe&xtE;yR=uHZ7g^1VpFbdd+E!!p4R z;HVTW6sfBZZZ9ATc<&PTOaQ^1z^P(wmf#ljeQX_sL=sJ@-pm*YlccU`fx!PB2KPHK zRB*6<52}b9eEtiIjIRI{Qj$2Omgaec8uN6OzLVJp^-NqG6O`!!HO8kEMNg|@1u z;CVdSZ*^@&h?IL`;Gt4Q%Jd6V9;Q&uRc(ZeI+DSQ3j5-?MlW`!;~zOawifkx1pIKO zKZ;t=c)T8e3rE+Te$e@LjRrhK1|l_6(Dk!bYIA2SRt7m45(5Z~rB%+HhGWl$#^ETy zs<0JN2W=ZIeSmpX8iXbD-qJQ$&qcy^x;SGiP`(+~|9-#W+?gzHkGnefrYLw&P2}V2 zCE|a7z%7bgF|d!GQl6$FleVx>mujg%Z(;8jcdo&#!gJ+&L09v zXLOxjcfPXIv3hZ$6(kFAM_=T!wV|%nRptVpt2HAtJ7DvaQPY{zJW4>Q>@FgvYhT6z zRt)v|qSKr?$t3Jra4OFV5At(0GUr&dpBu#@MXd&&WA)O_KNG&Bxy@j?XG6dh>vp4} zpv8IpF6(0*?L|)C)mI^!ZJW;Gh(0C|o5FZe7PUmSV$MN<6pm*}#^iAt(g-5TB8X=j z#IdV1c{#M9qQ6Hrrr^NV7D{k%f!2-|5U+D4bQ=FCz!H!}ZT{0czzwUC))g(Humc5=tJVEf2>P@0yDuUX-a) z#yq5UyC!s%I}}C}*Cvv}Qg$GV(=l2dP~2ynX|DRBdF1yfpmc87dJ0OPiH1Gply74pQ=k@Y5oNBv!<_SUC)ueK z(%jF4;}p+;eLgVlch!vzLwOGB7(j+?Wu7;Xfsx6>SbzU^UbbYOow^;1ZYeDcEc2`? zcYzXjo;AYB8sB8iZnEY!88aIttgQQwmbh0*fD6RFmSCz&qBd6mDot_$sz`K{C{tFc z#nK5FfJ;mxv@k$XERF-sNuu(&NbQ}@)e>B$FqE!l?qJclDqgMttFv!Dp!Bb3?};LH!14r;HdA{|h%PQs+XOO@FA@!y&U|Kv`hg7%B1 zvY?jIEobIXTv~x=Vm7^J3Ad#=lEf6`JUpMHa(Gn#%MR?%wFBGh`|EE-zwW=SZir$1 z514}4MqtVU!nR8kmeqvo@ARAR1CEB{N|3(Fjn5Kyl&LLw9# z+V)wf0E54CWm*P@Ayf{XmlM&IrcLH53XM6Fb|7+0n+D3XsbiTo)hyGdj@5%PF{oz@ zn3TO(PmGi`sn_!h)U~gOTnFs@(~}1a9B0irM!6~@j;@4N9Gil9p?MVB|ETr-W7bj= z$689QtZEXCSGp*>(}?P8{RLAg8`@lZsd2{cNe)HDn?xPlz*5C`)F39tuBq!w4XL(k zQbb74AwsfNPDqkyE2p_X7E^yz_r~JVDAh|={hXx}rp{95+@O&Gd(AthLL5thf|j^u zOWnE?qu?cbB{g<6!v${m<;>IEoYSO%5m^BsJ?Q+{+AOQ)`~bef2nv|b8XUz%v^ zr7<59U~rqp;AG1i6*8hm{(4DQ9rsKh?x9N%rW`1e?ExNR)`qym-ZHl6q6^TP6 zvjTIXcqR>Le(0dZQ8+V4vTcs!qh;%008_`KPlzaqvq7i~UUXtmVbDOwfC$DqE(^zU z2WFPSlGv|cE)g7e`xQ*rQdwKXX+p#)hD~+6rgibyEgn9g;xG_|Fd=IIKN?mRxqPD6V<=kxgu>NidEFcLBVT z#ugY7CH49y6avHTh>cgcQ8rnbsJr@yjxtQi+E+>1$sMJ&TsU51z4WEcxMsvwIY5B` zf4a35z`|^8rL(fLKAFX}ma?C-)fvwHzGXF&0|(-ui$W2 zVDAMBBXC5t0Y0E{T3}w;sl|-{IFZcwZxd$5zf3eU{`f$Akiyk}7qfI}(L%@P%&9CT-O|26bQ^UN`sdc2M)lZF5Nb?lgLrHaxB zh|*}nXR@f`zi3efKLIt`!esxht`b+&{hLZFN&(PKBsxUSGRaUY>c&HOabs}C3SU?q z&ddR0NPMp)#>%97;J1+ciUQBjvp$eXbbu8fGWG_@f(*m}n8MSqZE0cWkrRVzOo{2x z5+O;w7V>bP5-(Gu+>B&$NvUMGx$@DvT{tgwep2|_xm>%-eC1rJ@ab*#lhU)ox6SM~ z*08|GftoNzb=n?``Yo@zUUtWs9Z>+NuY;2KO8YCdU(2>{7a=VR6T+a*XZxrZ4K?3dw^CcTTkE!x zZ@8)^j#gS_bLx$@YWH_%k$vIR%yrd+*^!^pe&ubc85>f?Vm zRe*X#a~jEAXOJ8ty{62cbRBvTdhYJl;bNTuX&QDafc}TUjk>vhvP|YKQxRSHpF@KV z?>e3or+JT5t)YYvX$+guh+IiY3GE-!e&Z5NvG5`fzeYr7jM0&jie4#Cpi7{qtEhF8 zA~2Kxxp(KuG`!Z=CF{4=s%velblXQ$54icQz;2n9;fVw;dgo@#c)g>1Mwa4aj9R|0dBi++dBsxkH+b@oFA2uKb zxneXKjTKwO_qVQ8&?x*}7WrEE8rDGDq$Nr?80o-ra@*bSReGsQsYlZ;kl{!vp>*Wq zU*h{eS$uy|_wlrq4BSs*<6t#TZ87*s-QbPZ%qfM3>co`1pD=nzyOhI;GN16T<`)k6 z{M8!+7uLm3rWYT(7ugnl>`r7tN~>&Y>C=-JkiT-82{16fY1r>n3r-MbpMzEFQd{;z z1Bb)zuBVD?+m<-*6oBFsGxwYhd^4xCE6!6gJfr!^-xN%iFd*R6x<_;57eELe)}^!rx`&<`3;vYP3jv;!Lmcn(2vN{BjB~`P!raD^}|~?BYf(j3?3I@b0@4 zk9=HK#7R;5jZ+DfD9?c-c%qQriPAawr%z9b8(+L+a>%XVa_!+-t-xCC%`K%)22Kl&yOL8?tZ-A5Bjl$#DttOV zTt8~i5>7-xH`KcP{Brwvbx!;hyu*N{i!kr=aFouTo+9@#aUTy?%H9f!$n5^bi5Wpq zMNa)N8Ur$-jFX3vG$%32tra8DUTJtIFz7{9^fXR#(>T!~$$JZyTqbl=tIk>CUbXNT zE4n9LjMFaH^n2W4HrHEIiTH{~q0LOyK=g}O0zvHGjQ$GGUZ+sx@!*Wcc}Yy`3-U4Q*%ae#n-Fdca?=YrDOm?J)tm-MV}u z+n2vz*Tc5H5;dWX)_-MjnE$ZHGNrOOe&!TUTX)YQCi?!W)4Ah6!j0J&iv^cNqboS^ zXipfM{*fTm*XRpIOc?oK0hVao35V8-g>$txLLu4JMYH9otj|q1b1OeLU+cPUOhf^e znH3nYOoeqCIX8S`S6EPeEeQ0^1;a>NI9uJ=7P%d692)zt-C!_42eXDIL$Y5mYS~@T zIG&4rCy&~aULcvor#1K*1Ets>HcpuFKb`w4V$c6r2Lcedef1r!1@JpYG`{dpfPcy4 zDLw%{`$PwME^qy#2Cq0$zd9w%UG5>fNt<{f9JX^0_<&RCWPO_jXCm``6F#P=@`rk- zKHla0T-|^1B(p8sXPF;ddHIgXe&xhJO)^asfyd$|9*Lj8tw6y5{;l3+O3{#c#;2-u zt`ty&r$Enq-t~6M;@htGm@Uw_g|3cQFz?pIu6L+NpCcqAY1K}|DXrR`N;tMPzzG(F zYAc!|hiWS~V6X*MPIFaGRTW~*`H(TxT^QMbW=;3zUb(sF-+Mo;h)3xFxUZ=?Czj0x zDlN3$YiC=i1!EoiXR+l0Qx=PK)~K(k`5aDf%1!DTuZpU;^zyf ztU~wRx0-nDVCEq}91UclV2Azhk34{C*59GAk!sH0uH!>dR$)7xFchJ5!o9exWCN2B z`CKgk@p4Jh#|iw)#}O!F%fcz@TJ{_h%<(LlI{;pK8c%xG6O1dK?qafCOEP)V$~MvJ zcH?L#07wpWO?*X}3apy@9jm4a(I7KY1PohSr4lEnM&+2X*$$=Iut+Fo!E)r-S1rZB z!IDM-8^xXJP?}?{MFV&M+^my5#DLBSlb6Kf6c8M15n&$}mhVgwbiz0vj|Hh8bA0knv(|rho?GCR_V|Xa+)$G{}uKP$ES?aR!RN%?^!c zI#kWFwthqG)|rgGR3%5e6wmwQZ0}k^%ZXZAD)L zuO@0vtZulsiwhZcXoh~R%Bu4J7IlG(&ekYFM2@Nac#3h8GL*_u5Wbv(ZTqi-j zM9Q-2CAayBtd)cmk6vY466Rw3qcv#?oJh}EN+kNY38g`Oema66?!{8j`gekWn1!%Y=YM5g^3PY4~y0UO@W53d%k0MdsS?u=UbJk zldXs`h}8VH0bD*oXU8eOG7F`V++0k-ZEdPmE*V@_QD_;H{`5e z1!e*RQ%=;u0iZjz&Hr~OXvAo;Fe33_wop_oWcoWN?cE)Psq(d>xs^`N$#MJrj+GVxBwe6?8kZonZWItFJ<%-QZb=eMWO z^au!D`O`u@eU@}D50t2zY`{94u@iIlEq7N}JK_!v#`9#hvmLYZEtL5;_HaWo<4lXIGkxHj6KKF&-7#?eN-9%-VPDyQpV2 zVx)k%Z>{)H-Jy3rEgqAnuUR>@ld$dsy_rSOr)my#P3t0`LRIr9>`VVleUi`at+l)0 zO25wE+vnK}zO7Q2Z~J?gw)j!4p<19h@l`;)KdRNylXVt%u8z>sp)c(`X;2j&!i_ja z^VdUZ^m;hAtdd}t_#N*gyy~b|YRll=NWG)UOOrzh^wI@+wz}F|U7gIuGVlqH_PV-D zIR_MwaA3M?6fJo(%>7NygatOO45{c)2=EP{*xb%}T^_SxUm^|r)Z)EjAbsadg_Jc9 z!w1|g8jjor9M0WUS;#%(Ja1?Snb|&yjRtds1B6<$7lxDFL4p}fw*=%4v!JYK z#28C2uc5+~@}*J!G|~b}S*Ns1qWg#CT8e7~sM4hrT9XCMGb{|NhkfWnGwu_=jh3TI zBsA9PAf=K64$Rz+uud7Qof3;OM7Dv3^e8Yo=0O#Qs)ggGcf6>DW2=SZ0^U?Mq?4Ha zu>nbmGYqL2d-BQ#EQO;)pDiM1Ex~JB-S)3&TwSScS%mCLfE1Ry+Y-5HAl)UU-y^*Z zecFc5D|Lv}F44LVeU(DCr4{~dbfd{almdr#<7;Z>aM*N)uA69S`JZ>FwFY`#ht{GU zR#)IecLGVD%M9(K(YI#*Ze5=E z$Ra0KveJXxm6Yh*zAWUC zG{T#0HJd|uB(i}0?2*tynq%wK?I2X|`%*09ZI{2VAqG5z3r!KXx0La5zoXD%`AX%p zJyPzG$0iv6)-kEcfYyNKpatyj))GH$&&x=d-_BPa_qT4A{zy=m2t*-ANos3r>JXA|EgF2mUo!q8PtmK3zwS3cVbRsTYk4pTU=mCa+NR zn#9wwk{{wxvZSS1fn!e0hUL%xKzX2j0;_!`>6;e`2oI3Y%?StTZJ}V08Ks@S7^Od% zQEH_xo8UgnJrQ93>)aEGSI7Q0Y-QlzCg@3b7~K*X5A2bo*B{0}n@Op!2>1s=x3R*p z6B>8rHPmy-rjd-i!WUIQF&<#f9ch%(7yN{Q@8~4r+{qpJVB&Q#)P^!8>?_55Pscf# zi76~`*XyEX+ja~bo#sDo`Cd9|dPj;ZH+RpGMTsA=Ht`m4E%7Y+PP=V-xB0raR^408 zi%B-8h$ZoE#gSN1ZJso}lYE;eR+}dU+?i}*u9%xYG(ydsX^vVS&vHM@)_5kl#?Mc;18BgZR%P$*{9<>E+AYay0_wFIt(}!O@XCaj< z4FDktG1Y?J5}WsH@CQosmL2=IvNyv*-NN~;Vo=~<(A{Au+_8%>BZhFjPw(N~#8xGV zTZcoWN{UIor6)eMc2|;6_VPk|1`=^9_LvrgJ=hO|E@ffA5Iz1$+Uf&Lmt`wgg1{*7 z9*~n{n)r(k>yqcEZgYTio8_m*fe#>acA;OA`e#ieXQqz1*$zIXBTG!96B zt|{GdATCx{50DQC{4SGmoxrzPnJ;D+=H;iS<>d?UldZEtyErm#a^f@UYjWn@dwUiE z3e+diVkyopqYkAkY7Da_W@#h|HYep4t5<174MFMc`9^$a%sTgsS?6Bkj3Il}ac3f`z0iwkSc zIVGSyU8$%k9L(G_kx?4v8+he0)3h&G?Y!0)!s;@%4B$i8roCm+*>b%&ORY=WH{~Py^4xK$^7&WuRYyofEhadMaP?Q2r}1bK!+7hvIm}G zu(NB5G%AcE*=kk-GygaBn?`YI7+Vd$*G=y_&&pg|tjslYwmHW3HC36hwX~VnMJ&X1 zhK0CZT~)8>mt#3iDy1=RfLjx-H7xeynslSrJl^xBj_W5Qf3Um}TK2s2{7lYsr|_J0 zj5wyOT@xx&bx)$*w8rSQ{Xf?Jv(jy&yQKMzs8{WUjQ*@-=LlHD!|#3jJ1f^Z8G)W$ zsi9kPEmIQhmZ_X|lVm)iX4b_rl~>xCeW9oY35-|Y%m{U=?m;Q%aP75#h1F`iu5mw7 zrk%$&Exh9ccouVB*STfA&hmZxxMg{^uV~M>CbCbr{;lMmv@qP{`&}gGZBj5|3mY7v z3XhZ>*4tNUJPZ%AZFrDvyo>#ydE_5SwzX7hDvvA1;_wz&Jfbg3Tfj{@Nj2%`2^#p^ zsc0zD2Jc3k`mT4!QF-g=vUb%tlHNT{W;s@K=(AG7nz`T0+u z@PYwG5x%s(A{B{aWplss9vYvW3QAFItf9KXSRRr#%UUrY{Vuln_B~->o6(E*Jz`E7Arj!&t!w zeBD@q{X>7D9k|N&{L4Kw+Tt}nK*K8DAE^@3ny4Vl_7ONA_VqPT9$UV2odh3My15JPuY45HfQHx_=_ z(?{JXZYRvU9#|V{v_?Dqf7!KZ-wsu~)6e-OnOE$WB)9b&M#CdjJgc8dSK{Gi8ihmy zIQ*klJAg?%s{+XLBGl74g8lDRSXUssc0>%2T(@HAM?I)Y^Na%{Ay>N#fh zDma#&@v9ByGddh;I&WJJ4a|mg%z{juz3Zwa?&&KOZ^x0p+yN9jSzR3fS;DdWj7_>h zBNve|DepSybLf9Re95G|Z<~}$li+^Nq`Wsr10lzxT=_ww0smMG%9)9GY@nW4@YXe3 z*;BhRQv1=jfoQnceETkAc*Gkx!*zS)wx+_Cg(GD}=Km9A{tq|gKi=Qhd}a%EaQD#TX~-BW;5 zZL^r6yPlT4%UsMBS{3gWTNRJy;!-YW&T@Le$^x}KE*E(rr8}&Uv<(qmGdq0{h^|KJ8HFT&luS|`z|RQBPSxP5VjaM zmU6SZ5f^yD0!qYU7zsiW>J&*>P8>BJGDR1r9JQV}5soNf!mLWKH4Fzc^)yvg>4|OH z=r=$_*dpUJJX94K#6fmSFLbyBG(y9m6Y2x5M`VwNa!r~?&Z@?mnu@C1j_p)>niOMj3sJx=sG-!VdQ>P?cq@z|c4)!qzo>x>W z16Sqpn^#squ%z5FFO+AD;0^2YlYmhE#&5pFFThgMtSN;uM$ zwW1P;aVjbuY$;99{6IJvE~>uPe zbthUt5ZgcyT0qbeHn5H7{x2MDHa8l&s!NiDX|6net^>&7dN{5ah*2y@=BKApiSXV) zbOca!KDOEcx{xy&l65=@BGmD@FKu<-_+f+Ia=?Ci4e;5m__0jNAusQQf0ZBgFiJ~G z{4&b+Rf(ICmf&~;fAk%Y=$XFh!{AN)CPc*vg&~Lg8}9sWS*HHr+8wB!6xHtd9a($g zcfapoNDG}SShWXdw1e%8;DlnO0Ad0ba z@>nR89y@+5ET%fwufJOMU6dNw!w_IaUq42eX<^&>Mqqayk30-zE??&j#?H}O!~U#m zZI|cs4#vlNoTG@H)}}{)bfi`_UK!~Tk2M}WWyab(-%<)O{~Y1uG@gz{&%9~frd&NDLF-D>s88@*vn4|O7rR}}d6+JP!5^fxxK8p8?<2erHq_LTf z7;{Cly=C~>(W1A#L0LWQ40P@d^L=Mvwi&K92LJ@Xu+ktZ+r_4;dNfd07XJC1+MVQv zEP8{B^zeecJ$yDZ(u9 zU0vOg2B44Xiul#sFXq-zuV9e-1&$-uJNS-P@X&fqd!5Kv;@;ceML7}Mt)H3gS>}0X z1?kcWMQZ2wjVr=fq^++FLuQ#&V-7oiEftw2?$gXpNQ^Ec0a6oJ)$suHr_|sZ6TBrW z7SU1PM2kYYBlaU5s&Om!p;7$Qz~)lfoP>kFNBm=T6~9IHx9=0oyoLF|Vs&TFURGqQ z1NSf8mGY8I&svgS4#gdrqVy0AqW!myu?j;|E<~g^!|O!9d3r)uU=<6M z-H9lPncS4}_cc^=vdi$b9OOCvso_O^0T^c2Xl1YYr$X`P{CwdCyka|Sb>##0EKufN z0P5VP4scHnn!1}cqI83NtJ2;E?jqKlsB7rK=W%@RTQhsJ2B=n_de1xXud|r=Acu($ z7*@+o=pPQjgPO7k0%0%PJU}1VuD(n5J#@G0hF)gS;zF#TCtf+T2&AXRB{-z3t$1_}$$!pd`-EcJE9peah zAr6|*28j{*tY!OgCIA0@4*j(s=A)bRcRSws(i6#@ zP@@3v*=0t6hx=0dG#^W!=2I$<2m8Qxdu|%y5;o_6;@@QsIN<_WN6BLR#QE&dXUn3GNC@Eb1@1mcXbK#d5AH3 zDAw-VO578hXyOXiC|GaCheB_jQd}xq6yq#$QRpn;+@}ocoI2$!@bA3`4KpLo1}r%l z1qZAeRYW8#W00-57;YQ(8M`QrP^p=dxC4sqF5Go1JAu@MQgvn z_$Oc1c#s==p#??5sDhKOglT~IZ(Plof6v8BJpf~&a9vp_0G7%hDo|1xvs1XctVP%6 z6PIVO`Sy+$hQlftgAUd;uG-FIc5J7icNQ2k>mTdBO#k{wY~uLtYl$r<9`C|nx)a|w zN}F$&;Q#MGY~p_y}G{i_WfpcefRy_ zch$E`>+pMH>0Rw@b!}*Y7_o;xXC4IvO-NRQIi#Fa_L?5 zU1bf1MnCEs&^b2xaE$feZQ__V*Ra3J`nwO%!27l8`$~Ot9SW~+yxm=|uS4JU`iJWK zrTRv#`fh1`L-q}SJ6v^%sxD#GC91lFRo{JpewH@hzlFXx-_@Z2bhpIaJ`5{waps-M zhj+9hFv+EN@1bw}0soEhVTm8qe--tzq8=*h0sea!!bsk~?^Iw^AF#=_^$HAmjefv? zWA(7aFYxaYzFbl-)r0!_P@2!L6yF`pfDV*F=1ZI-vs@9YK_?uAdff2V*ffVjP9om~* zBDRV{QDafY;gK){K0}Q*W81 z{38(ZV2d*Q@UVy{uR3?~m&1fTBLUufE&(`EBjJjoE&OvrO2wqqx{rB%%&?FUv6J4zXqB(lEwt+K2#J2wP=K9+z;Aaa* z|6bv4U&PD5Zva~U09XQX-&!3p{u(U6+qEM=9M!itmkr&0Z3+8_PaCkx@0MWA^(XqU zR2#g5T5B5>`m=5-%7H%|uf6>M-NPtfz{wkLs~c~j>KfiY^(CkT^Mkth87B5&Pr`M=FzU)C z!clb%us>|oMg_napcb|rusZ&W^Q+cAEb$+l<@!3zVZ9>%IE6a|-vE+$7+@xJD5zbS z5nL#+$MO%3bPWB<)xeA%l{8IMO=JS{P5gO@q=?y{AS%0 zasH+_RDfizjJWv9_{U$)gnzO#;m>dhlgC=4#Kz3PV51HUpUI>dAK;!1Hqo!(-V3&p zZX@nm&Nm|6e5FI;i3)J?Dwo7!lmDELVk>V@ukF*>8E-C(CFptl_3I#b#-5#F@dd+p z%Ugtzpm7F3m_H?>Nn;8BZ@NdKc4lmAe6`5p4n@FsPft2L=5k93PK?M`;c4aG2j@`9 zjxZr2ebq7!02|%bvi@AQT%{?ZY~D@tuIb&WK&M9Vc4vvk?-sdfzNTu{O|$FY>E&P` znAvNOamdZ%(^IgCf~Yh9=lbRB3N@2r*PohTZP)(e<=ItZEHqz!$0@a?x0H&`gM7H_ zE`>G+=LNo&TxK#_R6+~<_EK*)@pj8wyA3TLkO4?`ugY0}#-8iXXh)r@=nz6EPgB`8 zN6)w~Vt7~sRiuc#%bymq@SpnskbZ+~AdoGEI{z~trK+BeTSYefN|Kk@mH=Alf$L1( z4tJF3(3Xo&u`?CH#cU;7BS<8xT(Rh=sx)0vOlUT&B$O4sm6kwB=A;YAnF$_w2jG7# zUGeYAg1yB^13#Ff=WgVv+lLY$TWOU-DfmZq{4L$a{2%B+No@Vz3?CH^^wUjzEJ|Kw z67A|7R#1|1ShZ5LdvxAY$Lgzm%9W7=(uL$>u9O<7E~j4PN~%fdlIjJNv}nJutMipT z!WnAEt>h(#qhV|O?S&#O^rYAwAhy&fw*!x@m$5dszWchOue%1i!8lFg*-w!i&kMaH zHaJmU1;1Cv{niNX&3hF+amGL9Mr%4dSn_n4v64qgNwGU=6HHRumnZFRkYx z2+7A$5J?T227bCz&!rpQwmBTnHx);jBQ%V=twEM-u~N_V73YC^hXlwIR=XwA>ODby z0ip8xsD{EHnyiJq8@LT_lzb3x*O%D%!O&y^l#^nvB0bO}aw@SYOHvhFW z|EbplBI?GtPuxhuWGZtWkD$~MI-JaJ$n!$d>(R1bifG$QN}Up;l7|gin~@*u2R3U3 z(qH6jBQ*_W5q6L_&@pPw1d>fp>#S4hNwNbwh1TOhSwq98(!dB97Ichhq*eSQ&{rs} z!oSGT234Raf@F_JL~bC;k|+$O=*%%{p(gr*Tt_rLPffk?VGYr?8(WhPVz~y-Y;YBC z`j7J1)YL(#X@fV*1WvMH%34~4P%Zne^)k)BCm~-NpT9uz<4png_%P04t|V+}A5j3> z&k`+5)p6I(0%hp2>`N<8Rh|xKx;qk*AWxVej|~AHO0!cPNR(`8qa5|yYkECD%#yG@ zi-hel_4~+#ptMvbjvM8eIHPScuWs}l!dBZ`Bdw-!^@3fx)BZiUYDnx&dW_UDwWtC&uKOp_XdMOSkGBP?N(T*=13)vT4@6J;;k6Pw#^F4wY`yf zyrlB~q&bE1C&lIal)(B%X8y^4rWL=zV2x)gZ;x#Bw7E+uj-i%HBPMTh{iwXo&xQ1x z8ejm05=K;jsg)f5mG%M0aQ*aDpm|m$DyVCg=EP-ny;U?_G{?@+t7!$_(jM;P1u%xkf zRThhCx43q^sdrvnUUny9=juv&W}{^}#r1maReua*bZKG04t-9UAkE+Ph79M3?Dk!4 zVZMsAyA*s+%gArW2`YS2_MP z4UK`}%FwcI{B3nLRul`9U%AyQlMi0}B8kQhZfX(~QUNaz?+l&VOmlkdXV%!ySYLqg z8i6*_>(_)yBW04Q>Q0+g4Kt!;SVrBG`^KrYoEYC)CaBB_QP|GTZpvRS8*iiKT& zIUR5vcX8G%UVm=}duyW*v%xcKgJ;!Agx|>l?u%t$KNA2Sf3yvuT}eHHt>U^afDNc- zc~Qa_DYt1%HNHD5l7Q`Igk9H47IdDGHMKKhrfo|ic4(02v314lSYodItWfUOlDZvP zBBd3kyiXL_ZJEcOGHtLoHx5_1>j152aQUe9A@?(DvP>t~Bp=Weha3aNvfaLO>_BHSzvd0P=}NLi z(XT^AG4bVMQL}@f+n$vgCtCyCDNqg38RQ{l=^%}=O0***(XprkT_9*lTAPZLW0QLu z26Z6y%tkn**~lWz`beWc{xi+$O!C5vPHtmv#SwykB9nSjruj`DNdwV& zEm3ws(GM1q8v}q7LofWH`})qlnsx52iAP5B5npH|{<)W-mc`4+Eq)HUSy(^~Ido;S zEk~9*&XP3)yn!wCK}RuulbjtoZf@yXD?Lg!#a}YoB8*+IytZ}keeNsZnn*axQA+Ey zk&Wf?>gr&1bvd>{_1^2WWu`WSMw^BUGc$6hYx$0L{qH?%8Ze43CVw-Nm8)lE0T;2k zUFUeLVGcmiBEp?JKBO0Yqm_EpQIP@?T-MNMIYG+Z|rn_qJBMMhqRCr!_h~r^}Vl#@R!fzNfq?KT0Ef!+DAYzQ9 zl+IwB8c**{5{a)-d}F^j8^luq=RT3K2ecG)!H5RPV$y&#y9^`XNKYSSpeMHp$z5D2 zaVV^K32;Zy#Yi>^*9BGi<0KwUMwo+M$=yn8wc7tF2{aPNXy{RQHy*?Zg$swjJH(f% zess_|jWc;5$Z5(Vgsm40#R!WLSdFN-CN3zR&`TBSz6m1-Iq&1X(V66s3AqG^!wMd`4aBTP2pNnlf40KIiRmOL077w_8t}KXhT$R z<}83nf`isPgmCftrYv{?VFH zO*F`pXO@JPKLiwXm(H=km~0nv=aCUq4`*<*MPKPFg(I@61_`mo`}|4ZcK@JS$d)?N z9|dagkzQz&C;(f9tS8Lk+o&wRf_d~qJ+c00=c`>{5RMvFFtPg*J8jYJfj>@R{^T)b zKOP-W#HD@eNqwPWnmw11wWAYz<2G zTC&n8vzZjCs^DJ0SLa!p1z(=3aEuJ!bG$;xWD~~eY&fpn5BNcDdj5b;;mYbIz$|Pz zo2V#<{cxa;Z!s89*HT®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

@; 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

9Lxbl}Mia^rYLydn8$twDdy_1MTE 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ξ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%0DXa?{2DGb@<_*J@O3QD>L!nw-*whW#n3uW0Y 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 Vd=5SbpZ~F+{|9q~Dzcv5> literal 0 HcmV?d00001 diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..17515c1 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,15 @@ + + + + + + + + DesignHub - 设计师任务管理平台 + + +

+ + + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..6fcb647 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,4793 @@ +{ + "name": "designhub-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "designhub-frontend", + "version": "1.0.0", + "dependencies": { + "@ant-design/icons-vue": "^7.0.1", + "ant-design-vue": "^4.1.0", + "axios": "^1.6.2", + "dayjs": "^1.11.19", + "echarts": "^5.4.3", + "lodash-es": "^4.17.21", + "pinia": "^2.1.7", + "vue": "^3.4.0", + "vue-router": "^4.2.5" + }, + "devDependencies": { + "@types/lodash-es": "^4.17.12", + "@types/node": "^20.10.5", + "@typescript-eslint/eslint-plugin": "^6.15.0", + "@typescript-eslint/parser": "^6.15.0", + "@vitejs/plugin-vue": "^5.0.0", + "@vue/tsconfig": "^0.5.0", + "eslint": "^8.55.0", + "eslint-plugin-vue": "^9.19.2", + "prettier": "^3.1.1", + "sass": "^1.69.5", + "typescript": "~5.3.0", + "unplugin-auto-import": "^0.17.2", + "unplugin-vue-components": "^0.26.0", + "vite": "^5.0.0", + "vue-tsc": "^1.8.27" + } + }, + "node_modules/@ant-design/colors": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz", + "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT" + }, + "node_modules/@ant-design/icons-vue": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons-vue/-/icons-vue-7.0.1.tgz", + "integrity": "sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-svg": "^4.2.1" + }, + "peerDependencies": { + "vue": ">=3.0.3" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", + "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", + "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", + "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", + "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", + "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", + "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", + "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", + "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", + "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", + "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", + "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", + "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", + "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", + "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", + "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", + "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", + "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", + "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", + "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", + "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", + "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", + "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@simonwep/pickr": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@simonwep/pickr/-/pickr-1.8.2.tgz", + "integrity": "sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==", + "license": "MIT", + "dependencies": { + "core-js": "^3.15.1", + "nanopop": "^2.1.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "20.19.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz", + "integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.11.1.tgz", + "integrity": "sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "1.11.1" + } + }, + "node_modules/@volar/source-map": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.11.1.tgz", + "integrity": "sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "muggle-string": "^0.3.1" + } + }, + "node_modules/@volar/typescript": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.11.1.tgz", + "integrity": "sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "1.11.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.24.tgz", + "integrity": "sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/shared": "3.5.24", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.24.tgz", + "integrity": "sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.24", + "@vue/shared": "3.5.24" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.24.tgz", + "integrity": "sha512-8EG5YPRgmTB+YxYBM3VXy8zHD9SWHUJLIGPhDovo3Z8VOgvP+O7UP5vl0J4BBPWYD9vxtBabzW1EuEZ+Cqs14g==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/compiler-core": "3.5.24", + "@vue/compiler-dom": "3.5.24", + "@vue/compiler-ssr": "3.5.24", + "@vue/shared": "3.5.24", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.24.tgz", + "integrity": "sha512-trOvMWNBMQ/odMRHW7Ae1CdfYx+7MuiQu62Jtu36gMLXcaoqKvAyh+P73sYG9ll+6jLB6QPovqoKGGZROzkFFg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.24", + "@vue/shared": "3.5.24" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/language-core": { + "version": "1.8.27", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.27.tgz", + "integrity": "sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "~1.11.1", + "@volar/source-map": "~1.11.1", + "@vue/compiler-dom": "^3.3.0", + "@vue/shared": "^3.3.0", + "computeds": "^0.0.1", + "minimatch": "^9.0.3", + "muggle-string": "^0.3.1", + "path-browserify": "^1.0.1", + "vue-template-compiler": "^2.7.14" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz", + "integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.24" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.24.tgz", + "integrity": "sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.24", + "@vue/shared": "3.5.24" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.24.tgz", + "integrity": "sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.24", + "@vue/runtime-core": "3.5.24", + "@vue/shared": "3.5.24", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.24.tgz", + "integrity": "sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.24", + "@vue/shared": "3.5.24" + }, + "peerDependencies": { + "vue": "3.5.24" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.24.tgz", + "integrity": "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.5.1.tgz", + "integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ant-design-vue": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-4.2.6.tgz", + "integrity": "sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-vue": "^7.0.0", + "@babel/runtime": "^7.10.5", + "@ctrl/tinycolor": "^3.5.0", + "@emotion/hash": "^0.9.0", + "@emotion/unitless": "^0.8.0", + "@simonwep/pickr": "~1.8.0", + "array-tree-filter": "^2.1.0", + "async-validator": "^4.0.0", + "csstype": "^3.1.1", + "dayjs": "^1.10.5", + "dom-align": "^1.12.1", + "dom-scroll-into-view": "^2.0.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.15", + "resize-observer-polyfill": "^1.5.1", + "scroll-into-view-if-needed": "^2.2.25", + "shallow-equal": "^1.0.0", + "stylis": "^4.1.3", + "throttle-debounce": "^5.0.0", + "vue-types": "^3.0.0", + "warning": "^4.0.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design-vue" + }, + "peerDependencies": { + "vue": ">=3.2.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-tree-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz", + "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==", + "license": "MIT" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", + "license": "MIT" + }, + "node_modules/computeds": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/computeds/-/computeds-0.0.1.tgz", + "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-js": { + "version": "3.46.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.46.0.tgz", + "integrity": "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-align": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.4.tgz", + "integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==", + "license": "MIT" + }, + "node_modules/dom-scroll-into-view": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz", + "integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==", + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/echarts": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz", + "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "5.6.1" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-vue": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.33.0.tgz", + "integrity": "sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "globals": "^13.24.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.3", + "vue-eslint-parser": "^9.4.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loose-envify/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.3.1.tgz", + "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanopop": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/nanopop/-/nanopop-2.4.2.tgz", + "integrity": "sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", + "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.5", + "@rollup/rollup-android-arm64": "4.52.5", + "@rollup/rollup-darwin-arm64": "4.52.5", + "@rollup/rollup-darwin-x64": "4.52.5", + "@rollup/rollup-freebsd-arm64": "4.52.5", + "@rollup/rollup-freebsd-x64": "4.52.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", + "@rollup/rollup-linux-arm64-gnu": "4.52.5", + "@rollup/rollup-linux-arm64-musl": "4.52.5", + "@rollup/rollup-linux-loong64-gnu": "4.52.5", + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-musl": "4.52.5", + "@rollup/rollup-linux-s390x-gnu": "4.52.5", + "@rollup/rollup-linux-x64-gnu": "4.52.5", + "@rollup/rollup-linux-x64-musl": "4.52.5", + "@rollup/rollup-openharmony-arm64": "4.52.5", + "@rollup/rollup-win32-arm64-msvc": "4.52.5", + "@rollup/rollup-win32-ia32-msvc": "4.52.5", + "@rollup/rollup-win32-x64-gnu": "4.52.5", + "@rollup/rollup-win32-x64-msvc": "4.52.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sass": { + "version": "1.93.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.3.tgz", + "integrity": "sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.31", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", + "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^1.0.20" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shallow-equal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unimport": { + "version": "3.14.6", + "resolved": "https://registry.npmjs.org/unimport/-/unimport-3.14.6.tgz", + "integrity": "sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.4", + "acorn": "^8.14.0", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "fast-glob": "^3.3.3", + "local-pkg": "^1.0.0", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "pathe": "^2.0.1", + "picomatch": "^4.0.2", + "pkg-types": "^1.3.0", + "scule": "^1.3.0", + "strip-literal": "^2.1.1", + "unplugin": "^1.16.1" + } + }, + "node_modules/unimport/node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unimport/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unimport/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/unimport/node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unimport/node_modules/local-pkg/node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/unimport/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/unplugin": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz", + "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/unplugin-auto-import": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-0.17.8.tgz", + "integrity": "sha512-CHryj6HzJ+n4ASjzwHruD8arhbdl+UXvhuAIlHDs15Y/IMecG3wrf7FVg4pVH/DIysbq/n0phIjNHAjl7TG7Iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.0", + "fast-glob": "^3.3.2", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.10", + "minimatch": "^9.0.4", + "unimport": "^3.7.2", + "unplugin": "^1.11.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@nuxt/kit": "^3.2.2", + "@vueuse/core": "*" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@vueuse/core": { + "optional": true + } + } + }, + "node_modules/unplugin-auto-import/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/unplugin-vue-components": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.26.0.tgz", + "integrity": "sha512-s7IdPDlnOvPamjunVxw8kNgKNK8A5KM1YpK5j/p97jEKTjlPNrA0nZBiSfAKKlK1gWZuyWXlKL5dk3EDw874LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.6", + "@rollup/pluginutils": "^5.0.4", + "chokidar": "^3.5.3", + "debug": "^4.3.4", + "fast-glob": "^3.3.1", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.3", + "minimatch": "^9.0.3", + "resolve": "^1.22.4", + "unplugin": "^1.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-components/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/unplugin-vue-components/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/unplugin-vue-components/node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unplugin-vue-components/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.24.tgz", + "integrity": "sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.24", + "@vue/compiler-sfc": "3.5.24", + "@vue/runtime-dom": "3.5.24", + "@vue/server-renderer": "3.5.24", + "@vue/shared": "3.5.24" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-eslint-parser": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", + "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/vue-router": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.3.tgz", + "integrity": "sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-template-compiler": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", + "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "1.8.27", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.27.tgz", + "integrity": "sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "~1.11.1", + "@vue/language-core": "1.8.27", + "semver": "^7.5.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/vue-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/vue-types/-/vue-types-3.0.2.tgz", + "integrity": "sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==", + "license": "MIT", + "dependencies": { + "is-plain-object": "3.0.1" + }, + "engines": { + "node": ">=10.15.0" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zrender": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz", + "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..eddc04b --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,43 @@ +{ + "name": "designhub-frontend", + "version": "1.0.0", + "description": "设计师任务抢单管理系统 - 前端", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "build:check": "vue-tsc && vite build", + "preview": "vite preview", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "format": "prettier --write src/" + }, + "dependencies": { + "@ant-design/icons-vue": "^7.0.1", + "@heroicons/vue": "^2.2.0", + "ant-design-vue": "^4.1.0", + "axios": "^1.6.2", + "dayjs": "^1.11.19", + "echarts": "^5.4.3", + "lodash-es": "^4.17.21", + "pinia": "^2.1.7", + "vue": "^3.4.0", + "vue-router": "^4.2.5" + }, + "devDependencies": { + "@types/lodash-es": "^4.17.12", + "@types/node": "^20.10.5", + "@typescript-eslint/eslint-plugin": "^6.15.0", + "@typescript-eslint/parser": "^6.15.0", + "@vitejs/plugin-vue": "^4.6.0", + "@vue/tsconfig": "^0.5.0", + "eslint": "^8.55.0", + "eslint-plugin-vue": "^9.19.2", + "prettier": "^3.1.1", + "sass": "^1.69.5", + "typescript": "~5.3.0", + "unplugin-auto-import": "^0.17.2", + "unplugin-vue-components": "^0.26.0", + "vite": "^4.5.3", + "vue-tsc": "^1.8.27" + } +} diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml new file mode 100644 index 0000000..0a6dd26 --- /dev/null +++ b/frontend/pnpm-lock.yaml @@ -0,0 +1,3030 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@ant-design/icons-vue': + specifier: ^7.0.1 + version: 7.0.1(vue@3.5.24(typescript@5.3.3)) + '@heroicons/vue': + specifier: ^2.2.0 + version: 2.2.0(vue@3.5.24(typescript@5.3.3)) + ant-design-vue: + specifier: ^4.1.0 + version: 4.2.6(vue@3.5.24(typescript@5.3.3)) + axios: + specifier: ^1.6.2 + version: 1.13.2 + dayjs: + specifier: ^1.11.19 + version: 1.11.19 + echarts: + specifier: ^5.4.3 + version: 5.6.0 + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 + pinia: + specifier: ^2.1.7 + version: 2.3.1(typescript@5.3.3)(vue@3.5.24(typescript@5.3.3)) + vue: + specifier: ^3.4.0 + version: 3.5.24(typescript@5.3.3) + vue-router: + specifier: ^4.2.5 + version: 4.6.3(vue@3.5.24(typescript@5.3.3)) + devDependencies: + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 + '@types/node': + specifier: ^20.10.5 + version: 20.19.24 + '@typescript-eslint/eslint-plugin': + specifier: ^6.15.0 + version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.3.3))(eslint@8.57.1)(typescript@5.3.3) + '@typescript-eslint/parser': + specifier: ^6.15.0 + version: 6.21.0(eslint@8.57.1)(typescript@5.3.3) + '@vitejs/plugin-vue': + specifier: ^5.0.0 + version: 5.2.4(vite@5.4.21(@types/node@20.19.24)(sass@1.93.3))(vue@3.5.24(typescript@5.3.3)) + '@vue/tsconfig': + specifier: ^0.5.0 + version: 0.5.1 + eslint: + specifier: ^8.55.0 + version: 8.57.1 + eslint-plugin-vue: + specifier: ^9.19.2 + version: 9.33.0(eslint@8.57.1) + prettier: + specifier: ^3.1.1 + version: 3.6.2 + sass: + specifier: ^1.69.5 + version: 1.93.3 + typescript: + specifier: ~5.3.0 + version: 5.3.3 + unplugin-auto-import: + specifier: ^0.17.2 + version: 0.17.8(rollup@4.53.1) + unplugin-vue-components: + specifier: ^0.26.0 + version: 0.26.0(@babel/parser@7.28.5)(rollup@4.53.1)(vue@3.5.24(typescript@5.3.3)) + vite: + specifier: ^5.0.0 + version: 5.4.21(@types/node@20.19.24)(sass@1.93.3) + vue-tsc: + specifier: ^1.8.27 + version: 1.8.27(typescript@5.3.3) + +packages: + + '@ant-design/colors@6.0.0': + resolution: {integrity: sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==} + + '@ant-design/icons-svg@4.4.2': + resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==} + + '@ant-design/icons-vue@7.0.1': + resolution: {integrity: sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==} + peerDependencies: + vue: '>=3.0.3' + + '@antfu/utils@0.7.10': + resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@ctrl/tinycolor@3.6.1': + resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} + engines: {node: '>=10'} + + '@emotion/hash@0.9.2': + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + + '@emotion/unitless@0.8.1': + resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@heroicons/vue@2.2.0': + resolution: {integrity: sha512-G3dbSxoeEKqbi/DFalhRxJU4mTXJn7GwZ7ae8NuEQzd1bqdd0jAbdaBZlHPcvPD2xI1iGzNVB4k20Un2AguYPw==} + peerDependencies: + vue: '>= 3' + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@parcel/watcher-android-arm64@2.5.1': + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.1': + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.1': + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.1': + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.1': + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm-musl@2.5.1': + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.5.1': + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.5.1': + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.5.1': + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.5.1': + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.1': + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.1': + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.1': + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + engines: {node: '>= 10.0.0'} + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.53.1': + resolution: {integrity: sha512-bxZtughE4VNVJlL1RdoSE545kc4JxL7op57KKoi59/gwuU5rV6jLWFXXc8jwgFoT6vtj+ZjO+Z2C5nrY0Cl6wA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.53.1': + resolution: {integrity: sha512-44a1hreb02cAAfAKmZfXVercPFaDjqXCK+iKeVOlJ9ltvnO6QqsBHgKVPTu+MJHSLLeMEUbeG2qiDYgbFPU48g==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.53.1': + resolution: {integrity: sha512-usmzIgD0rf1syoOZ2WZvy8YpXK5G1V3btm3QZddoGSa6mOgfXWkkv+642bfUUldomgrbiLQGrPryb7DXLovPWQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.53.1': + resolution: {integrity: sha512-is3r/k4vig2Gt8mKtTlzzyaSQ+hd87kDxiN3uDSDwggJLUV56Umli6OoL+/YZa/KvtdrdyNfMKHzL/P4siOOmg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.53.1': + resolution: {integrity: sha512-QJ1ksgp/bDJkZB4daldVmHaEQkG4r8PUXitCOC2WRmRaSaHx5RwPoI3DHVfXKwDkB+Sk6auFI/+JHacTekPRSw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.53.1': + resolution: {integrity: sha512-J6ma5xgAzvqsnU6a0+jgGX/gvoGokqpkx6zY4cWizRrm0ffhHDpJKQgC8dtDb3+MqfZDIqs64REbfHDMzxLMqQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.53.1': + resolution: {integrity: sha512-JzWRR41o2U3/KMNKRuZNsDUAcAVUYhsPuMlx5RUldw0E4lvSIXFUwejtYz1HJXohUmqs/M6BBJAUBzKXZVddbg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.53.1': + resolution: {integrity: sha512-L8kRIrnfMrEoHLHtHn+4uYA52fiLDEDyezgxZtGUTiII/yb04Krq+vk3P2Try+Vya9LeCE9ZHU8CXD6J9EhzHQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.53.1': + resolution: {integrity: sha512-ysAc0MFRV+WtQ8li8hi3EoFi7us6d1UzaS/+Dp7FYZfg3NdDljGMoVyiIp6Ucz7uhlYDBZ/zt6XI0YEZbUO11Q==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.53.1': + resolution: {integrity: sha512-UV6l9MJpDbDZZ/fJvqNcvO1PcivGEf1AvKuTcHoLjVZVFeAMygnamCTDikCVMRnA+qJe+B3pSbgX2+lBMqgBhA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.53.1': + resolution: {integrity: sha512-UDUtelEprkA85g95Q+nj3Xf0M4hHa4DiJ+3P3h4BuGliY4NReYYqwlc0Y8ICLjN4+uIgCEvaygYlpf0hUj90Yg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.53.1': + resolution: {integrity: sha512-vrRn+BYhEtNOte/zbc2wAUQReJXxEx2URfTol6OEfY2zFEUK92pkFBSXRylDM7aHi+YqEPJt9/ABYzmcrS4SgQ==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.53.1': + resolution: {integrity: sha512-gto/1CxHyi4A7YqZZNznQYrVlPSaodOBPKM+6xcDSCMVZN/Fzb4K+AIkNz/1yAYz9h3Ng+e2fY9H6bgawVq17w==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.53.1': + resolution: {integrity: sha512-KZ6Vx7jAw3aLNjFR8eYVcQVdFa/cvBzDNRFM3z7XhNNunWjA03eUrEwJYPk0G8V7Gs08IThFKcAPS4WY/ybIrQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.53.1': + resolution: {integrity: sha512-HvEixy2s/rWNgpwyKpXJcHmE7om1M89hxBTBi9Fs6zVuLU4gOrEMQNbNsN/tBVIMbLyysz/iwNiGtMOpLAOlvA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.53.1': + resolution: {integrity: sha512-E/n8x2MSjAQgjj9IixO4UeEUeqXLtiA7pyoXCFYLuXpBA/t2hnbIdxHfA7kK9BFsYAoNU4st1rHYdldl8dTqGA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.53.1': + resolution: {integrity: sha512-IhJ087PbLOQXCN6Ui/3FUkI9pWNZe/Z7rEIVOzMsOs1/HSAECCvSZ7PkIbkNqL/AZn6WbZvnoVZw/qwqYMo4/w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.53.1': + resolution: {integrity: sha512-0++oPNgLJHBblreu0SFM7b3mAsBJBTY0Ksrmu9N6ZVrPiTkRgda52mWR7TKhHAsUb9noCjFvAw9l6ZO1yzaVbA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.53.1': + resolution: {integrity: sha512-VJXivz61c5uVdbmitLkDlbcTk9Or43YC2QVLRkqp86QoeFSqI81bNgjhttqhKNMKnQMWnecOCm7lZz4s+WLGpQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.53.1': + resolution: {integrity: sha512-NmZPVTUOitCXUH6erJDzTQ/jotYw4CnkMDjCYRxNHVD9bNyfrGoIse684F9okwzKCV4AIHRbUkeTBc9F2OOH5Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.53.1': + resolution: {integrity: sha512-2SNj7COIdAf6yliSpLdLG8BEsp5lgzRehgfkP0Av8zKfQFKku6JcvbobvHASPJu4f3BFxej5g+HuQPvqPhHvpQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.53.1': + resolution: {integrity: sha512-rLarc1Ofcs3DHtgSzFO31pZsCh8g05R2azN1q3fF+H423Co87My0R+tazOEvYVKXSLh8C4LerMK41/K7wlklcg==} + cpu: [x64] + os: [win32] + + '@simonwep/pickr@1.8.2': + resolution: {integrity: sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.20': + resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==} + + '@types/node@20.19.24': + resolution: {integrity: sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==} + + '@types/semver@7.7.1': + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + + '@typescript-eslint/eslint-plugin@6.21.0': + resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@6.21.0': + resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@6.21.0': + resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/type-utils@6.21.0': + resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@6.21.0': + resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/typescript-estree@6.21.0': + resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@6.21.0': + resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + + '@typescript-eslint/visitor-keys@6.21.0': + resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vitejs/plugin-vue@5.2.4': + resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + + '@volar/language-core@1.11.1': + resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==} + + '@volar/source-map@1.11.1': + resolution: {integrity: sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==} + + '@volar/typescript@1.11.1': + resolution: {integrity: sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==} + + '@vue/compiler-core@3.5.24': + resolution: {integrity: sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==} + + '@vue/compiler-dom@3.5.24': + resolution: {integrity: sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==} + + '@vue/compiler-sfc@3.5.24': + resolution: {integrity: sha512-8EG5YPRgmTB+YxYBM3VXy8zHD9SWHUJLIGPhDovo3Z8VOgvP+O7UP5vl0J4BBPWYD9vxtBabzW1EuEZ+Cqs14g==} + + '@vue/compiler-ssr@3.5.24': + resolution: {integrity: sha512-trOvMWNBMQ/odMRHW7Ae1CdfYx+7MuiQu62Jtu36gMLXcaoqKvAyh+P73sYG9ll+6jLB6QPovqoKGGZROzkFFg==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/language-core@1.8.27': + resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.5.24': + resolution: {integrity: sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==} + + '@vue/runtime-core@3.5.24': + resolution: {integrity: sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==} + + '@vue/runtime-dom@3.5.24': + resolution: {integrity: sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==} + + '@vue/server-renderer@3.5.24': + resolution: {integrity: sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==} + peerDependencies: + vue: 3.5.24 + + '@vue/shared@3.5.24': + resolution: {integrity: sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==} + + '@vue/tsconfig@0.5.1': + resolution: {integrity: sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ant-design-vue@4.2.6: + resolution: {integrity: sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==} + engines: {node: '>=12.22.0'} + peerDependencies: + vue: '>=3.2.0' + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-tree-filter@2.1.0: + resolution: {integrity: sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + async-validator@4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.13.2: + resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + compute-scroll-into-view@1.0.20: + resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} + + computeds@0.0.1: + resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + core-js@3.46.0: + resolution: {integrity: sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dom-align@1.12.4: + resolution: {integrity: sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==} + + dom-scroll-into-view@2.0.1: + resolution: {integrity: sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + echarts@5.6.0: + resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + eslint-plugin-vue@9.33.0: + resolution: {integrity: sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + exsolve@1.0.7: + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + immutable@5.1.4: + resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-object@3.0.1: + resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + local-pkg@0.4.3: + resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} + engines: {node: '>=14'} + + local-pkg@0.5.1: + resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} + engines: {node: '>=14'} + + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + engines: {node: '>=14'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.3.1: + resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanopop@2.4.2: + resolution: {integrity: sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pinia@2.3.1: + resolution: {integrity: sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==} + peerDependencies: + typescript: '>=4.4.4' + vue: ^2.7.0 || ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup@4.53.1: + resolution: {integrity: sha512-n2I0V0lN3E9cxxMqBCT3opWOiQBzRN7UG60z/WDKqdX2zHUS/39lezBcsckZFsV6fUTSnfqI7kHf60jDAPGKug==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + sass@1.93.3: + resolution: {integrity: sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg==} + engines: {node: '>=14.0.0'} + hasBin: true + + scroll-into-view-if-needed@2.2.31: + resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} + + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + shallow-equal@1.2.1: + resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-literal@2.1.1: + resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==} + + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + throttle-debounce@5.0.2: + resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} + engines: {node: '>=12.22'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + typescript@5.3.3: + resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unimport@3.14.6: + resolution: {integrity: sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==} + + unplugin-auto-import@0.17.8: + resolution: {integrity: sha512-CHryj6HzJ+n4ASjzwHruD8arhbdl+UXvhuAIlHDs15Y/IMecG3wrf7FVg4pVH/DIysbq/n0phIjNHAjl7TG7Iw==} + engines: {node: '>=14'} + peerDependencies: + '@nuxt/kit': ^3.2.2 + '@vueuse/core': '*' + peerDependenciesMeta: + '@nuxt/kit': + optional: true + '@vueuse/core': + optional: true + + unplugin-vue-components@0.26.0: + resolution: {integrity: sha512-s7IdPDlnOvPamjunVxw8kNgKNK8A5KM1YpK5j/p97jEKTjlPNrA0nZBiSfAKKlK1gWZuyWXlKL5dk3EDw874LQ==} + engines: {node: '>=14'} + peerDependencies: + '@babel/parser': ^7.15.8 + '@nuxt/kit': ^3.2.2 + vue: 2 || 3 + peerDependenciesMeta: + '@babel/parser': + optional: true + '@nuxt/kit': + optional: true + + unplugin@1.16.1: + resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==} + engines: {node: '>=14.0.0'} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-eslint-parser@9.4.3: + resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + vue-router@4.6.3: + resolution: {integrity: sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==} + peerDependencies: + vue: ^3.5.0 + + vue-template-compiler@2.7.16: + resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==} + + vue-tsc@1.8.27: + resolution: {integrity: sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==} + hasBin: true + peerDependencies: + typescript: '*' + + vue-types@3.0.2: + resolution: {integrity: sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==} + engines: {node: '>=10.15.0'} + peerDependencies: + vue: ^3.0.0 + + vue@3.5.24: + resolution: {integrity: sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + warning@4.0.3: + resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zrender@5.6.1: + resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==} + +snapshots: + + '@ant-design/colors@6.0.0': + dependencies: + '@ctrl/tinycolor': 3.6.1 + + '@ant-design/icons-svg@4.4.2': {} + + '@ant-design/icons-vue@7.0.1(vue@3.5.24(typescript@5.3.3))': + dependencies: + '@ant-design/colors': 6.0.0 + '@ant-design/icons-svg': 4.4.2 + vue: 3.5.24(typescript@5.3.3) + + '@antfu/utils@0.7.10': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/runtime@7.28.4': {} + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@ctrl/tinycolor@3.6.1': {} + + '@emotion/hash@0.9.2': {} + + '@emotion/unitless@0.8.1': {} + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.9.0(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@heroicons/vue@2.2.0(vue@3.5.24(typescript@5.3.3))': + dependencies: + vue: 3.5.24(typescript@5.3.3) + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@parcel/watcher-android-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-x64@2.5.1': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.1': + optional: true + + '@parcel/watcher-win32-arm64@2.5.1': + optional: true + + '@parcel/watcher-win32-ia32@2.5.1': + optional: true + + '@parcel/watcher-win32-x64@2.5.1': + optional: true + + '@parcel/watcher@2.5.1': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 + optional: true + + '@rollup/pluginutils@5.3.0(rollup@4.53.1)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.53.1 + + '@rollup/rollup-android-arm-eabi@4.53.1': + optional: true + + '@rollup/rollup-android-arm64@4.53.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.53.1': + optional: true + + '@rollup/rollup-darwin-x64@4.53.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.53.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.53.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.53.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.53.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.53.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.53.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.53.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.53.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.53.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.53.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.53.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.53.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.53.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.53.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.53.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.53.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.53.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.53.1': + optional: true + + '@simonwep/pickr@1.8.2': + dependencies: + core-js: 3.46.0 + nanopop: 2.4.2 + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.20 + + '@types/lodash@4.17.20': {} + + '@types/node@20.19.24': + dependencies: + undici-types: 6.21.0 + + '@types/semver@7.7.1': {} + + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.3.3))(eslint@8.57.1)(typescript@5.3.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.3.3) + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.3.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + semver: 7.7.3 + ts-api-utils: 1.4.3(typescript@5.3.3) + optionalDependencies: + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.3.3)': + dependencies: + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + eslint: 8.57.1 + optionalDependencies: + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + + '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.3.3)': + dependencies: + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.3.3) + debug: 4.4.3 + eslint: 8.57.1 + ts-api-utils: 1.4.3(typescript@5.3.3) + optionalDependencies: + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@6.21.0': {} + + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.3.3)': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.7.3 + ts-api-utils: 1.4.3(typescript@5.3.3) + optionalDependencies: + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.3.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1) + '@types/json-schema': 7.0.15 + '@types/semver': 7.7.1 + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) + eslint: 8.57.1 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.3.0': {} + + '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@20.19.24)(sass@1.93.3))(vue@3.5.24(typescript@5.3.3))': + dependencies: + vite: 5.4.21(@types/node@20.19.24)(sass@1.93.3) + vue: 3.5.24(typescript@5.3.3) + + '@volar/language-core@1.11.1': + dependencies: + '@volar/source-map': 1.11.1 + + '@volar/source-map@1.11.1': + dependencies: + muggle-string: 0.3.1 + + '@volar/typescript@1.11.1': + dependencies: + '@volar/language-core': 1.11.1 + path-browserify: 1.0.1 + + '@vue/compiler-core@3.5.24': + dependencies: + '@babel/parser': 7.28.5 + '@vue/shared': 3.5.24 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.24': + dependencies: + '@vue/compiler-core': 3.5.24 + '@vue/shared': 3.5.24 + + '@vue/compiler-sfc@3.5.24': + dependencies: + '@babel/parser': 7.28.5 + '@vue/compiler-core': 3.5.24 + '@vue/compiler-dom': 3.5.24 + '@vue/compiler-ssr': 3.5.24 + '@vue/shared': 3.5.24 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.24': + dependencies: + '@vue/compiler-dom': 3.5.24 + '@vue/shared': 3.5.24 + + '@vue/devtools-api@6.6.4': {} + + '@vue/language-core@1.8.27(typescript@5.3.3)': + dependencies: + '@volar/language-core': 1.11.1 + '@volar/source-map': 1.11.1 + '@vue/compiler-dom': 3.5.24 + '@vue/shared': 3.5.24 + computeds: 0.0.1 + minimatch: 9.0.5 + muggle-string: 0.3.1 + path-browserify: 1.0.1 + vue-template-compiler: 2.7.16 + optionalDependencies: + typescript: 5.3.3 + + '@vue/reactivity@3.5.24': + dependencies: + '@vue/shared': 3.5.24 + + '@vue/runtime-core@3.5.24': + dependencies: + '@vue/reactivity': 3.5.24 + '@vue/shared': 3.5.24 + + '@vue/runtime-dom@3.5.24': + dependencies: + '@vue/reactivity': 3.5.24 + '@vue/runtime-core': 3.5.24 + '@vue/shared': 3.5.24 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.24(vue@3.5.24(typescript@5.3.3))': + dependencies: + '@vue/compiler-ssr': 3.5.24 + '@vue/shared': 3.5.24 + vue: 3.5.24(typescript@5.3.3) + + '@vue/shared@3.5.24': {} + + '@vue/tsconfig@0.5.1': {} + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ant-design-vue@4.2.6(vue@3.5.24(typescript@5.3.3)): + dependencies: + '@ant-design/colors': 6.0.0 + '@ant-design/icons-vue': 7.0.1(vue@3.5.24(typescript@5.3.3)) + '@babel/runtime': 7.28.4 + '@ctrl/tinycolor': 3.6.1 + '@emotion/hash': 0.9.2 + '@emotion/unitless': 0.8.1 + '@simonwep/pickr': 1.8.2 + array-tree-filter: 2.1.0 + async-validator: 4.2.5 + csstype: 3.1.3 + dayjs: 1.11.19 + dom-align: 1.12.4 + dom-scroll-into-view: 2.0.1 + lodash: 4.17.21 + lodash-es: 4.17.21 + resize-observer-polyfill: 1.5.1 + scroll-into-view-if-needed: 2.2.31 + shallow-equal: 1.2.1 + stylis: 4.3.6 + throttle-debounce: 5.0.2 + vue: 3.5.24(typescript@5.3.3) + vue-types: 3.0.2(vue@3.5.24(typescript@5.3.3)) + warning: 4.0.3 + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@2.0.1: {} + + array-tree-filter@2.1.0: {} + + array-union@2.1.0: {} + + async-validator@4.2.5: {} + + asynckit@0.4.0: {} + + axios@1.13.2: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + binary-extensions@2.3.0: {} + + boolbase@1.0.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + compute-scroll-into-view@1.0.20: {} + + computeds@0.0.1: {} + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + confbox@0.2.2: {} + + core-js@3.46.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.1.3: {} + + dayjs@1.11.19: {} + + de-indent@1.0.2: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + delayed-stream@1.0.0: {} + + detect-libc@1.0.3: + optional: true + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dom-align@1.12.4: {} + + dom-scroll-into-view@2.0.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + echarts@5.6.0: + dependencies: + tslib: 2.3.0 + zrender: 5.6.1 + + entities@4.5.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + eslint-plugin-vue@9.33.0(eslint@8.57.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1) + eslint: 8.57.1 + globals: 13.24.0 + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.1.2 + semver: 7.7.3 + vue-eslint-parser: 9.4.3(eslint@8.57.1) + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - supports-color + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 3.4.3 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + exsolve@1.0.7: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.3.3: {} + + follow-redirects@1.15.11: {} + + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + ignore@5.3.2: {} + + immutable@5.1.4: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-plain-object@3.0.1: {} + + isexe@2.0.0: {} + + js-tokens@4.0.0: {} + + js-tokens@9.0.1: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + local-pkg@0.4.3: {} + + local-pkg@0.5.1: + dependencies: + mlly: 1.8.0 + pkg-types: 1.3.1 + + local-pkg@1.1.2: + dependencies: + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash-es@4.17.21: {} + + lodash.merge@4.6.2: {} + + lodash@4.17.21: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.3: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + ms@2.1.3: {} + + muggle-string@0.3.1: {} + + nanoid@3.3.11: {} + + nanopop@2.4.2: {} + + natural-compare@1.4.0: {} + + node-addon-api@7.1.1: + optional: true + + normalize-path@3.0.0: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-browserify@1.0.1: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pinia@2.3.1(typescript@5.3.3)(vue@3.5.24(typescript@5.3.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.24(typescript@5.3.3) + vue-demi: 0.14.10(vue@3.5.24(typescript@5.3.3)) + optionalDependencies: + typescript: 5.3.3 + transitivePeerDependencies: + - '@vue/composition-api' + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier@3.6.2: {} + + proxy-from-env@1.1.0: {} + + punycode@2.3.1: {} + + quansync@0.2.11: {} + + queue-microtask@1.2.3: {} + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + readdirp@4.1.2: {} + + resize-observer-polyfill@1.5.1: {} + + resolve-from@4.0.0: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup@4.53.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.53.1 + '@rollup/rollup-android-arm64': 4.53.1 + '@rollup/rollup-darwin-arm64': 4.53.1 + '@rollup/rollup-darwin-x64': 4.53.1 + '@rollup/rollup-freebsd-arm64': 4.53.1 + '@rollup/rollup-freebsd-x64': 4.53.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.1 + '@rollup/rollup-linux-arm-musleabihf': 4.53.1 + '@rollup/rollup-linux-arm64-gnu': 4.53.1 + '@rollup/rollup-linux-arm64-musl': 4.53.1 + '@rollup/rollup-linux-loong64-gnu': 4.53.1 + '@rollup/rollup-linux-ppc64-gnu': 4.53.1 + '@rollup/rollup-linux-riscv64-gnu': 4.53.1 + '@rollup/rollup-linux-riscv64-musl': 4.53.1 + '@rollup/rollup-linux-s390x-gnu': 4.53.1 + '@rollup/rollup-linux-x64-gnu': 4.53.1 + '@rollup/rollup-linux-x64-musl': 4.53.1 + '@rollup/rollup-openharmony-arm64': 4.53.1 + '@rollup/rollup-win32-arm64-msvc': 4.53.1 + '@rollup/rollup-win32-ia32-msvc': 4.53.1 + '@rollup/rollup-win32-x64-gnu': 4.53.1 + '@rollup/rollup-win32-x64-msvc': 4.53.1 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + sass@1.93.3: + dependencies: + chokidar: 4.0.3 + immutable: 5.1.4 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.1 + + scroll-into-view-if-needed@2.2.31: + dependencies: + compute-scroll-into-view: 1.0.20 + + scule@1.3.0: {} + + semver@7.7.3: {} + + shallow-equal@1.2.1: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + slash@3.0.0: {} + + source-map-js@1.2.1: {} + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-json-comments@3.1.1: {} + + strip-literal@2.1.1: + dependencies: + js-tokens: 9.0.1 + + stylis@4.3.6: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + text-table@0.2.0: {} + + throttle-debounce@5.0.2: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@1.4.3(typescript@5.3.3): + dependencies: + typescript: 5.3.3 + + tslib@2.3.0: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.20.2: {} + + typescript@5.3.3: {} + + ufo@1.6.1: {} + + undici-types@6.21.0: {} + + unimport@3.14.6(rollup@4.53.1): + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.53.1) + acorn: 8.15.0 + escape-string-regexp: 5.0.0 + estree-walker: 3.0.3 + fast-glob: 3.3.3 + local-pkg: 1.1.2 + magic-string: 0.30.21 + mlly: 1.8.0 + pathe: 2.0.3 + picomatch: 4.0.3 + pkg-types: 1.3.1 + scule: 1.3.0 + strip-literal: 2.1.1 + unplugin: 1.16.1 + transitivePeerDependencies: + - rollup + + unplugin-auto-import@0.17.8(rollup@4.53.1): + dependencies: + '@antfu/utils': 0.7.10 + '@rollup/pluginutils': 5.3.0(rollup@4.53.1) + fast-glob: 3.3.3 + local-pkg: 0.5.1 + magic-string: 0.30.21 + minimatch: 9.0.5 + unimport: 3.14.6(rollup@4.53.1) + unplugin: 1.16.1 + transitivePeerDependencies: + - rollup + + unplugin-vue-components@0.26.0(@babel/parser@7.28.5)(rollup@4.53.1)(vue@3.5.24(typescript@5.3.3)): + dependencies: + '@antfu/utils': 0.7.10 + '@rollup/pluginutils': 5.3.0(rollup@4.53.1) + chokidar: 3.6.0 + debug: 4.4.3 + fast-glob: 3.3.3 + local-pkg: 0.4.3 + magic-string: 0.30.21 + minimatch: 9.0.5 + resolve: 1.22.11 + unplugin: 1.16.1 + vue: 3.5.24(typescript@5.3.3) + optionalDependencies: + '@babel/parser': 7.28.5 + transitivePeerDependencies: + - rollup + - supports-color + + unplugin@1.16.1: + dependencies: + acorn: 8.15.0 + webpack-virtual-modules: 0.6.2 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + vite@5.4.21(@types/node@20.19.24)(sass@1.93.3): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.53.1 + optionalDependencies: + '@types/node': 20.19.24 + fsevents: 2.3.3 + sass: 1.93.3 + + vue-demi@0.14.10(vue@3.5.24(typescript@5.3.3)): + dependencies: + vue: 3.5.24(typescript@5.3.3) + + vue-eslint-parser@9.4.3(eslint@8.57.1): + dependencies: + debug: 4.4.3 + eslint: 8.57.1 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + lodash: 4.17.21 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + + vue-router@4.6.3(vue@3.5.24(typescript@5.3.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.24(typescript@5.3.3) + + vue-template-compiler@2.7.16: + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + vue-tsc@1.8.27(typescript@5.3.3): + dependencies: + '@volar/typescript': 1.11.1 + '@vue/language-core': 1.8.27(typescript@5.3.3) + semver: 7.7.3 + typescript: 5.3.3 + + vue-types@3.0.2(vue@3.5.24(typescript@5.3.3)): + dependencies: + is-plain-object: 3.0.1 + vue: 3.5.24(typescript@5.3.3) + + vue@3.5.24(typescript@5.3.3): + dependencies: + '@vue/compiler-dom': 3.5.24 + '@vue/compiler-sfc': 3.5.24 + '@vue/runtime-dom': 3.5.24 + '@vue/server-renderer': 3.5.24(vue@3.5.24(typescript@5.3.3)) + '@vue/shared': 3.5.24 + optionalDependencies: + typescript: 5.3.3 + + warning@4.0.3: + dependencies: + loose-envify: 1.4.0 + + webpack-virtual-modules@0.6.2: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrappy@1.0.2: {} + + xml-name-validator@4.0.0: {} + + yocto-queue@0.1.0: {} + + zrender@5.6.1: + dependencies: + tslib: 2.3.0 diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..3f5806f --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,46 @@ + + + + + + diff --git a/frontend/src/api/admin.ts b/frontend/src/api/admin.ts new file mode 100644 index 0000000..c18d037 --- /dev/null +++ b/frontend/src/api/admin.ts @@ -0,0 +1,73 @@ +import request from '@/utils/request' +import type { Task } from './task' + +/** + * 管理员分配任务 + */ +export const assignTask = (taskId: number, designerId: number) => { + return request.post(`/admin/tasks/${taskId}/assign`, null, { + params: { designerId } + }) +} + +/** + * 管理员转移任务 + */ +export const transferTask = (taskId: number, newDesignerId: number, reason: string) => { + return request.post(`/admin/tasks/${taskId}/transfer`, { + newDesignerId, + reason + }) +} + +/** + * 批量分配任务 + */ +export const batchAssignTasks = (taskIds: number[], designerId: number) => { + return request.post('/admin/tasks/batch-assign', { + taskIds, + designerId + }) +} + +/** + * 调整任务优先级 + */ +export const adjustTaskPriority = (taskId: number, priority: string) => { + return request.put(`/admin/tasks/${taskId}/priority`, null, { + params: { priority } + }) +} + +/** + * 获取任务池列表(待分配的任务) + */ +export const getTaskPool = () => { + return request.get('/admin/tasks/pool') +} + +/** + * 获取所有进行中的任务 + */ +export const getInProgressTasks = () => { + return request.get('/admin/tasks/in-progress') +} + +/** + * 强制完成任务 + */ +export const forceCompleteTask = (taskId: number, reason: string) => { + return request.post(`/admin/tasks/${taskId}/force-complete`, { + reason + }) +} + +/** + * 管理员取消任务 + */ +export const cancelTask = (taskId: number, reason: string) => { + return request.post(`/admin/tasks/${taskId}/cancel`, { + reason + }) +} + diff --git a/frontend/src/api/analytics.ts b/frontend/src/api/analytics.ts new file mode 100644 index 0000000..ca4e5d3 --- /dev/null +++ b/frontend/src/api/analytics.ts @@ -0,0 +1,131 @@ +import request from '@/utils/request'; + +// 需求统计数据类型定义 +export interface UserDemandStat { + userId: number; + userName: string; + deptName: string; + totalDemands: number; + completedDemands: number; + pendingDemands: number; + totalScore: number; + avgScore: number; + completionRate: number; + satisfactionRate: number; +} + +export interface DeptDemandStat { + deptId: number; + deptName: string; + totalDemands: number; + completedDemands: number; + pendingDemands: number; + totalScore: number; + remainingScore: number; + dailyLimit: number; + weeklyLimit: number; + monthlyLimit: number; + dailyUsed: number; + weeklyUsed: number; + monthlyUsed: number; + utilizationRate: number; + alertLevel: string; +} + +export interface TrendData { + date: string; + demandCount: number; + completedCount: number; + scoreUsed: number; +} + +export interface TypeDistribution { + typeName: string; + count: number; + percentage: number; + totalScore: number; +} + +export interface OverviewData { + totalDemands: number; + todayDemands: number; + weekDemands: number; + monthDemands: number; + completedDemands: number; + pendingDemands: number; + canceledDemands: number; + totalScoreUsed: number; + todayScoreUsed: number; + weekScoreUsed: number; + monthScoreUsed: number; + avgCompletionTime: number; + avgSatisfaction: number; +} + +export interface DemandStatistics { + userStats: UserDemandStat[]; + deptStats: DeptDemandStat[]; + trendData: TrendData[]; + typeDistribution: TypeDistribution[]; + overview: OverviewData; +} + +/** + * 获取需求统计数据 + */ +export const getDemandStatistics = async ( + dateRange: string = 'month', + deptId?: number, + userId?: number +): Promise => { + const params: any = { dateRange }; + if (deptId) params.deptId = deptId; + if (userId) params.userId = userId; + + // 使用统一的 request 工具,它会自动处理 Result 包装 + // 注意:baseURL 已经包含 /api 前缀,这里不要再写 /api 开头,避免变成 /api/api/... + const response = await request.get('/analytics/demand-statistics', { params }); + + // 添加调试日志 + console.log('Analytics API Response:', response); + + // request 拦截器已经处理了错误情况,这里 response 应该是 Result 对象 + // 如果 response 是 Result 对象,取 data 字段;否则直接返回 + if (response && typeof response === 'object') { + // 检查是否是 Result 格式 { code, data, message } + if ('data' in response && response.data !== undefined && response.data !== null) { + console.log('Returning response.data:', response.data); + return response.data as DemandStatistics; + } + // 如果 response 本身就是数据对象(包含 overview 或 trendData),直接返回 + if ('overview' in response || 'trendData' in response) { + console.log('Returning response directly:', response); + return response as unknown as DemandStatistics; + } + } + + // 如果都不匹配,返回默认数据结构 + console.warn('Invalid response format, returning default structure'); + return { + overview: { + totalDemands: 0, + todayDemands: 0, + weekDemands: 0, + monthDemands: 0, + completedDemands: 0, + pendingDemands: 0, + canceledDemands: 0, + totalScoreUsed: 0, + todayScoreUsed: 0, + weekScoreUsed: 0, + monthScoreUsed: 0, + avgCompletionTime: 0, + avgSatisfaction: 0 + }, + trendData: [], + typeDistribution: [], + userStats: [], + deptStats: [] + }; +}; + diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts new file mode 100644 index 0000000..9f3c7a0 --- /dev/null +++ b/frontend/src/api/auth.ts @@ -0,0 +1,96 @@ +import request from '@/utils/request' + +const API_BASE = '' + +export interface LoginParams { + username: string + password: string + loginType?: string + authCode?: string +} + +// 注册参数 +export interface RegisterParams { + username: string + password: string + realName: string + roleId: number + deptId: number + email?: string + phone?: string +} + +export interface LoginResponse { + token: string // 后端返回的是 token + accessToken?: string // 兼容旧版 + refreshToken?: string + tokenType?: string + expiresIn?: number + userId?: number + username?: string + realName?: string + avatar?: string + roleIds?: string + userInfo?: UserInfo // 可选,后端可能不返回 +} + +export interface UserInfo { + id: number + username: string + realName: string + email: string + phone: string + avatar: string + deptId: number + deptName: string + roles: string[] + permissions: string[] +} + +/** + * 用户登录 + */ +export const login = (data: LoginParams) => { + return request.post( + `/auth/login`, + data + ) +} + +/** + * 用户注册 + */ +export const register = (data: RegisterParams) => { + return request.post( + `/auth/register`, + data + ) +} + +/** + * 获取用户信息 + */ +export const getUserInfo = () => { + return request.get( + `/auth/userinfo` + ) +} + +/** + * 退出登录 + */ +export const logout = () => { + return request.post( + `/auth/logout` + ) +} + +/** + * 刷新Token + */ +export const refreshToken = () => { + return request.post( + `/auth/refresh` + ) +} + diff --git a/frontend/src/api/config.ts b/frontend/src/api/config.ts new file mode 100644 index 0000000..eea7764 --- /dev/null +++ b/frontend/src/api/config.ts @@ -0,0 +1,313 @@ +import request from '@/utils/request' + +export interface DemandType { + id: number + name: string + unitScore: number + estimatedHours: number + description?: string + sortOrder?: number + isEnabled?: number + allowedDeptIds?: string // 可接单部门ID列表(逗号分隔) + departmentIds?: number[] // 前端使用的部门ID数组 +} + +export interface Brand { + id: number + name: string + brandCode?: string + logoUrl?: string + description?: string + sortOrder?: number + isEnabled?: number +} + +export interface Platform { + id: number + name: string + platformCode?: string + iconUrl?: string + description?: string + sortOrder?: number + isEnabled?: number +} + +export interface Category { + id: number + parentId?: number + name: string + categoryCode?: string + level?: number + isLeaf?: number + path?: string + description?: string + sortOrder?: number + isEnabled?: number +} + +export interface TeamScorePool { + id: number + deptId: number + deptName: string + balance: number + totalRecharge?: number + totalConsume?: number + totalEarn?: number + limitEnabled?: number + limitPeriod?: string + periodLimit?: number + periodUsed?: number + periodStartTime?: string + alertThreshold?: number +} + +export interface TaskSubType { + id: number + name: string + code: string + description?: string + sortOrder?: number + isEnabled?: number + createTime?: string + updateTime?: string +} + +/** + * 获取所有需求类型(包括禁用的,用于管理页面) + */ +export const getAllDemandTypes = () => { + return request.get('/config/demand-types') +} + +/** + * 获取所有启用的需求类型(用于下拉选择) + */ +export const getEnabledDemandTypes = () => { + return request.get('/config/demand-types/enabled') +} + +/** + * 获取所有品牌(包括禁用的,用于管理页面) + */ +export const getAllBrands = () => { + return request.get('/config/brands') +} + +/** + * 获取所有启用的品牌(用于下拉选择) + */ +export const getEnabledBrands = () => { + return request.get('/config/brands/enabled') +} + +/** + * 获取所有平台(包括禁用的,用于管理页面) + */ +export const getAllPlatforms = () => { + return request.get('/config/platforms') +} + +/** + * 获取所有启用的平台(用于下拉选择) + */ +export const getEnabledPlatforms = () => { + return request.get('/config/platforms/enabled') +} + +/** + * 获取所有叶子类目(包括禁用的,用于管理页面) + */ +export const getAllLeafCategories = () => { + return request.get('/config/categories/leaf') +} + +/** + * 获取所有启用的叶子类目(用于下拉选择) + */ +export const getEnabledLeafCategories = () => { + return request.get('/config/categories/leaf/enabled') +} + +/** + * 根据部门ID获取积分池 + */ +export const getScorePoolByDeptId = (deptId: number) => { + return request.get(`/config/score-pool/dept/${deptId}`) +} + +/** + * 检查积分余额是否足够 + */ +export const checkBalance = (deptId: number, score: number) => { + return request.get('/config/score-pool/check-balance', { + params: { deptId, score } + }) +} + +/** + * 检查是否超过周期限制 + */ +export const checkPeriodLimit = (deptId: number, score: number) => { + return request.get('/config/score-pool/check-limit', { + params: { deptId, score } + }) +} + +// ========== 需求类型 CRUD ========== + +/** + * 获取所有需求类型(别名) + */ +export const getDemandTypes = getAllDemandTypes + +/** + * 创建需求类型 + */ +export const createDemandType = (data: Partial) => { + return request.post('/config/demand-types', data) +} + +/** + * 更新需求类型 + */ +export const updateDemandType = (id: number, data: Partial) => { + return request.put(`/config/demand-types/${id}`, data) +} + +/** + * 删除需求类型 + */ +export const deleteDemandType = (id: number) => { + return request.delete(`/config/demand-types/${id}`) +} + +// ========== 品牌 CRUD ========== + +/** + * 获取所有品牌(别名) + */ +export const getBrands = getAllBrands + +/** + * 创建品牌 + */ +export const createBrand = (data: Partial) => { + return request.post('/config/brands', data) +} + +/** + * 更新品牌 + */ +export const updateBrand = (id: number, data: Partial) => { + return request.put(`/config/brands/${id}`, data) +} + +/** + * 删除品牌 + */ +export const deleteBrand = (id: number) => { + return request.delete(`/config/brands/${id}`) +} + +// ========== 平台 CRUD ========== + +/** + * 获取所有平台(别名) + */ +export const getPlatforms = getAllPlatforms + +/** + * 创建平台 + */ +export const createPlatform = (data: Partial) => { + return request.post('/config/platforms', data) +} + +/** + * 更新平台 + */ +export const updatePlatform = (id: number, data: Partial) => { + return request.put(`/config/platforms/${id}`, data) +} + +/** + * 删除平台 + */ +export const deletePlatform = (id: number) => { + return request.delete(`/config/platforms/${id}`) +} + +// ========== 类目 CRUD ========== + +/** + * 获取所有类目(包括非叶子节点) + */ +export const getCategories = () => { + return request.get('/config/categories') +} + +/** + * 创建类目 + */ +export const createCategory = (data: Partial) => { + return request.post('/config/categories', data) +} + +/** + * 更新类目 + */ +export const updateCategory = (id: number, data: Partial) => { + return request.put(`/config/categories/${id}`, data) +} + +/** + * 删除类目 + */ +export const deleteCategory = (id: number) => { + return request.delete(`/config/categories/${id}`) +} + +// ========== 任务子类型 CRUD ========== + +/** + * 获取所有任务子类型 + */ +export const getAllTaskSubTypes = () => { + return request.get('/task-sub-types') +} + +/** + * 获取启用的任务子类型 + */ +export const getEnabledTaskSubTypes = () => { + return request.get('/task-sub-types/enabled') +} + +/** + * 创建任务子类型 + */ +export const createTaskSubType = (data: Partial) => { + return request.post('/task-sub-types', data) +} + +/** + * 更新任务子类型 + */ +export const updateTaskSubType = (id: number, data: Partial) => { + return request.put(`/task-sub-types/${id}`, data) +} + +/** + * 删除任务子类型 + */ +export const deleteTaskSubType = (id: number) => { + return request.delete(`/task-sub-types/${id}`) +} + +/** + * 切换任务子类型启用状态 + */ +export const toggleTaskSubTypeEnabled = (id: number) => { + return request.post(`/task-sub-types/${id}/toggle-enabled`) +} + diff --git a/frontend/src/api/contest.ts b/frontend/src/api/contest.ts new file mode 100644 index 0000000..716b193 --- /dev/null +++ b/frontend/src/api/contest.ts @@ -0,0 +1,153 @@ +import request from '@/utils/request' +import type { Result, PageResult } from '@/api/types' + +// 竞赛活动接口类型 +export interface Contest { + id: number + name: string + description: string + contestType: 'monthly' | 'weekly' | 'special' + startTime: string + endTime: string + participantScope: 'all' | 'department' | 'user' + departmentIds?: number[] + userIds?: number[] + rules: Record + rewards: Array<{ rank: number; score: number; badge?: string }> + participationReward?: { minTasks: number; score: number } + status: 'draft' | 'scheduled' | 'active' | 'ended' | 'cancelled' + participantCount: number + creatorId: number + createTime: string + updateTime: string +} + +export interface ContestParticipant { + id: number + contestId: number + userId: number + userName: string + deptId: number + deptName: string + taskCompleted: number + fiveStarCount: number + earlyCompletionCount: number + praiseCount: number + noDelayDays: number + totalScore: number + currentRank: number + rewardClaimed: number + rewardScore: number + rewardClaimTime?: string + createTime: string + updateTime: string +} + +export interface ContestCreateRequest { + name: string + description?: string + contestType: string + startTime: string + endTime: string + participantScope: string + departmentIds?: number[] + userIds?: number[] + rules: string + rewards: string + participationReward?: string +} + +// ==================== 竞赛活动 API ==================== + +/** + * 创建竞赛 + */ +export function createContest(data: ContestCreateRequest): Promise { + return request.post>('/contests', data) + .then(res => res.data) +} + +/** + * 更新竞赛 + */ +export function updateContest(id: number, data: Partial): Promise { + return request.put>(`/contests/${id}`, data) + .then(res => res.data) +} + +/** + * 发布竞赛 + */ +export function publishContest(id: number): Promise { + return request.post>(`/contests/${id}/publish`) + .then(res => res.data) +} + +/** + * 结束竞赛 + */ +export function endContest(id: number): Promise { + return request.post>(`/contests/${id}/end`) + .then(res => res.data) +} + +/** + * 取消竞赛 + */ +export function cancelContest(id: number): Promise { + return request.post>(`/contests/${id}/cancel`) + .then(res => res.data) +} + +/** + * 删除竞赛 + */ +export function deleteContest(id: number): Promise { + return request.delete>(`/contests/${id}`) + .then(res => res.data) +} + +/** + * 获取竞赛列表 + */ +export function getContestList(params: { + page?: number + pageSize?: number + status?: string +}): Promise> { + return request.get>>('/contests', { params }) + .then(res => res.data) +} + +/** + * 获取竞赛详情 + */ +export function getContestById(id: number): Promise { + return request.get>(`/contests/${id}`) + .then(res => res.data) +} + +/** + * 获取竞赛排行榜 + */ +export function getContestLeaderboard(id: number, limit?: number): Promise { + return request.get>(`/contests/${id}/leaderboard`, { + params: { limit } + }).then(res => res.data) +} + +/** + * 获取我的竞赛数据 + */ +export function getMyContestData(contestId: number): Promise { + return request.get>(`/contests/${contestId}/my`) + .then(res => res.data) +} + +/** + * 领取竞赛奖励 + */ +export function claimContestReward(contestId: number): Promise { + return request.post>(`/contests/${contestId}/claim-reward`) + .then(res => res.data) +} diff --git a/frontend/src/api/delayPenalty.ts b/frontend/src/api/delayPenalty.ts new file mode 100644 index 0000000..1c54185 --- /dev/null +++ b/frontend/src/api/delayPenalty.ts @@ -0,0 +1,61 @@ +import request from './request' + +export interface DelayPenaltyRecord { + id: number + taskId: number + designerId: number + delayDays: number + delayHours: number + delayLevel: 'light' | 'general' | 'severe' | 'major' + penaltyScore: number + originalDeadline: string + actualTime: string + status: 'confirmed' | 'appealed' | 'approved' | 'rejected' + appealReason?: string + appealTime?: string + appealResult?: string + approverId?: number + approveTime?: string + createTime: string + updateTime: string +} + +/** + * 根据任务ID查询延误记录 + */ +export const getDelayRecordsByTaskId = (taskId: number) => { + return request.get(`/delay-penalty/task/${taskId}`) +} + +/** + * 根据设计师ID查询延误记录 + */ +export const getDelayRecordsByDesignerId = (designerId: number) => { + return request.get(`/delay-penalty/designer/${designerId}`) +} + +/** + * 统计设计师的延误扣分总数 + */ +export const getTotalPenaltyScore = (designerId: number) => { + return request.get(`/delay-penalty/designer/${designerId}/total`) +} + +/** + * 申诉延误扣分 + */ +export const appealDelayPenalty = (recordId: number, appealReason: string) => { + return request.post(`/delay-penalty/${recordId}/appeal`, null, { + params: { appealReason } + }) +} + +/** + * 管理员审批申诉 + */ +export const approveAppeal = (recordId: number, approved: boolean, appealResult?: string) => { + return request.post(`/delay-penalty/${recordId}/approve`, null, { + params: { approved, appealResult } + }) +} + diff --git a/frontend/src/api/department.ts b/frontend/src/api/department.ts new file mode 100644 index 0000000..69b0e0d --- /dev/null +++ b/frontend/src/api/department.ts @@ -0,0 +1,99 @@ +import request from '@/utils/request' + +export type DeptType = 'demand' | 'receiving' + +export interface DepartmentOverview { + id: number + deptName: string + deptCode?: string + deptType: DeptType + parentId?: number + leaderId?: number + leaderName?: string + description?: string + sortOrder?: number + status?: number + scoreBalance?: number + totalRecharge?: number + totalConsume?: number + totalEarn?: number + limitEnabled?: number + limitPeriod?: 'DAILY' | 'WEEKLY' | 'MONTHLY' + periodLimit?: number + periodUsed?: number + alertThreshold?: number + overLimitAction?: 'alert' | 'block' + limitAlert?: boolean + allowedDemandTypes?: string[] + memberCount?: number +} + +export interface DepartmentPayload { + deptName: string + deptCode?: string + deptType: DeptType + parentId?: number | null + leaderId?: number | null + sortOrder?: number + status?: number + allowedDemandTypes?: string[] + limitEnabled?: number + limitPeriod?: 'DAILY' | 'WEEKLY' | 'MONTHLY' + periodLimit?: number + alertThreshold?: number + overLimitAction?: 'alert' | 'block' + initialScoreBalance?: number +} + +export interface DepartmentUpdatePayload extends Partial {} + +export interface LimitSettingsPayload { + limitEnabled: number + limitPeriod?: 'DAILY' | 'WEEKLY' | 'MONTHLY' + periodLimit?: number + alertThreshold?: number + overLimitAction?: 'alert' | 'block' +} + +export interface ScoreAdjustPayload { + action: 'recharge' | 'deduct' + amount: number + remark?: string +} + +export const fetchDepartmentOverview = () => { + return request.get('/departments/overview') +} + +export const createDepartment = (data: DepartmentPayload) => { + return request.post('/departments', data) +} + +export const updateDepartment = (id: number, data: DepartmentUpdatePayload) => { + return request.put(`/departments/${id}`, data) +} + +export const updateLimitSettings = (id: number, data: LimitSettingsPayload) => { + return request.put(`/departments/${id}/limit`, data) +} + +export const deleteDepartment = (id: number) => { + return request.delete(`/departments/${id}`) +} + +export const adjustDepartmentScore = (id: number, data: ScoreAdjustPayload) => { + return request.post(`/departments/${id}/score-adjust`, data) +} + +export const getDepartmentScorePool = (id: number) => { + return request.get(`/departments/${id}/score-pool`) +} + +export const getDepartmentTree = () => { + return request.get('/departments/tree') +} + +export const getDepartmentList = (params?: any) => { + return request.get('/departments', { params }) +} + diff --git a/frontend/src/api/designer.ts b/frontend/src/api/designer.ts new file mode 100644 index 0000000..e278afc --- /dev/null +++ b/frontend/src/api/designer.ts @@ -0,0 +1,63 @@ +import request from '@/utils/request' + +export interface DesignerStats { + userId: number + userName: string + avatar?: string + deptName?: string + inProgressCount: number + queueCount: number + totalScore: number + delayedCount: number + totalTaskCount: number + completedCount: number + avgRating: number + goodReviewCount: number + completionRate: number + ontimeRate: number + onlineStatus: number + lastActiveTime: string + // 前端扩展字段 + showTasks?: boolean + loadingTasks?: boolean + inProgressTasks?: any[] +} + +/** + * 获取所有设计师统计信息 + */ +export const getAllDesignerStats = () => { + return request.get('/designers/stats') +} + +/** + * 获取单个设计师统计信息 + */ +export const getDesignerStats = (userId: number) => { + return request.get(`/designers/stats/${userId}`) +} + +/** + * 获取排行榜 + * @param type 类型:score-积分榜,efficiency-效率榜,quality-好评榜,diligence-勤勉榜 + * @param limit 返回数量 + * @param month 月份:YYYY-MM格式,可选,不传则查询当月 + */ +export const getLeaderboard = (type: string = 'score', limit: number = 10, month?: string) => { + const params: any = { type, limit } + if (month) { + params.month = month + } + return request.get('/designers/leaderboard', { + params + }) +} + +/** + * 获取设计师进行中的任务列表 + * @param userId 设计师ID + */ +export const getDesignerInProgressTasks = (userId: number) => { + return request.get(`/designers/${userId}/in-progress-tasks`) +} + diff --git a/frontend/src/api/dingtalk.ts b/frontend/src/api/dingtalk.ts new file mode 100644 index 0000000..d968839 --- /dev/null +++ b/frontend/src/api/dingtalk.ts @@ -0,0 +1,81 @@ +import request from '@/utils/request' +import type { Result } from '@/api/types' + +// 钉钉配置接口类型 +export interface DingTalkConfig { + id?: number + name: string + agentId?: string + appKey?: string + appSecret?: string + corpId?: string + robotWebhook?: string + robotSecret?: string + enabled: number + enabledFeatures?: { + notification?: boolean + login?: boolean + attendance?: boolean + sync?: boolean + } + createTime?: string + updateTime?: string +} + +// ==================== 钉钉集成 API ==================== + +/** + * 获取钉钉配置 + */ +export function getDingTalkConfig(): Promise { + return request.get>('/dingtalk/config') + .then(res => res.data) +} + +/** + * 保存钉钉配置 + */ +export function saveDingTalkConfig(config: DingTalkConfig): Promise { + return request.post>('/dingtalk/config', config) + .then(res => res.data) +} + +/** + * 测试钉钉连接 + */ +export function testDingTalkConnection(config: DingTalkConfig): Promise { + return request.post>('/dingtalk/test', config) + .then(res => res.data) +} + +/** + * 发送测试消息 + */ +export function sendTestMessage(title: string, content: string): Promise { + return request.post>('/dingtalk/test-message', { title, content }) + .then(res => res.data) +} + +/** + * 获取钉钉部门列表 + */ +export function getDingTalkDepartments(): Promise { + return request.get>('/dingtalk/departments') + .then(res => res.data) +} + +/** + * 同步钉钉用户 + */ +export function syncDingTalkUsers(): Promise { + return request.post>('/dingtalk/sync-users') + .then(res => res.data) +} + +/** + * 检查功能是否启用 + */ +export function isDingTalkFeatureEnabled(feature: string): Promise { + return request.get>(`/dingtalk/feature/${feature}`) + .then(res => res.data) +} diff --git a/frontend/src/api/file.ts b/frontend/src/api/file.ts new file mode 100644 index 0000000..4a65b7e --- /dev/null +++ b/frontend/src/api/file.ts @@ -0,0 +1,176 @@ +import axios from 'axios' +import { message } from 'ant-design-vue' + +const API_BASE = (import.meta as any).env?.VITE_API_BASE_URL || '/api' + +export interface FileUpload { + id: number + originalName: string + storedName: string + filePath: string + fileUrl: string + fileSize: number + fileType: string + uploaderId: number + uploaderName: string + relatedType?: string + relatedId?: number + createTime: string +} + +/** + * 上传文件到S3 + * @param files 文件列表 + * @param relatedType 关联类型(可选,如 'task') + * @param relatedId 关联ID(可选) + * @param onProgress 上传进度回调 + */ +export const uploadFilesToS3 = async ( + files: File[], + relatedType?: string, + relatedId?: number, + onProgress?: (progress: number) => void +): Promise => { + console.log('[uploadFilesToS3] 开始上传,文件数量:', files.length, 'relatedType:', relatedType, 'relatedId:', relatedId) + + const formData = new FormData() + files.forEach((file, index) => { + console.log(`[uploadFilesToS3] 添加文件 ${index + 1}:`, file.name, file.size, file.type) + formData.append('files', file) + }) + if (relatedType) { + formData.append('relatedType', relatedType) + } + if (relatedId) { + formData.append('relatedId', relatedId.toString()) + } + + const token = localStorage.getItem('token') + console.log('[uploadFilesToS3] Token存在:', !!token) + console.log('[uploadFilesToS3] 请求URL:', `${API_BASE}/file/upload`) + + return new Promise((resolve, reject) => { + console.log('[uploadFilesToS3] 发送POST请求到:', `${API_BASE}/file/upload`) + axios.post<{ code: number; message: string; data: string[] }>( + `${API_BASE}/file/upload`, + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + 'Authorization': token ? `Bearer ${token}` : '' + }, + onUploadProgress: (progressEvent) => { + if (onProgress && progressEvent.total) { + const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total) + onProgress(percent) + } + } + } + ) + .then(response => { + console.log('[uploadFilesToS3] 上传响应:', response.data) + if (response.data.code === 200 && response.data.data) { + console.log('[uploadFilesToS3] 上传成功,文件URLs:', response.data.data) + resolve(response.data.data) + } else { + console.error('[uploadFilesToS3] 上传失败,响应:', response.data) + message.error(response.data.message || '上传失败') + reject(new Error(response.data.message || '上传失败')) + } + }) + .catch(error => { + console.error('[uploadFilesToS3] 上传异常:', error) + console.error('[uploadFilesToS3] 错误详情:', { + message: error.message, + response: error.response?.data, + status: error.response?.status + }) + const errorMsg = error.response?.data?.message || error.message || '上传失败' + message.error(errorMsg) + reject(new Error(errorMsg)) + }) + }) +} + +/** + * 下载文件(需要登录) + * @param fileId 文件ID + */ +export const downloadFile = (fileId: number) => { + const token = localStorage.getItem('token') + const url = `${API_BASE}/file/download/${fileId}` + + // 创建隐藏的a标签进行下载 + const link = document.createElement('a') + link.href = url + link.style.display = 'none' + + // 添加Authorization header(通过fetch实现) + fetch(url, { + headers: { + 'Authorization': token ? `Bearer ${token}` : '' + } + }) + .then(response => { + if (response.ok) { + return response.blob() + } else { + throw new Error('下载失败') + } + }) + .then(blob => { + const downloadUrl = window.URL.createObjectURL(blob) + link.href = downloadUrl + link.download = '' + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + window.URL.revokeObjectURL(downloadUrl) + }) + .catch(error => { + message.error('文件下载失败: ' + error.message) + }) +} + +/** + * 获取文件信息 + */ +export const getFileInfo = (fileId: number): Promise => { + return axios.get(`${API_BASE}/file/${fileId}`, { + headers: { + 'Authorization': `Bearer ${localStorage.getItem('token')}` + } + }).then(response => { + if (response.data.code === 200) { + return response.data.data + } else { + throw new Error(response.data.message || '获取文件信息失败') + } + }) +} + +/** + * 根据关联信息获取文件列表 + */ +export const getFilesByRelated = (relatedType: string, relatedId: number): Promise => { + return axios.get(`${API_BASE}/file/list`, { + params: { relatedType, relatedId }, + headers: { + 'Authorization': `Bearer ${localStorage.getItem('token')}` + } + }).then(response => { + if (response.data.code === 200) { + return response.data.data + } else { + throw new Error(response.data.message || '获取文件列表失败') + } + }) +} + +/** + * 直接打开文件URL(用于S3公开URL) + * @param fileUrl 文件URL + */ +export const openFileUrl = (fileUrl: string) => { + window.open(fileUrl, '_blank') +} diff --git a/frontend/src/api/goal.ts b/frontend/src/api/goal.ts new file mode 100644 index 0000000..e3643aa --- /dev/null +++ b/frontend/src/api/goal.ts @@ -0,0 +1,127 @@ +import request from '@/utils/request' +import type { Result } from '@/api/types' + +// 用户目标接口类型 +export interface UserGoal { + id: number + userId: number + goalType: 'task_count' | 'score' | 'rank' | 'rating' + goalName: string + description?: string + targetValue: number + currentValue: number + period: 'daily' | 'weekly' | 'monthly' | 'quarterly' | 'yearly' | 'custom' + periodStart: string + periodEnd: string + status: 'active' | 'completed' | 'failed' | 'cancelled' + completedTime?: string + rewardScore: number + rewardClaimed: number + reminderType: 'none' | 'daily' | 'before_deadline' + createTime: string + updateTime: string +} + +export interface GoalCreateRequest { + goalType: string + goalName: string + description?: string + targetValue: number + period: string + periodStart?: string + periodEnd?: string + rewardScore?: number + reminderType?: string +} + +// ==================== 目标管理 API ==================== + +/** + * 创建目标 + */ +export function createGoal(data: GoalCreateRequest): Promise { + return request.post>('/goals', data) + .then(res => res.data) +} + +/** + * 创建预设目标 + */ +export function createPresetGoal(type: string): Promise { + return request.post>(`/goals/preset/${type}`) + .then(res => res.data) +} + +/** + * 更新目标 + */ +export function updateGoal(id: number, data: Partial): Promise { + return request.put>(`/goals/${id}`, data) + .then(res => res.data) +} + +/** + * 更新目标进度 + */ +export function updateGoalProgress(id: number, progressValue: number): Promise { + return request.put>(`/goals/${id}/progress`, null, { + params: { progressValue } + }).then(res => res.data) +} + +/** + * 获取我的目标列表 + */ +export function getMyGoals(status?: string): Promise { + return request.get>('/goals/my', { + params: { status } + }).then(res => res.data) +} + +/** + * 获取我的活跃目标 + */ +export function getMyActiveGoals(): Promise { + return request.get>('/goals/my/active') + .then(res => res.data) +} + +/** + * 获取目标详情 + */ +export function getGoalById(id: number): Promise { + return request.get>(`/goals/${id}`) + .then(res => res.data) +} + +/** + * 领取目标奖励 + */ +export function claimGoalReward(id: number): Promise { + return request.post>(`/goals/${id}/claim-reward`) + .then(res => res.data) +} + +/** + * 取消目标 + */ +export function cancelGoal(id: number): Promise { + return request.post>(`/goals/${id}/cancel`) + .then(res => res.data) +} + +/** + * 删除目标 + */ +export function deleteGoal(id: number): Promise { + return request.delete>(`/goals/${id}`) + .then(res => res.data) +} + +// 预设目标类型 +export const PRESET_GOAL_TYPES = [ + { type: 'weekly_task', name: '本周完成10个任务', description: '在本周内完成10个设计任务', reward: 50 }, + { type: 'monthly_task', name: '本月完成40个任务', description: '在本月内完成40个设计任务', reward: 200 }, + { type: 'monthly_score', name: '本月获得1000积分', description: '在本月内累计获得1000积分', reward: 100 }, + { type: 'top_10', name: '进入排行榜前10', description: '在本月排行榜中进入前10名', reward: 150 }, +] diff --git a/frontend/src/api/incentive.ts b/frontend/src/api/incentive.ts new file mode 100644 index 0000000..4351882 --- /dev/null +++ b/frontend/src/api/incentive.ts @@ -0,0 +1,116 @@ +import request from '@/utils/request' +import type { Result } from '@/api/types' + +// 成就接口类型定义 +export interface Achievement { + id: number + achievementCode: string + achievementName: string + description: string + iconUrl?: string + achievementType: string + condition: string + rewardScore: number + level: string + sortOrder: number + status: number + createTime: string + updateTime: string +} + +export interface UserAchievement { + id: number + userId: number + achievementId: number + achievedTime: string + progressValue: number + rewardClaimed: number + claimTime?: string + createTime: string + updateTime: string +} + +// 签到接口类型定义 +export interface CheckIn { + id: number + userId: number + checkInDate: string + continuousDays: number + rewardScore: number + remark?: string + createTime: string +} + +export interface CheckInStats { + continuousDays: number + totalDays: number + hasCheckedInToday: boolean +} + +// ==================== 成就相关API ==================== + +/** + * 获取所有成就列表 + */ +export function getAllAchievements(): Promise { + return request.get>('/achievements') + .then(res => res.data) +} + +/** + * 获取用户已达成的成就 + */ +export function getUserAchievements(userId: number): Promise { + return request.get>(`/achievements/user/${userId}`) + .then(res => res.data) +} + +/** + * 领取成就奖励 + */ +export function claimAchievementReward(achievementId: number, userId: number): Promise { + return request.post>(`/achievements/${achievementId}/claim`, null, { + params: { userId } + }).then(res => res.data) +} + +/** + * 初始化默认成就 + */ +export function initDefaultAchievements(): Promise { + return request.post>('/achievements/init') + .then(res => res.data) +} + +// ==================== 签到相关API ==================== + +/** + * 用户签到 + */ +export function checkIn(): Promise { + return request.post>('/check-in').then(res => res.data) +} + +/** + * 检查今天是否已签到 + */ +export function hasCheckedInToday(): Promise { + return request.get>('/check-in/today').then(res => res.data) +} + +/** + * 获取签到历史记录 + */ +export function getCheckInHistory(limit: number = 30): Promise { + return request.get>('/check-in/history', { + params: { limit } + }).then(res => res.data) +} + +/** + * 获取签到统计信息 + */ +export function getCheckInStats(): Promise { + return request.get>('/check-in/stats').then(res => res.data) +} + diff --git a/frontend/src/api/notification.ts b/frontend/src/api/notification.ts new file mode 100644 index 0000000..f7b349b --- /dev/null +++ b/frontend/src/api/notification.ts @@ -0,0 +1,81 @@ +import request from '@/utils/request' +import type { Result } from '@/api/types' + +// 通知接口类型定义 +export interface Notification { + id: number + notificationType: string + receiverId: number + senderId?: number + title: string + content: string + relatedId?: number + relatedType?: string + linkUrl?: string + isRead: number + readTime?: string + pushDingtalk?: number + dingtalkStatus?: string + dingtalkPushTime?: string + priority: string + createTime: string + updateTime: string +} + +export interface PageResult { + records: T[] + total: number + size: number + current: number + pages: number +} + +/** + * 获取用户通知列表(分页) + */ +export function getNotifications(params: { + receiverId: number + pageNum?: number + pageSize?: number + unreadOnly?: boolean +}): Promise> { + return request.get>>('/notifications', { params }) + .then(res => res.data) +} + +/** + * 获取未读数量 + */ +export function getUnreadCount(receiverId: number): Promise { + return request.get>('/notifications/unread-count', { + params: { receiverId } + }).then(res => res.data) +} + +/** + * 标记为已读 + */ +export function markAsRead(id: number, receiverId: number): Promise { + return request.put>(`/notifications/${id}/read`, null, { + params: { receiverId } + }).then(res => res.data) +} + +/** + * 批量标记所有为已读 + */ +export function markAllAsRead(receiverId: number): Promise { + return request.put>('/notifications/read-all', null, { + params: { receiverId } + }).then(res => res.data) +} + +/** + * 删除通知 + */ +export function deleteNotification(id: number, receiverId: number): Promise { + return request.delete>(`/notifications/${id}`, { + params: { receiverId } + }).then(res => res.data) +} + diff --git a/frontend/src/api/permission.ts b/frontend/src/api/permission.ts new file mode 100644 index 0000000..da993dc --- /dev/null +++ b/frontend/src/api/permission.ts @@ -0,0 +1,87 @@ +import axios from 'axios'; + +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || ''; + +// 权限相关类型定义 +export interface Permission { + id?: number; + parentId?: number; + permissionCode: string; + permissionName: string; + type?: string; + path?: string; + method?: string; + icon?: string; + component?: string; + description?: string; + sortOrder?: number; + status?: number; + createTime?: string; + updateTime?: string; + children?: Permission[]; +} + +/** + * 获取所有权限列表 + */ +export const getAllPermissions = async (): Promise => { + const response = await axios.get(`${API_BASE_URL}/api/permissions`); + return response.data.data; +}; + +/** + * 获取权限树形结构 + */ +export const getPermissionTree = async (): Promise => { + const response = await axios.get(`${API_BASE_URL}/api/permissions/tree`); + return response.data.data; +}; + +/** + * 根据ID获取权限 + */ +export const getPermissionById = async (id: number): Promise => { + const response = await axios.get(`${API_BASE_URL}/api/permissions/${id}`); + return response.data.data; +}; + +/** + * 根据用户ID获取权限列表 + */ +export const getPermissionsByUserId = async (userId: number): Promise => { + const response = await axios.get(`${API_BASE_URL}/api/permissions/user/${userId}`); + return response.data.data; +}; + +/** + * 根据用户ID获取权限代码列表 + */ +export const getPermissionCodesByUserId = async (userId: number): Promise => { + const response = await axios.get(`${API_BASE_URL}/api/permissions/user/${userId}/codes`); + return response.data.data; +}; + +/** + * 创建权限 + */ +export const createPermission = async (permission: Permission): Promise => { + const response = await axios.post(`${API_BASE_URL}/api/permissions`, permission); + return response.data.data; +}; + +/** + * 更新权限 + */ +export const updatePermission = async (id: number, permission: Partial): Promise => { + const response = await axios.put(`${API_BASE_URL}/api/permissions/${id}`, permission); + return response.data.data; +}; + +/** + * 删除权限 + */ +export const deletePermission = async (id: number): Promise => { + const response = await axios.delete(`${API_BASE_URL}/api/permissions/${id}`); + return response.data.data; +}; + diff --git a/frontend/src/api/rewardRule.ts b/frontend/src/api/rewardRule.ts new file mode 100644 index 0000000..6139a15 --- /dev/null +++ b/frontend/src/api/rewardRule.ts @@ -0,0 +1,142 @@ +import request from '@/utils/request' +import type { Result, PageResult } from '@/api/types' + +// 奖励规则接口类型 +export interface RewardRule { + id: number + name: string + description: string + ruleType: 'daily' | 'weekly' | 'monthly' | 'task' | 'rating' + triggerCondition: { + type: string + operator: string + value: number + } + rewardType: 'score' | 'badge' | 'both' + rewardScore: number + rewardBadge?: any + scope: 'all' | 'designer' | 'department' + scopeDeptIds?: number[] + enabled: number + cronExpression?: string + lastExecuteTime?: string + priority: number + creatorId: number + createTime: string + updateTime: string +} + +export interface RewardRuleCreateRequest { + name: string + description?: string + ruleType: string + triggerCondition: string + rewardType: string + rewardScore: number + scope: string + scopeDeptIds?: string + priority?: number +} + +// ==================== 奖励规则 API ==================== + +/** + * 创建奖励规则 + */ +export function createRewardRule(data: RewardRuleCreateRequest): Promise { + return request.post>('/reward-rules', data) + .then(res => res.data) +} + +/** + * 更新奖励规则 + */ +export function updateRewardRule(id: number, data: Partial): Promise { + return request.put>(`/reward-rules/${id}`, data) + .then(res => res.data) +} + +/** + * 启用规则 + */ +export function enableRewardRule(id: number): Promise { + return request.post>(`/reward-rules/${id}/enable`) + .then(res => res.data) +} + +/** + * 禁用规则 + */ +export function disableRewardRule(id: number): Promise { + return request.post>(`/reward-rules/${id}/disable`) + .then(res => res.data) +} + +/** + * 删除规则 + */ +export function deleteRewardRule(id: number): Promise { + return request.delete>(`/reward-rules/${id}`) + .then(res => res.data) +} + +/** + * 获取规则列表 + */ +export function getRewardRuleList(params: { + page?: number + pageSize?: number + ruleType?: string +}): Promise> { + return request.get>>('/reward-rules', { params }) + .then(res => res.data) +} + +/** + * 获取规则详情 + */ +export function getRewardRuleById(id: number): Promise { + return request.get>(`/reward-rules/${id}`) + .then(res => res.data) +} + +/** + * 获取启用的规则 + */ +export function getEnabledRewardRules(): Promise { + return request.get>('/reward-rules/enabled') + .then(res => res.data) +} + +/** + * 初始化默认规则 + */ +export function initDefaultRewardRules(): Promise { + return request.post>('/reward-rules/init') + .then(res => res.data) +} + +// 规则类型选项 +export const RULE_TYPE_OPTIONS = [ + { value: 'daily', label: '每日奖励' }, + { value: 'weekly', label: '每周奖励' }, + { value: 'monthly', label: '每月奖励' }, + { value: 'task', label: '任务奖励' }, + { value: 'rating', label: '好评奖励' }, +] + +// 触发条件类型 +export const TRIGGER_TYPE_OPTIONS = [ + { value: 'task_count', label: '完成任务数' }, + { value: 'five_star_count', label: '五星好评数' }, + { value: 'rating', label: '任务评分' }, +] + +// 比较运算符 +export const OPERATOR_OPTIONS = [ + { value: '>=', label: '大于等于' }, + { value: '>', label: '大于' }, + { value: '==', label: '等于' }, + { value: '<=', label: '小于等于' }, + { value: '<', label: '小于' }, +] diff --git a/frontend/src/api/role.ts b/frontend/src/api/role.ts new file mode 100644 index 0000000..ac7c524 --- /dev/null +++ b/frontend/src/api/role.ts @@ -0,0 +1,85 @@ +import axios from 'axios'; + +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || ''; + +// 角色相关类型定义 +export interface Role { + id?: number; + roleCode: string; + roleName: string; + description?: string; + level?: number; + dataScope?: string; + sortOrder?: number; + status?: number; + builtIn?: number; + createTime?: string; + updateTime?: string; +} + +/** + * 获取所有角色列表 + */ +export const getAllRoles = async (): Promise => { + const response = await axios.get(`${API_BASE_URL}/api/roles`); + return response.data.data; +}; + +/** + * 根据ID获取角色 + */ +export const getRoleById = async (id: number): Promise => { + const response = await axios.get(`${API_BASE_URL}/api/roles/${id}`); + return response.data.data; +}; + +/** + * 创建角色 + */ +export const createRole = async (role: Role): Promise => { + const response = await axios.post(`${API_BASE_URL}/api/roles`, role); + return response.data.data; +}; + +/** + * 更新角色 + */ +export const updateRole = async (id: number, role: Partial): Promise => { + const response = await axios.put(`${API_BASE_URL}/api/roles/${id}`, role); + return response.data.data; +}; + +/** + * 删除角色 + */ +export const deleteRole = async (id: number): Promise => { + const response = await axios.delete(`${API_BASE_URL}/api/roles/${id}`); + return response.data.data; +}; + +/** + * 为角色分配权限 + */ +export const assignRolePermissions = async (roleId: number, permissionIds: number[]): Promise => { + const response = await axios.post(`${API_BASE_URL}/api/roles/${roleId}/permissions`, permissionIds); + return response.data.data; +}; + +/** + * 获取角色的权限ID列表 + */ +export const getRolePermissionIds = async (roleId: number): Promise => { + const response = await axios.get(`${API_BASE_URL}/api/roles/${roleId}/permissions`); + return response.data.data; +}; + +/** + * 启用/禁用角色 + */ +export const toggleRoleStatus = async (id: number, status: number): Promise => { + const response = await axios.put(`${API_BASE_URL}/api/roles/${id}/status`, null, { + params: { status } + }); + return response.data.data; +}; + diff --git a/frontend/src/api/s3.ts b/frontend/src/api/s3.ts new file mode 100644 index 0000000..1429a36 --- /dev/null +++ b/frontend/src/api/s3.ts @@ -0,0 +1,70 @@ +import request from '@/utils/request' + +export interface S3Config { + id?: number + endpoint: string + bucket: string + accessKeyId: string + secretAccessKey: string + region: string + publicDomain?: string + enablePathStyle?: boolean + filePath?: string + isActive?: boolean + createTime?: string + updateTime?: string +} + +/** + * 获取激活的S3配置 + */ +export const getActiveS3Config = async (): Promise => { + const response = await request.get('/s3/config/active') + return response?.data || null +} + +/** + * 获取所有S3配置 + */ +export const getAllS3Configs = async (): Promise => { + const response = await request.get('/s3/config/list') + return response?.data || [] +} + +/** + * 根据ID获取S3配置 + */ +export const getS3ConfigById = async (id: number): Promise => { + const response = await request.get(`/s3/config/${id}`) + return response?.data +} + +/** + * 创建S3配置 + */ +export const createS3Config = async (config: S3Config): Promise => { + const response = await request.post('/s3/config', config) + return response?.data +} + +/** + * 更新S3配置 + */ +export const updateS3Config = async (id: number, config: S3Config): Promise => { + const response = await request.put(`/s3/config/${id}`, config) + return response?.data +} + +/** + * 删除S3配置 + */ +export const deleteS3Config = async (id: number): Promise => { + await request.delete(`/s3/config/${id}`) +} + +/** + * 激活指定的S3配置 + */ +export const activateS3Config = async (id: number): Promise => { + await request.put(`/s3/config/${id}/activate`) +} diff --git a/frontend/src/api/scorePool.ts b/frontend/src/api/scorePool.ts new file mode 100644 index 0000000..27c5280 --- /dev/null +++ b/frontend/src/api/scorePool.ts @@ -0,0 +1,181 @@ +import request from '@/utils/request' +import type { Result, PageResult } from '@/api/types' + +// 积分池接口类型 +export interface ScorePool { + id: number + name: string + poolType: 'monthly' | 'quarterly' | 'yearly' | 'special' + year: number + month?: number + quarter?: number + totalBudget: number + usedScore: number + remainingScore: number + taskRewardBudget: number + taskRewardUsed: number + ratingRewardBudget: number + ratingRewardUsed: number + contestRewardBudget: number + contestRewardUsed: number + checkinRewardBudget: number + checkinRewardUsed: number + otherRewardBudget: number + otherRewardUsed: number + overflowStrategy: 'stop' | 'alert' | 'auto_increase' + alertThreshold: number + status: 'active' | 'closed' | 'settled' + creatorId: number + createTime: string + updateTime: string +} + +export interface ScorePoolRecord { + id: number + poolId: number + userId: number + userName: string + rewardType: string + scoreChange: number + beforeRemaining: number + afterRemaining: number + bizType?: string + bizId?: number + remark?: string + createTime: string +} + +export interface ScorePoolCreateRequest { + name: string + poolType: string + year: number + month?: number + quarter?: number + totalBudget: number + taskRewardBudget?: number + ratingRewardBudget?: number + contestRewardBudget?: number + checkinRewardBudget?: number + otherRewardBudget?: number + overflowStrategy?: string + alertThreshold?: number +} + +export interface PoolStatistics { + totalBudget: number + usedScore: number + remainingScore: number + usagePercent: number + categories: { + task: { budget: number; used: number } + rating: { budget: number; used: number } + contest: { budget: number; used: number } + checkin: { budget: number; used: number } + other: { budget: number; used: number } + } +} + +// ==================== 积分池 API ==================== + +/** + * 创建积分池 + */ +export function createScorePool(data: ScorePoolCreateRequest): Promise { + return request.post>('/score-pools', data) + .then(res => res.data) +} + +/** + * 更新积分池 + */ +export function updateScorePool(id: number, data: Partial): Promise { + return request.put>(`/score-pools/${id}`, data) + .then(res => res.data) +} + +/** + * 获取积分池列表 + */ +export function getScorePoolList(params: { + page?: number + pageSize?: number + poolType?: string + year?: number +}): Promise> { + return request.get>>('/score-pools', { params }) + .then(res => res.data) +} + +/** + * 获取积分池详情 + */ +export function getScorePoolById(id: number): Promise { + return request.get>(`/score-pools/${id}`) + .then(res => res.data) +} + +/** + * 获取当前活跃积分池 + */ +export function getCurrentActivePool(): Promise { + return request.get>('/score-pools/current') + .then(res => res.data) +} + +/** + * 获取积分池统计信息 + */ +export function getPoolStatistics(id: number): Promise { + return request.get>(`/score-pools/${id}/statistics`) + .then(res => res.data) +} + +/** + * 获取积分池使用记录 + */ +export function getPoolRecords(id: number, params: { + page?: number + pageSize?: number +}): Promise> { + return request.get>>(`/score-pools/${id}/records`, { params }) + .then(res => res.data) +} + +/** + * 关闭积分池 + */ +export function closeScorePool(id: number): Promise { + return request.post>(`/score-pools/${id}/close`) + .then(res => res.data) +} + +/** + * 结算积分池 + */ +export function settleScorePool(id: number): Promise { + return request.post>(`/score-pools/${id}/settle`) + .then(res => res.data) +} + +/** + * 删除积分池 + */ +export function deleteScorePool(id: number): Promise { + return request.delete>(`/score-pools/${id}`) + .then(res => res.data) +} + +// 积分池类型选项 +export const POOL_TYPE_OPTIONS = [ + { value: 'monthly', label: '月度池' }, + { value: 'quarterly', label: '季度池' }, + { value: 'yearly', label: '年度池' }, + { value: 'special', label: '专项池' }, +] + +// 超支策略选项 +export const OVERFLOW_STRATEGY_OPTIONS = [ + { value: 'stop', label: '停止发放' }, + { value: 'alert', label: '提醒继续' }, + { value: 'auto_increase', label: '自动增加预算' }, +] diff --git a/frontend/src/api/system.ts b/frontend/src/api/system.ts new file mode 100644 index 0000000..11d5694 --- /dev/null +++ b/frontend/src/api/system.ts @@ -0,0 +1,99 @@ +import request from '@/utils/request' + +export interface SystemConfig { + id?: number + configKey: string + configValue: string + configGroup?: string + configName?: string + description?: string + valueType?: string + builtIn?: number + sortOrder?: number +} + +/** + * 获取系统配置值 + */ +export const getSystemConfig = (key: string) => { + return request.get(`/system-config/key/${key}`) +} + +/** + * 获取所有系统配置 + */ +export const getAllSystemConfigs = () => { + return request.get('/system-config') +} + +/** + * 根据分组获取系统配置 + */ +export const getSystemConfigsByGroup = (group: string) => { + return request.get(`/system-config/group/${group}`) +} + +/** + * 获取所有系统配置的Map + */ +export const getAllSystemConfigsMap = () => { + return request.get>('/system-config/map') +} + +/** + * 根据分组获取系统配置Map + */ +export const getSystemConfigsMapByGroup = (group: string) => { + return request.get>(`/system-config/map/group/${group}`) +} + +/** + * 创建系统配置 + */ +export const createSystemConfig = (config: { + configKey: string + configValue: string + configGroup?: string + configName?: string + description?: string + valueType?: string + builtIn?: number + sortOrder?: number +}) => { + return request.post('/system-config', config) +} + +/** + * 更新系统配置 + */ +export const updateSystemConfig = (id: number, config: { + configValue?: string + configName?: string + description?: string + valueType?: string + sortOrder?: number +}) => { + return request.put(`/system-config/${id}`, config) +} + +/** + * 批量更新系统配置值 + */ +export const batchUpdateSystemConfigs = (configMap: Record) => { + return request.put('/system-config/batch', configMap) +} + +/** + * 删除系统配置 + */ +export const deleteSystemConfig = (id: number) => { + return request.delete(`/system-config/${id}`) +} + +/** + * 初始化默认配置 + */ +export const initDefaultConfigs = () => { + return request.post('/system-config/init') +} + diff --git a/frontend/src/api/task.ts b/frontend/src/api/task.ts new file mode 100644 index 0000000..0a3e088 --- /dev/null +++ b/frontend/src/api/task.ts @@ -0,0 +1,516 @@ +import request from '@/utils/request' + +export interface Task { + id?: number + taskNo?: string + title: string + taskTypeId: number + taskSubType?: string + quantity?: number + score?: number + estimatedHours?: number + brandId?: number + categoryId?: number + spuInfo?: string + platformId?: number + attachmentUrl?: string + deliveryPath?: string + publisherId?: number + publisherDeptId?: number + receiverId?: number + receiverDeptId?: number + receiverName?: string + receiverDeptName?: string + releaseType: string + status?: string + auditStatus?: string + auditorId?: number + auditorName?: string + auditTime?: string + auditComment?: string + progress?: number + startTime?: string + deadline?: string + completeTime?: string + rating?: number + reviewComment?: string + rejectReason?: string + isUrgent?: number + isDelayed?: number + createTime?: string + updateTime?: string + remark?: string + // 流水线任务字段 + isPipeline?: number + pipelineMode?: 'sequential' | 'parallel' + // 两级审核字段 + approvalStatus?: string // pending_demand_approval/pending_receiving_approval/approved/rejected_by_demand/rejected_by_receiving + currentApproverId?: number + currentApproverRole?: string // demand_leader/receiving_leader + currentApproverName?: string // 当前审核人姓名 + demandApprovalTime?: string + receivingApprovalTime?: string + // 关联字段(从后端查询返回) + brandName?: string + categoryName?: string + platformName?: string + publisherName?: string + publisherDeptName?: string +} + +// 任务子项(流水线任务) +export interface TaskItem { + id?: number + taskId: number + itemNo?: string + taskTypeId: number + taskTypeName?: string + quantity: number + unitScore: number + totalScore: number + estimatedHours?: number + receiverId?: number + receiverName?: string + status: string // locked/pending/in_progress/completed/rejected + progress?: number + isLocked?: number + prevItemId?: number + deliveryPath?: string + submitTime?: string + completeTime?: string + sortOrder: number + createTime?: string + updateTime?: string + // 主任务信息 + mainTaskTitle?: string + pipelineMode?: 'sequential' | 'parallel' + totalItems?: number + // 其他关联字段 + brandName?: string + categoryName?: string + platformName?: string + publisherName?: string + spuInfo?: string + taskSubType?: string + attachmentUrl?: string +} + +// 创建流水线任务请求 +export interface PipelineTaskRequest { + title: string + isPipeline: boolean + pipelineMode: 'sequential' | 'parallel' + items: Array<{ + taskTypeId: number + quantity: number + sortOrder: number + }> + brandId?: number + categoryId?: number + platformId?: number + spuInfo?: string + taskSubType?: string + attachmentUrl?: string + remark?: string + priority?: string +} + +export interface TaskAuditRequest { + taskId: number + result: 'pass' | 'reject' + comment?: string +} + +export interface TaskListParams { + status?: string + taskTypeId?: number + page?: number + pageSize?: number +} + +export interface TaskHallParams { + taskTypeId?: number + demandTypeId?: number + brandId?: number + categoryId?: number + platformId?: number + taskSubType?: string + minScore?: number + maxScore?: number + keyword?: string + publisherName?: string + timeRange?: 'today' | 'week' | 'month' + sortBy?: 'latest' | 'score' | 'deadline' | 'hot' + page?: number + pageSize?: number +} + +export interface MyTasksParams { + role?: 'publisher' | 'receiver' + status?: string + page?: number + pageSize?: number +} + +/** + * 创建任务 + */ +export const createTask = (data: Task) => { + return request.post('/tasks', data) +} + +/** + * 获取任务列表 + */ +export const getTaskList = (params: TaskListParams) => { + return request.get('/tasks', { params }) +} + +/** + * 获取任务详情 + */ +export const getTaskById = (id: number) => { + return request.get(`/tasks/${id}`) +} + +/** + * 更新任务 + */ +export const updateTask = (id: number, data: Partial) => { + return request.put(`/tasks/${id}`, data) +} + +/** + * 抢单 + */ +export const grabTask = (id: number) => { + return request.post(`/tasks/${id}/grab`) +} + +/** + * 提交任务 + */ +export const submitTask = (id: number, deliveryPath: string) => { + return request.post(`/tasks/${id}/submit`, null, { + params: { deliveryPath } + }) +} + +/** + * 审核任务 + */ +export const reviewTask = ( + id: number, + approved: boolean, + comment?: string, + rating?: number +) => { + return request.post(`/tasks/${id}/review`, null, { + params: { approved, comment, rating } + }) +} + +/** + * 取消任务 + */ +export const cancelTask = (id: number, reason: string) => { + return request.post(`/tasks/${id}/cancel`, null, { + params: { reason } + }) +} + +/** + * 获取任务大厅 + */ +export const getTaskHall = (params: TaskHallParams) => { + return request.get('/tasks/hall', { params }) +} + +/** + * 获取已完成任务列表(根据角色权限) + */ +export const getCompletedTasks = (params: any) => { + return request.get('/tasks/completed', { params }) +} + +/** + * 获取我的进行中任务列表 + */ +export const getMyInProgressTasks = () => { + return request.get('/tasks/my-in-progress') +} + +/** + * 批量更新任务进度 + */ +export const batchUpdateProgress = (updates: Array<{taskId: number, progress: number, remark?: string}>) => { + return request.post('/tasks/batch-update-progress', { updates }) +} + +/** + * 获取我的任务 + */ +export const getMyTasks = (params: MyTasksParams) => { + return request.get('/tasks/my', { params }) +} + +/** + * 提交任务审核 + */ +export const submitTaskAudit = (id: number) => { + return request.post(`/tasks/${id}/submit-audit`) +} + +/** + * 审核任务(部门负责人) + */ +export const auditTask = (data: TaskAuditRequest) => { + return request.post('/tasks/audit', data) +} + +/** + * 强制指派任务 + */ +export const assignTask = (taskId: number, assigneeId: number, comment?: string) => { + return request.post('/tasks/assign', { + taskId, + assigneeId, + assignType: 'force', + comment + }) +} + +/** + * 获取负责人任务列表(需求部门负责人) + */ +export const getLeaderTaskList = (params: { auditStatus?: string; page?: number; pageSize?: number }) => { + return request.get('/tasks/leader/demand', { params }) +} + +/** + * 获取接单部门负责人视图 + */ +export const getLeaderReceivingTaskList = (params: { status?: string; page?: number; pageSize?: number }) => { + return request.get('/tasks/leader/receiving', { params }) +} + +/** + * 工作台统计数据接口 + */ +export interface DashboardStats { + availableCount?: number + publishedCount?: number // 发布的任务数(当前用户当月发布的任务总数) + inProgressCount: number + completedCount: number + reviewCount: number + totalScore: number + todayStartedCount: number + todayCompletedCount: number + weekCompletedCount: number + lastWeekCompletedCount: number +} + +/** + * 获取工作台统计数据 + */ +export const getDashboardStats = (params?: { month?: string }) => { + return request.get('/tasks/dashboard/stats', { params }) +} + +/** + * 通用获取任务列表(支持状态筛选) + */ +export const getTasks = (params: any) => { + return request.get('/tasks', { params }) +} + +/** + * 更新任务进度 + */ +export const updateTaskProgress = (taskId: number, progress: number) => { + return request.post(`/tasks/${taskId}/progress`, { progress }) +} + +/** + * 完结任务(提交审核) + */ +export const completeTask = (taskId: number, data: { deliveryPath: string }) => { + return request.post(`/tasks/${taskId}/complete`, data) +} + +/** + * 审核通过任务 + */ +export const approveTask = (taskId: number) => { + return request.post(`/tasks/${taskId}/approve`) +} + +/** + * 驳回任务 + */ +export const rejectTask = (taskId: number, data: { reason: string }) => { + return request.post(`/tasks/${taskId}/reject`, data) +} + +// ==================== 流水线任务相关 API ==================== + +/** + * 创建流水线任务 + */ +export const createPipelineTask = (data: PipelineTaskRequest) => { + return request.post('/tasks/pipeline', data) +} + +/** + * 获取可接单的子任务列表 + */ +export const getAvailableTaskItems = (params: { + page?: number + pageSize?: number + taskTypeId?: number + keyword?: string +}) => { + return request.get('/tasks/items/available', { params }) +} + +/** + * 获取进行中的子任务列表 + */ +export const getInProgressTaskItems = (params: { + page?: number + pageSize?: number +}) => { + return request.get('/tasks/items/in-progress', { params }) +} + +/** + * 获取已完成的子任务列表 + */ +export const getCompletedTaskItems = (params: { + page?: number + pageSize?: number +}) => { + return request.get('/tasks/items/completed', { params }) +} + +/** + * 根据主任务ID获取所有子任务 + */ +export const getTaskItemsByTaskId = (taskId: number) => { + return request.get(`/tasks/items/task/${taskId}`) +} + +/** + * 获取子任务详情 + */ +export const getTaskItemById = (itemId: number) => { + return request.get(`/tasks/items/${itemId}`) +} + +/** + * 抢单子任务 + */ +export const grabTaskItem = (itemId: number) => { + return request.post(`/tasks/items/${itemId}/grab`) +} + +/** + * 更新子任务进度 + */ +export const updateTaskItemProgress = (itemId: number, progress: number) => { + return request.post(`/tasks/items/${itemId}/progress`, null, { + params: { progress } + }) +} + +/** + * 完结子任务 + */ +export const completeTaskItem = (itemId: number, data: { deliveryPath: string }) => { + return request.post(`/tasks/items/${itemId}/complete`, data) +} + +/** + * 驳回子任务 + */ +export const rejectTaskItem = (itemId: number, data: { reason: string }) => { + return request.post(`/tasks/items/${itemId}/reject`, data) +} + +/** + * 获取主任务进度统计 + */ +export const getTaskItemsProgress = (taskId: number) => { + return request.get(`/tasks/items/task/${taskId}/progress`) +} + +// ==================== 两级审核API ==================== + +/** + * 任务审核DTO + */ +export interface TaskApprovalDTO { + comment?: string // 审核意见 + taskUpdates?: TaskUpdateDTO // 任务修改(可选) +} + +/** + * 任务更新DTO + */ +export interface TaskUpdateDTO { + title?: string + taskSubType?: string + brandId?: number + platformId?: number + categoryId?: number + spuInfo?: string + remark?: string + attachmentUrl?: string + taskTypes?: Array<{ + taskTypeId: number + quantity: number + }> +} + +/** + * 获取待审核任务列表 + */ +export const getPendingApprovalTasks = () => { + return request.get('/tasks/pending-approval') +} + +/** + * 获取所有待审核任务(处于审批流程中的任务) + */ +export const getAllPendingApprovalTasks = (params?: { taskTypeId?: number; page?: number; pageSize?: number }) => { + return request.get('/tasks/pending-approval-all', { params }) +} + +/** + * 需求部门负责人审核通过 + */ +export const approveDemandLevel = (taskId: number, data: TaskApprovalDTO) => { + return request.post(`/tasks/${taskId}/approve-demand`, data) +} + +/** + * 需求部门负责人驳回 + */ +export const rejectByDemandLeader = (taskId: number, reason: string) => { + return request.post(`/tasks/${taskId}/reject-demand`, { reason }) +} + +/** + * 接单部门负责人审核通过 + */ +export const approveReceivingLevel = (taskId: number, data: TaskApprovalDTO) => { + return request.post(`/tasks/${taskId}/approve-receiving`, data) +} + +/** + * 接单部门负责人驳回 + */ +export const rejectByReceivingLeader = (taskId: number, reason: string) => { + return request.post(`/tasks/${taskId}/reject-receiving`, { reason }) +} + diff --git a/frontend/src/api/teamPK.ts b/frontend/src/api/teamPK.ts new file mode 100644 index 0000000..3cde509 --- /dev/null +++ b/frontend/src/api/teamPK.ts @@ -0,0 +1,114 @@ +import request from '@/utils/request' +import type { Result, PageResult } from '@/api/types' + +// 团队PK接口类型 +export interface TeamPK { + id: number + name: string + description: string + teamADeptId: number + teamAName: string + teamBDeptId: number + teamBName: string + startTime: string + endTime: string + dimensions: Record + teamAScore: number + teamBScore: number + teamATaskCount: number + teamBTaskCount: number + teamAFiveStarCount: number + teamBFiveStarCount: number + teamAOnTimeRate: number + teamBOnTimeRate: number + rewards: { winnerReward: number; loserReward: number; mvpReward?: number } + status: 'draft' | 'scheduled' | 'active' | 'ended' | 'cancelled' + winner?: 'A' | 'B' | 'draw' + mvpUserId?: number + creatorId: number + createTime: string + updateTime: string +} + +export interface TeamPKCreateRequest { + name: string + description?: string + teamADeptId: number + teamAName: string + teamBDeptId: number + teamBName: string + startTime: string + endTime: string + dimensions: string + rewards: string +} + +// ==================== 团队PK API ==================== + +/** + * 创建团队PK + */ +export function createTeamPK(data: TeamPKCreateRequest): Promise { + return request.post>('/team-pk', data) + .then(res => res.data) +} + +/** + * 发布团队PK + */ +export function publishTeamPK(id: number): Promise { + return request.post>(`/team-pk/${id}/publish`) + .then(res => res.data) +} + +/** + * 结束团队PK + */ +export function endTeamPK(id: number): Promise { + return request.post>(`/team-pk/${id}/end`) + .then(res => res.data) +} + +/** + * 取消团队PK + */ +export function cancelTeamPK(id: number): Promise { + return request.post>(`/team-pk/${id}/cancel`) + .then(res => res.data) +} + +/** + * 删除团队PK + */ +export function deleteTeamPK(id: number): Promise { + return request.delete>(`/team-pk/${id}`) + .then(res => res.data) +} + +/** + * 获取团队PK列表 + */ +export function getTeamPKList(params: { + page?: number + pageSize?: number + status?: string +}): Promise> { + return request.get>>('/team-pk', { params }) + .then(res => res.data) +} + +/** + * 获取团队PK详情 + */ +export function getTeamPKById(id: number): Promise { + return request.get>(`/team-pk/${id}`) + .then(res => res.data) +} + +/** + * 刷新PK数据 + */ +export function refreshTeamPK(id: number): Promise { + return request.post>(`/team-pk/${id}/refresh`) + .then(res => res.data) +} diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts new file mode 100644 index 0000000..4f8ef5a --- /dev/null +++ b/frontend/src/api/types.ts @@ -0,0 +1,29 @@ +// API 通用类型定义 + +/** + * API 响应结果 + */ +export interface Result { + code: number + message: string + data: T +} + +/** + * 分页结果 + */ +export interface PageResult { + records: T[] + total: number + size: number + current: number + pages: number +} + +/** + * 分页请求参数 + */ +export interface PageParams { + page?: number + pageSize?: number +} diff --git a/frontend/src/api/upload.ts b/frontend/src/api/upload.ts new file mode 100644 index 0000000..ca49b06 --- /dev/null +++ b/frontend/src/api/upload.ts @@ -0,0 +1,63 @@ +import axios from 'axios' +import { message } from 'ant-design-vue' + +const API_BASE = (import.meta as any).env?.VITE_API_BASE_URL || '/api' + +export interface UploadResponse { + fileName: string + filePath: string + fileUrl: string + fileSize: number + fileType: string +} + +/** + * 上传文件 + */ +export const uploadFile = (file: File, onProgress?: (progress: number) => void): Promise => { + const formData = new FormData() + formData.append('file', file) + + const token = localStorage.getItem('token') + + return new Promise((resolve, reject) => { + axios.post<{ code: number; message: string; data: UploadResponse }>( + `${API_BASE}/upload/file`, + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + 'Authorization': token ? `Bearer ${token}` : '' + }, + onUploadProgress: (progressEvent) => { + if (onProgress && progressEvent.total) { + const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total) + onProgress(percent) + } + } + } + ) + .then(response => { + if (response.data.code === 200 && response.data.data) { + resolve(response.data.data) + } else { + message.error(response.data.message || '上传失败') + reject(new Error(response.data.message || '上传失败')) + } + }) + .catch(error => { + const errorMsg = error.response?.data?.message || error.message || '上传失败' + message.error(errorMsg) + reject(new Error(errorMsg)) + }) + }) +} + +/** + * 下载文件 + */ +export const downloadFile = (filePath: string) => { + const url = `${API_BASE}/upload/download?path=${encodeURIComponent(filePath)}` + window.open(url, '_blank') +} + diff --git a/frontend/src/api/user.ts b/frontend/src/api/user.ts new file mode 100644 index 0000000..23cda73 --- /dev/null +++ b/frontend/src/api/user.ts @@ -0,0 +1,141 @@ +import request from '@/utils/request'; + +// 用户相关类型定义 +export interface User { + id?: number; + username: string; + realName?: string; + phone?: string; + email?: string; + deptId?: number; + deptName?: string; + status?: number; + avatar?: string; + createTime?: string; + updateTime?: string; +} + +export interface PageResult { + records: T[]; + total: number; + size: number; + current: number; + pages: number; +} + +/** + * 分页查询用户列表 + */ +export const getUserList = async ( + pageNum: number = 1, + pageSize: number = 10, + keyword?: string, + deptId?: number, + status?: number +): Promise> => { + const params: any = { pageNum, pageSize }; + if (keyword) params.keyword = keyword; + if (deptId) params.deptId = deptId; + if (status !== undefined) params.status = status; + + const response = await request.get('/users', { params }); + return response.data; +}; + +/** + * 获取全部用户列表 + */ +export const getAllUsers = async (): Promise => { + const response = await request.get('/users/all'); + return response.data || []; +}; + +/** + * 根据ID获取用户 + */ +export const getUserById = async (id: number): Promise => { + const response = await request.get(`/users/${id}`); + return response.data; +}; + +/** + * 创建用户 + */ +export const createUser = async (user: User): Promise => { + const response = await request.post('/users', user); + return response.data; +}; + +/** + * 更新用户 + */ +export const updateUser = async (id: number, user: Partial): Promise => { + const response = await request.put(`/users/${id}`, user); + return response.data; +}; + +/** + * 删除用户 + */ +export const deleteUser = async (id: number): Promise => { + const response = await request.delete(`/users/${id}`); + return response.data; +}; + +/** + * 重置密码 + */ +export const resetPassword = async (id: number, newPassword: string = '123456'): Promise => { + const response = await request.post(`/users/${id}/reset-password`, { newPassword }); + return response.data; +}; + +/** + * 修改密码 + */ +export const changePassword = async ( + id: number, + oldPassword: string, + newPassword: string +): Promise => { + const response = await request.post(`/users/${id}/change-password`, { + oldPassword, + newPassword + }); + return response.data; +}; + +/** + * 为用户分配角色 + */ +export const assignUserRoles = async (userId: number, roleIds: number[]): Promise => { + const response = await request.post(`/users/${userId}/roles`, roleIds); + return response.data; +}; + +/** + * 获取用户的角色ID列表 + */ +export const getUserRoleIds = async (userId: number): Promise => { + const response = await request.get(`/users/${userId}/roles`); + return response.data; +}; + +/** + * 启用/禁用用户 + */ +export const toggleUserStatus = async (id: number, status: number): Promise => { + const response = await request.put(`/users/${id}/status`, null, { + params: { status } + }); + return response.data; +}; + +/** + * 调整用户所属部门 + */ +export const updateUserDepartment = async (id: number, deptId: number | null): Promise => { + const response = await request.put(`/users/${id}/department`, { deptId }); + return response.data; +}; + diff --git a/frontend/src/components/DailyProgressReminder.vue b/frontend/src/components/DailyProgressReminder.vue new file mode 100644 index 0000000..46c80ec --- /dev/null +++ b/frontend/src/components/DailyProgressReminder.vue @@ -0,0 +1,391 @@ + + + + + diff --git a/frontend/src/components/Icon.vue b/frontend/src/components/Icon.vue new file mode 100644 index 0000000..7fc1336 --- /dev/null +++ b/frontend/src/components/Icon.vue @@ -0,0 +1,219 @@ + + + + + + diff --git a/frontend/src/components/IncentiveModal.vue b/frontend/src/components/IncentiveModal.vue new file mode 100644 index 0000000..07a5636 --- /dev/null +++ b/frontend/src/components/IncentiveModal.vue @@ -0,0 +1,281 @@ + + + + + + diff --git a/frontend/src/components/MilestoneCard.vue b/frontend/src/components/MilestoneCard.vue new file mode 100644 index 0000000..25c20af --- /dev/null +++ b/frontend/src/components/MilestoneCard.vue @@ -0,0 +1,390 @@ + + + + + + diff --git a/frontend/src/components/Navbar.vue b/frontend/src/components/Navbar.vue new file mode 100644 index 0000000..b60a055 --- /dev/null +++ b/frontend/src/components/Navbar.vue @@ -0,0 +1,438 @@ + + + + + + diff --git a/frontend/src/components/NotificationBell.vue b/frontend/src/components/NotificationBell.vue new file mode 100644 index 0000000..85ead6d --- /dev/null +++ b/frontend/src/components/NotificationBell.vue @@ -0,0 +1,410 @@ + + + + + + diff --git a/frontend/src/components/RankingCard.vue b/frontend/src/components/RankingCard.vue new file mode 100644 index 0000000..e8c6a23 --- /dev/null +++ b/frontend/src/components/RankingCard.vue @@ -0,0 +1,293 @@ + + + + + + diff --git a/frontend/src/components/TaskCard.vue b/frontend/src/components/TaskCard.vue new file mode 100644 index 0000000..5569632 --- /dev/null +++ b/frontend/src/components/TaskCard.vue @@ -0,0 +1,442 @@ + + + + + diff --git a/frontend/src/layouts/DefaultLayout.vue b/frontend/src/layouts/DefaultLayout.vue new file mode 100644 index 0000000..f393d4d --- /dev/null +++ b/frontend/src/layouts/DefaultLayout.vue @@ -0,0 +1,25 @@ + + + + + + diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000..d778cd0 --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,17 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import router from './router' +import App from './App.vue' + +// 引入 Ant Design Vue 样式 +import 'ant-design-vue/dist/reset.css' +// 引入全局样式 +import './styles/index.scss' + +const app = createApp(App) + +app.use(createPinia()) +app.use(router) + +app.mount('#app') + diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts new file mode 100644 index 0000000..a203ffe --- /dev/null +++ b/frontend/src/router/index.ts @@ -0,0 +1,248 @@ +import { createRouter, createWebHistory } from 'vue-router' +import type { RouteRecordRaw } from 'vue-router' +import { useAuthStore } from '@/stores/auth' + +const routes: RouteRecordRaw[] = [ + { + path: '/', + name: 'Home', + redirect: '/tasks' + }, + { + path: '/tasks', + name: 'TaskHall', + component: () => import('@/views/TaskHall.vue'), + meta: { + title: '任务大厅' + } + }, + { + path: '/tasks/create', + name: 'CreateTask', + component: () => import('@/views/CreateTask.vue'), + meta: { + title: '发起任务' + } + }, + { + path: '/my-tasks', + name: 'MyTasks', + component: () => import('@/views/MyTasks.vue'), + meta: { + title: '我的任务' + } + }, + { + path: '/leader-workbench', + name: 'LeaderWorkbench', + component: () => import('@/views/LeaderWorkbench.vue'), + meta: { + title: '负责人工作台' + } + }, + { + path: '/designer-hall', + name: 'DesignerHall', + component: () => import('@/views/DesignerHall.vue'), + meta: { + title: '设计师大厅' + } + }, + { + path: '/analytics', + name: 'Analytics', + component: () => import('@/views/Analytics.vue'), + meta: { + title: '数据分析' + } + }, + { + path: '/settings', + name: 'Settings', + component: () => import('@/views/Settings.vue'), + meta: { + title: '系统设置' + } + }, + { + path: '/users', + name: 'UserManagement', + component: () => import('@/views/UserManagement.vue'), + meta: { + title: '用户管理' + } + }, + { + path: '/roles', + name: 'RoleManagement', + component: () => import('@/views/RoleManagement.vue'), + meta: { + title: '角色管理' + } + }, + { + path: '/notifications', + name: 'Notifications', + component: () => import('@/views/Notifications.vue'), + meta: { + title: '消息中心' + } + }, + { + path: '/checkin', + name: 'CheckIn', + component: () => import('@/views/CheckIn.vue'), + meta: { + title: '每日签到' + } + }, + { + path: '/achievements', + name: 'Achievements', + component: () => import('@/views/Achievements.vue'), + meta: { + title: '成就中心' + } + }, + { + path: '/contests', + name: 'Contests', + component: () => import('@/views/Contests.vue'), + meta: { + title: '竞赛活动' + } + }, + { + path: '/goals', + name: 'Goals', + component: () => import('@/views/Goals.vue'), + meta: { + title: '我的目标' + } + }, + { + path: '/team-pk', + name: 'TeamPK', + component: () => import('@/views/TeamPK.vue'), + meta: { + title: '团队PK' + } + }, + { + path: '/incentive', + name: 'IncentiveCenter', + component: () => import('@/views/IncentiveCenter.vue'), + meta: { + title: '激励中心' + } + }, + { + path: '/admin/tasks', + name: 'AdminTaskManagement', + component: () => import('@/views/admin/TaskManagement.vue'), + meta: { + title: '任务调配管理' + } + }, + { + path: '/admin/reward-rules', + name: 'RewardRules', + component: () => import('@/views/admin/RewardRules.vue'), + meta: { + title: '自动奖励规则' + } + }, + { + path: '/admin/score-pool', + name: 'ScorePoolManagement', + component: () => import('@/views/admin/ScorePoolManagement.vue'), + meta: { + title: '激励积分池管理' + } + }, + { + path: '/login', + name: 'Login', + component: () => import('@/views/Login.vue'), + meta: { + title: '登录', + layout: 'blank' + } + }, + { + path: '/:pathMatch(.*)*', + name: 'NotFound', + component: () => import('@/views/NotFound.vue'), + meta: { + title: '页面未找到', + layout: 'blank' + } + } +] + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes +}) + +// 路由守卫 +router.beforeEach(async (to, from, next) => { + // 设置页面标题 + document.title = `${to.meta.title || ''} - DesignHub` + + // 身份验证逻辑 + const authStore = useAuthStore() + const token = localStorage.getItem('token') + + // 如果访问登录页 + if (to.path === '/login') { + // 如果已登录,跳转到首页 + if (token && authStore.isLoggedIn) { + next('/') + return + } + next() + return + } + + // 如果未登录,跳转到登录页 + if (!token) { + next('/login') + return + } + + // 如果有token但authStore中没有用户信息,尝试恢复登录状态 + if (token && !authStore.userInfo) { + try { + // 恢复token到authStore + authStore.token = token + + // 尝试获取用户信息验证token有效性 + await authStore.fetchUserInfo() + + // 如果获取用户信息失败,说明token无效,清除并跳转登录页 + if (!authStore.userInfo) { + localStorage.removeItem('token') + localStorage.removeItem('userInfo') + authStore.token = '' + next('/login') + return + } + } catch (error) { + // 如果获取用户信息失败,清除token并跳转登录页 + console.error('验证登录状态失败:', error) + localStorage.removeItem('token') + localStorage.removeItem('userInfo') + authStore.token = '' + authStore.userInfo = null + next('/login') + return + } + } + + // 确保异步操作完成后再继续导航 + next() +}) + +export default router + diff --git a/frontend/src/stores/auth.ts b/frontend/src/stores/auth.ts new file mode 100644 index 0000000..78baec2 --- /dev/null +++ b/frontend/src/stores/auth.ts @@ -0,0 +1,167 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import * as authApi from '@/api/auth' +import type { UserInfo } from '@/api/auth' +import { message } from 'ant-design-vue' +import { useRouter } from 'vue-router' + +export const useAuthStore = defineStore('auth', () => { + const router = useRouter() + + // State + const token = ref(localStorage.getItem('token') || '') + + // 安全地解析 userInfo + const getUserInfoFromStorage = (): UserInfo | null => { + try { + const stored = localStorage.getItem('userInfo') + if (!stored || stored === 'undefined' || stored === 'null') { + return null + } + return JSON.parse(stored) + } catch (error) { + console.error('解析 userInfo 失败:', error) + return null + } + } + + const userInfo = ref(getUserInfoFromStorage()) + + // Getters + const isLoggedIn = computed(() => !!token.value) + const userName = computed(() => userInfo.value?.realName || userInfo.value?.username || '') + const userRoles = computed(() => userInfo.value?.roles || []) + + // Actions + /** + * 登录 + */ + const login = async (username: string, password: string) => { + try { + const response = await authApi.login({ + username, + password, + loginType: 'password' + }) + + const loginData = response.data + + // 保存 token + const accessToken = loginData.token || loginData.accessToken + token.value = accessToken + localStorage.setItem('token', accessToken) + + // 登录成功后立即获取完整用户信息(包括deptId) + try { + await fetchUserInfo() + } catch (error) { + console.error('获取用户信息失败:', error) + // 即使获取失败也不影响登录,使用基本信息 + const userInfoData = { + id: loginData.userId, + username: loginData.username, + realName: loginData.realName, + avatar: loginData.avatar, + email: '', + phone: '', + deptId: null, + deptName: '', + roles: loginData.roles || [], + permissions: [] + } + userInfo.value = userInfoData + localStorage.setItem('userInfo', JSON.stringify(userInfoData)) + } + + message.success('登录成功') + return true + } catch (error: any) { + console.error('登录失败:', error) + message.error(error.message || '登录失败') + return false + } + } + + /** + * 退出登录 + */ + const logout = async () => { + try { + await authApi.logout() + } catch (error) { + console.error('退出登录失败:', error) + } finally { + // 清除本地数据 + token.value = '' + userInfo.value = null + localStorage.removeItem('token') + localStorage.removeItem('userInfo') + + // 跳转到登录页 + router.push('/login') + message.success('已退出登录') + } + } + + /** + * 获取用户信息 + */ + const fetchUserInfo = async () => { + try { + const response = await authApi.getUserInfo() + const userData = response.data + userInfo.value = userData + localStorage.setItem('userInfo', JSON.stringify(userData)) + return userData + } catch (error) { + console.error('获取用户信息失败:', error) + // 如果获取用户信息失败,清除登录状态 + token.value = '' + userInfo.value = null + localStorage.removeItem('token') + localStorage.removeItem('userInfo') + throw error + } + } + + /** + * 刷新Token + */ + const refreshToken = async () => { + try { + const response = await authApi.refreshToken() + if (response.data.code === 200) { + const loginData = response.data.data + // 后端返回的是 token,不是 accessToken + const accessToken = loginData.token || loginData.accessToken + token.value = accessToken + localStorage.setItem('token', accessToken) + + // 如果返回了用户信息,也更新 + if (loginData.userInfo) { + userInfo.value = loginData.userInfo + localStorage.setItem('userInfo', JSON.stringify(loginData.userInfo)) + } + + return true + } + return false + } catch (error) { + console.error('刷新Token失败:', error) + return false + } + } + + return { + token, + userInfo, + isLoggedIn, + userName, + userRoles, + login, + logout, + fetchUserInfo, + refreshToken + } +}) + diff --git a/frontend/src/stores/config.ts b/frontend/src/stores/config.ts new file mode 100644 index 0000000..fb7b68f --- /dev/null +++ b/frontend/src/stores/config.ts @@ -0,0 +1,132 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import * as configApi from '@/api/config' +import type { DemandType, Brand, Platform, Category } from '@/api/config' + +/** + * 全局配置数据Store + * 用于管理需求类型、品牌、平台、类目等配置数据 + * 确保数据在整个项目中同步 + */ +export const useConfigStore = defineStore('config', () => { + // 需求类型(只存储启用的,用于下拉选择) + const demandTypes = ref([]) + + // 品牌(只存储启用的) + const brands = ref([]) + + // 平台(只存储启用的) + const platforms = ref([]) + + // 类目(只存储叶子类目,用于下拉选择) + const categories = ref([]) + + /** + * 加载启用的需求类型 + */ + const loadDemandTypes = async () => { + try { + const response = await configApi.getEnabledDemandTypes() + if (response && response.data) { + demandTypes.value = response.data + } + } catch (error) { + console.error('加载需求类型失败:', error) + } + } + + /** + * 加载启用的品牌 + */ + const loadBrands = async () => { + try { + const response = await configApi.getEnabledBrands() + if (response && response.data) { + brands.value = response.data + } + } catch (error) { + console.error('加载品牌失败:', error) + } + } + + /** + * 加载启用的平台 + */ + const loadPlatforms = async () => { + try { + const response = await configApi.getEnabledPlatforms() + if (response && response.data) { + platforms.value = response.data + } + } catch (error) { + console.error('加载平台失败:', error) + } + } + + /** + * 加载启用的叶子类目 + */ + const loadCategories = async () => { + try { + const response = await configApi.getEnabledLeafCategories() + if (response && response.data) { + categories.value = response.data + } + } catch (error) { + console.error('加载类目失败:', error) + } + } + + /** + * 加载所有配置数据 + */ + const loadAllConfigs = async () => { + await Promise.all([ + loadDemandTypes(), + loadBrands(), + loadPlatforms(), + loadCategories() + ]) + } + + /** + * 刷新配置数据(当系统设置页面更新后调用) + */ + const refreshConfigs = async (type?: 'demandType' | 'brand' | 'platform' | 'category') => { + switch (type) { + case 'demandType': + await loadDemandTypes() + break + case 'brand': + await loadBrands() + break + case 'platform': + await loadPlatforms() + break + case 'category': + await loadCategories() + break + default: + await loadAllConfigs() + } + + // 触发自定义事件,通知其他组件更新 + window.dispatchEvent(new CustomEvent('configUpdated', { + detail: { type } + })) + } + + return { + demandTypes, + brands, + platforms, + categories, + loadDemandTypes, + loadBrands, + loadPlatforms, + loadCategories, + loadAllConfigs, + refreshConfigs + } +}) + diff --git a/frontend/src/stores/navbarConfig.ts b/frontend/src/stores/navbarConfig.ts new file mode 100644 index 0000000..790ef35 --- /dev/null +++ b/frontend/src/stores/navbarConfig.ts @@ -0,0 +1,144 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import request from '@/utils/request' + +/** + * 导航栏配置数据类型 + */ +export interface NavbarConfig { + // 主菜单显示配置 + menuItems: { + tasks: boolean + designer_hall: boolean + incentive: boolean + analytics: boolean + } + // 功能按钮显示配置 + actions: { + notification: boolean + checkin: boolean + achievement: boolean + settings: boolean + } +} + +/** + * 导航栏配置Store + * 用于管理导航栏菜单项和功能按钮的显示/隐藏 + */ +export const useNavbarConfigStore = defineStore('navbarConfig', () => { + // 导航栏配置 + const config = ref({ + menuItems: { + tasks: true, + designer_hall: true, + incentive: true, + analytics: true + }, + actions: { + notification: true, + checkin: true, + achievement: true, + settings: true + } + }) + + /** + * 从后端加载导航栏配置 + */ + const loadConfig = async () => { + try { + const response = await request.get>('/system-config/map/group/navbar') + if (response.data && Object.keys(response.data).length > 0) { + const data = response.data + + // 解析主菜单配置(如果没有配置则默认为 true) + config.value.menuItems = { + tasks: data['navbar.menu.tasks'] !== 'false', + designer_hall: data['navbar.menu.designer_hall'] !== 'false', + incentive: data['navbar.menu.incentive'] !== 'false', + analytics: data['navbar.menu.analytics'] !== 'false' + } + + // 解析功能按钮配置(如果没有配置则默认为 true) + config.value.actions = { + notification: data['navbar.action.notification'] !== 'false', + checkin: data['navbar.action.checkin'] !== 'false', + achievement: data['navbar.action.achievement'] !== 'false', + settings: data['navbar.action.settings'] !== 'false' + } + + console.log('[NavbarConfig] 配置加载成功:', config.value) + } else { + console.log('[NavbarConfig] 数据库中没有配置,使用默认配置(全部显示)') + // 数据库中没有配置,使用默认配置(全部显示) + resetToDefault() + } + } catch (error) { + console.error('[NavbarConfig] 加载配置失败:', error) + // 失败时使用默认配置(全部显示) + resetToDefault() + } + } + + /** + * 保存导航栏配置到后端 + */ + const saveConfig = async (newConfig: NavbarConfig) => { + try { + // 将配置转换为 key-value 格式 + const configMap: Record = { + 'navbar.menu.tasks': String(newConfig.menuItems.tasks), + 'navbar.menu.designer_hall': String(newConfig.menuItems.designer_hall), + 'navbar.menu.incentive': String(newConfig.menuItems.incentive), + 'navbar.menu.analytics': String(newConfig.menuItems.analytics), + 'navbar.action.notification': String(newConfig.actions.notification), + 'navbar.action.checkin': String(newConfig.actions.checkin), + 'navbar.action.achievement': String(newConfig.actions.achievement), + 'navbar.action.settings': String(newConfig.actions.settings) + } + + await request.put('/system-config/batch', configMap) + + // 保存成功后更新本地配置 + config.value = { ...newConfig } + + console.log('[NavbarConfig] 配置保存成功') + + // 触发自定义事件,通知导航栏组件更新 + window.dispatchEvent(new CustomEvent('navbarConfigUpdated')) + + return true + } catch (error) { + console.error('[NavbarConfig] 保存配置失败:', error) + throw error + } + } + + /** + * 重置为默认配置(全部显示) + */ + const resetToDefault = () => { + config.value = { + menuItems: { + tasks: true, + designer_hall: true, + incentive: true, + analytics: true + }, + actions: { + notification: true, + checkin: true, + achievement: true, + settings: true + } + } + } + + return { + config, + loadConfig, + saveConfig, + resetToDefault + } +}) diff --git a/frontend/src/stores/siteSettings.ts b/frontend/src/stores/siteSettings.ts new file mode 100644 index 0000000..cb5c920 --- /dev/null +++ b/frontend/src/stores/siteSettings.ts @@ -0,0 +1,126 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { getSystemConfigsByGroup } from '@/api/system' + +export const useSiteSettingsStore = defineStore('siteSettings', () => { + const siteName = ref('DesignHub') + const logoUrl = ref('') + + // 加载网站设置 + const loadSiteSettings = async () => { + try { + const response = await getSystemConfigsByGroup('site') + const configs = response.data || [] + + const siteNameConfig = configs.find((c: any) => c.configKey === 'site.name') + const logoConfig = configs.find((c: any) => c.configKey === 'site.logo') + + if (siteNameConfig) { + siteName.value = siteNameConfig.configValue || 'DesignHub' + } else { + siteName.value = localStorage.getItem('siteName') || 'DesignHub' + } + + if (logoConfig) { + logoUrl.value = logoConfig.configValue || '' + } else { + logoUrl.value = localStorage.getItem('logoUrl') || '' + } + + // 更新浏览器标题和favicon + updateBrowserInfo() + } catch (error) { + console.error('加载网站设置失败:', error) + // 如果API失败,从localStorage读取(向后兼容) + siteName.value = localStorage.getItem('siteName') || 'DesignHub' + logoUrl.value = localStorage.getItem('logoUrl') || '' + updateBrowserInfo() + } + } + + // 更新网站设置 + const updateSiteSettings = (newSiteName: string, newLogoUrl: string) => { + siteName.value = newSiteName || 'DesignHub' + logoUrl.value = newLogoUrl || '' + + // 保存到localStorage + localStorage.setItem('siteName', siteName.value) + if (logoUrl.value) { + localStorage.setItem('logoUrl', logoUrl.value) + } else { + localStorage.removeItem('logoUrl') + } + + // 更新浏览器标题和favicon + updateBrowserInfo() + + // 触发自定义事件,通知其他组件更新 + window.dispatchEvent(new CustomEvent('siteSettingsUpdated', { + detail: { + siteName: siteName.value, + logoUrl: logoUrl.value + } + })) + } + + // 更新浏览器标题和favicon + const updateBrowserInfo = () => { + // 更新浏览器标题 + document.title = siteName.value || 'DesignHub' + + // 更新favicon(如果有logo) + if (logoUrl.value) { + // 移除所有现有的favicon链接 + const existingLinks = document.querySelectorAll("link[rel*='icon']") + existingLinks.forEach(link => link.remove()) + + // 创建新的favicon链接 + const link = document.createElement('link') + link.rel = 'icon' + + // 处理base64和普通URL + if (logoUrl.value.startsWith('data:')) { + // base64格式 + const mimeMatch = logoUrl.value.match(/data:([^;]+)/) + link.type = mimeMatch?.[1] || 'image/png' + link.href = logoUrl.value + } else { + // 普通URL,添加时间戳强制刷新 + link.type = 'image/x-icon' + link.href = `${logoUrl.value}?t=${Date.now()}` + } + + // 添加到head + document.getElementsByTagName('head')[0].appendChild(link) + + // 同时添加apple-touch-icon(移动端支持) + const appleLink = document.createElement('link') + appleLink.rel = 'apple-touch-icon' + if (logoUrl.value.startsWith('data:')) { + appleLink.href = logoUrl.value + } else { + appleLink.href = `${logoUrl.value}?t=${Date.now()}` + } + document.getElementsByTagName('head')[0].appendChild(appleLink) + } else { + // 如果没有logo,移除所有favicon链接 + const existingLinks = document.querySelectorAll("link[rel*='icon']") + existingLinks.forEach(link => { + // 保留默认的vite.svg favicon(如果存在) + const href = (link as HTMLLinkElement).href + if (!href.includes('vite.svg')) { + link.remove() + } + }) + } + } + + return { + siteName, + logoUrl, + loadSiteSettings, + updateSiteSettings, + updateBrowserInfo + } +}) + diff --git a/frontend/src/styles/index.scss b/frontend/src/styles/index.scss new file mode 100644 index 0000000..4aabfe3 --- /dev/null +++ b/frontend/src/styles/index.scss @@ -0,0 +1,1440 @@ +/** + * 全局样式入口 + * Cursor 风格设计系统 + * 参考 Cursor 官方网站 (cursor.com) 的设计规范 + */ + +@import './variables.scss'; + +// ===== CSS 变量(用于 JavaScript 访问)===== +:root { + // 背景色 + --bg-primary: #{$bg-primary}; + --bg-secondary: #{$bg-secondary}; + --bg-tertiary: #{$bg-tertiary}; + --bg-card: #{$bg-card}; + --bg-card-hover: #{$bg-card-hover}; + --bg-elevated: #{$bg-elevated}; + + // 文字色 + --text-primary: #{$text-primary}; + --text-secondary: #{$text-secondary}; + --text-tertiary: #{$text-tertiary}; + --text-disabled: #{$text-disabled}; + + // 品牌色 + --brand-primary: #{$brand-primary}; + --brand-hover: #{$brand-hover}; + --brand-pressed: #{$brand-pressed}; + + // 功能色 + --color-success: #{$color-success}; + --color-warning: #{$color-warning}; + --color-error: #{$color-error}; + --color-info: #{$color-info}; + + // 边框色 + --border-primary: #{$border-primary}; + --border-secondary: #{$border-secondary}; + --border-divider: #{$border-divider}; + + // 间距 + --space-sm: #{$space-sm}; + --space-md: #{$space-md}; + --space-lg: #{$space-lg}; + --space-xl: #{$space-xl}; + + // 圆角 + --radius-small: #{$radius-small}; + --radius-medium: #{$radius-medium}; + --radius-large: #{$radius-large}; + + // 过渡 + --transition-fast: #{$transition-fast}; + --transition-base: #{$transition-base}; + --transition-slow: #{$transition-slow}; + --ease-out: #{$ease-out}; + + // 布局 + --navbar-height: #{$navbar-height}; + --container-max-width: #{$container-max-width}; +} + +// ===== 全局重置 ===== +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + font-size: 16px; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; +} + +body { + margin: 0; + font-family: $font-family-zh; + font-size: $font-size-base; + line-height: 1.5; + color: $text-primary; + background: $bg-primary; + overflow-x: hidden; +} + +// ===== 链接样式 ===== +a { + color: $brand-primary; + text-decoration: none; + transition: color $transition-base $ease-out; + + &:hover { + color: $brand-hover; + } +} + +// ===== 滚动条样式(Cursor 风格:更细腻的滚动条)===== +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: $border-primary; + border-radius: $radius-full; + + &:hover { + background: $border-color; + } +} + +// ===== 通用工具类 ===== + +// 容器 +.container { + max-width: $container-max-width; + margin: 0 auto; + padding: 0 $space-xl; + + @media (max-width: $breakpoint-tablet) { + padding: 0 $space-lg; + } + + @media (max-width: $breakpoint-mobile) { + padding: 0 $space-md; + } +} + +// 卡片(Cursor 风格:更简洁的卡片) +.card { + background: $bg-card; + border: 1px solid $border-primary; + border-radius: $radius-md; + padding: $space-lg; + transition: all $transition-base $ease-out; + + &:hover { + border-color: $border-color; + background: $bg-card-hover; + box-shadow: $shadow-base; + } +} + +// 按钮样式(Cursor 风格:更柔和的按钮) +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: $space-sm; + padding: 10px 20px; + font-size: $font-size-base; + font-weight: $font-weight-medium; + line-height: 1.5; + border-radius: $radius-md; + border: none; + cursor: pointer; + transition: all $transition-base $ease-out; + white-space: nowrap; + user-select: none; + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +} + +.btn-primary { + background: $brand-primary; + color: #000000 !important; // 黑色文字 + + &:hover:not(:disabled) { + background: $brand-hover; + color: #000000 !important; // 黑色文字 + box-shadow: 0 2px 8px rgba($brand-primary, 0.25); + } + + &:active:not(:disabled) { + background: $brand-pressed; + color: #000000 !important; // 黑色文字 + transform: scale(0.98); + } + + // 确保按钮内的所有文字和图标都是黑色 + span, + .anticon { + color: #000000 !important; + } +} + +.btn-secondary { + background: $bg-tertiary; + color: $text-primary; + border: 1px solid $border-primary; + + &:hover:not(:disabled) { + background: $bg-card; + border-color: $border-color; + color: $text-primary; + } +} + +.btn-text { + background: transparent; + color: $text-secondary; + padding: $space-sm $space-md; + + &:hover:not(:disabled) { + color: $brand-primary; + } +} + +// 输入框(Cursor 风格:更柔和的输入框) +.input { + width: 100%; + padding: 10px 16px; + font-size: $font-size-base; + line-height: 1.5; + color: $text-primary; + background: $bg-tertiary; + border: 1px solid $border-primary; + border-radius: $radius-md; + transition: all $transition-base $ease-out; + + &:focus { + outline: none; + border-color: $brand-primary; + background: $bg-card; + box-shadow: 0 0 0 2px rgba($brand-primary, 0.08); + } + + &::placeholder { + color: $text-tertiary; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +} + +// ===== 全局 Ant Design 输入框样式统一 ===== +// 统一所有页面的输入框样式(与登录页面一致) + +// 基础输入框 +.ant-input, +.ant-input-affix-wrapper { + background: $bg-secondary !important; + border: 1px solid $border-color !important; + border-radius: $radius-md !important; + color: $text-white !important; + font-size: $font-size-base; + transition: all 0.2s ease; + height: 48px !important; + line-height: 48px !important; + padding: 0 12px !important; + box-sizing: border-box; + + &::placeholder { + color: $text-tertiary !important; + line-height: normal; + } + + &:hover { + background: $bg-secondary !important; + border-color: rgba($brand-primary, 0.5) !important; + color: $text-white !important; + } + + &:focus, + &.ant-input-focused, + &:focus-visible { + background: $bg-secondary !important; + border-color: $brand-primary !important; + box-shadow: 0 0 0 2px rgba($brand-primary, 0.1) !important; + color: $text-white !important; + outline: none !important; + } + + &:disabled { + background: $bg-tertiary !important; + border-color: $border-primary !important; + color: $text-disabled !important; + cursor: not-allowed; + } +} + +// 输入框大小变体 +.ant-input-sm { + height: 32px !important; + line-height: 32px !important; + font-size: $font-size-sm !important; + padding: 0 8px !important; +} + +.ant-input-lg { + height: 48px !important; + line-height: 48px !important; + font-size: $font-size-base !important; + padding: 0 12px !important; +} + +// 搜索框 +.ant-input-search { + .ant-input { + border-radius: $radius-md 0 0 $radius-md !important; + } + + .ant-input-search-button { + background: $bg-secondary !important; + border: 1px solid $border-color !important; + border-left: none !important; + color: $text-white !important; + height: 48px !important; + border-radius: 0 $radius-md $radius-md 0 !important; + + &:hover { + background: $bg-tertiary !important; + border-color: rgba($brand-primary, 0.5) !important; + color: $text-white !important; + } + } + + &:hover .ant-input, + &.ant-input-search-focused .ant-input { + border-color: rgba($brand-primary, 0.5) !important; + } + + &.ant-input-search-focused .ant-input { + border-color: $brand-primary !important; + box-shadow: 0 0 0 2px rgba($brand-primary, 0.1) !important; + } +} + +// 密码输入框 +.ant-input-password { + background: $bg-secondary !important; + border: 1px solid $border-color !important; + border-radius: $radius-md !important; + height: 48px !important; + width: 100% !important; + box-shadow: none !important; + transition: all 0.2s ease; + padding: 0 !important; + display: flex; + align-items: center; + + .ant-input { + background: transparent !important; + border: none !important; + box-shadow: none !important; + color: $text-white !important; + height: 48px; + line-height: 48px; + font-size: $font-size-base; + padding: 0 40px 0 12px !important; + text-align: left; + flex: 1; + width: 100%; + box-sizing: border-box; + + &::placeholder { + color: $text-tertiary !important; + line-height: normal; + } + + &:hover { + background: transparent !important; + color: $text-white !important; + } + + &:focus, + &:focus-visible { + background: transparent !important; + color: $text-white !important; + outline: none !important; + box-shadow: none !important; + } + } + + .ant-input-password-icon { + color: $text-secondary; + right: 12px; + + &:hover { + color: $text-primary; + } + } + + &:hover { + background: $bg-secondary !important; + border-color: rgba($brand-primary, 0.5) !important; + } + + &.ant-input-password-focused, + &:focus, + &:focus-visible { + background: $bg-secondary !important; + border-color: $brand-primary !important; + box-shadow: 0 0 0 2px rgba($brand-primary, 0.1) !important; + outline: none !important; + } +} + +// 下拉选择框 +.ant-select { + .ant-select-selector { + background: $bg-secondary !important; + border: 1px solid $border-color !important; + border-radius: $radius-md !important; + color: $text-white !important; + height: 48px !important; + transition: all 0.2s ease; + + .ant-select-selection-item, + .ant-select-selection-placeholder { + color: $text-white !important; + line-height: 46px !important; + } + + .ant-select-selection-placeholder { + color: $text-tertiary !important; + } + } + + &:hover .ant-select-selector { + border-color: rgba($brand-primary, 0.5) !important; + } + + &.ant-select-focused .ant-select-selector { + border-color: $brand-primary !important; + box-shadow: 0 0 0 2px rgba($brand-primary, 0.1) !important; + } + + .ant-select-arrow { + color: $text-secondary !important; + } + + // 大小变体 + &.ant-select-sm .ant-select-selector { + height: 32px !important; + + .ant-select-selection-item, + .ant-select-selection-placeholder { + line-height: 30px !important; + } + } + + &.ant-select-lg .ant-select-selector { + height: 48px !important; + + .ant-select-selection-item, + .ant-select-selection-placeholder { + line-height: 46px !important; + } + } +} + +// 下拉菜单样式 +.ant-select-dropdown { + background: $bg-card !important; + border: 1px solid $border-color !important; + border-radius: $radius-md !important; + box-shadow: $shadow-elevated !important; + + .ant-select-item { + color: $text-white !important; + background: transparent !important; + + &:hover { + background: $bg-tertiary !important; + } + + &.ant-select-item-option-selected { + background: rgba($brand-primary, 0.1) !important; + color: $brand-primary !important; + } + + &.ant-select-item-option-active { + background: $bg-tertiary !important; + } + } +} + +// 文本域(Textarea) +.ant-textarea, +textarea.ant-input { + background: $bg-secondary !important; + border: 1px solid $border-color !important; + border-radius: $radius-md !important; + color: $text-white !important; + font-size: $font-size-base !important; + transition: all 0.2s ease !important; + min-height: auto !important; + padding: 12px !important; + line-height: 1.5 !important; + height: auto !important; + resize: vertical !important; + + &::placeholder { + color: $text-tertiary !important; + } + + &:hover { + background: $bg-secondary !important; + border-color: rgba($brand-primary, 0.5) !important; + } + + &:focus, + &:focus-visible { + background: $bg-secondary !important; + border-color: $brand-primary !important; + box-shadow: 0 0 0 2px rgba($brand-primary, 0.1) !important; + outline: none !important; + } +} + +// Textarea 包装器 +.ant-input-textarea { + .ant-input { + background: $bg-secondary !important; + border: 1px solid $border-color !important; + + &:hover { + border-color: rgba($brand-primary, 0.5) !important; + } + + &:focus { + border-color: $brand-primary !important; + box-shadow: 0 0 0 2px rgba($brand-primary, 0.1) !important; + } + } +} + +// 数字输入框 +.ant-input-number { + width: 100%; + background: $bg-secondary !important; + border: 1px solid $border-color !important; + border-radius: $radius-md !important; + height: 48px !important; + + .ant-input-number-input { + background: transparent !important; + border: none !important; + color: $text-white !important; + height: 46px !important; + line-height: 46px !important; + padding: 0 12px !important; + + &::placeholder { + color: $text-tertiary !important; + } + } + + &:hover { + border-color: rgba($brand-primary, 0.5) !important; + } + + &.ant-input-number-focused { + border-color: $brand-primary !important; + box-shadow: 0 0 0 2px rgba($brand-primary, 0.1) !important; + } +} + +// ===== 全局 Ant Design 按钮样式统一 ===== +// 统一所有页面的主要按钮(绿色按钮)文字颜色为黑色 + +// 确保所有 primary 按钮都是绿色(覆盖 Ant Design 默认蓝色) +.ant-btn-primary, +.ant-btn[type="primary"], +button.ant-btn-primary, +a.ant-btn-primary { + background: $brand-primary !important; + border-color: $brand-primary !important; + color: #000000 !important; // 黑色文字 + + &:hover:not(:disabled), + &:hover:not(.ant-btn-loading) { + background: $brand-hover !important; + border-color: $brand-hover !important; + color: #000000 !important; // 黑色文字 + } + + &:active:not(:disabled), + &:focus:not(:disabled) { + background: $brand-pressed !important; + border-color: $brand-pressed !important; + color: #000000 !important; // 黑色文字 + } + + &:disabled, + &.ant-btn-loading { + background: $bg-tertiary !important; + border-color: $bg-tertiary !important; + color: $text-disabled !important; + opacity: 0.5; + } + + // 确保按钮内的所有文字和图标都是黑色 + span, + .anticon, + .ant-btn-loading-icon, + .anticon-loading { + color: #000000 !important; + } + + // 确保加载状态下的图标也是黑色 + &.ant-btn-loading { + .anticon-loading { + color: #000000 !important; + } + } +} + +// 确保登录页面的按钮也是黑色文字 +.login-button { + color: #000000 !important; + + span, + .anticon { + color: #000000 !important; + } +} + +// 单选按钮组(Radio Button Group)- 现代化独立按钮设计 +.ant-radio-group { + display: flex; + gap: 8px; // 按钮之间的间距 + align-items: center; // 垂直居中对齐 + + .ant-radio-button-wrapper { + background: $bg-tertiary !important; + border: 1px solid transparent !important; // 默认透明边框 + border-radius: 8px !important; // 圆角 + color: $text-secondary !important; + padding: 6px 16px !important; // 增加垂直padding + font-size: 13px !important; + line-height: 20px !important; // 固定行高,确保文字居中 + height: 32px !important; // 固定高度 + display: inline-flex !important; // 使用flex布局 + align-items: center !important; // 垂直居中 + justify-content: center !important; // 水平居中 + transition: all 0.2s ease !important; + margin: 0 !important; // 移除默认 margin + + // 移除默认的连接样式 + &::before { + display: none !important; // 隐藏分隔线 + } + + &:first-child { + border-radius: 8px !important; // 确保第一个按钮也是圆角 + } + + &:last-child { + border-radius: 8px !important; // 确保最后一个按钮也是圆角 + } + + &:hover:not(.ant-radio-button-wrapper-checked) { + background: rgba($brand-primary, 0.05) !important; + border-color: rgba($brand-primary, 0.3) !important; + color: $brand-primary !important; + transform: translateY(-1px); + } + + &.ant-radio-button-wrapper-checked { + background: rgba($brand-primary, 0.12) !important; + border-color: $brand-primary !important; + color: $brand-primary !important; + font-weight: 500 !important; + box-shadow: 0 0 0 2px rgba($brand-primary, 0.1) !important; + + &:hover { + background: rgba($brand-primary, 0.15) !important; + border-color: $brand-primary !important; + color: $brand-primary !important; + } + + &:active { + background: rgba($brand-primary, 0.2) !important; + border-color: $brand-primary !important; + color: $brand-primary !important; + } + } + + &:focus-within { + box-shadow: 0 0 0 2px rgba($brand-primary, 0.1) !important; + } + + // 确保内部文字也垂直居中 + > span { + display: inline-flex; + align-items: center; + line-height: 20px; + } + } +} + +// 普通单选按钮(Radio) +.ant-radio-wrapper { + color: $text-primary !important; + + .ant-radio { + .ant-radio-inner { + background: $bg-secondary !important; + border-color: $border-primary !important; + } + + &:hover .ant-radio-inner { + border-color: $brand-primary !important; + } + + &.ant-radio-checked .ant-radio-inner { + background: $brand-primary !important; + border-color: $brand-primary !important; + + &::after { + background: #000000 !important; // 选中时内部圆点为黑色 + } + } + } +} + +// Switch 开关 - 全局统一样式 +.ant-switch { + background: $bg-tertiary !important; + border-color: $border-primary !important; + color: $text-primary !important; + + // 未选中状态的文字 + .ant-switch-inner { + color: $text-primary !important; + } + + &.ant-switch-checked { + background: $brand-primary !important; + border-color: $brand-primary !important; + + // 选中状态的文字(checked-children)必须是黑色 + .ant-switch-inner { + color: #000000 !important; + } + + // 选中状态的文字内容也要是黑色 + .ant-switch-inner-checked { + color: #000000 !important; + } + } + + .ant-switch-handle { + &::before { + background: $text-primary !important; + } + } + + &.ant-switch-checked .ant-switch-handle { + &::before { + background: #000000 !important; // 选中时手柄为黑色 + } + } +} + +// 额外确保所有选中的 Switch 文字都是黑色(最高优先级) +.ant-switch.ant-switch-checked { + .ant-switch-inner, + .ant-switch-inner-checked { + color: #000000 !important; + } +} + +// 日期选择器 - 选中日期的文字必须是黑色 +.ant-picker-cell-selected { + .ant-picker-cell-inner { + background: $brand-primary !important; + color: #000000 !important; // 选中日期文字为黑色 + } +} + +// 日期选择器下拉面板中的选中日期 +.ant-picker-dropdown { + .ant-picker-cell-selected .ant-picker-cell-inner { + background: $brand-primary !important; + color: #000000 !important; // 选中日期文字为黑色 + } +} + +// Tag 标签 - 绿色 Tag 的文字必须是黑色 +.ant-tag-green, +.ant-tag[class*="green"] { + background: $brand-primary !important; + border-color: $brand-primary !important; + color: #000000 !important; // 绿色 Tag 文字为黑色 +} + +// 确保所有带有绿色背景的 Tag 文字都是黑色 +.ant-tag { + &.ant-tag-green { + background: $brand-primary !important; + border-color: $brand-primary !important; + color: #000000 !important; + } +} + +// 徽章 +.badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + height: 20px; + padding: 0 6px; + font-size: $font-size-xs; + font-weight: $font-weight-medium; + line-height: 1; + color: $text-white; + background: $color-error; + border-radius: $radius-full; +} + +// 标签 +.tag { + display: inline-flex; + align-items: center; + gap: $space-xs; + padding: 4px 12px; + font-size: $font-size-sm; + font-weight: $font-weight-medium; + border-radius: $radius-small; + + &.tag-success { + color: $color-success; + background: rgba($color-success, 0.1); + border: 1px solid rgba($color-success, 0.3); + } + + &.tag-warning { + color: $color-warning; + background: rgba($color-warning, 0.1); + border: 1px solid rgba($color-warning, 0.3); + } + + &.tag-error { + color: $color-error; + background: rgba($color-error, 0.1); + border: 1px solid rgba($color-error, 0.3); + } + + &.tag-info { + color: $color-info; + background: rgba($color-info, 0.1); + border: 1px solid rgba($color-info, 0.3); + } +} + +// 文本工具类 +.text-primary { + color: $text-primary; +} + +.text-secondary { + color: $text-secondary; +} + +.text-tertiary { + color: $text-tertiary; +} + +.text-brand { + color: $brand-primary; +} + +.text-success { + color: $color-success; +} + +.text-warning { + color: $color-warning; +} + +.text-error { + color: $color-error; +} + +// 间距工具类 +.mt-sm { margin-top: $space-sm; } +.mt-md { margin-top: $space-md; } +.mt-lg { margin-top: $space-lg; } +.mt-xl { margin-top: $space-xl; } + +.mb-sm { margin-bottom: $space-sm; } +.mb-md { margin-bottom: $space-md; } +.mb-lg { margin-bottom: $space-lg; } +.mb-xl { margin-bottom: $space-xl; } + +.ml-sm { margin-left: $space-sm; } +.ml-md { margin-left: $space-md; } +.ml-lg { margin-left: $space-lg; } + +.mr-sm { margin-right: $space-sm; } +.mr-md { margin-right: $space-md; } +.mr-lg { margin-right: $space-lg; } + +.p-sm { padding: $space-sm; } +.p-md { padding: $space-md; } +.p-lg { padding: $space-lg; } +.p-xl { padding: $space-xl; } + +// Flex 布局 +.flex { + display: flex; +} + +.flex-center { + display: flex; + align-items: center; + justify-content: center; +} + +.flex-between { + display: flex; + align-items: center; + justify-content: space-between; +} + +.flex-col { + display: flex; + flex-direction: column; +} + +.gap-sm { gap: $space-sm; } +.gap-md { gap: $space-md; } +.gap-lg { gap: $space-lg; } + +// 隐藏/显示 +.hidden { + display: none !important; +} + +@media (max-width: $breakpoint-mobile) { + .hidden-mobile { + display: none !important; + } +} + +@media (min-width: calc(#{$breakpoint-mobile} + 1px)) { + .show-mobile { + display: none !important; + } +} + +// 加载动画 +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.loading-spinner { + width: 20px; + height: 20px; + border: 2px solid $border-primary; + border-top-color: $brand-primary; + border-radius: $radius-full; + animation: spin 0.8s linear infinite; +} + +// ===== 全局 Ant Design 表格样式统一 ===== +// 统一所有页面的表格样式(深色主题,避免悬停闪白) +:deep(.ant-table) { + // 表头统一样式 + .ant-table-thead > tr > th { + background: $bg-tertiary !important; + border-color: $border-primary !important; + color: $text-primary !important; + font-weight: $font-weight-semibold !important; + font-size: 14px !important; + padding: 16px 12px !important; + text-align: left !important; + white-space: nowrap !important; + + // 第一个表头单元格 + &:first-child { + padding-left: 20px !important; + border-top-left-radius: $radius-md !important; + } + + // 最后一个表头单元格(通常是操作列) + &:last-child { + padding-right: 20px !important; + text-align: center !important; + border-top-right-radius: $radius-md !important; + } + + // 固定列样式 + &.ant-table-cell-fix-right, + &.ant-table-cell-fix-left { + background: $bg-tertiary !important; + } + } + + .ant-table-tbody > tr { + background: $bg-card !important; + transition: none !important; // 移除过渡避免闪烁 + + > td { + background: $bg-card !important; + transition: none !important; + } + + &:hover { + background: $bg-card-hover !important; + transition: none !important; + + > td { + background: $bg-card-hover !important; + transition: none !important; + } + } + } + + // 覆盖 Ant Design 默认样式 + .ant-table-tbody > tr.ant-table-row { + background: $bg-card !important; + transition: none !important; + + > td { + background: $bg-card !important; + transition: none !important; + } + + &:hover { + background: $bg-card-hover !important; + transition: none !important; + + > td { + background: $bg-card-hover !important; + transition: none !important; + } + } + } + + // 确保所有悬停状态都是深色 + .ant-table-tbody > tr:hover > td, + .ant-table-tbody > tr.ant-table-row:hover > td { + background: $bg-card-hover !important; + transition: none !important; + } +} + +// ===== 全局 Ant Design 分页样式统一 ===== +// 统一所有页面的分页组件样式(深色主题) +:deep(.ant-pagination) { + .ant-pagination-item { + background: $bg-tertiary !important; + border-color: $border-primary !important; + + a { + color: $text-primary !important; + } + + &:hover:not(.ant-pagination-item-active) { + border-color: $brand-primary !important; + + a { + color: $brand-primary !important; + } + } + + &.ant-pagination-item-active { + background: $brand-primary !important; + border-color: $brand-primary !important; + + a { + color: #000000 !important; // 当前页文字为黑色 + } + + &:hover { + background: $brand-hover !important; + border-color: $brand-hover !important; + + a { + color: #000000 !important; + } + } + } + } + + .ant-pagination-prev, + .ant-pagination-next { + .ant-pagination-item-link { + background: $bg-tertiary !important; + border-color: $border-primary !important; + color: $text-primary !important; + + &:hover:not(:disabled) { + border-color: $brand-primary !important; + color: $brand-primary !important; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + } + } + + .ant-pagination-jump-prev, + .ant-pagination-jump-next { + .ant-pagination-item-link { + color: $text-primary !important; + + &:hover { + color: $brand-primary !important; + } + } + } + + .ant-pagination-options { + .ant-select { + .ant-select-selector { + background: $bg-tertiary !important; + border-color: $border-primary !important; + color: $text-primary !important; + } + } + + .ant-pagination-options-size-changer { + color: $text-primary !important; + } + } + + .ant-pagination-total-text { + color: $text-primary !important; + } + + // 表格内分页样式 + &.ant-pagination { + .ant-pagination-item { + background: $bg-tertiary !important; + border-color: $border-primary !important; + + a { + color: $text-primary !important; + } + + &.ant-pagination-item-active { + background: $brand-primary !important; + border-color: $brand-primary !important; + + a { + color: #000000 !important; // 当前页文字为黑色 + } + } + } + } +} + +// 表格分页容器 +:deep(.ant-table-pagination) { + .ant-pagination { + .ant-pagination-item { + background: $bg-tertiary !important; + border-color: $border-primary !important; + + a { + color: $text-primary !important; + } + + &.ant-pagination-item-active { + background: $brand-primary !important; + border-color: $brand-primary !important; + + a { + color: #000000 !important; // 当前页文字为黑色 + } + } + } + } +} + +// ===== 全局 Ant Design Modal 弹窗样式统一 ===== +// 统一所有页面的弹窗样式(深色主题) +.ant-modal { + .ant-modal-content { + background: $bg-card !important; + border: 1px solid $border-primary !important; + border-radius: $radius-lg !important; + box-shadow: $shadow-elevated !important; + } + + .ant-modal-header { + background: $bg-card !important; + border-bottom: 1px solid $border-primary !important; + padding: $space-lg $space-xl !important; + + .ant-modal-title { + color: $text-primary !important; + font-size: $font-size-xl !important; + font-weight: $font-weight-semibold !important; + } + } + + .ant-modal-body { + background: $bg-card !important; + padding: $space-xl !important; + color: $text-primary !important; + } + + .ant-modal-footer { + background: $bg-card !important; + border-top: 1px solid $border-primary !important; + padding: $space-md $space-xl !important; + + .ant-btn { + min-width: 80px; + } + } + + .ant-modal-close { + color: $text-secondary !important; + + &:hover { + color: $text-primary !important; + } + } +} + +// Modal 确认框样式 +.ant-modal-confirm { + .ant-modal-confirm-title { + color: $text-primary !important; + } + + .ant-modal-confirm-content { + color: $text-secondary !important; + } +} + +// ===== 全局 Ant Design Drawer 抽屉样式统一 ===== +.ant-drawer { + .ant-drawer-content { + background: $bg-card !important; + } + + .ant-drawer-header { + background: $bg-card !important; + border-bottom: 1px solid $border-primary !important; + padding: $space-lg $space-xl !important; + + .ant-drawer-title { + color: $text-primary !important; + font-size: $font-size-xl !important; + font-weight: $font-weight-semibold !important; + } + + .ant-drawer-close { + color: $text-secondary !important; + + &:hover { + color: $text-primary !important; + } + } + } + + .ant-drawer-body { + background: $bg-card !important; + padding: $space-xl !important; + color: $text-primary !important; + } + + .ant-drawer-footer { + background: $bg-card !important; + border-top: 1px solid $border-primary !important; + padding: $space-md $space-xl !important; + } +} + +// ===== 全局 Ant Design Form 表单样式统一 ===== +.ant-form { + .ant-form-item-label > label { + color: $text-primary !important; + font-weight: $font-weight-medium; + } + + .ant-form-item-required::before { + color: $color-error !important; + } + + .ant-form-item-explain-error { + color: $color-error !important; + font-size: $font-size-sm; + } +} + +// ===== 全局 Ant Design Descriptions 描述列表样式统一 ===== +.ant-descriptions { + .ant-descriptions-header { + .ant-descriptions-title { + color: $text-primary !important; + } + } + + .ant-descriptions-view { + border-color: $border-primary !important; + } + + .ant-descriptions-item-label { + background: $bg-tertiary !important; + color: $text-secondary !important; + border-color: $border-primary !important; + } + + .ant-descriptions-item-content { + background: $bg-card !important; + color: $text-primary !important; + border-color: $border-primary !important; + } + + &.ant-descriptions-bordered { + .ant-descriptions-item-label, + .ant-descriptions-item-content { + border-color: $border-primary !important; + } + } +} + +// ===== 全局 Ant Design Checkbox 复选框样式统一 ===== +.ant-checkbox-wrapper { + color: $text-primary !important; + + .ant-checkbox { + .ant-checkbox-inner { + background: $bg-secondary !important; + border-color: $border-primary !important; + } + + &:hover .ant-checkbox-inner { + border-color: $brand-primary !important; + } + + &.ant-checkbox-checked .ant-checkbox-inner { + background: $brand-primary !important; + border-color: $brand-primary !important; + + &::after { + border-color: #000000 !important; // 勾选标记为黑色 + } + } + } +} + +// ===== 全局 Ant Design Menu 菜单样式统一 ===== +.ant-dropdown-menu { + background: $bg-card !important; + border: 1px solid $border-primary !important; + border-radius: $radius-md !important; + box-shadow: $shadow-elevated !important; + + .ant-dropdown-menu-item { + color: $text-primary !important; + + &:hover { + background: $bg-tertiary !important; + } + + &.ant-dropdown-menu-item-danger { + color: $color-error !important; + + &:hover { + background: rgba($color-error, 0.1) !important; + } + } + } +} + +// ===== 全局 Ant Design Popconfirm 确认弹出框样式统一 ===== +.ant-popconfirm { + .ant-popover-inner { + background: $bg-card !important; + border: 1px solid $border-primary !important; + border-radius: $radius-md !important; + } + + .ant-popconfirm-message-title { + color: $text-primary !important; + } + + .ant-popconfirm-description { + color: $text-secondary !important; + } +} + +// ===== 全局 Ant Design Tooltip 提示框样式统一 ===== +.ant-tooltip { + .ant-tooltip-inner { + background: $bg-elevated !important; + color: $text-primary !important; + border-radius: $radius-sm !important; + } + + .ant-tooltip-arrow-content { + background: $bg-elevated !important; + } +} + +// ===== 全局 Ant Design Message 消息提示样式统一 ===== +.ant-message { + .ant-message-notice-content { + background: $bg-card !important; + border: 1px solid $border-primary !important; + border-radius: $radius-md !important; + box-shadow: $shadow-elevated !important; + color: $text-primary !important; + } +} + +// ===== 全局 Ant Design Spin 加载组件样式统一 ===== +.ant-spin { + .ant-spin-dot-item { + background: $brand-primary !important; + } +} + +// ===== 全局 Ant Design Empty 空状态样式统一 ===== +.ant-empty { + .ant-empty-description { + color: $text-secondary !important; + } +} + diff --git a/frontend/src/styles/variables.scss b/frontend/src/styles/variables.scss new file mode 100644 index 0000000..829c34c --- /dev/null +++ b/frontend/src/styles/variables.scss @@ -0,0 +1,126 @@ +/** + * Cursor 风格设计系统变量 + * 参考 Cursor 官方网站 (cursor.com) 的设计规范 + */ + +// ===== 配色方案 ===== + +// 深色模式 - 背景色系(Cursor 风格:更柔和的深色) +$bg-primary: #0A0A0A; // 主背景 - 接近纯黑但更柔和 +$bg-secondary: #141414; // 次要背景 - 卡片和容器 +$bg-tertiary: #1A1A1A; // 三级背景 - 嵌套容器 +$bg-card: #1E1E1E; // 卡片背景 - 更柔和的灰色 +$bg-card-hover: #252525; // 卡片悬停 - 轻微提亮 +$bg-elevated: #2A2A2A; // 提升背景 - 模态框等 + +// 深色模式 - 文字色系(Cursor 风格:高对比度但不过于刺眼) +$text-primary: #E5E5E5; // 主文字 - 柔和的白色 +$text-secondary: #A0A0A0; // 次要文字 - 中等灰色 +$text-tertiary: #6B6B6B; // 三级文字 - 较暗灰色 +$text-disabled: #404040; // 禁用文字 +$text-white: #FFFFFF; // 纯白色 + +// 品牌色(标准绿色) +$brand-primary: #31f08b; // 主品牌色 - 标准绿色 +$primary-color: #31f08b; // 别名,与 $brand-primary 相同 +$brand-hover: #28d977; // 悬停色 - 深绿色 +$brand-pressed: #1fc266; // 按下色 - 更深绿色 + +// 功能色(统一使用绿色系,保持简洁) +$color-success: #31f08b; // 成功色 - 使用品牌色 +$color-warning: #F59E0B; // 警告色 - 橙色(仅用于警告场景) +$color-error: #EF4444; // 错误色 - 红色(仅用于错误场景) +$color-info: #31f08b; // 信息色 - 品牌色 + +// 边框色(Cursor 风格:更细腻的边框) +$border-primary: #2A2A2A; // 主边框 - 更柔和 +$border-secondary: #1E1E1E; // 次要边框 +$border-divider: #1A1A1A; // 分割线 +$border-color: #2A2A2A; // 别名,与 $border-primary 相同 + +// ===== 间距系统(8px 基准)===== +$space-xxs: 2px; +$space-xs: 4px; +$space-sm: 8px; +$space-md: 16px; +$space-lg: 24px; +$space-xl: 32px; +$space-xxl: 48px; +$space-2xl: 48px; // 别名,与 $space-xxl 相同 +$space-xxxl: 64px; +$space-3xl: 64px; // 别名,与 $space-xxxl 相同 + +// ===== 圆角规范(Cursor 风格:更圆润的边角)===== +$radius-micro: 4px; +$radius-small: 6px; // 更小的圆角 +$radius-sm: 6px; // 别名 +$radius-base: 8px; // 基础圆角 +$radius-medium: 10px; // 中等圆角 +$radius-md: 10px; // 别名 +$radius-large: 12px; // 大圆角 +$radius-lg: 12px; // 别名 +$radius-xlarge: 16px; // 超大圆角 +$radius-xl: 16px; // 别名 +$radius-full: 50%; + +// ===== 字体系统(Cursor 风格:现代无衬线字体)===== +$font-family-base: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", + "Helvetica Neue", Arial, "Noto Sans", sans-serif; +$font-family-zh: "Inter", -apple-system, BlinkMacSystemFont, "PingFang SC", + "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-family-mono: "SF Mono", "Monaco", "Cascadia Code", "Roboto Mono", + "Consolas", "Fira Code", monospace; + +// 字体大小 +$font-size-xs: 11px; // 0.6875rem +$font-size-sm: 12px; // 0.75rem +$font-size-base: 14px; // 0.875rem +$font-size-lg: 16px; // 1rem +$font-size-xl: 20px; // 1.25rem +$font-size-2xl: 24px; // 1.5rem +$font-size-3xl: 32px; // 2rem +$font-size-4xl: 40px; // 2.5rem + +// 字重 +$font-weight-light: 300; +$font-weight-regular: 400; +$font-weight-normal: 400; // 别名,与 $font-weight-regular 相同 +$font-weight-medium: 500; +$font-weight-semibold: 600; +$font-weight-bold: 700; + +// ===== 阴影系统(Cursor 风格:更细腻的阴影)===== +$shadow-light: 0 1px 2px rgba(0, 0, 0, 0.1); +$shadow-base: 0 2px 8px rgba(0, 0, 0, 0.12); +$shadow-elevated: 0 4px 16px rgba(0, 0, 0, 0.15); +$shadow-strong: 0 8px 24px rgba(0, 0, 0, 0.2); +$shadow-dark: 0 2px 8px rgba(0, 0, 0, 0.25); + +// ===== 过渡动画 ===== +$transition-fast: 150ms; +$transition-base: 250ms; +$transition-slow: 350ms; + +// 缓动函数(OpenAI 风格) +$ease-out: cubic-bezier(0.16, 1, 0.3, 1); +$ease-in-out: cubic-bezier(0.45, 0, 0.55, 1); + +// ===== Z-index 层级 ===== +$z-index-dropdown: 1000; +$z-index-modal: 1050; +$z-index-popover: 1100; +$z-index-tooltip: 1200; +$z-index-notification: 1300; + +// ===== 布局 ===== +$navbar-height: 64px; +$sidebar-width: 240px; +$sidebar-collapsed-width: 64px; +$container-max-width: 1200px; + +// ===== 断点 ===== +$breakpoint-mobile: 768px; +$breakpoint-tablet: 1024px; +$breakpoint-desktop: 1200px; +$breakpoint-wide: 1440px; + diff --git a/frontend/src/types/auto-imports.d.ts b/frontend/src/types/auto-imports.d.ts new file mode 100644 index 0000000..a606bd4 --- /dev/null +++ b/frontend/src/types/auto-imports.d.ts @@ -0,0 +1,87 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +export {} +declare global { + const EffectScope: typeof import('vue')['EffectScope'] + const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] + const computed: typeof import('vue')['computed'] + const createApp: typeof import('vue')['createApp'] + const createPinia: typeof import('pinia')['createPinia'] + const customRef: typeof import('vue')['customRef'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const defineStore: typeof import('pinia')['defineStore'] + const effectScope: typeof import('vue')['effectScope'] + const getActivePinia: typeof import('pinia')['getActivePinia'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const h: typeof import('vue')['h'] + const inject: typeof import('vue')['inject'] + const isProxy: typeof import('vue')['isProxy'] + const isReactive: typeof import('vue')['isReactive'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] + const mapActions: typeof import('pinia')['mapActions'] + const mapGetters: typeof import('pinia')['mapGetters'] + const mapState: typeof import('pinia')['mapState'] + const mapStores: typeof import('pinia')['mapStores'] + const mapWritableState: typeof import('pinia')['mapWritableState'] + const markRaw: typeof import('vue')['markRaw'] + const nextTick: typeof import('vue')['nextTick'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] + const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] + const provide: typeof import('vue')['provide'] + const reactive: typeof import('vue')['reactive'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const setActivePinia: typeof import('pinia')['setActivePinia'] + const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const storeToRefs: typeof import('pinia')['storeToRefs'] + const toRaw: typeof import('vue')['toRaw'] + const toRef: typeof import('vue')['toRef'] + const toRefs: typeof import('vue')['toRefs'] + const toValue: typeof import('vue')['toValue'] + const triggerRef: typeof import('vue')['triggerRef'] + const unref: typeof import('vue')['unref'] + const useAttrs: typeof import('vue')['useAttrs'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVars: typeof import('vue')['useCssVars'] + const useId: typeof import('vue')['useId'] + const useLink: typeof import('vue-router')['useLink'] + const useModel: typeof import('vue')['useModel'] + const useRoute: typeof import('vue-router')['useRoute'] + const useRouter: typeof import('vue-router')['useRouter'] + const useSlots: typeof import('vue')['useSlots'] + const useTemplateRef: typeof import('vue')['useTemplateRef'] + const watch: typeof import('vue')['watch'] + const watchEffect: typeof import('vue')['watchEffect'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] +} +// for type re-export +declare global { + // @ts-ignore + export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' + import('vue') +} diff --git a/frontend/src/types/components.d.ts b/frontend/src/types/components.d.ts new file mode 100644 index 0000000..ffbc4c3 --- /dev/null +++ b/frontend/src/types/components.d.ts @@ -0,0 +1,67 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +export {} + +declare module 'vue' { + export interface GlobalComponents { + AAlert: typeof import('ant-design-vue/es')['Alert'] + ABadge: typeof import('ant-design-vue/es')['Badge'] + AButton: typeof import('ant-design-vue/es')['Button'] + ACalendar: typeof import('ant-design-vue/es')['Calendar'] + ACheckbox: typeof import('ant-design-vue/es')['Checkbox'] + ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup'] + ACol: typeof import('ant-design-vue/es')['Col'] + ADatePicker: typeof import('ant-design-vue/es')['DatePicker'] + ADescriptions: typeof import('ant-design-vue/es')['Descriptions'] + ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem'] + ADivider: typeof import('ant-design-vue/es')['Divider'] + ADrawer: typeof import('ant-design-vue/es')['Drawer'] + ADropdown: typeof import('ant-design-vue/es')['Dropdown'] + AEmpty: typeof import('ant-design-vue/es')['Empty'] + AForm: typeof import('ant-design-vue/es')['Form'] + AFormItem: typeof import('ant-design-vue/es')['FormItem'] + AInput: typeof import('ant-design-vue/es')['Input'] + AInputNumber: typeof import('ant-design-vue/es')['InputNumber'] + AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] + AInputSearch: typeof import('ant-design-vue/es')['InputSearch'] + AMenu: typeof import('ant-design-vue/es')['Menu'] + AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider'] + AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] + AModal: typeof import('ant-design-vue/es')['Modal'] + AMonthPicker: typeof import('ant-design-vue/es')['MonthPicker'] + APagination: typeof import('ant-design-vue/es')['Pagination'] + APopconfirm: typeof import('ant-design-vue/es')['Popconfirm'] + AProgress: typeof import('ant-design-vue/es')['Progress'] + ARadio: typeof import('ant-design-vue/es')['Radio'] + ARadioButton: typeof import('ant-design-vue/es')['RadioButton'] + ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] + ARate: typeof import('ant-design-vue/es')['Rate'] + ARow: typeof import('ant-design-vue/es')['Row'] + ASelect: typeof import('ant-design-vue/es')['Select'] + ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] + ASlider: typeof import('ant-design-vue/es')['Slider'] + ASpace: typeof import('ant-design-vue/es')['Space'] + ASpin: typeof import('ant-design-vue/es')['Spin'] + ASwitch: typeof import('ant-design-vue/es')['Switch'] + ATable: typeof import('ant-design-vue/es')['Table'] + ATabPane: typeof import('ant-design-vue/es')['TabPane'] + ATabs: typeof import('ant-design-vue/es')['Tabs'] + ATag: typeof import('ant-design-vue/es')['Tag'] + ATextarea: typeof import('ant-design-vue/es')['Textarea'] + ATree: typeof import('ant-design-vue/es')['Tree'] + AUpload: typeof import('ant-design-vue/es')['Upload'] + DailyProgressReminder: typeof import('./../components/DailyProgressReminder.vue')['default'] + Icon: typeof import('./../components/Icon.vue')['default'] + IncentiveModal: typeof import('./../components/IncentiveModal.vue')['default'] + MilestoneCard: typeof import('./../components/MilestoneCard.vue')['default'] + Navbar: typeof import('./../components/Navbar.vue')['default'] + NotificationBell: typeof import('./../components/NotificationBell.vue')['default'] + RankingCard: typeof import('./../components/RankingCard.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + TaskCard: typeof import('./../components/TaskCard.vue')['default'] + } +} diff --git a/frontend/src/utils/request.ts b/frontend/src/utils/request.ts new file mode 100644 index 0000000..152d692 --- /dev/null +++ b/frontend/src/utils/request.ts @@ -0,0 +1,116 @@ +import axios from 'axios' +import type { AxiosInstance, AxiosResponse } from 'axios' +import { message } from 'ant-design-vue' + +// 创建 axios 实例 +const service: AxiosInstance = axios.create({ + baseURL: (import.meta as any).env?.VITE_API_BASE_URL || '/api', + timeout: 15000 +}) + +// 请求拦截器 +service.interceptors.request.use( + (config) => { + const base = (config.baseURL || '') as string + let url = (config.url || '') as string + + // 统一去重:如果 baseURL 末尾带 /api,url 又以 /api/ 开头,则自动去掉 url 前面的 /api + // 例如 baseURL = '/api' 且 url = '/api/analytics/xxx',最终会发到 '/api/analytics/xxx' 而不是 '/api/api/analytics/xxx' + if (base.endsWith('/api') && url.startsWith('/api/')) { + url = url.replace(/^\/api\//, '/') + config.url = url + } + + // 调试:打印最终请求地址,帮助排查 404 / 重复前缀问题(生产环境可按需注释) + try { + // eslint-disable-next-line no-console + console.debug('[HTTP] request:', { + baseURL: config.baseURL, + url: config.url, + method: config.method + }) + } catch (e) { + // ignore + } + // 调试:打印最终请求地址,帮助排查 404 / 重复前缀问题(生产环境可按需注释) + try { + // eslint-disable-next-line no-console + console.debug('[HTTP] request:', { + baseURL: config.baseURL, + url: config.url, + method: config.method + }) + } catch (e) { + // ignore + } + + // 从 localStorage 获取 token + const token = localStorage.getItem('token') + if (token) { + config.headers['Authorization'] = `Bearer ${token}` + } + return config + }, + (error) => { + console.error('请求错误:', error) + return Promise.reject(error) + } +) + +// 响应拦截器 +service.interceptors.response.use( + (response: AxiosResponse) => { + const res = response.data + + // 如果返回的状态码不是 200,说明接口报错了 + if (res.code && res.code !== 200) { + message.error(res.message || '请求失败') + + // 401: 未授权 + if (res.code === 401) { + // 清除 token + localStorage.removeItem('token') + localStorage.removeItem('userInfo') + // 跳转到登录页 + window.location.href = '/login' + } + + return Promise.reject(new Error(res.message || '请求失败')) + } + + return res + }, + (error) => { + console.error('响应错误:', error) + + if (error.response) { + const status = error.response.status + switch (status) { + case 401: + message.error('未授权,请登录') + localStorage.removeItem('token') + localStorage.removeItem('userInfo') + window.location.href = '/login' + break + case 403: + message.error('拒绝访问') + break + case 404: + message.error('请求的资源不存在') + break + case 500: + message.error('服务器内部错误') + break + default: + message.error(error.response.data.message || '请求失败') + } + } else { + message.error('网络错误,请检查您的网络连接') + } + + return Promise.reject(error) + } +) + +export default service + diff --git a/frontend/src/views/Achievements.vue b/frontend/src/views/Achievements.vue new file mode 100644 index 0000000..e2599c9 --- /dev/null +++ b/frontend/src/views/Achievements.vue @@ -0,0 +1,533 @@ + + + + + + diff --git a/frontend/src/views/Analytics.vue b/frontend/src/views/Analytics.vue new file mode 100644 index 0000000..fdbae33 --- /dev/null +++ b/frontend/src/views/Analytics.vue @@ -0,0 +1,887 @@ + + + + + + diff --git a/frontend/src/views/CheckIn.vue b/frontend/src/views/CheckIn.vue new file mode 100644 index 0000000..f155f7f --- /dev/null +++ b/frontend/src/views/CheckIn.vue @@ -0,0 +1,559 @@ + + + + + + diff --git a/frontend/src/views/Contests.vue b/frontend/src/views/Contests.vue new file mode 100644 index 0000000..81c124c --- /dev/null +++ b/frontend/src/views/Contests.vue @@ -0,0 +1,734 @@ + + + + + diff --git a/frontend/src/views/CreateTask.vue b/frontend/src/views/CreateTask.vue new file mode 100644 index 0000000..9957a60 --- /dev/null +++ b/frontend/src/views/CreateTask.vue @@ -0,0 +1,1482 @@ + + + + + + diff --git a/frontend/src/views/DesignerHall.vue b/frontend/src/views/DesignerHall.vue new file mode 100644 index 0000000..7f00f60 --- /dev/null +++ b/frontend/src/views/DesignerHall.vue @@ -0,0 +1,944 @@ + + + + + diff --git a/frontend/src/views/Goals.vue b/frontend/src/views/Goals.vue new file mode 100644 index 0000000..7e651fa --- /dev/null +++ b/frontend/src/views/Goals.vue @@ -0,0 +1,650 @@ + + + + + diff --git a/frontend/src/views/IncentiveCenter.vue b/frontend/src/views/IncentiveCenter.vue new file mode 100644 index 0000000..2e823f2 --- /dev/null +++ b/frontend/src/views/IncentiveCenter.vue @@ -0,0 +1,507 @@ + + + + + diff --git a/frontend/src/views/LeaderWorkbench.vue b/frontend/src/views/LeaderWorkbench.vue new file mode 100644 index 0000000..b4a1e9c --- /dev/null +++ b/frontend/src/views/LeaderWorkbench.vue @@ -0,0 +1,697 @@ + + + + + diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue new file mode 100644 index 0000000..367e7a3 --- /dev/null +++ b/frontend/src/views/Login.vue @@ -0,0 +1,846 @@ + + + + + + diff --git a/frontend/src/views/MyTasks.vue b/frontend/src/views/MyTasks.vue new file mode 100644 index 0000000..1c28a56 --- /dev/null +++ b/frontend/src/views/MyTasks.vue @@ -0,0 +1,1785 @@ + + + + + + diff --git a/frontend/src/views/NotFound.vue b/frontend/src/views/NotFound.vue new file mode 100644 index 0000000..dfd85b7 --- /dev/null +++ b/frontend/src/views/NotFound.vue @@ -0,0 +1,283 @@ + + + + + diff --git a/frontend/src/views/Notifications.vue b/frontend/src/views/Notifications.vue new file mode 100644 index 0000000..4750d08 --- /dev/null +++ b/frontend/src/views/Notifications.vue @@ -0,0 +1,491 @@ + + + + + + diff --git a/frontend/src/views/RoleManagement.vue b/frontend/src/views/RoleManagement.vue new file mode 100644 index 0000000..a69b90a --- /dev/null +++ b/frontend/src/views/RoleManagement.vue @@ -0,0 +1,280 @@ + + + + + + diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue new file mode 100644 index 0000000..9f357b7 --- /dev/null +++ b/frontend/src/views/Settings.vue @@ -0,0 +1,3715 @@ + + + + + + + diff --git a/frontend/src/views/TaskHall.vue b/frontend/src/views/TaskHall.vue new file mode 100644 index 0000000..0323993 --- /dev/null +++ b/frontend/src/views/TaskHall.vue @@ -0,0 +1,3626 @@ + + + + + + diff --git a/frontend/src/views/TaskHall.vue.backup b/frontend/src/views/TaskHall.vue.backup new file mode 100644 index 0000000..81b047c --- /dev/null +++ b/frontend/src/views/TaskHall.vue.backup @@ -0,0 +1,2821 @@ + + + + + + diff --git a/frontend/src/views/TeamPK.vue b/frontend/src/views/TeamPK.vue new file mode 100644 index 0000000..6a81527 --- /dev/null +++ b/frontend/src/views/TeamPK.vue @@ -0,0 +1,675 @@ + + + + + diff --git a/frontend/src/views/UserManagement.vue b/frontend/src/views/UserManagement.vue new file mode 100644 index 0000000..b35feff --- /dev/null +++ b/frontend/src/views/UserManagement.vue @@ -0,0 +1,624 @@ + + + + + + diff --git a/frontend/src/views/admin/RewardRules.vue b/frontend/src/views/admin/RewardRules.vue new file mode 100644 index 0000000..574fe41 --- /dev/null +++ b/frontend/src/views/admin/RewardRules.vue @@ -0,0 +1,543 @@ + + + + + diff --git a/frontend/src/views/admin/ScorePoolManagement.vue b/frontend/src/views/admin/ScorePoolManagement.vue new file mode 100644 index 0000000..c489813 --- /dev/null +++ b/frontend/src/views/admin/ScorePoolManagement.vue @@ -0,0 +1,797 @@ + + + + + diff --git a/frontend/src/views/admin/TaskManagement.vue b/frontend/src/views/admin/TaskManagement.vue new file mode 100644 index 0000000..6945985 --- /dev/null +++ b/frontend/src/views/admin/TaskManagement.vue @@ -0,0 +1,472 @@ + + + + + + diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..cb18f57 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + /* Path mapping */ + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + + /* Additional */ + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} + diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..e428d50 --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} + diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..fb16d4c --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,63 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { fileURLToPath, URL } from 'node:url' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + // 自动导入 Vue 相关函数 + AutoImport({ + imports: ['vue', 'vue-router', 'pinia'], + dts: 'src/types/auto-imports.d.ts', + eslintrc: { + enabled: true + } + }), + // 自动导入组件 + Components({ + resolvers: [ + AntDesignVueResolver({ + importStyle: false // 使用 CSS 而不是 less + }) + ], + dts: 'src/types/components.d.ts' + }) + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + }, + server: { + port: 3000, + proxy: { + '/api': { + target: 'http://localhost:8080', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '') + } + } + }, + build: { + rollupOptions: { + output: { + // 为每次构建生成唯一的文件名,避免缓存问题 + entryFileNames: `assets/[name]-[hash]-${Date.now()}.js`, + chunkFileNames: `assets/[name]-[hash]-${Date.now()}.js`, + assetFileNames: `assets/[name]-[hash]-${Date.now()}.[ext]` + } + } + }, + css: { + preprocessorOptions: { + scss: { + additionalData: '@import "@/styles/variables.scss";' + } + } + } +}) + diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..5e28189 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,47 @@ +server { + listen 8888; + server_name 38.246.244.140; + + root /opt/designhub/frontend/dist; + index index.html; + + access_log /opt/designhub/logs/nginx-access.log; + error_log /opt/designhub/logs/nginx-error.log; + + # 前端路由 + location / { + try_files $uri $uri/ /index.html; + + # 禁止缓存 HTML 文件 + if ($request_filename ~* .*\.(?:htm|html)$) { + add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate"; + } + } + + # API 代理 + location /api/ { + proxy_pass http://localhost:8080/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + } + + # 静态资源缓存(JS/CSS等) + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 30d; + add_header Cache-Control "public, immutable"; + } + + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml; + gzip_min_length 1000; +} diff --git a/scripts/health-check.sh b/scripts/health-check.sh new file mode 100644 index 0000000..f2f0bee --- /dev/null +++ b/scripts/health-check.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# 简易健康检查脚本 +# Usage: ./scripts/health-check.sh http://localhost:8080/api + +API_BASE=${1:-"http://localhost:8080/api"} +HEALTH_URL="${API_BASE%/}/system/health" + +echo "Checking backend health from ${HEALTH_URL} ..." + +response=$(curl -s -w "\n%{http_code}" "${HEALTH_URL}") +body=$(echo "$response" | head -n -1) +status=$(echo "$response" | tail -n 1) + +if [ "$status" != "200" ]; then + echo "❌ Health check request failed, status code: $status" + echo "Response: $body" + exit 1 +fi + +db_up=$(echo "$body" | jq -r '.data.databaseUp' 2>/dev/null) + +if [ "$db_up" = "true" ]; then + echo "✅ Database connection OK" +else + echo "❌ Database check failed" +fi + +echo "$body" | jq '.' 2>/dev/null || echo "$body" + diff --git a/公网服务器部署指南.md b/公网服务器部署指南.md new file mode 100644 index 0000000..c7b9826 --- /dev/null +++ b/公网服务器部署指南.md @@ -0,0 +1,407 @@ +# DesignHub 公网服务器部署指南 + +**服务器信息:** +- IP: 45.192.99.48 +- 端口: 22 +- 用户名: root +- 密码: 1blZllMIp7 +- 系统: CentOS Stream 9 + +--- + +## 快速部署脚本 + +已创建自动化部署脚本:`deploy_to_public_server.sh` + +执行方式: +```bash +cd /Users/lanmu/Desktop/qiangdanxitong +bash deploy_to_public_server.sh +``` + +--- + +## 手动部署步骤(如果脚本失败) + +### 第一步:连接服务器 + +```bash +ssh root@45.192.99.48 +# 密码: 1blZllMIp7 +``` + +### 第二步:安装系统依赖 + +```bash +# 安装基础工具 +dnf install -y curl wget git unzip + +# 安装 Java 17 +dnf install -y java-17-openjdk java-17-openjdk-devel + +# 安装 Maven +dnf install -y maven + +# 安装 MySQL +dnf install -y mysql-server mysql +systemctl enable mysqld +systemctl start mysqld + +# 设置 MySQL root 密码 +mysqladmin -u root password "DesignHub@2025_Root" + +# 安装 Redis +dnf install -y redis +systemctl enable redis +systemctl start redis + +# Nginx 和 Node.js/pnpm 已安装,跳过 +``` + +### 第三步:创建目录结构 + +```bash +mkdir -p /opt/designhub/{backend,frontend,logs,uploads} +``` + +### 第四步:配置 MySQL 数据库 + +```bash +mysql -uroot -pDesignHub@2025_Root << 'EOF' +CREATE DATABASE IF NOT EXISTS designhub CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE USER IF NOT EXISTS 'designhub'@'localhost' IDENTIFIED BY 'DesignHub@2025_DB'; +GRANT ALL PRIVILEGES ON designhub.* TO 'designhub'@'localhost'; +FLUSH PRIVILEGES; +EOF +``` + +### 第五步:上传代码 + +**方式1:使用 rsync(推荐)** + +在本地执行: +```bash +# 安装 sshpass(Mac) +brew install hudochenkov/sshpass/sshpass + +# 上传后端代码 +sshpass -p '1blZllMIp7' rsync -avz \ + --exclude='target' --exclude='*.class' \ + -e "ssh -o StrictHostKeyChecking=no" \ + backend/ root@45.192.99.48:/opt/designhub/backend/ + +# 上传前端代码 +sshpass -p '1blZllMIp7' rsync -avz \ + --exclude='node_modules' --exclude='dist' \ + -e "ssh -o StrictHostKeyChecking=no" \ + frontend/ root@45.192.99.48:/opt/designhub/frontend/ + +# 上传数据库SQL文件(如果有) +sshpass -p '1blZllMIp7' scp -o StrictHostKeyChecking=no \ + database/*.sql root@45.192.99.48:/tmp/ +``` + +**方式2:使用 scp** + +```bash +# 上传整个项目目录 +sshpass -p '1blZllMIp7' scp -r \ + -o StrictHostKeyChecking=no \ + backend root@45.192.99.48:/opt/designhub/ + +sshpass -p '1blZllMIp7' scp -r \ + -o StrictHostKeyChecking=no \ + frontend root@45.192.99.48:/opt/designhub/ +``` + +### 第六步:导入数据库(如果需要) + +```bash +# 在服务器上执行 +cd /tmp +mysql -uroot -pDesignHub@2025_Root designhub < create_s3_config.sql +# 导入其他SQL文件... +``` + +### 第七步:配置后端 + +```bash +cd /opt/designhub/backend + +# 创建生产环境配置 +cat > src/main/resources/application-prod.yml << 'EOF' +spring: + datasource: + url: jdbc:mysql://localhost:3306/designhub?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true + username: designhub + password: DesignHub@2025_DB + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + minimum-idle: 10 + maximum-pool-size: 50 + connection-timeout: 30000 + idle-timeout: 600000 + max-lifetime: 1800000 + + redis: + host: localhost + port: 6379 + password: DesignHub@2025_Redis + database: 0 + timeout: 3000 + +jwt: + secret: DesignHub_Prod_Secret_Key_2025_Very_Strong + access-token-expiration: 7200000 + refresh-token-expiration: 604800000 + +server: + port: 8080 + +logging: + level: + root: INFO + com.designhub: INFO + file: + name: /opt/designhub/logs/application.log + max-size: 10MB + max-history: 30 + +file: + upload: + path: /opt/designhub/uploads + max-size: 52428800 +EOF + +# 构建后端 +mvn clean package -DskipTests + +# 复制JAR文件 +cp target/designhub-backend-1.0.0.jar /opt/designhub/designhub.jar +``` + +### 第八步:配置前端 + +```bash +cd /opt/designhub/frontend + +# 创建生产环境配置 +cat > .env.production << 'EOF' +VITE_APP_BASE_API=http://45.192.99.48/api +VITE_APP_TITLE=DesignHub +VITE_APP_VERSION=6.0 +EOF + +# 安装依赖 +pnpm install + +# 构建 +pnpm build +``` + +### 第九步:配置系统服务 + +```bash +# 创建后端服务 +cat > /etc/systemd/system/designhub-backend.service << 'EOF' +[Unit] +Description=DesignHub Backend Service +After=network.target mysqld.service redis.service + +[Service] +Type=simple +User=root +WorkingDirectory=/opt/designhub +ExecStart=/usr/bin/java -jar -Xms512m -Xmx1024m /opt/designhub/designhub.jar --spring.profiles.active=prod +Restart=on-failure +RestartSec=10 + +StandardOutput=append:/opt/designhub/logs/backend.log +StandardError=append:/opt/designhub/logs/backend-error.log + +[Install] +WantedBy=multi-user.target +EOF + +# 创建 Nginx 配置 +cat > /etc/nginx/conf.d/designhub.conf << 'EOF' +server { + listen 80; + server_name 45.192.99.48; + + root /opt/designhub/frontend/dist; + index index.html; + + access_log /opt/designhub/logs/nginx-access.log; + error_log /opt/designhub/logs/nginx-error.log; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://localhost:8080/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 30d; + add_header Cache-Control "public, immutable"; + } + + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml; + gzip_min_length 1000; +} +EOF + +# 重新加载 systemd +systemctl daemon-reload + +# 测试 Nginx 配置 +nginx -t + +# 重启 Nginx +systemctl restart nginx +systemctl enable nginx +``` + +### 第十步:启动后端服务 + +```bash +# 启动后端服务 +systemctl start designhub-backend +systemctl enable designhub-backend + +# 查看状态 +systemctl status designhub-backend + +# 查看日志 +tail -f /opt/designhub/logs/application.log +``` + +--- + +## 验证部署 + +### 检查服务状态 + +```bash +# 检查后端服务 +systemctl status designhub-backend + +# 检查 MySQL +systemctl status mysqld + +# 检查 Redis +systemctl status redis + +# 检查 Nginx +systemctl status nginx +``` + +### 测试访问 + +- 前端地址: http://45.192.99.48 +- API地址: http://45.192.99.48/api +- API文档: http://45.192.99.48/api/doc.html (如果启用) + +### 查看日志 + +```bash +# 后端日志 +tail -f /opt/designhub/logs/application.log + +# 后端错误日志 +tail -f /opt/designhub/logs/backend-error.log + +# Nginx 访问日志 +tail -f /opt/designhub/logs/nginx-access.log + +# Nginx 错误日志 +tail -f /opt/designhub/logs/nginx-error.log +``` + +--- + +## 常用命令 + +### 服务管理 + +```bash +# 重启后端 +systemctl restart designhub-backend + +# 停止后端 +systemctl stop designhub-backend + +# 查看后端日志 +journalctl -u designhub-backend -f + +# 重启 Nginx +systemctl restart nginx +``` + +### 数据库管理 + +```bash +# 连接数据库 +mysql -u designhub -pDesignHub@2025_DB designhub + +# 备份数据库 +mysqldump -u designhub -pDesignHub@2025_DB designhub > backup.sql + +# 恢复数据库 +mysql -u designhub -pDesignHub@2025_DB designhub < backup.sql +``` + +--- + +## 故障排查 + +### 后端无法启动 + +1. 检查 Java 版本:`java -version` +2. 检查 JAR 文件:`ls -lh /opt/designhub/designhub.jar` +3. 检查日志:`tail -100 /opt/designhub/logs/backend-error.log` +4. 手动运行:`cd /opt/designhub && java -jar designhub.jar --spring.profiles.active=prod` + +### 数据库连接失败 + +1. 检查 MySQL 状态:`systemctl status mysqld` +2. 测试连接:`mysql -u designhub -pDesignHub@2025_DB designhub` +3. 检查配置文件:`cat /opt/designhub/backend/src/main/resources/application-prod.yml` + +### 前端无法访问 + +1. 检查 Nginx 状态:`systemctl status nginx` +2. 检查 Nginx 配置:`nginx -t` +3. 检查前端文件:`ls -lh /opt/designhub/frontend/dist/` +4. 查看 Nginx 错误日志:`tail -100 /opt/designhub/logs/nginx-error.log` + +--- + +## 注意事项 + +1. **安全建议**: + - 修改 MySQL root 密码 + - 修改 Redis 密码 + - 修改 JWT secret + - 配置防火墙规则 + +2. **性能优化**: + - 根据服务器配置调整 JVM 参数(-Xms, -Xmx) + - 配置 MySQL 连接池大小 + - 配置 Redis 连接池 + +3. **备份策略**: + - 定期备份数据库 + - 备份配置文件 + - 备份上传的文件 diff --git a/域名和HTTPS配置指南.md b/域名和HTTPS配置指南.md new file mode 100644 index 0000000..6a18967 --- /dev/null +++ b/域名和HTTPS配置指南.md @@ -0,0 +1,533 @@ +# 域名和HTTPS配置指南 + +## 🌐 域名信息 + +**域名**: qd.yydd.ai +**公网服务器**: 156.225.27.52:50232 +**内网端口**: 5246 (frp穿透) + +--- + +## 📋 配置步骤 + +### ✅ 已完成的工作 + +1. ✅ **Certbot安装** - SSL证书工具已安装 +2. ✅ **Nginx配置** - 反向代理配置已创建 +3. ✅ **HTTP配置** - 临时HTTP访问已配置 +4. ✅ **HTTPS配置文件** - 已预创建(待证书申请后启用) + +### ⏳ 待完成的工作 + +1. **DNS解析配置**(必须先完成) + ``` + 需要在域名管理后台添加A记录: + + 主机记录: qd + 记录类型: A + 记录值: 156.225.27.52 + TTL: 600(或默认) + ``` + +2. **验证DNS解析** + ```bash + # 在本地或任意机器执行 + ping qd.yydd.ai + # 应该解析到 156.225.27.52 + + nslookup qd.yydd.ai + # 应该返回 156.225.27.52 + ``` + +3. **申请SSL证书**(DNS解析生效后) + ```bash + ssh -p 50232 root@156.225.27.52 + # 密码:it5NuKGemX + + # 申请证书 + certbot certonly \ + --webroot \ + --webroot-path=/www/wwwroot/qd.yydd.ai \ + --email admin@yydd.ai \ + -d qd.yydd.ai \ + --agree-tos \ + --non-interactive + ``` + +4. **启用HTTPS配置** + ```bash + # 删除旧的HTTP配置 + rm -f /www/server/panel/vhost/nginx/qd.yydd.ai.conf + + # 启用HTTPS配置 + mv /www/server/panel/vhost/nginx/qd.yydd.ai_ssl.conf \ + /www/server/panel/vhost/nginx/qd.yydd.ai.conf + + # 重载Nginx + nginx -t && nginx -s reload + ``` + +5. **配置自动续期** + ```bash + # 添加定时任务(每天凌晨2点检查续期) + crontab -e + # 添加: + 0 2 * * * certbot renew --quiet --post-hook "nginx -s reload" + ``` + +--- + +## 🔧 当前配置状态 + +### 服务器架构 +``` +用户浏览器 + ↓ HTTPS +qd.yydd.ai (公网服务器 156.225.27.52:80/443) + ↓ Nginx反向代理 +127.0.0.1:5246 (frp穿透端口) + ↓ frp内网穿透 +内网服务器 127.0.0.1:8888 + ↓ Nginx代理 +后端API 127.0.0.1:8080 + ↓ Spring Boot +MySQL 127.0.0.1:3306 +``` + +### 配置文件位置 + +| 类型 | 路径 | +|------|------| +| **公网服务器** | | +| HTTP配置 | /www/server/panel/vhost/nginx/qd.yydd.ai.conf | +| HTTPS配置 | /www/server/panel/vhost/nginx/qd.yydd.ai_ssl.conf | +| SSL证书 | /etc/letsencrypt/live/qd.yydd.ai/ | +| | | +| **内网服务器** | | +| frp配置 | /etc/frp/frpc.ini | +| 后端服务 | /opt/designhub/designhub.jar | +| Nginx配置 | /etc/nginx/sites-available/designhub.conf | + +--- + +## 🎯 快速测试 + +### 当前可用的访问方式 + +1. **IP直接访问(已可用)** + - http://156.225.27.52:5246 + - 登录:admin / 123456 + +2. **域名HTTP访问(需DNS解析后)** + - http://qd.yydd.ai + - 登录:admin / 123456 + +3. **域名HTTPS访问(需DNS+证书)** + - https://qd.yydd.ai + - 登录:admin / 123456 + +### 测试命令 + +```bash +# 测试DNS解析 +ping qd.yydd.ai + +# 测试HTTP访问 +curl -I http://qd.yydd.ai + +# 测试HTTPS访问(证书申请后) +curl -I https://qd.yydd.ai +``` + +--- + +## 📝 DNS配置示例 + +### 在域名管理后台配置 + +**域名**: yydd.ai +**子域名**: qd + +**添加A记录**: +``` +类型: A +主机记录: qd +记录值: 156.225.27.52 +TTL: 600 +``` + +配置后等待5-10分钟DNS生效。 + +--- + +## 🔒 SSL证书申请流程 + +### 方式1:自动申请(推荐) + +**前提**: DNS已解析 + +```bash +ssh -p 50232 root@156.225.27.52 + +# 一键申请证书 +certbot certonly \ + --webroot \ + --webroot-path=/www/wwwroot/qd.yydd.ai \ + --email admin@yydd.ai \ + -d qd.yydd.ai \ + --agree-tos \ + --non-interactive + +# 如果成功,会显示: +# Congratulations! Your certificate and chain have been saved at: +# /etc/letsencrypt/live/qd.yydd.ai/fullchain.pem +``` + +### 方式2:使用宝塔面板 + +1. 登录宝塔面板 +2. 网站 → 添加站点 +3. 域名:qd.yydd.ai +4. SSL → Let's Encrypt → 申请 + +--- + +## 🔄 HTTPS配置激活 + +### 证书申请成功后 + +```bash +ssh -p 50232 root@156.225.27.52 + +# 1. 检查证书文件 +ls -la /etc/letsencrypt/live/qd.yydd.ai/ + +# 2. 启用HTTPS配置 +rm -f /www/server/panel/vhost/nginx/qd.yydd.ai.conf +mv /www/server/panel/vhost/nginx/qd.yydd.ai_ssl.conf \ + /www/server/panel/vhost/nginx/qd.yydd.ai.conf + +# 3. 测试并重载Nginx +nginx -t && nginx -s reload + +# 4. 验证HTTPS +curl -I https://qd.yydd.ai +``` + +--- + +## 🔐 自动续期配置 + +Let's Encrypt证书有效期90天,需要配置自动续期: + +```bash +# 添加定时任务 +crontab -e + +# 添加以下行(每天凌晨2点检查续期) +0 2 * * * certbot renew --quiet --post-hook "nginx -s reload" >> /var/log/certbot-renew.log 2>&1 +``` + +--- + +## 📊 完整访问地址汇总 + +### 🎯 推荐访问地址(配置完成后) + +**HTTPS域名**(最终推荐): +``` +https://qd.yydd.ai +``` + +### 🔧 备用访问地址 + +1. **HTTP域名**: + ``` + http://qd.yydd.ai + ``` + +2. **IP+端口**: + ``` + http://156.225.27.52:5246 + ``` + +3. **SSH管理**: + ```bash + # 公网服务器 + ssh -p 50232 root@156.225.27.52 + # 密码:it5NuKGemX + + # 内网服务器 + ssh -p 5204 root@156.225.27.52 + # 密码:Jsjj@8888 + ``` + +--- + +## 🎨 配置文件示例 + +### Nginx HTTPS完整配置 + +```nginx +# /www/server/panel/vhost/nginx/qd.yydd.ai.conf + +# HTTP自动跳转HTTPS +server { + listen 80; + server_name qd.yydd.ai; + + location /.well-known/acme-challenge/ { + root /www/wwwroot/qd.yydd.ai; + } + + location / { + return 301 https://$server_name$request_uri; + } +} + +# HTTPS主配置 +server { + listen 443 ssl http2; + server_name qd.yydd.ai; + + # SSL证书 + ssl_certificate /etc/letsencrypt/live/qd.yydd.ai/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/qd.yydd.ai/privkey.pem; + + # SSL优化 + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + client_max_body_size 100M; + + # 反向代理 + location / { + proxy_pass http://127.0.0.1:5246; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + proxy_connect_timeout 300; + proxy_send_timeout 300; + proxy_read_timeout 300; + } + + access_log /www/wwwlogs/qd.yydd.ai.https.access.log; + error_log /www/wwwlogs/qd.yydd.ai.https.error.log; +} +``` + +--- + +## ⚠️ 重要提示 + +### 1. DNS解析优先 +**必须先完成DNS解析**,再申请SSL证书! + +Let's Encrypt需要通过域名验证您的服务器所有权。 + +### 2. 防火墙配置 +确保80和443端口开放: +```bash +# CentOS防火墙 +firewall-cmd --permanent --add-port=80/tcp +firewall-cmd --permanent --add-port=443/tcp +firewall-cmd --reload + +# 或检查是否已开放 +firewall-cmd --list-ports +``` + +### 3. 证书有效期 +- Let's Encrypt证书有效期:90天 +- 建议提前30天自动续期 +- 已配置自动续期定时任务 + +--- + +## 🧪 故障排查 + +### DNS未解析 +```bash +# 检查DNS +nslookup qd.yydd.ai +ping qd.yydd.ai + +# 如果解析失败,联系域名管理员配置A记录 +``` + +### 证书申请失败 +```bash +# 查看详细日志 +cat /var/log/letsencrypt/letsencrypt.log + +# 常见原因: +# 1. DNS未解析到服务器 +# 2. 80端口被占用或防火墙阻止 +# 3. Nginx配置错误 +``` + +### HTTPS无法访问 +```bash +# 检查证书文件 +ls -la /etc/letsencrypt/live/qd.yydd.ai/ + +# 检查Nginx配置 +nginx -t + +# 查看错误日志 +tail -50 /www/wwwlogs/qd.yydd.ai.https.error.log +``` + +--- + +## 🎯 一键完成脚本(DNS解析后使用) + +```bash +#!/bin/bash +# 保存为 /root/setup_https.sh + +echo "=== DesignHub HTTPS配置脚本 ===" + +# 1. 申请SSL证书 +certbot certonly \ + --webroot \ + --webroot-path=/www/wwwroot/qd.yydd.ai \ + --email admin@yydd.ai \ + -d qd.yydd.ai \ + --agree-tos \ + --non-interactive + +if [ $? -eq 0 ]; then + echo "✅ SSL证书申请成功" + + # 2. 启用HTTPS配置 + rm -f /www/server/panel/vhost/nginx/qd.yydd.ai.conf + mv /www/server/panel/vhost/nginx/qd.yydd.ai_ssl.conf \ + /www/server/panel/vhost/nginx/qd.yydd.ai.conf + + # 3. 重载Nginx + nginx -t && nginx -s reload + + # 4. 配置自动续期 + (crontab -l 2>/dev/null; echo "0 2 * * * certbot renew --quiet --post-hook 'nginx -s reload'") | crontab - + + echo "" + echo "🎉 HTTPS配置完成!" + echo "访问地址: https://qd.yydd.ai" +else + echo "❌ SSL证书申请失败" + echo "请检查DNS是否解析成功" +fi +``` + +使用方法: +```bash +chmod +x /root/setup_https.sh +/root/setup_https.sh +``` + +--- + +## 📞 当前状态 + +### ✅ 已配置 +- ✅ 公网服务器Nginx配置 +- ✅ 反向代理到5246端口 +- ✅ Certbot工具已安装 +- ✅ HTTPS配置文件已准备 + +### ⏳ 待配置 +- ⏳ **DNS解析** - 需要在域名管理后台配置 +- ⏳ **SSL证书** - DNS解析后自动申请 +- ⏳ **HTTPS启用** - 证书申请后启用 + +--- + +## 🚀 完成后的访问方式 + +### 最终推荐访问地址 +``` +https://qd.yydd.ai +``` + +**登录账号**: +- 用户名:admin +- 密码:123456 + +### 功能页面 +- 登录页面: https://qd.yydd.ai +- 任务大厅: https://qd.yydd.ai/#/task-hall +- 设计师大厅: https://qd.yydd.ai/#/designer +- 数据分析: https://qd.yydd.ai/#/analytics +- 系统设置: https://qd.yydd.ai/#/settings + +--- + +## 📊 配置进度 + +| 步骤 | 状态 | 说明 | +|------|------|------| +| 1. 服务器迁移 | ✅ 完成 | 数据和服务已迁移到内网服务器 | +| 2. frp穿透 | ✅ 完成 | 内网5246端口已穿透 | +| 3. Certbot安装 | ✅ 完成 | SSL工具已安装 | +| 4. Nginx配置 | ✅ 完成 | 反向代理已配置 | +| 5. DNS解析 | ⏳ 待配置 | **需要您在域名后台配置** | +| 6. SSL证书 | ⏳ 待申请 | DNS解析后自动申请 | +| 7. HTTPS启用 | ⏳ 待启用 | 证书申请后启用 | + +**当前进度**: 60% (4/7步骤完成) + +--- + +## 💡 下一步操作 + +### 立即需要做的事情 + +**1. 配置DNS解析**(最重要) + +请访问您的域名管理后台(yydd.ai的DNS管理): + +添加A记录: +``` +主机记录: qd +记录类型: A +记录值: 156.225.27.52 +TTL: 600(10分钟) +``` + +**2. 等待DNS生效**(5-10分钟) + +验证命令: +```bash +ping qd.yydd.ai +``` + +**3. 申请SSL证书** + +DNS解析成功后,执行: +```bash +ssh -p 50232 root@156.225.27.52 +/root/setup_https.sh +``` + +**4. 访问测试** + +打开浏览器: +``` +https://qd.yydd.ai +``` + +--- + +**创建时间**: 2025-12-23 +**状态**: ⏳ 等待DNS解析配置 +**预计完成时间**: DNS配置后10分钟内 diff --git a/服务器和数据库配置清单.md b/服务器和数据库配置清单.md new file mode 100644 index 0000000..cc6dfba --- /dev/null +++ b/服务器和数据库配置清单.md @@ -0,0 +1,849 @@ +# DesignHub 服务器配置文档 + +**最后更新**: 2025-12-23 +**系统状态**: ✅ 生产环境运行中 +**版本**: v2.0 (迁移后) + +--- + +## 🌐 访问地址 + +### 🎯 主要访问地址 + +| 类型 | 地址 | 说明 | +|------|------|------| +| **生产地址** | https://qd.yydd.ai | ⭐ 推荐使用(HTTPS安全) | +| HTTP访问 | http://qd.yydd.ai | 自动跳转到HTTPS | +| IP访问 | http://156.225.27.52:5246 | 备用访问方式 | + +### 🔐 登录账号 + +| 用户名 | 密码 | 角色 | +|--------|------|------| +| admin | 123456 | 超级管理员 | +| designer1 | 123456 | 设计师 | +| designer2 | 123456 | 设计师 | +| requester1 | 123456 | 需求方 | +| requester2 | 123456 | 需求方 | + +--- + +## 🖥️ 服务器信息 + +### 📡 公网服务器(反向代理服务器) + +| 项目 | 值 | +|------|-----| +| **服务器IP** | 156.225.27.52 | +| **SSH端口** | 50232 | +| **SSH用户名** | root | +| **SSH密码** | it5NuKGemX | +| **操作系统** | CentOS 7 x86_64 | +| **Nginx版本** | 1.24.0 | +| **作用** | SSL终止、反向代理、域名绑定 | + +**SSH连接命令**: +```bash +ssh -p 50232 root@156.225.27.52 +# 或使用sshpass +sshpass -p 'it5NuKGemX' ssh -p 50232 root@156.225.27.52 +``` + +### 🏢 内网服务器(应用服务器) + +| 项目 | 值 | +|------|-----| +| **服务器IP** | 156.225.27.52(通过不同端口访问) | +| **SSH端口** | 5204 | +| **SSH用户名** | root | +| **SSH密码** | Jsjj@8888 | +| **操作系统** | Ubuntu 24.04.2 LTS | +| **Java版本** | OpenJDK 17.0.17 | +| **Maven版本** | 3.8.7 | +| **作用** | 运行DesignHub应用 | + +**SSH连接命令**: +```bash +ssh -p 5204 root@156.225.27.52 +# 或使用sshpass +sshpass -p 'Jsjj@8888' ssh -p 5204 root@156.225.27.52 +``` + +--- + +## 🏗️ 系统架构 + +``` +用户浏览器 + ↓ HTTPS/TLS 1.3 (443端口) +https://qd.yydd.ai (公网服务器 156.225.27.52) + ↓ Nginx反向代理 (SSL终止) +127.0.0.1:5246 (frp穿透端口) + ↓ frp内网穿透 +内网服务器 (Ubuntu 24.04) + ↓ Nginx (127.0.0.1:8888) +后端Spring Boot (127.0.0.1:8080) + ↓ JDBC +MySQL 8.0 (127.0.0.1:3306) +Redis (127.0.0.1:6379) +``` + +--- + +## 💾 数据库配置 + +### MySQL 8.0 + +| 项目 | 值 | +|------|-----| +| **主机** | localhost (内网服务器) | +| **端口** | 3306 | +| **数据库名** | designhub | +| **用户名** | designhub | +| **密码** | DesignHub@2025_DB | +| **Root密码** | DesignHub@2025_Root | +| **字符集** | utf8mb4 | +| **数据表数** | 39个 | + +**连接命令**: +```bash +# SSH到内网服务器 +ssh -p 5204 root@156.225.27.52 + +# 连接MySQL +mysql -u designhub -p'DesignHub@2025_DB' designhub +``` + +**数据库备份**: +```bash +# 备份 +mysqldump -u designhub -p'DesignHub@2025_DB' designhub > backup_$(date +%Y%m%d_%H%M%S).sql + +# 恢复 +mysql -u designhub -p'DesignHub@2025_DB' designhub < backup.sql +``` + +### Redis + +| 项目 | 值 | +|------|-----| +| **主机** | localhost (内网服务器) | +| **端口** | 6379 | +| **密码** | DesignHub@2025_Redis | +| **数据库** | 0 | + +**连接命令**: +```bash +# SSH到内网服务器 +ssh -p 5204 root@156.225.27.52 + +# 连接Redis +redis-cli -a 'DesignHub@2025_Redis' +``` + +--- + +## 🔧 服务配置 + +### 端口分配 + +#### 公网服务器 +| 服务 | 端口 | 说明 | +|------|------|------| +| SSH | 50232 | 远程管理 | +| HTTP | 80 | 自动跳转到HTTPS | +| HTTPS | 443 | SSL加密访问 | +| DesignHub | 5246 | frp穿透后的应用端口 | + +#### 内网服务器 +| 服务 | 端口 | 说明 | +|------|------|------| +| SSH | 5204 | 远程管理 | +| Nginx | 8888 | 前端和API代理 | +| 后端服务 | 8080 | Spring Boot应用 | +| MySQL | 3306 | 数据库 | +| Redis | 6379 | 缓存 | +| frp客户端 | 7000 | frp连接端口 | + +--- + +## 📂 目录结构 + +### 公网服务器 + +``` +/www/server/ +├── nginx/ +│ └── conf/ +│ └── nginx.conf # Nginx主配置 +├── panel/ +│ └── vhost/ +│ └── nginx/ +│ └── qd.yydd.ai.conf # DesignHub站点配置 +└── wwwroot/ + └── qd.yydd.ai/ # SSL证书验证目录 + +/etc/letsencrypt/ +└── live/ + └── qd.yydd.ai/ # SSL证书 + ├── fullchain.pem # 完整证书链 + ├── privkey.pem # 私钥 + ├── cert.pem # 证书 + └── chain.pem # 中间证书 + +/www/wwwlogs/ +├── qd.yydd.ai.https.access.log # HTTPS访问日志 +└── qd.yydd.ai.https.error.log # HTTPS错误日志 +``` + +### 内网服务器 + +``` +/opt/designhub/ +├── backend/ # 后端源代码 +│ ├── src/ +│ ├── pom.xml +│ └── target/ +├── frontend/ # 前端构建文件 +│ ├── index.html +│ ├── assets/ +│ └── ... +├── logs/ # 日志目录 +│ ├── backend.log # 后端日志 +│ └── application.log # 应用日志 +├── uploads/ # 上传文件目录 +└── designhub.jar # 运行的JAR包 + +/etc/ +├── nginx/ +│ └── sites-available/ +│ └── designhub.conf # Nginx配置 +├── systemd/ +│ └── system/ +│ └── designhub-backend.service # 后端服务 +└── frp/ + └── frpc.ini # frp客户端配置 +``` + +--- + +## 📝 配置文件位置 + +### 公网服务器 + +| 配置 | 路径 | 说明 | +|------|------|------| +| Nginx主配置 | /www/server/nginx/conf/nginx.conf | 宝塔面板管理 | +| 站点配置 | /www/server/panel/vhost/nginx/qd.yydd.ai.conf | DesignHub站点配置 | +| SSL证书 | /etc/letsencrypt/live/qd.yydd.ai/ | Let's Encrypt证书 | +| 访问日志 | /www/wwwlogs/qd.yydd.ai.https.access.log | HTTPS访问日志 | +| 错误日志 | /www/wwwlogs/qd.yydd.ai.https.error.log | HTTPS错误日志 | + +### 内网服务器 + +| 配置 | 路径 | 说明 | +|------|------|------| +| 后端配置 | /opt/designhub/backend/src/main/resources/application-prod.yml | Spring Boot配置 | +| Nginx配置 | /etc/nginx/sites-available/designhub.conf | 反向代理配置 | +| 后端服务 | /etc/systemd/system/designhub-backend.service | systemd服务配置 | +| frp配置 | /etc/frp/frpc.ini | frp客户端配置 | +| MySQL配置 | /etc/mysql/my.cnf | MySQL配置 | +| Redis配置 | /etc/redis/redis.conf | Redis配置 | + +--- + +## 🔐 SSL证书配置 + +### 证书信息 + +| 项目 | 值 | +|------|-----| +| **域名** | qd.yydd.ai | +| **证书提供商** | Let's Encrypt | +| **证书类型** | 免费DV证书 | +| **有效期** | 2025-12-23 至 2026-03-23 (90天) | +| **自动续期** | ✅ 已配置 | +| **协议** | TLS 1.2, TLS 1.3 | +| **HTTP/2** | ✅ 已启用 | + +### 证书续期 + +**自动续期任务** (已配置): +```bash +# 每天凌晨2点检查续期 +0 2 * * * certbot renew --quiet --post-hook 'nginx -s reload' >> /var/log/certbot-renew.log 2>&1 +``` + +**手动续期**: +```bash +# SSH到公网服务器 +ssh -p 50232 root@156.225.27.52 + +# 测试续期(不会实际续期) +certbot renew --dry-run + +# 手动续期 +certbot renew +nginx -s reload +``` + +--- + +## 🌐 frp内网穿透配置 + +### frp客户端配置 (内网服务器) + +**配置文件**: `/etc/frp/frpc.ini` + +```ini +[common] +server_addr = 156.225.27.52 +server_port = 7000 +token = your_token_here + +[designhub-web] +name = designhub-web +type = tcp +local_ip = 127.0.0.1 +local_port = 8888 +remote_port = 5246 +``` + +**服务管理**: +```bash +# SSH到内网服务器 +ssh -p 5204 root@156.225.27.52 + +# 启动frp客户端 +systemctl start frpc + +# 停止 +systemctl stop frpc + +# 重启 +systemctl restart frpc + +# 查看状态 +systemctl status frpc + +# 查看日志 +journalctl -u frpc -f +``` + +--- + +## 🚀 运维命令 + +### 公网服务器管理 + +```bash +# SSH连接 +ssh -p 50232 root@156.225.27.52 + +# Nginx管理 +nginx -t # 测试配置 +nginx -s reload # 重载配置(不中断服务) +systemctl restart nginx # 重启Nginx +systemctl status nginx # 查看状态 + +# 查看日志 +tail -f /www/wwwlogs/qd.yydd.ai.https.access.log # 访问日志 +tail -f /www/wwwlogs/qd.yydd.ai.https.error.log # 错误日志 + +# SSL证书管理 +certbot certificates # 查看所有证书 +certbot renew --dry-run # 测试续期 +``` + +### 内网服务器管理 + +```bash +# SSH连接 +ssh -p 5204 root@156.225.27.52 + +# 查看所有服务状态 +systemctl status designhub-backend mysql nginx redis-server frpc + +# 后端服务 +systemctl start designhub-backend # 启动 +systemctl stop designhub-backend # 停止 +systemctl restart designhub-backend # 重启 +systemctl status designhub-backend # 状态 +journalctl -u designhub-backend -f # 查看日志 + +# Nginx +systemctl restart nginx # 重启 +nginx -t # 测试配置 +ss -tlnp | grep 8888 # 检查端口 + +# MySQL +systemctl restart mysql # 重启 +systemctl status mysql # 状态 +mysql -u designhub -p'DesignHub@2025_DB' designhub # 连接 + +# Redis +systemctl restart redis-server # 重启 +systemctl status redis-server # 状态 +redis-cli -a 'DesignHub@2025_Redis' # 连接 + +# frp客户端 +systemctl restart frpc # 重启 +systemctl status frpc # 状态 +journalctl -u frpc -f # 查看日志 +``` + +### 日志查看 + +```bash +# 后端应用日志 +tail -f /opt/designhub/logs/backend.log +tail -f /opt/designhub/logs/application.log + +# 后端服务日志 +journalctl -u designhub-backend -f +journalctl -u designhub-backend -n 100 --no-pager + +# Nginx日志(内网) +tail -f /var/log/nginx/access.log +tail -f /var/log/nginx/error.log + +# frp日志 +journalctl -u frpc -f +``` + +--- + +## 📦 部署更新流程 + +### 更新后端代码 + +```bash +# 1. 本地同步代码到内网服务器 +cd /Users/lanmu/Desktop/qiangdanxitong/backend +rsync -avz --exclude='target' -e "ssh -p 5204" \ + src/ root@156.225.27.52:/opt/designhub/backend/src/ + +# 2. SSH到内网服务器编译 +ssh -p 5204 root@156.225.27.52 +cd /opt/designhub/backend +mvn clean package -DskipTests + +# 3. 替换JAR包 +cp target/designhub-backend-1.0.0.jar /opt/designhub/designhub.jar + +# 4. 重启后端服务 +systemctl restart designhub-backend + +# 5. 验证服务 +systemctl status designhub-backend +tail -f /opt/designhub/logs/backend.log + +# 6. 测试访问 +curl -I https://qd.yydd.ai +``` + +### 更新前端代码 + +```bash +# 1. 本地构建 +cd /Users/lanmu/Desktop/qiangdanxitong/frontend +pnpm build + +# 2. 上传到内网服务器 +rsync -avz -e "ssh -p 5204" \ + dist/ root@156.225.27.52:/opt/designhub/frontend/ + +# 3. 清除浏览器缓存(当前配置已禁用Nginx缓存) +# 直接刷新浏览器即可看到更新 + +# 4. 测试访问 +curl -I https://qd.yydd.ai +``` + +### 一键部署脚本 + +```bash +#!/bin/bash +# 保存为 deploy.sh + +echo "=== DesignHub 一键部署 ===" + +# 后端部署 +echo "【1/4】同步后端代码..." +rsync -avz --exclude='target' -e "ssh -p 5204" \ + backend/src/ root@156.225.27.52:/opt/designhub/backend/src/ + +echo "【2/4】编译后端..." +ssh -p 5204 root@156.225.27.52 << 'EOF' +cd /opt/designhub/backend +mvn clean package -DskipTests +cp target/designhub-backend-1.0.0.jar /opt/designhub/designhub.jar +systemctl restart designhub-backend +EOF + +# 前端部署 +echo "【3/4】构建前端..." +cd frontend +pnpm build + +echo "【4/4】上传前端..." +rsync -avz -e "ssh -p 5204" \ + dist/ root@156.225.27.52:/opt/designhub/frontend/ + +echo "" +echo "✅ 部署完成!" +echo "访问地址: https://qd.yydd.ai" +``` + +使用方法: +```bash +chmod +x deploy.sh +./deploy.sh +``` + +--- + +## 🔍 故障排查 + +### 1. HTTPS无法访问 + +```bash +# 检查公网服务器Nginx +ssh -p 50232 root@156.225.27.52 +systemctl status nginx +nginx -t +tail -50 /www/wwwlogs/qd.yydd.ai.https.error.log + +# 检查SSL证书 +openssl x509 -in /etc/letsencrypt/live/qd.yydd.ai/fullchain.pem -noout -dates + +# 检查端口监听 +ss -tlnp | grep 443 +``` + +### 2. 后端服务无法启动 + +```bash +# SSH到内网服务器 +ssh -p 5204 root@156.225.27.52 + +# 查看详细日志 +journalctl -u designhub-backend --no-pager -n 100 + +# 检查端口占用 +ss -tlnp | grep 8080 + +# 手动启动测试 +cd /opt/designhub +java -jar designhub.jar --spring.profiles.active=prod + +# 检查配置文件 +cat /opt/designhub/backend/src/main/resources/application-prod.yml +``` + +### 3. 数据库连接失败 + +```bash +# 检查MySQL状态 +systemctl status mysql + +# 测试连接 +mysql -u designhub -p'DesignHub@2025_DB' -e "SELECT 1" + +# 检查用户权限 +mysql -u root -p'DesignHub@2025_Root' -e "SHOW GRANTS FOR 'designhub'@'localhost';" + +# 查看错误日志 +tail -50 /var/log/mysql/error.log +``` + +### 4. frp穿透失败 + +```bash +# SSH到内网服务器 +ssh -p 5204 root@156.225.27.52 + +# 检查frpc状态 +systemctl status frpc + +# 查看日志 +journalctl -u frpc -n 100 --no-pager + +# 测试本地服务 +curl -I http://127.0.0.1:8888 + +# 测试穿透后端口 +curl -I http://127.0.0.1:5246 +``` + +### 5. Nginx 502错误 + +```bash +# 检查后端服务是否运行 +ssh -p 5204 root@156.225.27.52 +systemctl status designhub-backend + +# 检查Nginx配置 +nginx -t + +# 查看错误日志 +tail -50 /var/log/nginx/error.log + +# 测试后端连接 +curl -I http://127.0.0.1:8080/api/health +``` + +### 6. 页面更新不生效 + +```bash +# 当前配置已禁用Nginx缓存,直接刷新即可 + +# 如仍有问题,检查前端文件 +ssh -p 5204 root@156.225.27.52 +ls -la /opt/designhub/frontend/ +cat /opt/designhub/frontend/index.html + +# 清除浏览器缓存 +# Chrome: Ctrl+Shift+Delete +# 或使用无痕模式测试 +``` + +--- + +## 📊 监控检查 + +### 健康检查命令 + +```bash +# 一键检查所有服务(内网服务器) +ssh -p 5204 root@156.225.27.52 << 'EOF' +echo "=== DesignHub 服务状态 ===" +echo "" +echo "【后端服务】" +systemctl is-active designhub-backend +echo "" +echo "【MySQL】" +systemctl is-active mysql +echo "" +echo "【Redis】" +systemctl is-active redis-server +echo "" +echo "【Nginx】" +systemctl is-active nginx +echo "" +echo "【frp客户端】" +systemctl is-active frpc +echo "" +echo "【端口监听】" +ss -tlnp | grep -E "8080|8888|3306|6379" +EOF + +# 检查公网服务器 +ssh -p 50232 root@156.225.27.52 << 'EOF' +echo "=== 公网服务器状态 ===" +echo "" +echo "【Nginx】" +systemctl is-active nginx +echo "" +echo "【SSL证书有效期】" +openssl x509 -in /etc/letsencrypt/live/qd.yydd.ai/fullchain.pem -noout -dates +EOF +``` + +### 性能监控 + +```bash +# CPU和内存使用 +ssh -p 5204 root@156.225.27.52 +top -bn1 | head -20 + +# 磁盘使用 +df -h + +# 网络连接 +ss -s + +# 后端JVM内存 +jps -lv +``` + +--- + +## 🌐 DNS配置 + +### 域名解析配置 + +| 类型 | 主机记录 | 记录值 | TTL | +|------|----------|--------|-----| +| A记录 | qd | 156.225.27.52 | 600 | + +### 验证DNS解析 + +```bash +# 查询域名解析 +nslookup qd.yydd.ai + +# Ping测试 +ping qd.yydd.ai + +# 详细查询 +dig qd.yydd.ai +``` + +--- + +## 🔒 安全配置 + +### 防火墙规则 + +**公网服务器**: +- 开放端口: 50232(SSH), 80(HTTP), 443(HTTPS) +- 通过宝塔面板管理 + +**内网服务器**: +```bash +# 查看防火墙状态 +ufw status + +# 开放必要端口(如需要) +ufw allow 5204/tcp # SSH +ufw allow 8888/tcp # Nginx +``` + +### SSH安全 + +**建议**: +- ✅ 使用非标准SSH端口(已实施:50232、5204) +- ✅ 使用强密码 +- 建议: 配置SSH密钥认证 +- 建议: 禁用root密码登录(配置密钥后) + +--- + +## 🎯 快速参考 + +### 常用命令速查 + +```bash +# ============ 公网服务器 ============ +# SSH连接 +ssh -p 50232 root@156.225.27.52 + +# 重载Nginx +nginx -s reload + +# 查看HTTPS日志 +tail -f /www/wwwlogs/qd.yydd.ai.https.access.log + +# SSL证书续期 +certbot renew + + +# ============ 内网服务器 ============ +# SSH连接 +ssh -p 5204 root@156.225.27.52 + +# 重启后端 +systemctl restart designhub-backend + +# 查看后端日志 +tail -f /opt/designhub/logs/backend.log + +# 连接数据库 +mysql -u designhub -p'DesignHub@2025_DB' designhub + +# 连接Redis +redis-cli -a 'DesignHub@2025_Redis' + +# 查看所有服务状态 +systemctl status designhub-backend mysql nginx redis-server frpc + + +# ============ 本地开发 ============ +# 部署后端 +rsync -avz --exclude='target' -e "ssh -p 5204" backend/src/ root@156.225.27.52:/opt/designhub/backend/src/ +ssh -p 5204 root@156.225.27.52 'cd /opt/designhub/backend && mvn clean package -DskipTests && cp target/*.jar /opt/designhub/designhub.jar && systemctl restart designhub-backend' + +# 部署前端 +cd frontend && pnpm build +rsync -avz -e "ssh -p 5204" dist/ root@156.225.27.52:/opt/designhub/frontend/ + +# 测试访问 +curl -I https://qd.yydd.ai +``` + +--- + +## 📚 相关文档 + +- **🎉 项目部署完成-最终报告.md** - 完整部署记录 +- **域名和HTTPS配置指南.md** - SSL证书配置详解 +- **Nginx缓存配置说明.md** - 缓存策略说明 +- **DesignHub访问地址.md** - 访问地址汇总 +- **最终开发总结.md** - 功能开发总结 + +--- + +## 📞 技术支持 + +### 环境信息 + +**公网服务器**: +- 系统: CentOS 7 x86_64 +- Nginx: 1.24.0 +- Certbot: 1.11.0 + +**内网服务器**: +- 系统: Ubuntu 24.04.2 LTS +- Java: OpenJDK 17.0.17 +- Maven: 3.8.7 +- MySQL: 8.0 +- Redis: 7.x +- Nginx: 1.24.x + +### 访问测试 + +```bash +# HTTPS访问测试 +curl -I https://qd.yydd.ai + +# 应该返回: +# HTTP/2 200 +# cache-control: no-store, no-cache, must-revalidate... +``` + +--- + +## 📝 更新历史 + +| 日期 | 版本 | 更新内容 | +|------|------|----------| +| 2025-12-23 | v2.0 | 服务器迁移,添加域名和HTTPS | +| 2025-12-19 | v1.0 | 初始版本 | + +--- + +## 🎯 系统状态 + +### 当前配置 + +- ✅ **域名**: qd.yydd.ai +- ✅ **HTTPS**: 已启用(TLS 1.3) +- ✅ **HTTP/2**: 已启用 +- ✅ **SSL证书**: Let's Encrypt(自动续期) +- ✅ **缓存策略**: 已禁用(开发模式) +- ✅ **frp穿透**: 已配置(端口5246) +- ✅ **服务状态**: 所有服务正常运行 + +### 性能指标 + +- 🚀 **国内访问延迟**: < 100ms +- 🚀 **HTTPS加密**: TLS 1.3 (0-RTT) +- 🚀 **HTTP/2多路复用**: 已启用 +- 🚀 **缓存策略**: 无缓存(便于开发) + +--- + +**文档维护**: 请在服务器配置变更后及时更新此文档 +**最后更新**: 2025-12-23 +**状态**: ✅ 生产环境运行中 diff --git a/配置SSL证书说明.md b/配置SSL证书说明.md new file mode 100644 index 0000000..be4fc0f --- /dev/null +++ b/配置SSL证书说明.md @@ -0,0 +1,113 @@ +# SSL证书配置说明 + +## 当前状态 + +**域名**: QD.YYDD.AI +**服务器IP**: 45.192.99.48 +**域名当前解析**: 198.18.0.113 + +⚠️ **重要**: 域名解析尚未指向服务器IP,需要先配置DNS解析。 + +--- + +## 步骤1: 配置DNS解析 + +在域名管理面板中,将 `QD.YYDD.AI` 的A记录指向 `45.192.99.48` + +--- + +## 步骤2: 验证域名解析 + +```bash +# 在本地执行 +nslookup QD.YYDD.AI +# 应该返回: 45.192.99.48 +``` + +--- + +## 步骤3: 申请Let's Encrypt证书 + +**方法1: 使用certbot standalone模式(推荐)** + +```bash +ssh root@45.192.99.48 +systemctl stop nginx +certbot certonly --standalone -d QD.YYDD.AI --non-interactive --agree-tos --email admin@yydd.ai +systemctl start nginx +``` + +**方法2: 使用certbot nginx模式** + +```bash +ssh root@45.192.99.48 +certbot --nginx -d QD.YYDD.AI --non-interactive --agree-tos --email admin@yydd.ai +``` + +--- + +## 步骤4: 配置Nginx使用SSL证书 + +证书申请成功后,证书文件将位于: +- `/etc/letsencrypt/live/QD.YYDD.AI/fullchain.pem` +- `/etc/letsencrypt/live/QD.YYDD.AI/privkey.pem` + +然后更新Nginx配置(已包含在配置文件中)。 + +--- + +## 步骤5: 重启Nginx + +```bash +nginx -t +systemctl restart nginx +``` + +--- + +## 当前配置说明 + +已配置: +- ✅ HTTPS (443端口) +- ✅ HTTP重定向到HTTPS (80端口) +- ✅ 关闭所有缓存 +- ✅ 上传限制100M +- ✅ 使用域名QD.YYDD.AI + +待完成: +- ⏳ SSL证书申请(需等待DNS解析生效) +- ⏳ 自动续期配置 + +--- + +## 自动续期 + +Let's Encrypt证书有效期90天,需要配置自动续期: + +```bash +# 测试续期 +certbot renew --dry-run + +# 设置自动续期(cron任务已自动配置) +systemctl status certbot.timer +``` + +--- + +## 临时使用自签名证书 + +如果需要立即测试,可以使用自签名证书(浏览器会显示警告): + +```bash +mkdir -p /etc/nginx/ssl +openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout /etc/nginx/ssl/QD.YYDD.AI.key \ + -out /etc/nginx/ssl/QD.YYDD.AI.crt \ + -subj '/CN=QD.YYDD.AI' +``` + +然后在Nginx配置中使用: +```nginx +ssl_certificate /etc/nginx/ssl/QD.YYDD.AI.crt; +ssl_certificate_key /etc/nginx/ssl/QD.YYDD.AI.key; +``` diff --git a/钉钉登录完整配置指南.md b/钉钉登录完整配置指南.md new file mode 100644 index 0000000..75aa679 --- /dev/null +++ b/钉钉登录完整配置指南.md @@ -0,0 +1,881 @@ +# 钉钉登录完整配置指南 + +本文档提供管理员配置钉钉登录功能的完整步骤说明。 + +--- + +## 📋 **配置流程总览** + +``` +步骤1: 钉钉开放平台创建应用 + ↓ +步骤2: 获取AppKey和AppSecret + ↓ +步骤3: 在系统设置中配置钉钉信息 + ↓ +步骤4: 启用钉钉登录功能 + ↓ +步骤5: 配置部门映射(可选) + ↓ +步骤6: 测试钉钉登录 +``` + +--- + +## 一、钉钉开放平台配置 + +### 1.1 创建应用 + +1. **登录钉钉开放平台** + - 访问:https://open.dingtalk.com/ + - 使用管理员账号登录 + +2. **创建H5微应用** + - 进入"应用开发" → "企业内部应用" → "创建应用" + - 选择"H5微应用" + - 填写应用名称:`DesignHub` + - 上传应用图标(可选) + +3. **记录关键信息** + ``` + AppKey (ClientId): dingxxxxxxxxxxxxxx + AppSecret (ClientSecret): xxxxxxxxxxxxxxxxxxxxxxxx + AgentId: 123456789(工作通知需要) + CorpId: dingxxxxxxxxxxxxxxxx(企业ID) + ``` + +### 1.2 配置应用权限 + +在应用"权限管理"中,申请以下权限: + +| 权限名称 | 权限代码 | 用途 | +|---------|---------|------| +| 通讯录只读权限 | `Contact.User.Read` | 获取用户信息 | +| 身份验证 | `openid` | OAuth登录 | + +### 1.3 配置回调地址 + +在应用"开发配置"中设置: + +``` +回调域名: http://your-domain.com +或 +回调域名: http://38.246.244.140:8888 + +具体回调地址: +- http://your-domain.com/dingtalk-callback.html +- http://your-domain.com/api/auth/dingtalk/callback +``` + +⚠️ **重要**:回调域名必须与实际访问域名一致! + +--- + +## 二、系统设置中配置钉钉 + +### 2.1 访问系统设置 + +1. 使用管理员账号登录系统 +2. 点击导航栏右上角的"系统设置"按钮 +3. 切换到"钉钉集成"标签页 + +### 2.2 填写应用配置 + +**必填字段**: + +| 字段 | 说明 | 示例 | +|------|------|------| +| **AppKey** | 钉钉应用的AppKey | `dingxxxxxxxxxxxxxx` | +| **AppSecret** | 钉钉应用的AppSecret | `xxxxxxxxxxxxxxxxxxxxxxxx` | + +**可选字段**: + +| 字段 | 说明 | 用途 | +|------|------|------| +| AgentId | 应用的AgentId | 发送工作通知 | +| 企业ID (CorpId) | 企业CorpId | 钉钉内免密登录 | + +### 2.3 配置机器人(可选) + +如果需要使用钉钉机器人发送消息: + +1. 在钉钉群聊中添加"自定义机器人" +2. 获取Webhook地址 +3. 如果启用了"加签",获取密钥 +4. 填写到系统配置中: + - **机器人Webhook地址**: `https://oapi.dingtalk.com/robot/send?access_token=xxx` + - **机器人密钥**: `SECxxxxxxxxxxxxx`(如果启用加签) + +### 2.4 启用功能 + +勾选需要启用的功能: + +- ☑️ **钉钉登录** - 用户可使用钉钉扫码登录 +- ☑️ **消息通知** - 系统消息通过钉钉发送 +- ☑️ **用户同步** - 同步钉钉组织架构 + +### 2.5 保存并测试 + +1. 点击"保存配置"按钮 +2. 点击"测试连接"按钮,验证配置正确 +3. (可选)点击"发送测试消息"测试机器人 + +--- + +## 三、数据库配置验证 + +### 3.1 查看配置 + +```sql +-- 查看当前钉钉配置 +SELECT id, name, app_key, corp_id, enabled, enabled_features +FROM dingtalk_config +ORDER BY update_time DESC +LIMIT 1; +``` + +### 3.2 手动更新配置(如需要) + +```sql +-- 更新AppKey +UPDATE dingtalk_config +SET app_key = 'dingxxxxxxxxxxxxxx' +WHERE id = 1; + +-- 更新AppSecret +UPDATE dingtalk_config +SET app_secret = 'xxxxxxxxxxxxxxxxxxxxxxxx' +WHERE id = 1; + +-- 启用钉钉 +UPDATE dingtalk_config +SET enabled = 1 +WHERE id = 1; + +-- 启用钉钉登录功能 +UPDATE dingtalk_config +SET enabled_features = '{"login": true, "notification": false, "sync": false}' +WHERE id = 1; +``` + +### 3.3 验证配置生效 + +```bash +# 测试配置是否已加载 +curl http://localhost:8080/api/dingtalk/is-configured + +# 期望返回 +{"code":200,"data":true} # 已配置且启用 +{"code":200,"data":false} # 未配置或未启用 +``` + +--- + +## 四、部门映射配置 + +### 4.1 为什么需要部门映射? + +钉钉用户登录时,系统需要将用户分配到合适的系统部门: +- **需求部门**(demand):产品部、市场部、运营部 +- **接单部门**(receiving):设计部、创意部 + +### 4.2 配置方法 + +#### 方法1:手动配置钉钉部门ID + +```sql +-- 为系统部门绑定钉钉部门ID +UPDATE sys_dept +SET ding_dept_id = '123456789' -- 钉钉部门的dept_id +WHERE dept_name = '设计部'; +``` + +**如何获取钉钉部门ID?** +1. 登录钉钉管理后台 +2. 进入"通讯录" → "部门管理" +3. 点击部门,URL中显示的ID即为部门ID +4. 或使用API:`GET /api/dingtalk/departments` + +#### 方法2:通过部门名称自动匹配 + +确保系统部门名称与钉钉部门名称一致或包含关系: + +| 钉钉部门 | 系统部门 | 匹配结果 | +|---------|---------|---------| +| 设计部 | 设计部 | ✅ 完全匹配 | +| 研发中心-设计组 | 设计部 | ✅ 包含"设计" | +| UI设计团队 | 设计部 | ✅ 包含"设计" | +| 新部门A | - | ❌ 需手动选择 | + +### 4.3 查看部门映射状态 + +```sql +-- 查看哪些部门已配置钉钉ID +SELECT + id, + dept_name, + dept_type, + ding_dept_id, + CASE + WHEN ding_dept_id IS NOT NULL THEN '已绑定' + ELSE '未绑定' + END as status +FROM sys_dept +WHERE deleted = 0 +ORDER BY dept_type, id; +``` + +**建议**: +- ✅ 所有活跃部门都应配置`ding_dept_id` +- ✅ 定期检查钉钉组织架构变化 +- ✅ 新增部门时同步配置 + +--- + +## 五、首次登录用户处理 + +### 5.1 自动匹配成功 + +当钉钉用户的部门能匹配到系统部门时: + +``` +用户登录 → 获取钉钉信息 → 匹配系统部门成功 + ↓ + 创建用户(已分配部门和角色) + ↓ + 自动登录成功 +``` + +**日志示例**: +``` +[INFO] 通过钉钉部门ID精确匹配: 123456 -> 设计部 +[INFO] 自动匹配到系统部门: deptId=4, deptName=设计部, type=receiving +[INFO] 钉钉用户创建成功: userId=10, needSelectDept=false +``` + +### 5.2 无法自动匹配 + +当无法匹配时,系统会: + +``` +1. 创建用户记录(status=0,待完善) +2. 返回特殊错误码:1901 +3. 前端显示部门选择界面 +4. 用户选择系统部门 +5. 完成注册并登录 +``` + +**前端需要实现的界面**: +```vue + +``` + +--- + +## 六、配置检查清单 + +### 6.1 钉钉开放平台 ✓ + +- [ ] 已创建H5微应用 +- [ ] 已获取AppKey和AppSecret +- [ ] 已申请`Contact.User.Read`权限 +- [ ] 已申请`openid`权限 +- [ ] 已配置回调域名 +- [ ] 应用已发布上线 + +### 6.2 系统配置 ✓ + +- [ ] 已在"系统设置-钉钉集成"填写AppKey +- [ ] 已填写AppSecret +- [ ] 已勾选"钉钉登录"功能 +- [ ] 已点击"保存配置" +- [ ] 测试连接成功(绿色提示) + +### 6.3 部门映射 ✓ + +- [ ] 已为主要部门配置`ding_dept_id` +- [ ] 已确认部门名称匹配规则 +- [ ] 已测试新用户登录流程 + +### 6.4 数据库验证 ✓ + +```sql +-- 检查配置是否启用 +SELECT enabled, app_key, enabled_features +FROM dingtalk_config +WHERE id = 1; + +-- 期望结果 +enabled = 1 +app_key = 'dingxxxxx'(不为空) +enabled_features 包含 "login": true +``` + +--- + +## 七、API接口说明 + +### 7.1 配置相关API + +#### 获取配置 +``` +GET /api/dingtalk/config +需要认证: 是(管理员) + +Response: { + "code": 200, + "data": { + "id": 1, + "appKey": "dingxxxxx", + "appSecret": "******", // 已脱敏 + "enabled": 1, + "enabledFeatures": {...} + } +} +``` + +#### 保存配置 +``` +POST /api/dingtalk/config +需要认证: 是(管理员) + +Body: { + "appKey": "dingxxxxx", + "appSecret": "xxxxxxxx", + "corpId": "dingxxxxx", + "enabled": 1, + "enabledFeatures": { + "login": true, + "notification": true + } +} +``` + +#### 检查配置状态 +``` +GET /api/dingtalk/is-configured +需要认证: 否 + +Response: { + "code": 200, + "data": true // true=已配置,false=未配置 +} +``` + +### 7.2 登录相关API + +#### 发起登录 +``` +GET /api/dingtalk/oauth/login?redirectUrl={回调地址} +需要认证: 否 + +Response: { + "code": 200, + "data": { + "authUrl": "https://login.dingtalk.com/oauth2/auth?...", + "appKey": "dingxxxxx" + } +} +``` + +#### 登录回调 +``` +GET /api/auth/dingtalk/callback?code={授权码} +需要认证: 否 + +Response A(匹配成功): { + "code": 200, + "data": { + "token": "eyJhbGc...", + "userId": 123, + "username": "张三" + } +} + +Response B(需选择部门): { + "code": 1901, + "message": "需要选择部门", + "data": { + "needSelectDept": true, + "userId": 123, + "dingDepartments": "设计组,创意组" + } +} +``` + +#### 选择部门 +``` +POST /api/auth/dingtalk/select-department +需要认证: 否 + +Body: { + "userId": 123, + "deptId": 4 +} + +Response: { + "code": 200, + "data": { + "token": "eyJhbGc...", + "userId": 123, + "deptId": 4, + "deptName": "设计部" + } +} +``` + +--- + +## 八、数据库表结构 + +### 8.1 钉钉配置表(dingtalk_config) + +```sql +CREATE TABLE `dingtalk_config` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(100) NOT NULL DEFAULT '默认配置', + `agent_id` varchar(100) DEFAULT NULL, + `app_key` varchar(100) DEFAULT NULL, + `app_secret` varchar(200) DEFAULT NULL, + `corp_id` varchar(100) DEFAULT NULL, + `robot_webhook` varchar(500) DEFAULT NULL, + `robot_secret` varchar(200) DEFAULT NULL, + `enabled` tinyint NOT NULL DEFAULT '0', + `enabled_features` json DEFAULT NULL, + `access_token` varchar(500) DEFAULT NULL, + `token_expire_time` datetime DEFAULT NULL, + `create_time` datetime DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +); +``` + +### 8.2 用户表相关字段(sys_user) + +```sql +-- 钉钉相关字段 +`ding_user_id` varchar(100) DEFAULT NULL, -- 钉钉用户ID +`email` varchar(100) DEFAULT NULL, -- 钉钉用户标识 +`dept_id` bigint DEFAULT NULL, -- 系统部门ID +`dept_name` varchar(100) DEFAULT NULL, -- 部门名称 + +-- 唯一索引 +UNIQUE KEY `uk_ding_user_id` (`ding_user_id`), +UNIQUE KEY `uk_email` (`email`) +``` + +### 8.3 部门表相关字段(sys_dept) + +```sql +-- 钉钉映射字段 +`ding_dept_id` varchar(100) DEFAULT NULL, -- 钉钉部门ID +`dept_type` varchar(20) DEFAULT 'receiving', -- 部门类型 +``` + +--- + +## 九、配置示例 + +### 9.1 完整配置示例 + +**系统设置界面填写**: +``` +应用配置: + AgentId: 1234567890 + AppKey: dingoa12345678abcdefg + AppSecret: abc123def456ghi789jkl012mno345pqr678stu901vwx234 + 企业ID: dingabcdef1234567890abcdefg1234567890 + +机器人配置(可选): + Webhook: https://oapi.dingtalk.com/robot/send?access_token=xxx + 密钥: SECxxx(如果启用加签) + +启用功能: + ☑ 钉钉登录 + ☑ 消息通知 + ☐ 用户同步 +``` + +**对应数据库记录**: +```sql +{ + "id": 1, + "name": "DesignHub生产环境", + "agent_id": "1234567890", + "app_key": "dingoa12345678abcdefg", + "app_secret": "abc123def456...", + "corp_id": "dingabcdef...", + "enabled": 1, + "enabled_features": { + "login": true, + "notification": true, + "sync": false + } +} +``` + +### 9.2 部门映射示例 + +```sql +-- 设计部绑定钉钉设计部 +UPDATE sys_dept +SET ding_dept_id = '123456' +WHERE dept_name = '设计部'; + +-- 产品部绑定钉钉产品部 +UPDATE sys_dept +SET ding_dept_id = '789012' +WHERE dept_name = '产品部'; + +-- 验证绑定 +SELECT id, dept_name, dept_type, ding_dept_id +FROM sys_dept +WHERE deleted = 0; +``` + +--- + +## 十、测试流程 + +### 10.1 管理员测试配置 + +1. **测试连接** + ```bash + # 后端日志应显示 + [INFO] 获取钉钉AccessToken响应: {"errcode":0,"access_token":"xxx"} + ``` + +2. **测试机器人** + - 点击"发送测试消息" + - 检查钉钉群是否收到消息 + +### 10.2 测试用户登录 + +#### 场景1:能自动匹配部门的用户 + +1. 退出登录 +2. 点击"钉钉登录"(前端需要添加此按钮) +3. 扫码授权 +4. 自动创建用户并登录成功 +5. 验证用户信息正确(姓名、部门、角色) + +**验证SQL**: +```sql +SELECT id, username, real_name, ding_user_id, dept_id, dept_name, role_ids +FROM sys_user +WHERE email LIKE 'dingtalk_%' +ORDER BY create_time DESC +LIMIT 5; +``` + +#### 场景2:需要手动选择部门的用户 + +1. 使用未配置映射的钉钉部门的用户 +2. 扫码登录 +3. 前端应显示部门选择页面 +4. 选择系统部门(如"设计部") +5. 完成登录 + +### 10.3 测试部门同步 + +1. 在钉钉中将用户移动到其他部门 +2. 用户重新登录 +3. 检查日志是否显示部门变更 +4. 验证系统中用户部门是否自动更新 + +--- + +## 十一、故障排查 + +### 11.1 配置无法保存 + +**问题**:点击保存后没有成功提示 + +**检查**: +```bash +# 查看后端日志 +journalctl -u designhub-backend -n 50 | grep -i dingtalk + +# 检查数据库 +SELECT * FROM dingtalk_config ORDER BY update_time DESC LIMIT 1; +``` + +**可能原因**: +- 数据库连接失败 +- 字段验证失败 +- 权限不足 + +### 11.2 测试连接失败 + +**问题**:点击"测试连接"显示失败 + +**检查**: +1. AppKey和AppSecret是否正确 +2. 网络是否能访问钉钉API +3. 钉钉应用是否已发布 + +**测试命令**: +```bash +# 手动测试获取token +curl -X GET "https://oapi.dingtalk.com/gettoken?appkey=YOUR_APP_KEY&appsecret=YOUR_APP_SECRET" + +# 期望返回 +{"errcode":0,"errmsg":"ok","access_token":"xxx","expires_in":7200} +``` + +### 11.3 登录提示未配置 + +**问题**:用户点击钉钉登录,提示"未配置" + +**检查**: +```sql +-- 1. 配置是否存在 +SELECT * FROM dingtalk_config; + +-- 2. 是否已启用 +SELECT enabled FROM dingtalk_config WHERE id = 1; + +-- 3. 启用功能是否包含login +SELECT enabled_features FROM dingtalk_config WHERE id = 1; +``` + +**修复**: +```sql +-- 启用配置 +UPDATE dingtalk_config SET enabled = 1 WHERE id = 1; + +-- 启用登录功能 +UPDATE dingtalk_config +SET enabled_features = JSON_SET(enabled_features, '$.login', true) +WHERE id = 1; +``` + +### 11.4 用户部门匹配失败 + +**问题**:所有钉钉用户都需要手动选择部门 + +**解决方案**: +1. **配置ding_dept_id** + ```sql + -- 查看钉钉部门列表 + GET /api/dingtalk/departments + + -- 为系统部门配置钉钉部门ID + UPDATE sys_dept SET ding_dept_id = '123456' WHERE dept_name = '设计部'; + ``` + +2. **统一部门命名** + - 确保系统部门名称与钉钉部门名称一致 + - 或使用包含关系命名 + +--- + +## 十二、安全建议 + +### 12.1 密钥管理 + +- ✅ AppSecret已在数据库加密存储 +- ✅ 前端获取配置时已脱敏(显示`******`) +- ⚠️ 不要在日志中打印完整密钥 +- ⚠️ 定期更新AppSecret + +### 12.2 访问控制 + +- ✅ 钉钉配置API仅管理员可访问 +- ✅ 回调接口不需要认证(OAuth流程必需) +- ✅ 选择部门接口不需要认证(首次登录流程) + +### 12.3 数据安全 + +- ✅ 钉钉用户的email使用虚拟地址 +- ✅ 密码使用随机UUID + BCrypt加密 +- ✅ unionId作为全局唯一标识 + +--- + +## 十三、监控与维护 + +### 13.1 日常监控 + +```bash +# 查看钉钉登录日志 +journalctl -u designhub-backend --since today | grep -i dingtalk + +# 统计钉钉用户数量 +SELECT COUNT(*) FROM sys_user WHERE email LIKE 'dingtalk_%'; + +# 查看最近登录的钉钉用户 +SELECT id, username, real_name, dept_name, create_time +FROM sys_user +WHERE email LIKE 'dingtalk_%' +ORDER BY create_time DESC +LIMIT 10; +``` + +### 13.2 定期维护 + +**每月任务**: +- [ ] 检查AccessToken缓存是否正常 +- [ ] 验证部门映射准确性 +- [ ] 审查异常登录日志 +- [ ] 清理无效的待完善用户(status=0超过30天) + +**每季度任务**: +- [ ] 同步钉钉组织架构变化 +- [ ] 审查并更新部门映射 +- [ ] 检查钉钉应用权限状态 +- [ ] 更新AppSecret(如需要) + +--- + +## 十四、配置流程图 + +``` +┌─────────────────────────────────────────┐ +│ 1. 钉钉开放平台 │ +│ - 创建应用 │ +│ - 获取AppKey/AppSecret │ +│ - 配置回调地址 │ +│ - 申请权限 │ +└──────────────┬──────────────────────────┘ + ↓ +┌─────────────────────────────────────────┐ +│ 2. 系统设置-钉钉集成 │ +│ - 填写AppKey │ +│ - 填写AppSecret │ +│ - 勾选"钉钉登录" │ +│ - 点击"保存配置" │ +└──────────────┬──────────────────────────┘ + ↓ +┌─────────────────────────────────────────┐ +│ 3. 配置自动同步到数据库 │ +│ - dingtalk_config表 │ +│ - enabled = 1 │ +│ - enabled_features.login = true │ +└──────────────┬──────────────────────────┘ + ↓ +┌─────────────────────────────────────────┐ +│ 4. 后端自动从数据库读取 │ +│ - DingTalkService.getConfig() │ +│ - 缓存AccessToken │ +│ - 提供OAuth登录服务 │ +└──────────────┬──────────────────────────┘ + ↓ +┌─────────────────────────────────────────┐ +│ 5. 前端显示钉钉登录按钮 │ +│ - 检查 /api/dingtalk/is-configured │ +│ - 显示登录按钮 │ +└──────────────┬──────────────────────────┘ + ↓ +┌─────────────────────────────────────────┐ +│ 6. 用户可以使用钉钉登录 │ +│ - 扫码授权 │ +│ - 自动创建账号 │ +│ - 部门智能匹配 │ +└─────────────────────────────────────────┘ +``` + +--- + +## 十五、常见错误代码 + +| 错误码 | 错误信息 | 原因 | 解决方案 | +|-------|---------|------|----------| +| 1401 | 钉钉认证失败 | AppKey/AppSecret错误 | 检查配置 | +| 1402 | 钉钉接口调用失败 | 网络或权限问题 | 检查网络和权限 | +| 1901 | 需要选择部门 | 无法自动匹配部门 | 前端显示选择界面 | +| 400 | 参数错误 | code参数缺失或无效 | 重新发起登录 | + +--- + +## 十六、快速配置命令 + +### 16.1 一键配置测试环境 + +```sql +-- 配置钉钉应用(替换为实际值) +UPDATE dingtalk_config SET + app_key = 'YOUR_APP_KEY', + app_secret = 'YOUR_APP_SECRET', + corp_id = 'YOUR_CORP_ID', + agent_id = 'YOUR_AGENT_ID', + enabled = 1, + enabled_features = '{"login": true, "notification": true}' +WHERE id = 1; + +-- 配置部门映射 +UPDATE sys_dept SET ding_dept_id = '123456' WHERE dept_name = '设计部'; +UPDATE sys_dept SET ding_dept_id = '789012' WHERE dept_name = '产品部'; +``` + +### 16.2 验证配置 + +```bash +# 1. 检查配置状态 +curl http://localhost:8080/api/dingtalk/is-configured + +# 2. 获取登录URL +curl http://localhost:8080/api/dingtalk/oauth/login + +# 3. 查看配置(需要token) +curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:8080/api/dingtalk/config +``` + +--- + +## 十七、配置完成验证 + +### ✓ 配置完成的标志 + +- [ ] `/api/dingtalk/is-configured` 返回 `true` +- [ ] `/api/dingtalk/oauth/login` 能返回authUrl +- [ ] 系统设置界面显示钉钉配置信息 +- [ ] 测试连接显示成功 +- [ ] 至少一个部门配置了`ding_dept_id` + +### ✓ 功能正常的标志 + +- [ ] 新钉钉用户能成功登录 +- [ ] 用户信息正确保存到数据库 +- [ ] 部门自动匹配或手动选择正常 +- [ ] 用户角色自动分配正确 +- [ ] 后续登录无需重新选择部门 + +--- + +**配置完成时间**: 2025-12-22 +**配置状态**: ✅ 数据库表结构完整,配置流程就绪 +**后续步骤**: 管理员在系统设置中填写钉钉应用信息 + +--- + +**技术支持**: +- 钉钉开放平台:https://open.dingtalk.com/ +- 系统文档:`/钉钉OAuth登录配置文档.md` +- 同步机制:`/钉钉用户部门同步机制.md`