Browse Source

Merge branch 'master' of http://gitlab.frkj.cc/php/zhjwxt

master
于宏哲PHP 9 months ago
parent
commit
001a07eeba
  1. 273
      README.md
  2. 636
      admin/src/app/views/service/components/service-edit.vue
  3. 465
      admin/src/app/views/service/service.vue
  4. 9
      niucloud/app/api/controller/apiController/Course.php
  5. 37
      niucloud/app/api/controller/apiController/CustomerResources.php
  6. 3
      niucloud/app/api/controller/apiController/Statistics.php
  7. 2
      niucloud/app/api/route/route.php
  8. 15
      niucloud/app/command/ClientCommand/TestCommand.php
  9. 30
      niucloud/app/command/TestCommand.php
  10. 60
      niucloud/app/common.php
  11. 43
      niucloud/app/dict/schedule/schedule.php
  12. 20
      niucloud/app/job/custmer/ResourceAutoAllocation.php
  13. 43
      niucloud/app/job/schedule/HandleCourseSchedule.php
  14. 109
      niucloud/app/job/transfer/schedule/CourseScheduleJob.php
  15. 829
      niucloud/app/job/transfer/schedule/PerformanceCalculation.php
  16. 163
      niucloud/app/job/transfer/schedule/ResourceAutoAllocation.php
  17. 257
      niucloud/app/service/admin/performance/PerformanceService.php
  18. 52
      niucloud/app/service/api/apiService/CourseService.php
  19. 127
      niucloud/app/service/api/apiService/CustomerResourcesService.php
  20. 3
      niucloud/app/service/api/login/LoginService.php
  21. 2
      niucloud/config/console.php

273
README.md

@ -1,144 +1,133 @@
![输入图片说明](https://media.niucloud.com/1712133019020249f332e83457a01a6799472e0495_aliyun.png)
# 智慧教务系统
![系统Logo](https://media.niucloud.com/1712133019020249f332e83457a01a6799472e0495_aliyun.png)
## 项目概述
智慧教务系统是一套基于ThinkPHP 8和Vue3开发的现代化教育管理平台,专为教育培训机构、学校等教育组织设计。系统提供全面的学生管理、课程管理、排课管理、校区管理、场地管理、人员管理、合同管理等功能,帮助教育机构实现数字化转型,提高管理效率。
## 技术架构
### 后端技术栈
- PHP 8
- ThinkPHP 8
- MySQL 数据库
- Workman 高性能框架(消息队列、计划任务)
:fa-quote-left: 如果对您有帮助,您可以点右上角 ⭐“Star” 收藏一下 ,获取第一时间更新,谢谢! :fa-quote-right:
### NIUSHOP 开源商城 V6 技术选型
NIUSHOP V6 使用 **NIUCLOUD-ADMIN** 底层框架设计, 国内首家唯一支持TP8框架 ,前端采用市面最流行的技术栈 **Vite+TypeScript+Vue3+ElementPlus** ,后端采用 **THINKPHP8、PHP8** 语言搭建。配合 **Workman** 高性能框架实现消息队列,计划任务处理。内置集成用户权限、代码生成器、表单设计、云存储、短信发送、素材中心、微信及公众号、支付、模版消息推送Api模块一系列开箱即用功能,这是一款快速可以开发企业级应用的软件系统。
### 设计理念
强大的多应用+插件组合设计理念,低耦合,高内聚
全新生态设计,多应用聚合+多插件组合运营模式全新升级 ,支持共同会员体系下商城,会员卡,上门服务等等多种商业模式随机组合,DIY装修出最强的软件系统
![输入图片说明](https://www.niushop.com/app/web/view/public/img/product/b2cv6/low-play.mp4?v=4)
### 插件化,完全为开发者二次开发而生
V6底层采用插件化模式设计,可以做到多种插件共存,组合使用。比如您有一个项目是旅游的项目,这个项目的要求是,既有商城的功能,又有旅游项目的销售,还需要进行会员的管理,甚至于还要客服系统。传统的实现方式是,找多个源码,东拼西凑,二次开发,或者部署多套独立的系统,配合起来。而今天,使用V6,可以通过组装的方式,在一套体系中实现,随着发展,会有越来越多的各行各业的插件和应用上架。您对于项目的定制,可能只需要简单组装,装修页面,就可以最终实现功能交付。
![输入图片说明](https://www.niushop.com/app/web/view/public/img/product/b2cv6/addon-right.png)
### 首创强大的一键云安装,云编译,云发布,升级引擎
给我一个支点,必能撬动地球。V6简单方便的一键云安装,云编译工具,让您小白也能变大师。
V6内置在线升级功能,系统会全自动化帮您升级文件。产品的更新只需一键完成 。
HBUILDER, VSCODE,微信小程序开发工具,打包,上传,发布! V6强大的小程序一键傻瓜式发布系统,任何开发环境都不再需要搭建!鼠标一点完成小程序升级发布。
![输入图片说明](https://media.niucloud.com/171214000404e2574b6bfa3ff0a05fafbbb93ea23b_aliyun.mp4)
![输入图片说明](https://media.niucloud.com/17121421916f5969317fac428cb7001711e93d8ae3_aliyun.mp4)
![输入图片说明](https://media.niucloud.com/17121430761c9bef9042d7275c4227993149ddb2df_aliyun.mp4)
### NIUCLOUD-ADMIN 是什么?
NIUCLOUD-ADMIN是一款快速开发通用管理后台框架,整体功能架构全部精心设计!代码干净整洁!低耦合,高质量!!!前后端API接口完全分离 :raised_hands: !!!前端采用最新技术 **Vite+TypeScript+Vue3+ElementPlus** ,后台采用PHP8、MYSQL8、THINKPHP8 全部最新技术栈,内置Workman高性能消息队列,计划任务处理,完全兼容容器路由运行技术。 内置代码生成器,插件生成器,一键云编译、一键云部署,集成用户权限、表单设计、云存储、短信发送、素材中心、微信及公众号、Api模块一系列开箱即用功能,是一款快速搭建开发企业级应用的软件系统。源码100%开源无加密!框架采用MIT协议,终身免费,商用免费!
请到官方网站了解更多 http://www.niucloud.com
### NIUSHOP V6 和 NIUCLOUD-ADMIN 的区别和关系怎样的?
首先,NIUSHOP 产品系列是以商城系统(2016年立项研发,V1一直升级到V5版本, V6是完全从零研发的新产品)为主的独立的产品线。NIUCLOUD产品系列(从2022年底开始立项研发)是以NIUCLOUD-ADMIN框架(分单用户独立版、SAAS版)为根本,在此基础上发展各种应用插件,包括第三方开发者生态产品,主要以SAAS产品系列为主。而 NIUSHOP V6 是 使用 **NIUCLOUD-ADMIN** 框架单用户独立版设计的商城应用,以NIUSHOP品牌推广。一句话概括就是,单用户专业化系统以NIUSHOP品牌整体运营推广,SAAS版本插件和应用市场以及NIUCLOUD框架(单用户、SAAS)以NIUCLOUD品牌运营推广。NIUSHOP和NIUCLOUD都是牛之云科技有限公司投资研发运作。
### NIUCLOUD-ADMIN 技术特点
- 支持composer快速安装扩展,支持 **redis** 缓存以及消息队列,支持多语言设计开发,采用严格的 **restful** 的api设计开发。
- 后台前后端分离采用 **element-plus、vue3.0、typescript、vite、pina** 等前端技术,同时使用i18n支持国际化多语言开发。
- 手机端采用uniapp前后端分离,使用 **uview、vue3.0、typescript、vite、pina** 前端技术,支持h5,微信小程序,支付宝小程序,抖音小程序等使用场景。
- 支持安装多个应用多插件组合使用。
- 前端以及后端采用严格的多语言开发规范,包括前端展示,api接口返回,数据验证,错误返回等全部使用多语言设计规范,使开发者能够真生意义上实现多语言的开发需求。
- 框架已经搭建好常规系统的开发底层,具体的底层功能包括:管理员管理,权限管理,网站设置,计划任务管理,素材管理,会员管理,会员账户管理,微信公众号以及小程序管理,支付管理,第三方登录管理,消息管理,短信管理,文章管理,前端装修等全面的基础功能,这样开发者不需要开发基础的结构而专心开发业务。
- 内置支持微信/支付宝支付,微信公众号/小程序/短信消息管理,阿里云/腾讯云短信,七牛云/阿里云存储等基础的功能扩展,后续会根据实际业务不断扩展基础组件。
- 强大的代码生成器。开发者根据数据表可以一键生成基础的业务代码,包括:后台php业务代码以及对应的前端vue代码。
- 手机端内置了自定义装修,同时提供了基础的开发组件,强大的DIY组件自定义功能,允许开发者按照规范开发第三方DIY组件及自定义页面实现业务需求
### 强者归来,选择NIUSHOP 开源商城 V6, 不止于此,未来无限可能
酒香不怕巷子深,花香自有蝶飞来,NIUSHOP和NIUCLOUD开发者生态圈正在快速的膨胀发展,越来越多的开发者正在积极参与,统一的代码规范,统一的开发模式和思路,产品的二次开发和项目定制正在,规范化,积木化,快速简单化。只需用心细读一回代码,二次开发效率和质量完全得到保证!完全插件化的设计,多应用,多插件模式。随着生态的逐步完善,组合即用! 我们官方会努力帮大家搭建好基础服务平台,为所有的开发者,创业者,码农,互联网从业者,提供一个资源互换,信息共享,产品推广的生态圈。共享百万开发者产品,共享亿万市场资源。
### 界面截图 :point_right:
![输入图片说明](https://media.niucloud.com/1712132244c781785a8822b281c8d03f10134c9f97_aliyun.png)
![输入图片说明](https://media.niucloud.com/17121362221b4f7f3c15be7077a4fb351a829f1b35_aliyun.png)
![输入图片说明](https://media.niucloud.com/1716457294000dce7b84b5b719b0131e54f8dc38b9_aliyun.webp)
![输入图片说明](https://media.niucloud.com/171645729466dde1cba500222482ef11541cbff589_aliyun.webp)
![输入图片说明](https://media.niucloud.com/1716457294c65849f48ae7274a309f14fa960bb75a_aliyun.webp)
![输入图片说明](https://media.niucloud.com/171645729445f037decf7c4947501391af3a8f4d59_aliyun.webp)
### 操作指南
[NIUSHOP官网地址](https://www.niushop.com)
| [NIUCLOUD官网地址](https://www.niucloud.com)
| [服务市场](https://www.niucloud.com)
| [使用手册](https://www.niucloud.com/doc)
| [二开手册](https://www.niucloud.com/doc)
| [开发视频](https://www.niucloud.com/doc)
| [API接口手册](https://api.niucloud.com/apidoc.html?target_id=001)
| [论坛地址](https://bbs.niucloud.com)
### V6安装教程
- [安装指引说明](https://www.kancloud.cn/niushop/niushop_v6/3224842)
- [宝塔安装部署V6](https://www.kancloud.cn/niushop/niushop_v6/3226724)
- [PHPStudy安装部署V6](https://www.kancloud.cn/niushop/niushop_v6/3226728)
### 二次开发视频教程
- [开发准备工作与创建插件](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/1-1.mp4)
- [插件目录整体说明](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/2-1.mp4)
- [插件安装与打包原理](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/8-1.mp4)
- [消息队列](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/9-1.mp4)
- [计划任务](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/10-1.mp4)
- [DIY自定义小组件和页面装修开发](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/11-1.mp4)
- [支付接口开发](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/12-1.mp4)
- [插件升级包打包流程以及云编译](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/13-1.mp4)
### 演示地址
- 管理后台演示网址:[<a href='http://v6.site.niucloud.com/' target="_blank"> 查看 </a>]
<a href='http://v6.site.niucloud.com/' target="_blank">http://v6.site.niucloud.com 账号:admin 密码:123456
- H5前端演示网址:[<a href='https://v6.site.niucloud.com/wap/addon/shop/pages/index' target="_blank"> 查看 </a>]
<a href='https://v6.site.niucloud.com/wap/addon/shop/pages/index' target="_blank">https://v6.site.niucloud.com/wap/addon/shop/pages/index
### 加入开发者生态,一起助力成就程序员创业梦想!!!
加入企业微信群技术交流,请扫描下面二维码 :point_down:
![输入图片说明](https://media.niucloud.com/170312377249fc5bc70c5f914fda3d7c5cf3413ddc_aliyun.jpg)
### 产品LOGO
![输入图片说明](https://media.niucloud.com/1712452101f24e83b1d36c9078c433015d0b6f44c1_aliyun.png)
![输入图片说明](https://foruda.gitee.com/avatar/1682227978769691031/1342405_niushop_1682227978.png)
![输入图片说明](https://www.niucloud.com/_nuxt/login_logo.650a27e2.png)
### 开源使用须知
1.允许用于个人学习、毕业设计、教学案例、公益事业、商业使用;
2.本框架应用源代码所有权和著作权归niucloud官方所有,基于niucloud-admin框架开发的应用,所有权和著作权归应用开发商所有。但必须明确声明是基于niucloud-admin框架开发,请自觉遵守,否则产生的一切任何后果责任由侵权者自负;
3.禁止修改框架代码并再次发布框架衍生版等与niucloud-admin框架产生恶意竞争或对抗的行为;
4.本框架源码全部开源;包括前端,后端,无任何加密;
5.商用请仔细审查代码和漏洞,不得用于任一国家许可范围之外的商业应用,产生的一切任何后果责任自负;
6.一切事物有个人喜好的标准,本开源代码意在分享,不喜勿喷。
### 版权信息
版权所有Copyright © 2015-2025 niucloud-admin 版权所有
All rights reserved。
杭州数字云动科技有限公司
杭州牛之云科技有限公司
提供技术支持
### 前端技术栈
- Vue 3
- TypeScript
- Element Plus
- Vite
### 移动端技术栈
- UniApp
- Vue 3
- TypeScript
## 系统功能模块
### 学生管理
- 学生信息管理
- 学生档案管理
- 学生考勤管理
- 学生成绩管理
### 课程管理
- 课程信息管理
- 课程分类管理
- 课程资源管理
### 排课管理
- 课表编排
- 教师排课
- 教室安排
- 时间段管理
### 校区管理
- 校区信息管理
- 校区资源配置
### 场地管理
- 教室管理
- 场地预约
- 场地使用记录
### 人员管理
- 教师管理
- 职工管理
- 人员排班
### 合同管理
- 合同创建
- 合同审批
- 合同执行跟踪
### 学生课程管理
- 课时管理(总课时、赠送课时)
- 课程有效期管理
- 已用课时统计
- 单次课时设置
### 用户权限管理
- 用户管理
- 角色管理
- 菜单权限管理
- 操作日志记录
### 系统配置管理
- 系统参数配置
- 字典管理
- 附件管理
### 通知管理
- 微信通知
- 小程序通知
- 短信通知
- 通知日志记录
### 计划任务管理
- 定时任务配置
- 任务执行记录
- 任务调度管理
## 系统特点
### 插件化设计
系统采用插件化设计,支持多插件共存和组合使用,便于功能扩展和定制开发。
### 多端支持
同时支持PC管理端、H5移动端、微信小程序等多种终端,满足不同场景的使用需求。
### 多语言支持
系统内置多语言支持,包括前端展示、API接口返回、数据验证、错误提示等全方位的多语言设计。
### 高性能架构
采用ThinkPHP 8框架,结合Workman高性能消息队列和计划任务处理,保证系统的高效运行。
### 安全可靠
完善的权限管理机制,详细的操作日志记录,确保系统数据安全和操作可追溯。
## 数据库设计
系统采用MySQL数据库,主要表类别包括:
- 学生课程关联表(school_student_courses):记录学生选课信息、课时信息等
- 系统用户表(school_sys_user):管理系统用户信息
- 系统角色表(school_sys_role):管理角色及权限信息
- 系统菜单表(school_sys_menu):管理系统菜单及权限
- 系统配置表(school_sys_config):存储系统配置信息
- 系统字典表(school_sys_dict):管理系统字典数据
- 系统通知表(school_sys_notice):管理系统通知模板
- 通知日志表(school_sys_notice_log):记录通知发送日志
- 短信日志表(school_sys_notice_sms_log):记录短信发送日志
- 计划任务表(school_sys_cron_task):管理系统定时任务
- 计划任务日志表(school_sys_schedule_log):记录任务执行日志
- 用户操作日志表(school_sys_user_log):记录用户操作日志
- 附件管理表(school_sys_attachment):管理系统附件
## 版权信息
版权所有 Copyright © 2023-2024 智慧教务系统
杭州盛宇网络科技有限公司提供技术支持

636
admin/src/app/views/service/components/service-edit.vue

@ -1,302 +1,334 @@
<template>
<el-dialog v-model="showDialog" :title="formData.id ? t('updateService') : t('addService')" width="50%" class="diy-dialog-wrap" :destroy-on-close="true">
<el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
<el-form-item :label="t('serviceName')" prop="service_name">
<el-input v-model="formData.service_name" clearable :placeholder="t('serviceNamePlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('previewImageUrl')">
<upload-image v-model="formData.preview_image_url" />
</el-form-item>
<el-form-item :label="t('description')" prop="description">
<el-input v-model="formData.description" clearable :placeholder="t('descriptionPlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('serviceType')" prop="service_type">
<el-select class="input-width" v-model="formData.service_type" clearable :placeholder="t('serviceTypePlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in service_typeList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('executionRules')" prop="execution_rules">
<el-input v-model="formData.execution_rules" clearable :placeholder="t('executionRulesPlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('staffReminder')" prop="staff_reminder">
<el-select class="input-width" v-model="formData.staff_reminder" clearable :placeholder="t('staffReminderPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in staff_reminderList"
:key="index"
:label="item.name"
:value="Number(item.value)"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('customerReminder')" prop="customer_reminder">
<el-select class="input-width" v-model="formData.customer_reminder" clearable :placeholder="t('customerReminderPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in customer_reminderList"
:key="index"
:label="item.name"
:value="Number(item.value)"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('customerConfirmation')" prop="customer_confirmation">
<el-select class="input-width" v-model="formData.customer_confirmation" clearable :placeholder="t('customerConfirmationPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in customer_confirmationList"
:key="index"
:label="item.name"
:value="Number(item.value)"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('customerFeedback')" >
<el-input v-model="formData.customer_feedback" clearable :placeholder="t('customerFeedbackPlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('status')" prop="status">
<el-select class="input-width" v-model="formData.status" clearable :placeholder="t('statusPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in statusList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{
t('confirm')
}}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, watch } from 'vue'
import { useDictionary } from '@/app/api/dict'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import { addService, editService, getServiceInfo } from '@/app/api/service'
let showDialog = ref(false)
const loading = ref(false)
/**
* 表单数据
*/
const initialFormData = {
id: '',
service_name: '',
preview_image_url: '',
description: '',
service_type: '',
execution_rules: '',
staff_reminder: '',
customer_reminder: '',
customer_confirmation: '',
customer_feedback: '',
status: '',
}
const formData: Record<string, any> = reactive({ ...initialFormData })
const formRef = ref<FormInstance>()
//
const formRules = computed(() => {
return {
service_name: [
{ required: true, message: t('serviceNamePlaceholder'), trigger: 'blur' },
]
,
preview_image_url: [
{ required: true, message: t('previewImageUrlPlaceholder'), trigger: 'blur' },
]
,
description: [
{ required: true, message: t('descriptionPlaceholder'), trigger: 'blur' },
]
,
service_type: [
{ required: true, message: t('serviceTypePlaceholder'), trigger: 'blur' },
]
,
execution_rules: [
{ required: true, message: t('executionRulesPlaceholder'), trigger: 'blur' },
]
,
staff_reminder: [
{ required: true, message: t('staffReminderPlaceholder'), trigger: 'blur' },
]
,
customer_reminder: [
{ required: true, message: t('customerReminderPlaceholder'), trigger: 'blur' },
]
,
customer_confirmation: [
{ required: true, message: t('customerConfirmationPlaceholder'), trigger: 'blur' },
]
,
customer_feedback: [
{ required: true, message: t('customerFeedbackPlaceholder'), trigger: 'blur' },
]
,
status: [
{ required: true, message: t('statusPlaceholder'), trigger: 'blur' },
]
,
}
})
const emit = defineEmits(['complete'])
/**
* 确认
* @param formEl
*/
const confirm = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
let save = formData.id ? editService : addService
await formEl.validate(async (valid) => {
if (valid) {
loading.value = true
let data = formData
save(data).then(res => {
loading.value = false
showDialog.value = false
emit('complete')
}).catch(err => {
loading.value = false
})
}
})
}
//
let service_typeList = ref([])
const service_typeDictList = async () => {
service_typeList.value = await (await useDictionary('service_type')).data.dictionary
}
service_typeDictList();
watch(() => service_typeList.value, () => { formData.service_type = service_typeList.value[0].value })
let staff_reminderList = ref([])
const staff_reminderDictList = async () => {
staff_reminderList.value = await (await useDictionary('global_true_or_false')).data.dictionary
}
staff_reminderDictList();
watch(() => staff_reminderList.value, () => { formData.staff_reminder = staff_reminderList.value[0].value })
let customer_reminderList = ref([])
const customer_reminderDictList = async () => {
customer_reminderList.value = await (await useDictionary('global_true_or_false')).data.dictionary
}
customer_reminderDictList();
watch(() => customer_reminderList.value, () => { formData.customer_reminder = customer_reminderList.value[0].value })
let customer_confirmationList = ref([])
const customer_confirmationDictList = async () => {
customer_confirmationList.value = await (await useDictionary('global_true_or_false')).data.dictionary
}
customer_confirmationDictList();
watch(() => customer_confirmationList.value, () => { formData.customer_confirmation = customer_confirmationList.value[0].value })
let statusList = ref([])
const statusDictList = async () => {
statusList.value = await (await useDictionary('SiteStatus')).data.dictionary
}
statusDictList();
watch(() => statusList.value, () => { formData.status = statusList.value[0].value })
const setFormData = async (row: any = null) => {
Object.assign(formData, initialFormData)
loading.value = true
if(row){
const data = await (await getServiceInfo(row.id)).data
if (data) Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key]
})
}
loading.value = false
}
//
const mobileVerify = (rule: any, value: any, callback: any) => {
if (value && !/^1[3-9]\d{9}$/.test(value)) {
callback(new Error(t('generateMobile')))
} else {
callback()
}
}
//
const idCardVerify = (rule: any, value: any, callback: any) => {
if (value && !/^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(value)) {
callback(new Error(t('generateIdCard')))
} else {
callback()
}
}
//
const emailVerify = (rule: any, value: any, callback: any) => {
if (value && !/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/.test(value)) {
callback(new Error(t('generateEmail')))
} else {
callback()
}
}
//
const numberVerify = (rule: any, value: any, callback: any) => {
if (!Number.isInteger(value)) {
callback(new Error(t('generateNumber')))
} else {
callback()
}
}
defineExpose({
showDialog,
setFormData
})
</script>
<style lang="scss" scoped></style>
<style lang="scss">
.diy-dialog-wrap .el-form-item__label{
height: auto !important;
}
</style>
<template>
<el-dialog v-model="showDialog" :title="formData.id ? t('updateService') : t('addService')" width="50%"
class="diy-dialog-wrap" :destroy-on-close="true">
<el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form"
v-loading="loading">
<el-form-item :label="t('serviceName')" prop="service_name">
<el-input v-model="formData.service_name" clearable :placeholder="t('serviceNamePlaceholder')"
class="input-width"/>
</el-form-item>
<el-form-item :label="t('previewImageUrl')">
<upload-image v-model="formData.preview_image_url"/>
</el-form-item>
<el-form-item :label="t('description')" prop="description">
<el-input v-model="formData.description" clearable :placeholder="t('descriptionPlaceholder')"
class="input-width"/>
</el-form-item>
<el-form-item :label="t('serviceType')" prop="service_type">
<el-select class="input-width" v-model="formData.service_type" clearable
:placeholder="t('serviceTypePlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in service_typeList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('executionRules')" prop="execution_rules">
<el-select class="input-width" v-model="formData.execution_rules" clearable
:placeholder="t('executionRulesPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in execution_rulesList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('staffReminder')" prop="staff_reminder">
<el-select class="input-width" v-model="formData.staff_reminder" clearable
:placeholder="t('staffReminderPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in staff_reminderList"
:key="index"
:label="item.name"
:value="Number(item.value)"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('customerReminder')" prop="customer_reminder">
<el-select class="input-width" v-model="formData.customer_reminder" clearable
:placeholder="t('customerReminderPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in customer_reminderList"
:key="index"
:label="item.name"
:value="Number(item.value)"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('customerConfirmation')" prop="customer_confirmation">
<el-select class="input-width" v-model="formData.customer_confirmation" clearable
:placeholder="t('customerConfirmationPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in customer_confirmationList"
:key="index"
:label="item.name"
:value="Number(item.value)"
/>
</el-select>
</el-form-item>
<!-- <el-form-item :label="t('customerFeedback')">-->
<!-- <el-input v-model="formData.customer_feedback" clearable :placeholder="t('customerFeedbackPlaceholder')"-->
<!-- class="input-width"/>-->
<!-- </el-form-item>-->
<el-form-item :label="t('status')" prop="status">
<el-select class="input-width" v-model="formData.status" clearable :placeholder="t('statusPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in statusList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{
t('confirm')
}}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import {ref, reactive, computed, watch} from 'vue'
import {useDictionary} from '@/app/api/dict'
import {t} from '@/lang'
import type {FormInstance} from 'element-plus'
import {addService, editService, getServiceInfo} from '@/app/api/service'
let showDialog = ref(false)
const loading = ref(false)
/**
* 表单数据
*/
const initialFormData = {
id: '',
service_name: '',
preview_image_url: '',
description: '',
service_type: '',
execution_rules: '',
staff_reminder: '',
customer_reminder: '',
customer_confirmation: '',
customer_feedback: '',
status: '',
}
const formData: Record<string, any> = reactive({...initialFormData})
const formRef = ref<FormInstance>()
//
const formRules = computed(() => {
return {
service_name: [
{required: true, message: t('serviceNamePlaceholder'), trigger: 'blur'},
]
,
preview_image_url: [
{required: true, message: t('previewImageUrlPlaceholder'), trigger: 'blur'},
]
,
description: [
{required: true, message: t('descriptionPlaceholder'), trigger: 'blur'},
]
,
service_type: [
{required: true, message: t('serviceTypePlaceholder'), trigger: 'blur'},
]
,
execution_rules: [
{required: true, message: t('executionRulesPlaceholder'), trigger: 'blur'},
]
,
staff_reminder: [
{required: true, message: t('staffReminderPlaceholder'), trigger: 'blur'},
]
,
customer_reminder: [
{required: true, message: t('customerReminderPlaceholder'), trigger: 'blur'},
]
,
customer_confirmation: [
{required: true, message: t('customerConfirmationPlaceholder'), trigger: 'blur'},
]
,
customer_feedback: [
{required: true, message: t('customerFeedbackPlaceholder'), trigger: 'blur'},
]
,
status: [
{required: true, message: t('statusPlaceholder'), trigger: 'blur'},
]
,
}
})
const emit = defineEmits(['complete'])
/**
* 确认
* @param formEl
*/
const confirm = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
let save = formData.id ? editService : addService
await formEl.validate(async (valid) => {
if (valid) {
loading.value = true
let data = formData
save(data).then(res => {
loading.value = false
showDialog.value = false
emit('complete')
}).catch(err => {
loading.value = false
})
}
})
}
//
let service_typeList = ref([])
let execution_rulesList = ref([])
const service_typeDictList = async () => {
service_typeList.value = await (await useDictionary('service_type')).data.dictionary
execution_rulesList.value = await (await useDictionary('execution_rules')).data.dictionary
}
service_typeDictList();
watch(() => service_typeList.value, () => {
formData.service_type = service_typeList.value[0].value
})
let staff_reminderList = ref([])
const staff_reminderDictList = async () => {
staff_reminderList.value = await (await useDictionary('global_true_or_false')).data.dictionary
}
staff_reminderDictList();
watch(() => staff_reminderList.value, () => {
formData.staff_reminder = staff_reminderList.value[0].value
})
let customer_reminderList = ref([])
const customer_reminderDictList = async () => {
customer_reminderList.value = await (await useDictionary('global_true_or_false')).data.dictionary
}
customer_reminderDictList();
watch(() => customer_reminderList.value, () => {
formData.customer_reminder = customer_reminderList.value[0].value
})
let customer_confirmationList = ref([])
const customer_confirmationDictList = async () => {
customer_confirmationList.value = await (await useDictionary('global_true_or_false')).data.dictionary
}
customer_confirmationDictList();
watch(() => customer_confirmationList.value, () => {
formData.customer_confirmation = customer_confirmationList.value[0].value
})
let statusList = ref([])
const statusDictList = async () => {
statusList.value = await (await useDictionary('SiteStatus')).data.dictionary
}
statusDictList();
watch(() => statusList.value, () => {
formData.status = statusList.value[0].value
})
const setFormData = async (row: any = null) => {
Object.assign(formData, initialFormData)
loading.value = true
if (row) {
const data = await (await getServiceInfo(row.id)).data
if (data) Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key]
})
}
loading.value = false
}
//
const mobileVerify = (rule: any, value: any, callback: any) => {
if (value && !/^1[3-9]\d{9}$/.test(value)) {
callback(new Error(t('generateMobile')))
} else {
callback()
}
}
//
const idCardVerify = (rule: any, value: any, callback: any) => {
if (value && !/^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(value)) {
callback(new Error(t('generateIdCard')))
} else {
callback()
}
}
//
const emailVerify = (rule: any, value: any, callback: any) => {
if (value && !/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/.test(value)) {
callback(new Error(t('generateEmail')))
} else {
callback()
}
}
//
const numberVerify = (rule: any, value: any, callback: any) => {
if (!Number.isInteger(value)) {
callback(new Error(t('generateNumber')))
} else {
callback()
}
}
defineExpose({
showDialog,
setFormData
})
</script>
<style lang="scss" scoped></style>
<style lang="scss">
.diy-dialog-wrap .el-form-item__label {
height: auto !important;
}
</style>

465
admin/src/app/views/service/service.vue

@ -1,231 +1,234 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-lg">{{pageName}}</span>
<el-button type="primary" @click="addEvent">
{{ t('addService') }}
</el-button>
</div>
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="serviceTable.searchParam" ref="searchFormRef">
<el-form-item :label="t('serviceName')" prop="service_name">
<el-input v-model="serviceTable.searchParam.service_name" :placeholder="t('serviceNamePlaceholder')" />
</el-form-item>
<el-form-item :label="t('serviceType')" prop="service_type">
<el-select class="w-[280px]" v-model="serviceTable.searchParam.service_type" clearable :placeholder="t('serviceTypePlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in service_typeList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('status')" prop="status">
<el-select class="w-[280px]" v-model="serviceTable.searchParam.status" clearable :placeholder="t('statusPlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in statusList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadServiceList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="mt-[10px]">
<el-table :data="serviceTable.data" size="large" v-loading="serviceTable.loading">
<template #empty>
<span>{{ !serviceTable.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="service_name" :label="t('serviceName')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="description" :label="t('description')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column :label="t('serviceType')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in service_typeList">
<div v-if="item.value == row.service_type">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="customer_feedback" :label="t('customerFeedback')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column :label="t('status')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in statusList">
<div v-if="item.value == row.status">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column :label="t('operation')" fixed="right" min-width="120">
<template #default="{ row }">
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="serviceTable.page" v-model:page-size="serviceTable.limit"
layout="total, sizes, prev, pager, next, jumper" :total="serviceTable.total"
@size-change="loadServiceList()" @current-change="loadServiceList" />
</div>
</div>
<edit ref="editServiceDialog" @complete="loadServiceList" />
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue'
import { t } from '@/lang'
import { useDictionary } from '@/app/api/dict'
import { getServiceList, deleteService } from '@/app/api/service'
import { img } from '@/utils/common'
import { ElMessageBox,FormInstance } from 'element-plus'
import Edit from '@/app/views/service/components/service-edit.vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const pageName = route.meta.title;
let serviceTable = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam:{
"service_name":"",
"service_type":"",
"status":""
}
})
const searchFormRef = ref<FormInstance>()
//
const selectData = ref<any[]>([])
//
const service_typeList = ref([] as any[])
const service_typeDictList = async () => {
service_typeList.value = await (await useDictionary('service_type')).data.dictionary
}
service_typeDictList();
const staff_reminderList = ref([] as any[])
const staff_reminderDictList = async () => {
staff_reminderList.value = await (await useDictionary('global_true_or_false')).data.dictionary
}
staff_reminderDictList();
const customer_reminderList = ref([] as any[])
const customer_reminderDictList = async () => {
customer_reminderList.value = await (await useDictionary('global_true_or_false')).data.dictionary
}
customer_reminderDictList();
const customer_confirmationList = ref([] as any[])
const customer_confirmationDictList = async () => {
customer_confirmationList.value = await (await useDictionary('global_true_or_false')).data.dictionary
}
customer_confirmationDictList();
const statusList = ref([] as any[])
const statusDictList = async () => {
statusList.value = await (await useDictionary('SiteStatus')).data.dictionary
}
statusDictList();
/**
* 获取服务列表
*/
const loadServiceList = (page: number = 1) => {
serviceTable.loading = true
serviceTable.page = page
getServiceList({
page: serviceTable.page,
limit: serviceTable.limit,
...serviceTable.searchParam
}).then(res => {
serviceTable.loading = false
serviceTable.data = res.data.data
serviceTable.total = res.data.total
}).catch(() => {
serviceTable.loading = false
})
}
loadServiceList()
const editServiceDialog: Record<string, any> | null = ref(null)
/**
* 添加服务
*/
const addEvent = () => {
editServiceDialog.value.setFormData()
editServiceDialog.value.showDialog = true
}
/**
* 编辑服务
* @param data
*/
const editEvent = (data: any) => {
editServiceDialog.value.setFormData(data)
editServiceDialog.value.showDialog = true
}
/**
* 删除服务
*/
const deleteEvent = (id: number) => {
ElMessageBox.confirm(t('serviceDeleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning',
}
).then(() => {
deleteService(id).then(() => {
loadServiceList()
}).catch(() => {
})
})
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadServiceList()
}
</script>
<style lang="scss" scoped>
/* 多行超出隐藏 */
.multi-hidden {
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
</style>
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-lg">{{pageName}}</span>
<el-button type="primary" @click="addEvent">
{{ t('addService') }}
</el-button>
</div>
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="serviceTable.searchParam" ref="searchFormRef">
<el-form-item :label="t('serviceName')" prop="service_name">
<el-input v-model="serviceTable.searchParam.service_name" :placeholder="t('serviceNamePlaceholder')" />
</el-form-item>
<el-form-item :label="t('serviceType')" prop="service_type">
<el-select class="w-[280px]" v-model="serviceTable.searchParam.service_type" clearable :placeholder="t('serviceTypePlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in service_typeList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('status')" prop="status">
<el-select class="w-[280px]" v-model="serviceTable.searchParam.status" clearable :placeholder="t('statusPlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in statusList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadServiceList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="mt-[10px]">
<el-table :data="serviceTable.data" size="large" v-loading="serviceTable.loading">
<template #empty>
<span>{{ !serviceTable.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="service_name" :label="t('serviceName')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="description" :label="t('description')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column :label="t('serviceType')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in service_typeList">
<div v-if="item.value == row.service_type">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<!-- <el-table-column prop="customer_feedback" :label="t('customerFeedback')" min-width="120" :show-overflow-tooltip="true"/>-->
<el-table-column :label="t('status')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in statusList">
<div v-if="item.value == row.status">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column :label="t('operation')" fixed="right" min-width="120">
<template #default="{ row }">
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="serviceTable.page" v-model:page-size="serviceTable.limit"
layout="total, sizes, prev, pager, next, jumper" :total="serviceTable.total"
@size-change="loadServiceList()" @current-change="loadServiceList" />
</div>
</div>
<edit ref="editServiceDialog" @complete="loadServiceList" />
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue'
import { t } from '@/lang'
import { useDictionary } from '@/app/api/dict'
import { getServiceList, deleteService } from '@/app/api/service'
import { img } from '@/utils/common'
import { ElMessageBox,FormInstance } from 'element-plus'
import Edit from '@/app/views/service/components/service-edit.vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const pageName = route.meta.title;
let serviceTable = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam:{
"service_name":"",
"service_type":"",
"status":""
}
})
const searchFormRef = ref<FormInstance>()
//
const selectData = ref<any[]>([])
//
const service_typeList = ref([] as any[])
const service_typeDictList = async () => {
service_typeList.value = await (await useDictionary('service_type')).data.dictionary
}
service_typeDictList();
const staff_reminderList = ref([] as any[])
const staff_reminderDictList = async () => {
staff_reminderList.value = await (await useDictionary('global_true_or_false')).data.dictionary
}
staff_reminderDictList();
const customer_reminderList = ref([] as any[])
const customer_reminderDictList = async () => {
customer_reminderList.value = await (await useDictionary('global_true_or_false')).data.dictionary
}
customer_reminderDictList();
const customer_confirmationList = ref([] as any[])
const customer_confirmationDictList = async () => {
customer_confirmationList.value = await (await useDictionary('global_true_or_false')).data.dictionary
}
customer_confirmationDictList();
const statusList = ref([] as any[])
const statusDictList = async () => {
statusList.value = await (await useDictionary('SiteStatus')).data.dictionary
}
statusDictList();
/**
* 获取服务列表
*/
const loadServiceList = (page: number = 1) => {
serviceTable.loading = true
serviceTable.page = page
getServiceList({
page: serviceTable.page,
limit: serviceTable.limit,
...serviceTable.searchParam
}).then(res => {
serviceTable.loading = false
serviceTable.data = res.data.data
serviceTable.total = res.data.total
}).catch(() => {
serviceTable.loading = false
})
}
loadServiceList()
const editServiceDialog: Record<string, any> | null = ref(null)
/**
* 添加服务
*/
const addEvent = () => {
editServiceDialog.value.setFormData()
editServiceDialog.value.showDialog = true
}
/**
* 编辑服务
* @param data
*/
const editEvent = (data: any) => {
editServiceDialog.value.setFormData(data)
editServiceDialog.value.showDialog = true
}
/**
* 删除服务
*/
const deleteEvent = (id: number) => {
ElMessageBox.confirm(t('serviceDeleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning',
}
).then(() => {
deleteService(id).then(() => {
loadServiceList()
}).catch(() => {
})
})
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadServiceList()
}
</script>
<style lang="scss" scoped>
/* 多行超出隐藏 */
.multi-hidden {
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
</style>

9
niucloud/app/api/controller/apiController/Course.php

@ -114,5 +114,14 @@ class Course extends BaseApiService
return success((new CourseService())->schedule_list($data));
}
public function schedule_del(Request $request)
{
$data = $this->request->params([
["id", ''],
["resources_id", ''],
["remark", '']
]);
return (new CourseService())->schedule_del($data);
}
}

37
niucloud/app/api/controller/apiController/CustomerResources.php

@ -122,9 +122,6 @@ class CustomerResources extends BaseApiService
//客户资源-编辑
public function edit(Request $request)
{
$resource_sharing_id = $request->param('resource_sharing_id', '');//资源共享id
$customer_resources_id = $request->param('id', '');//客户资源表id
$promised_visit_time = $request->param('promised_visit_time', '');
@ -174,7 +171,6 @@ class CustomerResources extends BaseApiService
"preferred_class_time" => $optional_class_time,//可选上课时间
"distance" => $request->param('distance', ''),//距离
"communication" => $request->param('communication', ''),//沟通备注
"staff_id" => $request->param('staff_id', ''),//人员ID//如果没有就是当前登录人的员工id
"efficacious" => $request->param('efficacious', 1),
"first_visit_time" => $request->param('first_visit_time', ''),
@ -182,8 +178,9 @@ class CustomerResources extends BaseApiService
"second_visit_time" => $request->param('second_visit_time', ''),
"second_visit_status" => $request->param('second_visit_status', ''),
"chasing_orders" => $request->param('chasing_orders', ''),
"is_bm" => $request->param('is_bm', 1),
"consultation_remark" => $request->param('consultation_remark', ''),
"call_intent" => $request->param('call_intent', '')
];
@ -197,13 +194,6 @@ class CustomerResources extends BaseApiService
$six_speed_data['staff_id'] = $this->member_id;
}
foreach ($six_speed_data as $k => $v) {
// 排除 first_visit_status 和 second_visit_status 的必填校验
if (in_array($k, ['first_visit_status', 'second_visit_status'])) {
continue;
}
}
$res = (new CustomerResourcesService())->editData($where, $customer_resources_data, $six_speed_data);
if (!$res['code']) {
return fail($res['msg']);
@ -240,5 +230,28 @@ class CustomerResources extends BaseApiService
return success($res);
}
//修改用户课程的主教、助教、教务
public function updateUserCourse(Request $request)
{
$main_coach_id = $request->param('main_coach_id', '');
$assistant_ids = $request->param('assistant_ids', '');
$id = $request->param('id', '');
$education_id = $request->param('education_id', '');//查询类型|resource=客户资源,six_speed=六要素
if (empty($customer_resource_id) || empty($course_id) || empty($staff_id) || empty($type)) {
return fail('缺少必要参数');
}
//修改用户课程的主教、助教、教务
$res = (new CustomerResourcesService())->updateUserCourseInfo([
'id' => $id,
'main_coach_id' => $main_coach_id,
'assistant_ids' => $assistant_ids,
'education_id' => $education_id
]);
if (!$res['code']) {
return fail($res['msg']);
}
return success([]);
}
}

3
niucloud/app/api/controller/apiController/Statistics.php

@ -147,7 +147,8 @@ class Statistics extends BaseApiService
'yjsr' => $yjsr,
'wfpsl' => $wfpsl,
'bytc' => $bytc,
'gdsl' => $gdsl
'gdsl' => $gdsl,
'qtjl' => get_staff_performance_total($personnel_id, 'sales', '', date('Y-m-01'), date('Y-m-t'))
]
];
}elseif(in_array('sale',$role_key_arr) || in_array('sale_manager',$role_key_arr)){

2
niucloud/app/api/route/route.php

@ -341,6 +341,8 @@ Route::group(function () {
Route::get('course/scheduleList', 'apiController.course/schedule_list');
Route::post('course/schedule_del', 'apiController.course/schedule_del');
Route::get('per_list_call_up', 'member.Member/list_call_up');
Route::post('per_update_call_up', 'member.Member/update_call_up');

15
niucloud/app/command/ClientCommand/TestCommand.php

@ -0,0 +1,15 @@
<?php
namespace app\command\ClientCommand;
use think\console\Command;
use think\console\Input;
use think\console\Output;
class TestCommand extends Command
{
protected function execute(Input $input, Output $output)
{
$output->writeln('test');
}
}

30
niucloud/app/command/TestCommand.php

@ -0,0 +1,30 @@
<?php
declare (strict_types = 1);
namespace app\command;
use app\job\transfer\schedule\CourseScheduleJob;
use app\job\transfer\schedule\ResourceAutoAllocation;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
class TestCommand extends Command
{
protected function configure()
{
// 指令配置
$this->setName('testcommand')
->setDescription('the testcommand command');
}
protected function execute(Input $input, Output $output)
{
// 指令输出
$obj = new CourseScheduleJob();
$obj->doJob();
$output->writeln('testcommand');
}
}

60
niucloud/app/common.php

@ -12,6 +12,7 @@ use think\facade\Cache;
use core\util\Snowflake;
use app\service\core\upload\CoreImageService;
use app\service\core\sys\CoreSysConfigService;
use app\service\admin\performance\PerformanceService;
// 应用公共文件
@ -1301,3 +1302,62 @@ function getChineseWeekday($date)
return '星期' . $weekdays[$date->format('w')];
}
/**
* 添加绩效汇总记录
* @param array $data 绩效数据
* - staff_id: 员工ID (必填)
* - resource_id: 资源ID (必填)
* - order_id: 订单ID (可选)
* - order_status: 订单状态 (可选,默认pending)
* - performance_type: 绩效类型 (必填,如sales)
* - performance_value: 绩效金额 (必填)
* - remarks: 备注 (可选)
* @return int|string 插入的ID,失败返回0
*/
function add_performance_summary(array $data)
{
try {
$performanceService = new PerformanceService();
return $performanceService->addPerformance($data);
} catch (\Exception $e) {
\think\facade\Log::write('添加绩效汇总记录失败:' . $e->getMessage());
return 0;
}
}
/**
* 批量添加绩效汇总记录
* @param array $dataList 绩效数据列表
* @return int 插入的记录数
*/
function add_batch_performance_summary(array $dataList)
{
try {
$performanceService = new PerformanceService();
return $performanceService->addBatchPerformance($dataList);
} catch (\Exception $e) {
\think\facade\Log::write('批量添加绩效汇总记录失败:' . $e->getMessage());
return 0;
}
}
/**
* 获取员工绩效总额
* @param int $staffId 员工ID
* @param string $performanceType 绩效类型 (可选)
* @param string $orderStatus 订单状态 (可选)
* @param string $startDate 开始日期 (可选,格式:Y-m-d)
* @param string $endDate 结束日期 (可选,格式:Y-m-d)
* @return float 绩效总额
*/
function get_staff_performance_total(int $staffId, string $performanceType = '', string $orderStatus = '', string $startDate = '', string $endDate = '')
{
try {
$performanceService = new PerformanceService();
return $performanceService->calculateStaffPerformanceTotal($staffId, $performanceType, $orderStatus, $startDate, $endDate);
} catch (\Exception $e) {
\think\facade\Log::write('获取员工绩效总额失败:' . $e->getMessage());
return 0;
}
}

43
niucloud/app/dict/schedule/schedule.php

@ -2,30 +2,47 @@
return [
[
'key' => 'order_close',
'name' => '未支付订单自动关闭',
'key' => 'resource_auto_allocation',
'name' => '自动分配资源',
'desc' => '',
'time' => [
'type' => 'min',
'min' => 1
'type' => 'day',
'day' => 1,
'hour' => 0,
'min' => 5
],
'class' => '',
'class' => 'app\job\transfer\schedule\CheckFinish',
'function' => ''
],
[
'key' => 'transfer_check_finish',
'name' => '检验在线转账是否处理完毕',
'key' => 'course_schedule_job',
'name' => '自动排课',
'desc' => '',
'time' => [
'type' => 'min',
'type' => 'day',
'day' => 1,
'hour' => 0,
'min' => 5
],
'class' => 'app\job\transfer\schedule\CheckFinish',
'class' => 'app\job\transfer\schedule\CourseScheduleJob',
'function' => ''
],
[
'key' => 'resource_auto_allocation',
'name' => '自动分配资源',
'key' => 'PerformanceCalculation',
'name' => '核算绩效',
'desc' => '',
'time' => [
'type' => 'day',
'day' => 1,
'hour' => 1,
'min' => 5
],
'class' => 'app\job\transfer\schedule\PerformanceCalculation',
'function' => ''
],
[
'key' => 'HandleCourseSchedule',
'name' => '处理课程状态和学员状态',
'desc' => '',
'time' => [
'type' => 'day',
@ -33,7 +50,7 @@ return [
'hour' => 0,
'min' => 5
],
'class' => 'app\job\transfer\schedule\CheckFinish',
'class' => 'app\job\schedule\HandleCourseSchedule',
'function' => ''
]
],
];

20
niucloud/app/job/custmer/ResourceAutoAllocation.php

@ -1,20 +0,0 @@
<?php
namespace app\job\custmer;
use app\model\customer_resources\CustomerResources;
use app\model\resource_sharing\ResourceSharing;
use core\base\BaseJob;
class ResourceAutoAllocation extends BaseJob
{
public function doJob()
{
//获取当天的资源列表模型CustomerResources
// CustomerResources::where('created_at')
//获取当天销售获取资源的数量ResourceSharing
// ResourceSharing::where('shared_at')->
//遍历资源列表按照资源数量的最少的人员优先分配
return true;
}
}

43
niucloud/app/job/schedule/HandleCourseSchedule.php

@ -0,0 +1,43 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\job\schedule;
use app\model\course_schedule\CourseSchedule;
use core\base\BaseJob;
use think\facade\Log;
/**
* 队列异步调用定时任务
*/
class HandleCourseSchedule extends BaseJob
{
public function doJob()
{
Log::write('课程状态自动化任务开始' . date('Y-m-d h:i:s'));
$this->handleCourseStatus();
return true;
}
private function handleCourseStatus()
{
$list = CourseSchedule::where('course_date','<',date('Y-m-d'))->select();
if (!empty($list)) {
foreach ($list as $item) {
CourseSchedule::update([
'status' => 'completed'
], [
'id' => $item['id']
]);
}
}
}
}

109
niucloud/app/job/transfer/schedule/CourseScheduleJob.php

@ -4,9 +4,104 @@ namespace app\job\transfer\schedule;
use app\model\course_schedule\CourseSchedule;
use core\base\BaseJob;
use think\facade\Log;
class CourseScheduleJob extends BaseJob
{
/**
* 执行任务,将今天的自动排课复制到未来30天
* @return array 处理结果
*/
public function doJob()
{
Log::write('开始执行自动排课任务');
return $this->copyCoursesToFutureDays(30);
}
/**
* 将今天的自动排课复制到未来指定天数
* @param int $days 未来天数
* @return array 处理结果
*/
public function copyCoursesToFutureDays($days = 30)
{
// 获取今天日期
$today = date('Y-m-d');
// 获取所有今天auto_schedule=1的课程
$autoSchedules = CourseSchedule::where('auto_schedule', 1)
->where('course_date', $today)
->select();
Log::write('找到' . count($autoSchedules) . '个今天需要自动排课的课程');
$results = [
'total' => count($autoSchedules),
'inserted' => 0,
'skipped' => 0,
'details' => []
];
// 遍历每个课程,复制到未来30天
foreach ($autoSchedules as $schedule) {
$courseResults = $this->copyCourseToFutureDays($schedule, $days);
$results['inserted'] += $courseResults['inserted'];
$results['skipped'] += $courseResults['skipped'];
$results['details'][] = $courseResults;
}
Log::write('自动排课完成,共插入' . $results['inserted'] . '个课程,跳过' . $results['skipped'] . '个已存在课程');
return $results;
}
/**
* 将单个课程复制到未来指定天数
* @param CourseSchedule $schedule 课程安排
* @param int $days 未来天数
* @return array 处理结果
*/
protected function copyCourseToFutureDays(CourseSchedule $schedule, $days)
{
$result = [
'course_id' => $schedule->course_id,
'campus_id' => $schedule->campus_id,
'venue_id' => $schedule->venue_id,
'time_slot' => $schedule->time_slot,
'inserted' => 0,
'skipped' => 0,
'dates' => []
];
// 从明天开始,复制到未来指定天数
for ($i = 1; $i <= $days; $i++) {
// 计算目标日期
$targetDate = date('Y-m-d', strtotime("+{$i} days"));
// 检查该日期是否有相同的课程安排
$exists = $this->checkCourseExists($schedule, $targetDate);
if (!$exists) {
// 如果不存在,则插入新的课程安排
$newSchedule = $this->createNewSchedule($schedule, $targetDate);
$result['inserted']++;
$result['dates'][] = [
'date' => $targetDate,
'status' => 'inserted',
'id' => $newSchedule->id
];
} else {
$result['skipped']++;
$result['dates'][] = [
'date' => $targetDate,
'status' => 'skipped'
];
}
}
return $result;
}
/**
* 处理自动排课数据,按月份检查并插入不存在的课程
* @param int $month 月份(1-12)
@ -59,7 +154,7 @@ class CourseScheduleJob extends BaseJob
}
/**
* 检查指定月份是否有相同的课程安排
* 检查指定日期是否有相同的课程安排
* @param CourseSchedule $schedule 原始课程安排
* @param string $courseDate 课程日期
* @return bool 是否存在
@ -92,7 +187,17 @@ class CourseScheduleJob extends BaseJob
$newSchedule->time_slot = $schedule->time_slot;
$newSchedule->course_id = $schedule->course_id;
$newSchedule->auto_schedule = 1;
// 复制其他需要的字段...
$newSchedule->created_by = 'system';
// 复制其他所有字段(除了id和主键相关字段)
$attributes = $schedule->toArray();
foreach ($attributes as $key => $value) {
// 跳过id和主键相关字段
if ($key !== 'id' && $key !== 'course_date' && $key !== 'auto_schedule') {
$newSchedule->$key = $value;
}
}
$newSchedule->save();
return $newSchedule;

829
niucloud/app/job/transfer/schedule/PerformanceCalculation.php

@ -0,0 +1,829 @@
<?php
namespace app\job\transfer\schedule;
use app\model\course\Course;
use app\model\order_table\OrderTable;
use app\model\personnel\Personnel;
use app\model\resource_sharing\ResourceSharing;
use app\model\six_speed\SixSpeed;
use app\model\customer_resources\CustomerResources;
use app\service\admin\performance\PerformanceService;
use app\service\api\apiService\CommonService;
use core\base\BaseJob;
use think\facade\Db;
use think\facade\Log;
/**
* 销售绩效核算
* Class PerformanceCalculation
* @package app\job\transfer\schedule
*/
class PerformanceCalculation extends BaseJob
{
/**
* 员工工龄阶段
*/
const STAGE_TRIAL = '试用期1'; // 试用期
const STAGE_REGULAR = '转正'; // 转正
const STAGE_HALF_YEAR = '转正后半年'; // 转正后半年
/**
* 资源来源类型
*/
const SOURCE_INTERNAL_STAFF = 4; // 内部员工资源
/**
* 绩效服务类实例
* @var PerformanceService
*/
protected $performanceService;
/**
* 构造函数
*/
public function __construct()
{
parent::__construct();
$this->performanceService = new PerformanceService();
}
/**
* 执行任务
*/
public function doJob()
{
Log::write('开始执行销售绩效核算');
// 获取所有需要计算绩效的订单
$orders = $this->getOrders();
if (empty($orders)) {
Log::write('没有需要计算绩效的订单');
return;
}
// 获取绩效配置
$performanceConfig = $this->getPerformanceConfig();
if (empty($performanceConfig)) {
Log::write('未找到绩效配置');
return;
}
// 计算每个订单的绩效
$results = [];
foreach ($orders as $order) {
// 首先判断是否为内部员工资源
$isInternalStaffResource = $this->isInternalStaffResource($order);
if ($isInternalStaffResource) {
// 处理内部员工资源的绩效计算
$internalResult = $this->calculateInternalStaffPerformance($order, $performanceConfig);
if (!empty($internalResult)) {
$results[] = $internalResult;
}
} else {
// 判断是否为多人介入的订单
$isMultiPersonInvolved = $this->isMultiPersonInvolved($order);
if ($isMultiPersonInvolved) {
// 处理多人介入的绩效计算
$multiResults = $this->calculateMultiPersonPerformance($order, $performanceConfig);
if (!empty($multiResults)) {
$results = array_merge($results, $multiResults);
}
} else {
// 处理单人的绩效计算
$result = $this->calculateOrderPerformance($order, $performanceConfig);
$results[] = $result;
}
}
}
// 保存绩效结果
$this->savePerformanceResults($results);
Log::write('销售绩效核算完成,共处理' . count($results) . '个订单');
return $results;
}
/**
* 判断是否为内部员工资源
* @param array $order 订单信息
* @return bool 是否为内部员工资源
*/
protected function isInternalStaffResource($order)
{
if (empty($order['resource_id'])) {
return false;
}
// 查询资源表中的记录
$resource = CustomerResources::where('id', $order['resource_id'])->find();
// 如果没有找到资源记录,则不是内部员工资源
if (empty($resource)) {
return false;
}
// 判断资源来源是否为内部员工
return isset($resource['source']) && $resource['source'] == self::SOURCE_INTERNAL_STAFF;
}
/**
* 计算内部员工资源的绩效
* @param array $order 订单信息
* @param array $config 绩效配置
* @return array 绩效计算结果
*/
protected function calculateInternalStaffPerformance($order, $config)
{
$staffId = $order['staff_id'];
$campusId = isset($order['campus_id']) ? $order['campus_id'] : 0;
$resourceId = isset($order['resource_id']) ? $order['resource_id'] : 0;
// 获取员工信息
$staff = Personnel::where('id', $staffId)->find();
if (empty($staff)) {
Log::write('未找到员工信息,员工ID:' . $staffId);
return [
'order_id' => $order['id'],
'staff_id' => $staffId,
'performance_amount' => 0,
'status' => 'failed',
'message' => '未找到员工信息'
];
}
// 获取课程类型前缀
$courseType = $this->getCourseTypePrefix($order);
// 获取内部员工绩效配置
$xspjConfig = $config['XSPJ'];
$internalStaffKey = $courseType . '_internalStaff';
$performanceAmount = isset($xspjConfig[$internalStaffKey]) ? floatval($xspjConfig[$internalStaffKey]) : 0;
if ($performanceAmount <= 0) {
Log::write('未找到有效的内部员工绩效配置,订单ID:' . $order['id']);
return [
'order_id' => $order['id'],
'staff_id' => $staffId,
'performance_amount' => 0,
'status' => 'failed',
'message' => '未找到有效的内部员工绩效配置'
];
}
// 判断订单类型(新订单或续费)
$isRenewal = $this->isRenewalOrder($order);
// 获取新资源数和续费资源数
$newResourceCount = $isRenewal ? 0 : 1;
$renewResourceCount = $isRenewal ? 1 : 0;
// 保存使用的绩效配置和算法
$performanceConfig = json_encode([
'is_renewal' => $isRenewal,
'is_internal_staff' => true,
'performance_key' => $internalStaffKey
], JSON_UNESCAPED_UNICODE);
$performanceAlgorithm = json_encode([
'order_id' => $order['id'],
'resource_id' => $resourceId,
'internal_staff_resource' => true
], JSON_UNESCAPED_UNICODE);
// 添加一条绩效汇总记录
$this->addPerformanceSummary([
'staff_id' => $staffId,
'resource_id' => $resourceId,
'order_id' => $order['id'],
'order_status' => PerformanceService::ORDER_STATUS_PENDING,
'performance_type' => PerformanceService::PERFORMANCE_TYPE_SALES,
'performance_value' => $performanceAmount,
'remarks' => '内部员工关单绩效'
]);
return [
'order_id' => $order['id'],
'staff_id' => $staffId,
'personnel_id' => $staffId,
'campus_id' => $campusId,
'resource_id' => $resourceId,
'is_renewal' => $isRenewal,
'performance_amount' => $performanceAmount,
'new_resource_count' => $newResourceCount,
'renew_resource_count' => $renewResourceCount,
'performance_date' => date('Y-m-d'),
'performance_config' => $performanceConfig,
'performance_algorithm' => $performanceAlgorithm,
'remarks' => '内部员工关单绩效',
'status' => 'success',
'created_at' => time(),
'updated_at' => time()
];
}
/**
* 获取需要计算绩效的订单
* @return array 订单列表
*/
protected function getOrders()
{
// 这里可以根据实际需求筛选需要计算绩效的订单
// 例如:只计算已完成的订单、特定时间段内的订单等
$orders = OrderTable::with(['course', 'personnel'])
->where('order_status', 'completed') // 假设只计算已完成的订单
->where(function ($query) {
$query->where('performance_calculated', 0) // 未计算过绩效的订单
->whereOr('accounting_time', null); // 或者核算时间为空的订单
})
->select()
->toArray();
// 额外检查:过滤掉已经在绩效汇总表中存在的订单
if (!empty($orders)) {
$orderIds = array_column($orders, 'id');
$existingOrderIds = Db::name('school_performance_summary')
->whereIn('order_id', $orderIds)
->where('performance_type', PerformanceService::PERFORMANCE_TYPE_SALES)
->column('order_id');
if (!empty($existingOrderIds)) {
Log::write('发现' . count($existingOrderIds) . '个订单已在绩效汇总表中存在,将被跳过');
$orders = array_filter($orders, function($order) use ($existingOrderIds) {
return !in_array($order['id'], $existingOrderIds);
});
}
}
Log::write('找到' . count($orders) . '个需要计算绩效的订单');
return $orders;
}
/**
* 获取绩效配置
* @return array 绩效配置
*/
protected function getPerformanceConfig()
{
$commonService = new CommonService();
// 获取销售基础绩效配置
$xsyjConfig = $commonService->getDictionary(['key' => 'XSYJ']);
// 获取多人介入绩效配置
$xspjConfig = $commonService->getDictionary(['key' => 'XSPJ']);
// 获取课程基础绩效配置
$courseTypeConfig = $commonService->getDictionary(['key' => 'course_type']);
return [
'XSYJ' => $xsyjConfig,
'XSPJ' => $xspjConfig,
'course_type' => $courseTypeConfig
];
}
/**
* 判断是否为多人介入的订单
* @param array $order 订单信息
* @return bool 是否为多人介入
*/
protected function isMultiPersonInvolved($order)
{
if (empty($order['resource_id']) || empty($order['staff_id'])) {
return false;
}
// 查询资源共享表中的记录
$resourceSharing = ResourceSharing::where('resource_id', $order['resource_id'])
->where('shared_by', '<>', 0)
->order('id', 'asc')
->find();
// 如果没有找到资源共享记录,则不是多人介入
if (empty($resourceSharing)) {
return false;
}
// 如果资源共享记录中的user_id与订单的staff_id不同,则是多人介入
return $resourceSharing['user_id'] != $order['staff_id'];
}
/**
* 计算多人介入的绩效
* @param array $order 订单信息
* @param array $config 绩效配置
* @return array 绩效计算结果数组
*/
protected function calculateMultiPersonPerformance($order, $config)
{
$results = [];
$resourceId = $order['resource_id'];
$staffId = $order['staff_id'];
// 查询资源共享表中的记录(资源归属人)
$resourceOwner = ResourceSharing::where('resource_id', $resourceId)
->where('shared_by', '<>', 0)
->order('id', 'asc')
->find();
if (empty($resourceOwner)) {
Log::write('未找到资源归属人,订单ID:' . $order['id']);
return [];
}
$resourceOwnerId = $resourceOwner['user_id'];
// 查询六速表中的访问记录
$sixSpeed = SixSpeed::where('resource_id', $resourceId)->find();
// 确定介入类型
$visitType = 'followUp'; // 默认为跟进
$visitTypeDesc = '跟进关单';
if (!empty($sixSpeed)) {
if (!empty($sixSpeed['first_visit_time'])) {
$visitType = 'firstVisit';
$visitTypeDesc = '一访关单';
} elseif (!empty($sixSpeed['second_visit_time'])) {
$visitType = 'secondVisit';
$visitTypeDesc = '二访关单';
}
}
// 获取课程类型前缀
$courseType = $this->getCourseTypePrefix($order);
// 获取多人介入绩效配置
$xspjConfig = $config['XSPJ'];
$percentageKey = $courseType . '_' . $visitType;
$percentage = isset($xspjConfig[$percentageKey]) ? floatval($xspjConfig[$percentageKey]) : 0;
if ($percentage <= 0) {
Log::write('未找到有效的多人介入绩效配置,订单ID:' . $order['id']);
return [];
}
// 计算订单的基础绩效
$basePerformance = $this->calculateOrderBasePerformance($order, $config);
// 计算完成订单人的绩效
$staffPerformance = $basePerformance * ($percentage / 100);
// 计算资源归属人的绩效
$ownerPerformance = $basePerformance * (1 - $percentage / 100);
// 创建完成订单人的绩效记录
$staffResult = $this->createPerformanceResult($order, $staffId, $staffPerformance, $visitTypeDesc . '(完成订单人)');
$results[] = $staffResult;
// 创建资源归属人的绩效记录
$ownerResult = $this->createPerformanceResult($order, $resourceOwnerId, $ownerPerformance, $visitTypeDesc . '(资源归属人)');
$results[] = $ownerResult;
return $results;
}
/**
* 获取课程类型前缀
* @param array $order 订单信息
* @return string 课程类型前缀(xj, xf, qt)
*/
protected function getCourseTypePrefix($order)
{
// 判断订单类型
if (isset($order['order_type'])) {
if ($order['order_type'] == 1) {
return 'xj'; // 新建
} elseif ($order['order_type'] == 2) {
return 'xf'; // 续费
}
}
return 'qt'; // 其他
}
/**
* 计算订单的基础绩效金额
* @param array $order 订单信息
* @param array $config 绩效配置
* @return float 基础绩效金额
*/
protected function calculateOrderBasePerformance($order, $config)
{
// 获取员工信息
$staff = Personnel::where('id', $order['staff_id'])->find();
if (empty($staff)) {
return 0;
}
// 获取课程信息
$course = Course::where('id', $order['course_id'])->find();
if (empty($course)) {
return 0;
}
// 判断员工工龄阶段
$employmentStage = $this->getEmploymentStage($staff);
// 判断订单类型(新订单或续费)
$isRenewal = $this->isRenewalOrder($order);
// 计算绩效
return $this->calculatePerformance($order, $course, $employmentStage, $isRenewal, $config);
}
/**
* 创建绩效计算结果
* @param array $order 订单信息
* @param int $staffId 员工ID
* @param float $performance 绩效金额
* @param string $remarks 备注
* @return array 绩效计算结果
*/
protected function createPerformanceResult($order, $staffId, $performance, $remarks)
{
$campusId = isset($order['campus_id']) ? $order['campus_id'] : 0;
$resourceId = isset($order['resource_id']) ? $order['resource_id'] : 0;
// 获取员工信息
$staff = Personnel::where('id', $staffId)->find();
if (empty($staff)) {
return [
'order_id' => $order['id'],
'staff_id' => $staffId,
'performance_amount' => 0,
'status' => 'failed',
'message' => '未找到员工信息'
];
}
// 判断员工工龄阶段
$employmentStage = $this->getEmploymentStage($staff);
// 判断订单类型(新订单或续费)
$isRenewal = $this->isRenewalOrder($order);
// 获取新资源数和续费资源数
$newResourceCount = $isRenewal ? 0 : 1;
$renewResourceCount = $isRenewal ? 1 : 0;
// 保存使用的绩效配置和算法
$performanceConfig = json_encode([
'employment_stage' => $employmentStage,
'is_renewal' => $isRenewal,
'remarks' => $remarks
], JSON_UNESCAPED_UNICODE);
$performanceAlgorithm = json_encode([
'order_id' => $order['id'],
'resource_id' => $resourceId,
'multi_person_involved' => true
], JSON_UNESCAPED_UNICODE);
// 添加一条绩效汇总记录
$this->addPerformanceSummary([
'staff_id' => $staffId,
'resource_id' => $resourceId,
'order_id' => $order['id'],
'order_status' => PerformanceService::ORDER_STATUS_PENDING,
'performance_type' => PerformanceService::PERFORMANCE_TYPE_SALES,
'performance_value' => $performance,
'remarks' => $remarks
]);
return [
'order_id' => $order['id'],
'staff_id' => $staffId,
'personnel_id' => $staffId,
'campus_id' => $campusId,
'resource_id' => $resourceId,
'employment_stage' => $employmentStage,
'is_renewal' => $isRenewal,
'performance_amount' => $performance,
'new_resource_count' => $newResourceCount,
'renew_resource_count' => $renewResourceCount,
'performance_date' => date('Y-m-d'),
'performance_config' => $performanceConfig,
'performance_algorithm' => $performanceAlgorithm,
'remarks' => $remarks,
'status' => 'success',
'created_at' => time(),
'updated_at' => time()
];
}
/**
* 计算单个订单的绩效
* @param array $order 订单信息
* @param array $config 绩效配置
* @return array 绩效计算结果
*/
protected function calculateOrderPerformance($order, $config)
{
$staffId = $order['staff_id'];
$courseId = $order['course_id'];
$campusId = isset($order['campus_id']) ? $order['campus_id'] : 0;
$resourceId = isset($order['resource_id']) ? $order['resource_id'] : 0;
// 获取员工信息
$staff = Personnel::where('id', $staffId)->find();
if (empty($staff)) {
Log::write('未找到员工信息,员工ID:' . $staffId);
return [
'order_id' => $order['id'],
'staff_id' => $staffId,
'performance_amount' => 0,
'status' => 'failed',
'message' => '未找到员工信息'
];
}
// 获取课程信息
$course = Course::where('id', $courseId)->find();
if (empty($course)) {
Log::write('未找到课程信息,课程ID:' . $courseId);
return [
'order_id' => $order['id'],
'staff_id' => $staffId,
'performance_amount' => 0,
'status' => 'failed',
'message' => '未找到课程信息'
];
}
// 判断员工工龄阶段
$employmentStage = $this->getEmploymentStage($staff);
// 判断订单类型(新订单或续费)
$isRenewal = $this->isRenewalOrder($order);
// 计算绩效
$performance = $this->calculatePerformance($order, $course, $employmentStage, $isRenewal, $config);
// 获取新资源数和续费资源数
$newResourceCount = $isRenewal ? 0 : 1;
$renewResourceCount = $isRenewal ? 1 : 0;
// 保存使用的绩效配置和算法
$performanceConfig = json_encode([
'employment_stage' => $employmentStage,
'is_renewal' => $isRenewal,
'course_type' => $course['course_type']
], JSON_UNESCAPED_UNICODE);
$performanceAlgorithm = json_encode([
'base_commission' => $isRenewal ? 'renewal_commission' : 'course_type_num',
'order_id' => $order['id'],
'course_id' => $courseId
], JSON_UNESCAPED_UNICODE);
// 同时添加一条绩效汇总记录
$this->addPerformanceSummary([
'staff_id' => $staffId,
'resource_id' => $resourceId,
'order_id' => $order['id'],
'order_status' => PerformanceService::ORDER_STATUS_PENDING,
'performance_type' => PerformanceService::PERFORMANCE_TYPE_SALES,
'performance_value' => $performance,
'remarks' => '订单绩效自动计算'
]);
return [
'order_id' => $order['id'],
'staff_id' => $staffId,
'personnel_id' => $staffId,
'campus_id' => $campusId,
'course_id' => $courseId,
'employment_stage' => $employmentStage,
'is_renewal' => $isRenewal,
'performance_amount' => $performance,
'new_resource_count' => $newResourceCount,
'renew_resource_count' => $renewResourceCount,
'performance_date' => date('Y-m-d'),
'performance_config' => $performanceConfig,
'performance_algorithm' => $performanceAlgorithm,
'status' => 'success',
'created_at' => time(),
'updated_at' => time()
];
}
/**
* 判断员工工龄阶段
* @param object $staff 员工信息
* @return string 工龄阶段
*/
protected function getEmploymentStage($staff)
{
$joinTime = strtotime($staff['join_time']);
$now = time();
$monthsDiff = floor(($now - $joinTime) / (30 * 24 * 60 * 60));
if ($monthsDiff < 3) {
return self::STAGE_TRIAL; // 试用期
} else if ($monthsDiff < 6) {
return self::STAGE_REGULAR; // 转正
} else {
return self::STAGE_HALF_YEAR; // 转正后半年
}
}
/**
* 判断是否为续费订单
* @param array $order 订单信息
* @return bool 是否为续费订单
*/
protected function isRenewalOrder($order)
{
// 这里需要根据实际业务逻辑判断是否为续费订单
// 假设订单中有一个字段标记是否为续费
return isset($order['order_type']) && $order['order_type'] == 2;
}
/**
* 计算绩效金额
* @param array $order 订单信息
* @param object $course 课程信息
* @param string $employmentStage 工龄阶段
* @param bool $isRenewal 是否为续费订单
* @param array $config 绩效配置
* @return float 绩效金额
*/
protected function calculatePerformance($order, $course, $employmentStage, $isRenewal, $config)
{
// 获取对应工龄阶段的配置
$stageConfig = null;
foreach ($config['XSYJ'] as $item) {
if ($item['name'] == $employmentStage) {
$stageConfig = $item;
break;
}
}
if (empty($stageConfig)) {
Log::write('未找到对应工龄阶段的配置:' . $employmentStage);
return 0;
}
// 获取课程类型配置
$courseTypeConfig = null;
$courseType = $course['course_type'];
foreach ($config['course_type'] as $item) {
if ($item['value'] == $courseType) {
$courseTypeConfig = $item;
break;
}
}
if (empty($courseTypeConfig)) {
Log::write('未找到对应课程类型的配置:' . $courseType);
return 0;
}
// 获取基础提成金额
$baseCommission = $courseTypeConfig['num'];
// 如果是续费订单,根据续费率计算提成
if ($isRenewal) {
// 获取续费率
$renewalRate = $this->getRenewalRate($order['staff_id']);
// 查找适用的规则
$rule = null;
foreach ($stageConfig['rules'] as $r) {
$minRate = floatval($r['renewal_standard_min']);
$maxRate = !empty($r['renewal_standard_max']) ? floatval($r['renewal_standard_max']) : PHP_FLOAT_MAX;
if ($renewalRate >= $minRate && $renewalRate < $maxRate) {
$rule = $r;
break;
}
}
if (!empty($rule)) {
return floatval($rule['renewal_commission']);
}
} else {
// 新订单,直接返回基础提成
return floatval($baseCommission);
}
return 0;
}
/**
* 获取员工的续费率
* @param int $staffId 员工ID
* @return float 续费率
*/
protected function getRenewalRate($staffId)
{
// 这里需要根据实际业务逻辑计算员工的续费率
// 假设有一个表记录了员工的续费率
$rate = 95.0; // 默认值
// TODO: 从数据库中获取实际续费率
return $rate;
}
/**
* 保存绩效计算结果
* @param array $results 绩效计算结果
*/
protected function savePerformanceResults($results)
{
if (empty($results)) {
return;
}
try {
Db::startTrans();
foreach ($results as $result) {
if ($result['status'] == 'success') {
// 先检查订单是否已经计算过绩效
$existingRecord = Db::name('school_performance_summary')
->where('order_id', $result['order_id'])
->where('staff_id', $result['staff_id'])
->where('performance_type', PerformanceService::PERFORMANCE_TYPE_SALES)
->find();
if ($existingRecord) {
Log::write('订单ID:' . $result['order_id'] . ' 员工ID:' . $result['staff_id'] . ' 已存在绩效记录,跳过');
continue;
}
// 更新订单表,标记已计算绩效并记录核算时间
// 只有在处理完所有绩效记录后才更新订单表的核算状态
if (!isset($result['is_multi_person']) || !$result['is_multi_person']) {
OrderTable::where('id', $result['order_id'])
->update([
'performance_calculated' => 1,
'performance_amount' => $result['performance_amount'],
'accounting_time' => time() // 记录核算时间
]);
}
// 保存到绩效表 school_sales_performance
$performanceData = [
'personnel_id' => $result['personnel_id'],
'campus_id' => $result['campus_id'],
'performance_amount' => $result['performance_amount'],
'new_resource_count' => $result['new_resource_count'],
'renew_resource_count' => $result['renew_resource_count'],
'performance_date' => $result['performance_date'],
'performance_config' => $result['performance_config'],
'performance_algorithm' => $result['performance_algorithm'],
'created_at' => $result['created_at'],
'updated_at' => $result['updated_at']
];
Db::name('school_sales_performance')->insert($performanceData);
Log::write('成功保存绩效记录,员工ID:' . $result['personnel_id'] . ',绩效金额:' . $result['performance_amount'] . ',核算时间:' . date('Y-m-d H:i:s', time()));
}
}
// 更新所有已处理订单的状态
$orderIds = array_unique(array_column(array_filter($results, function($result) {
return $result['status'] == 'success';
}), 'order_id'));
if (!empty($orderIds)) {
OrderTable::whereIn('id', $orderIds)
->update([
'performance_calculated' => 1,
'accounting_time' => time()
]);
Log::write('成功更新' . count($orderIds) . '个订单的核算状态');
}
Db::commit();
Log::write('成功保存绩效计算结果');
} catch (\Exception $e) {
Db::rollback();
Log::write('保存绩效计算结果失败:' . $e->getMessage());
}
}
/**
* 添加绩效汇总记录
* @param array $data 绩效数据
* @return int|string 插入的ID
*/
public function addPerformanceSummary(array $data)
{
try {
return $this->performanceService->addPerformance($data);
} catch (\Exception $e) {
Log::write('添加绩效汇总记录失败:' . $e->getMessage());
return 0;
}
}
}

163
niucloud/app/job/transfer/schedule/ResourceAutoAllocation.php

@ -2,7 +2,10 @@
namespace app\job\transfer\schedule;
use app\model\campus_person_role\CampusPersonRole;
use app\model\resource_sharing\ResourceSharing;
use core\base\BaseJob;
use think\facade\Db;
use think\facade\Log;
/**
@ -10,37 +13,173 @@ use think\facade\Log;
*/
class ResourceAutoAllocation extends BaseJob
{
/**
* 执行任务
*/
public function doJob()
{
Log::write('自动分配资源');
Log::write('开始自动分配资源');
// 获取待分配的资源
$resources = $this->getResource();
if (empty($resources)) {
Log::write('没有可分配的资源');
return;
}
// 获取销售人员
$salesmen = $this->getSalesman();
if (empty($salesmen)) {
Log::write('没有可用的销售人员');
return;
}
// 分配资源
$this->allocateResource($resources, $salesmen);
Log::write('资源分配完成');
}
/**
* 获取销售人员
* 获取销售人员(角色ID为6或7的人员)
* @return array 销售人员列表
*/
public function getSalesman()
{
Log::write('获取销售人员');
// 获取角色ID为6或7的人员ID
$salesmen = CampusPersonRole::where('role_id', 'in', [6, 7])
->where('deleted_at', 0) // 未删除的记录
->field('person_id, role_id')
->select()
->toArray();
if (empty($salesmen)) {
Log::write('未找到销售人员');
return [];
}
// 获取每个销售人员当前拥有的资源数量
foreach ($salesmen as &$salesman) {
$resourceCount = ResourceSharing::where('shared_by', $salesman['person_id'])
->count();
$salesman['resource_count'] = $resourceCount;
}
// 按资源数量升序排序,资源少的排在前面
array_multisort(array_column($salesmen, 'resource_count'), SORT_ASC, $salesmen);
Log::write('找到' . count($salesmen) . '个销售人员');
return $salesmen;
}
/**
* 获取资源
* 获取待分配的资源
* @return array 待分配资源列表
*/
public function getResource()
{
Log::write('获取资源');
course_schedule
::where('status', 1)->select();
Log::write('获取待分配资源');
// 获取role_id不是6,7的,shared_by是0的资源ID
$resources = ResourceSharing::where(function ($query) {
$query->where('role_id', 'not in', [6, 7])
->whereOr('role_id', 'null');
})
->where('shared_by', 0)
->field('id, resource_id')
->select()
->toArray();
Log::write('找到' . count($resources) . '个待分配资源');
return $resources;
}
/**
* 按照现在销售人员的资源拥有情况分配资源
* 按照销售人员的资源拥有情况分配资源
* @param array $resources 待分配的资源列表
* @param array $salesmen 销售人员列表
*/
public function allocateResource()
public function allocateResource($resources, $salesmen)
{
Log::write('按照现在销售人员的资源拥有情况分配资源');
Log::write('按照销售人员的资源拥有情况分配资源');
if (empty($resources) || empty($salesmen)) {
Log::write('没有资源或销售人员,无法分配');
return;
}
// 记录分配结果
$allocations = [];
// 开始分配
foreach ($resources as $resource) {
// 重新获取销售人员的资源数量排序,确保每次分配都是给最少资源的人
$currentSalesmen = $this->refreshSalesmenResourceCount($salesmen);
if (empty($currentSalesmen)) {
Log::write('没有可用的销售人员');
break;
}
// 选择资源最少的销售人员
$targetSalesman = $currentSalesmen[0];
// 插入新的资源共享记录
try {
Db::startTrans();
// 插入新的资源分配记录
$insertData = [
'resource_id' => $resource['resource_id'],
'user_id' => $targetSalesman['person_id'],
'role_id' => $targetSalesman['role_id'],
'shared_by' => $targetSalesman['person_id'], // shared_by是接收资源的人员ID
'shared_at' => date('Y-m-d H:i:s')
];
ResourceSharing::create($insertData);
// 记录分配结果
$allocations[] = [
'resource_id' => $resource['resource_id'],
'salesman_id' => $targetSalesman['person_id']
];
Db::commit();
Log::write('资源ID:' . $resource['resource_id'] . ' 分配给销售人员ID:' . $targetSalesman['person_id']);
} catch (\Exception $e) {
Db::rollback();
Log::write('资源分配失败:' . $e->getMessage());
}
}
Log::write('成功分配' . count($allocations) . '个资源');
}
/**
* 刷新销售人员的资源数量并重新排序
* @param array $salesmen 销售人员列表
* @return array 更新后的销售人员列表
*/
private function refreshSalesmenResourceCount($salesmen)
{
if (empty($salesmen)) {
return [];
}
foreach ($salesmen as &$salesman) {
$resourceCount = ResourceSharing::where('shared_by', $salesman['person_id'])
->count();
$salesman['resource_count'] = $resourceCount;
}
// 按资源数量升序排序
array_multisort(array_column($salesmen, 'resource_count'), SORT_ASC, $salesmen);
return $salesmen;
}
}

257
niucloud/app/service/admin/performance/PerformanceService.php

@ -0,0 +1,257 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\service\admin\performance;
use core\base\BaseService;
use think\facade\Db;
use think\facade\Log;
/**
* 绩效服务类
* Class PerformanceService
* @package app\service\admin\performance
*/
class PerformanceService extends BaseService
{
/**
* 绩效类型:销售绩效
*/
const PERFORMANCE_TYPE_SALES = 'sales';
/**
* 绩效类型:市场绩效
*/
const PERFORMANCE_TYPE_MARKETING = 'marketing';
/**
* 订单状态:待处理
*/
const ORDER_STATUS_PENDING = 'pending';
/**
* 订单状态:已完成
*/
const ORDER_STATUS_COMPLETED = 'completed';
/**
* 添加绩效记录
* @param array $data 绩效数据
* @return int|string 插入的ID
*/
public function addPerformance(array $data)
{
try {
// 必填字段验证
$requiredFields = ['staff_id', 'resource_id', 'performance_type', 'performance_value'];
foreach ($requiredFields as $field) {
if (!isset($data[$field]) || empty($data[$field])) {
throw new \Exception('缺少必填字段:' . $field);
}
}
// 默认值设置
if (!isset($data['order_status'])) {
$data['order_status'] = self::ORDER_STATUS_PENDING;
}
if (!isset($data['created_at'])) {
$data['created_at'] = time();
}
if (!isset($data['updated_at'])) {
$data['updated_at'] = time();
}
// 插入数据
$id = Db::name('school_performance_summary')->insertGetId($data);
Log::write('成功添加绩效记录,ID:' . $id . ',员工ID:' . $data['staff_id'] . ',绩效类型:' . $data['performance_type']);
return $id;
} catch (\Exception $e) {
Log::write('添加绩效记录失败:' . $e->getMessage());
throw $e;
}
}
/**
* 更新绩效记录
* @param int $id 记录ID
* @param array $data 更新数据
* @return bool 是否成功
*/
public function updatePerformance(int $id, array $data)
{
try {
if (empty($id)) {
throw new \Exception('缺少绩效记录ID');
}
// 更新时间
if (!isset($data['updated_at'])) {
$data['updated_at'] = time();
}
// 更新数据
$result = Db::name('school_performance_summary')->where('id', $id)->update($data);
Log::write('成功更新绩效记录,ID:' . $id);
return $result !== false;
} catch (\Exception $e) {
Log::write('更新绩效记录失败:' . $e->getMessage());
throw $e;
}
}
/**
* 批量添加绩效记录
* @param array $dataList 绩效数据列表
* @return int 插入的记录数
*/
public function addBatchPerformance(array $dataList)
{
try {
if (empty($dataList)) {
return 0;
}
// 设置默认值
foreach ($dataList as &$data) {
if (!isset($data['order_status'])) {
$data['order_status'] = self::ORDER_STATUS_PENDING;
}
if (!isset($data['created_at'])) {
$data['created_at'] = time();
}
if (!isset($data['updated_at'])) {
$data['updated_at'] = time();
}
}
// 批量插入
$count = Db::name('school_performance_summary')->insertAll($dataList);
Log::write('成功批量添加绩效记录,数量:' . $count);
return $count;
} catch (\Exception $e) {
Log::write('批量添加绩效记录失败:' . $e->getMessage());
throw $e;
}
}
/**
* 获取绩效记录
* @param int $id 记录ID
* @return array|null 绩效记录
*/
public function getPerformance(int $id)
{
try {
if (empty($id)) {
throw new \Exception('缺少绩效记录ID');
}
$record = Db::name('school_performance_summary')->where('id', $id)->find();
return $record;
} catch (\Exception $e) {
Log::write('获取绩效记录失败:' . $e->getMessage());
throw $e;
}
}
/**
* 获取员工绩效列表
* @param int $staffId 员工ID
* @param string $performanceType 绩效类型
* @param string $orderStatus 订单状态
* @param string $startDate 开始日期
* @param string $endDate 结束日期
* @return array 绩效列表
*/
public function getStaffPerformanceList(int $staffId, string $performanceType = '', string $orderStatus = '', string $startDate = '', string $endDate = '')
{
try {
$query = Db::name('school_performance_summary')->where('staff_id', $staffId);
if (!empty($performanceType)) {
$query->where('performance_type', $performanceType);
}
if (!empty($orderStatus)) {
$query->where('order_status', $orderStatus);
}
if (!empty($startDate)) {
$startTime = strtotime($startDate . ' 00:00:00');
$query->where('created_at', '>=', $startTime);
}
if (!empty($endDate)) {
$endTime = strtotime($endDate . ' 23:59:59');
$query->where('created_at', '<=', $endTime);
}
$list = $query->order('created_at', 'desc')->select()->toArray();
return $list;
} catch (\Exception $e) {
Log::write('获取员工绩效列表失败:' . $e->getMessage());
throw $e;
}
}
/**
* 计算员工绩效总额
* @param int $staffId 员工ID
* @param string $performanceType 绩效类型
* @param string $orderStatus 订单状态
* @param string $startDate 开始日期
* @param string $endDate 结束日期
* @return float 绩效总额
*/
public function calculateStaffPerformanceTotal(int $staffId, string $performanceType = '', string $orderStatus = '', string $startDate = '', string $endDate = '')
{
try {
$query = Db::name('school_performance_summary')->where('staff_id', $staffId);
if (!empty($performanceType)) {
$query->where('performance_type', $performanceType);
}
if (!empty($orderStatus)) {
$query->where('order_status', $orderStatus);
}
if (!empty($startDate)) {
$startTime = strtotime($startDate . ' 00:00:00');
$query->where('created_at', '>=', $startTime);
}
if (!empty($endDate)) {
$endTime = strtotime($endDate . ' 23:59:59');
$query->where('created_at', '<=', $endTime);
}
$total = $query->sum('performance_value');
return floatval($total);
} catch (\Exception $e) {
Log::write('计算员工绩效总额失败:' . $e->getMessage());
throw $e;
}
}
}

52
niucloud/app/service/api/apiService/CourseService.php

@ -280,7 +280,8 @@ class CourseService extends BaseApiService
$query->select();
},'venue' => function($query) {
$query->select();
},'campus','studentCourses'])->select()->toArray();
},'campus','studentCourses'])
->select()->toArray();
foreach ($list as $k => $v) {
$student = Db::name('person_course_schedule')
->alias('pcs')
@ -324,12 +325,57 @@ class CourseService extends BaseApiService
$list = $personCourseSchedule
->alias('a')
->join(['school_customer_resources' => 'b'],'a.resources_id = b.id','left')
->join(['school_course_schedule' => 'c'],'c.id = a.schedule_id','left')
->where('a.schedule_id',$data['schedule_id'])
->field("b.name,a.status")
->select()->toArray();
->field("b.name,a.status,a.person_type,c.campus_id,b.id as resources_id")
->select()
->toArray();
return $list;
}
public function schedule_del(array $data)
{
$personCourseSchedule = new PersonCourseSchedule();
// 查询记录
$record = $personCourseSchedule->where([
'schedule_id' => $data['id'],
'resources_id' => $data['resources_id']
])->find();
if (!$record) {
return fail('未找到相关记录');
}
// 根据person_type执行不同操作
if ($record['person_type'] == 'customer_resource') {
// 如果是客户资源类型,直接删除记录
$personCourseSchedule->where([
'schedule_id' => $data['id'],
'resources_id' => $data['resources_id']
])->delete();
// 更新课程安排表的可用容量
$CourseSchedule = new CourseSchedule();
$CourseSchedule->where(['id' => $record['schedule_id']])->inc("available_capacity")->update();
return success('删除成功');
} else if ($record['person_type'] == 'student') {
// 如果是学生类型,更新状态为2并更新备注
$personCourseSchedule->where([
'id' => $data['id'],
'resources_id' => $data['resources_id']
])->update([
'status' => 2,
'remark' => $data['remark']
]);
return success('更新成功');
} else {
return fail('未知的人员类型');
}
}
}

127
niucloud/app/service/api/apiService/CustomerResourcesService.php

@ -23,6 +23,7 @@ use app\model\six_speed_modification_log\SixSpeedModificationLog;
use core\base\BaseApiService;
use think\facade\Db;
use think\facade\Event;
use think\facade\Log;
/**
* 客户资源服务层
@ -196,54 +197,53 @@ class CustomerResourcesService extends BaseApiService
$customer_resources_data['updated_at'] = $date;
$six_speed_data['updated_at'] = $date;
//开启事物
Db::startTrans();
try {
$customer_resources = CustomerResources::where('id', $where['id'])->find();
$six_speed = SixSpeed::where('resource_id', $where['id'])->find();
if ($customer_resources) {
$customer_resources = $customer_resources->toArray();
if (!$customer_resources['member_id'] && $six_speed) {
//新数据存在一访问 或者旧数据存在一访的情况 && 这用户没注册过member账号的情况下才给他创建member账号
if (!empty($six_speed_data['first_visit_status']) || $six_speed['first_visit_status']) {
$sex = 0;
switch ($customer_resources_data['gender']) {
case 'male'://男
$sex = 1;
break;
case 'female'://女
$sex = 2;
break;
default://其他
$sex = 0;
break;
}
$password = create_password($customer_resources_data['phone_number']);//创建密码
//给用户创建member账号
$member_id = Member::insertGetId([
'username' => $customer_resources_data['phone_number'],//会员用户名
'mobile' => $customer_resources_data['phone_number'],//手机号
'password' => $password,//会员密码
'nickname' => $customer_resources_data['name'],//会员昵称
'sex' => $sex,//性别 0保密 1男 2女
'member_time' => time(),//成为会员时间
]);
if ($member_id) {
$customer_resources_data['member_id'] = $member_id;
} else {
Db::rollback();
$res['msg'] = '创建用户账号失败';
return $res;
}
$sex = 0;
switch ($customer_resources_data['gender']) {
case 'male'://男
$sex = 1;
break;
case 'female'://女
$sex = 2;
break;
default://其他
$sex = 0;
break;
}
$password = create_password($customer_resources_data['phone_number']);//创建密码
//开启事物
// Db::startTrans();
//给用户创建member账号
$member_id = Member::insertGetId([
'username' => $customer_resources_data['phone_number'],//会员用户名
'mobile' => $customer_resources_data['phone_number'],//手机号
'password' => $password,//会员密码
'nickname' => $customer_resources_data['name'],//会员昵称
'sex' => $sex,//性别 0保密 1男 2女
'member_time' => time(),//成为会员时间
]);
if ($member_id) {
$customer_resources_data['member_id'] = $member_id;
} else {
Db::rollback();
$res['msg'] = '创建用户账号失败';
return $res;
}
}
}
$update_1 = CustomerResources::where('id', $where['id'])->update($customer_resources_data);//客户资源表
if (!$update_1) {
Db::rollback();
// Db::rollback();
return $res;
}
@ -262,7 +262,7 @@ class CustomerResourcesService extends BaseApiService
$id = CustomerResourceChanges::insertGetId($data);
if (!$id) {
Db::rollback();
// Db::rollback();
return $res;
}
}
@ -276,7 +276,7 @@ class CustomerResourcesService extends BaseApiService
//更新六要素
$sixSpeedUpdate = SixSpeed::where('id', $six_speed['id'])->update($six_speed_data);
if (!$sixSpeedUpdate) {
Db::rollback();
// Db::rollback();
return $res;
}
@ -294,7 +294,7 @@ class CustomerResourcesService extends BaseApiService
$id = SixSpeedModificationLog::insertGetId($data);
if (!$id) {
Db::rollback();
// Db::rollback();
return $res;
}
}
@ -302,12 +302,12 @@ class CustomerResourcesService extends BaseApiService
//创建六要素
$sixSpeedUpdate = SixSpeed::create($six_speed_data);
if (!$sixSpeedUpdate) {
Db::rollback();
// Db::rollback();
return $res;
}
}
Db::commit();
// Db::commit();
$res = [
'code' => 1,
'msg' => '操作成功'
@ -315,6 +315,8 @@ class CustomerResourcesService extends BaseApiService
return $res;
} catch (\Exception $exception) {
Db::rollback();
dd($exception);
Log::error(print_r($exception, true));
return $exception->getMessage();
}
}
@ -664,4 +666,49 @@ class CustomerResourcesService extends BaseApiService
}
public function updateUserCourseInfo($data)
{
// 验证必要参数
if (empty($data['id'])) {
return ['code' => false, 'msg' => '缺少必要参数id'];
}
try {
// 更新school_student_courses表中的字段
$update_data = [];
// 更新主教练ID
if (isset($data['main_coach_id'])) {
$update_data['main_coach_id'] = $data['main_coach_id'];
}
// 更新助教IDs
if (isset($data['assistant_ids'])) {
$update_data['assistant_ids'] = $data['assistant_ids'];
}
// 更新教务ID
if (isset($data['education_id'])) {
$update_data['education_id'] = $data['education_id'];
}
// 如果没有需要更新的数据,直接返回成功
if (empty($update_data)) {
return ['code' => true, 'msg' => '更新成功'];
}
// 执行更新操作
$res = \think\facade\Db::name('student_courses')
->where('id', $data['id'])
->update($update_data);
if ($res !== false) {
return ['code' => true, 'msg' => '更新成功'];
} else {
return ['code' => false, 'msg' => '更新失败'];
}
} catch (\Exception $e) {
return ['code' => false, 'msg' => '更新失败:' . $e->getMessage()];
}
}
}

3
niucloud/app/service/api/login/LoginService.php

@ -376,7 +376,8 @@ class LoginService extends BaseApiService
return [
'token' => $token_info['token'],//token
'expires_time' => $token_info['params']['exp'],//过期时间
'user_type' => $user_type//用户类型
'user_type' => $user_type,//用户类型
'userinfo' => $member_info,
];
}
}

2
niucloud/config/console.php

@ -19,6 +19,8 @@ $data = [
//wokrerman的启动停止和重启
'workerman' => 'app\command\workerman\Workerman',
'testcommand'=>'app\command\TestCommand'
],
];
return (new DictLoader("Console"))->load($data);

Loading…
Cancel
Save