智慧教务系统
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2267 lines
62 KiB

<?php
use app\model\campus_person_role\CampusPersonRole;
use app\model\personnel\Personnel;
use app\model\personnel\PersonnelSummary;
use app\model\sys\SysConfig;
use think\Container;
use think\Response;
use think\facade\Lang;
use think\facade\Queue;
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;
// 应用公共文件
/**
* 接口操作成功,返回信息
* @param array|string $msg
* @param array|string|bool|null $data
* @param int $code
* @param int $http_code
* @return Response
*/
function success($msg = 'SUCCESS', $data = [], int $code = 1, int $http_code = 200): Response
{
if (is_array($msg)) {
$data = $msg;
$msg = 'SUCCESS';
}
return Response::create(['data' => $data, 'msg' => get_lang($msg), 'code' => $code], 'json', $http_code);
}
/**
* 接口操作失败,返回信息
* @param $msg
* @param array|null $data
* @param int $code
* @param int $http_code
* @return Response
*/
function fail($msg = 'FAIL', ?array $data = [], int $code = 0, int $http_code = 200): Response
{
if (is_array($msg)) {
$data = $msg;
$msg = 'FAIL';
}
return Response::create(['data' => $data, 'msg' => get_lang($msg), 'code' => $code], 'json', $http_code);
}
/**
* 自动侦测语言并转化
* @param string $str
* @return lang()
*/
function get_lang($str)
{
return Lang::get($str);
}
/**
* 把返回的数据集转换成Tree
* @param $list 要转换的数据集
* @param string $pk
* @param string $pid
* @param string $child
* @param int $root
* @return array
*/
function list_to_tree($list, $pk = 'id', $pid = 'pid', $child = 'child', $root = 0)
{
// 创建Tree
$tree = array();
if (is_array($list)) {
// 创建基于主键的数组引用
$refer = array();
foreach ($list as $key => $data) {
$refer[$data[$pk]] =& $list[$key];
}
foreach ($list as $key => $data) {
// 判断是否存在parent
$parent_id = $data[$pid];
if ($root == $parent_id) {
$tree[] =& $list[$key];
} else {
if (isset($refer[$parent_id])) {
$parent =& $refer[$parent_id];
$parent[$child][] =& $list[$key];
}
}
}
}
return $tree;
}
/**
* 生成加密密码
* @param $password
* @param $salt 手动提供散列密码的盐值(salt)。这将避免自动生成盐值(salt)。,默认不填写将自动生成
* @return string
*/
function create_password($password, $salt = '')
{
return password_hash($password, PASSWORD_DEFAULT);
}
/**
* 校验比对密码和加密密码是否一致
* @param $password
* @param $hash
* @return bool
*/
function check_password($password, $hash)
{
if (!password_verify($password, $hash)) return false;
return true;
}
/**
* 获取键对应的值
* @param array $array 源数组
* @param array $keys 要提取的键数组
* @param string $index 二维组中指定提取的字段(唯一)
* @return array
*/
function array_keys_search($array, $keys, $index = '', $is_sort = true)
{
if (empty($array))
return $array;
if (empty($keys))
return [];
if (!empty($index) && count($array) != count($array, COUNT_RECURSIVE))
$array = array_column($array, null, $index);
$list = array();
foreach ($keys as $key) {
if (isset($array[$key])) {
if ($is_sort) {
$list[] = $array[$key];
} else {
$list[$key] = $array[$key];
}
}
}
return $list;
}
/**
* @notes 删除目标目录
* @param $path
* @param $delDir
* @return bool|void
*/
function del_target_dir($path, $delDir)
{
//没找到,不处理
if (!file_exists($path)) {
return false;
}
//打开目录句柄
$handle = opendir($path);
if ($handle) {
while (false !== ($item = readdir($handle))) {
if ($item != "." && $item != "..") {
if (is_dir("$path/$item")) {
del_target_dir("$path/$item", $delDir);
} else {
unlink("$path/$item");
}
}
}
closedir($handle);
if ($delDir) {
return rmdir($path);
}
} else {
if (file_exists($path)) {
return unlink($path);
}
return false;
}
}
/**
* 获取一些公共的系统参数
* @param string|null $key
* @return array|mixed
*/
function system_name(?string $key = '')
{
$params = [
'admin_token_name' => env('system.admin_token_name', 'token'),///todo !!! 注意 header参数 不能包含_ , 会自动转成 -
'api_token_name' => env('system.api_token_name', 'token'),
'channel_name' => env('system.channel_name', 'channel'),
];
if (!empty($key)) {
return $params[$key];
} else {
return $params;
}
}
/**
* 获取日期(默认不传参 获取当前日期)
* @param int|null $time
* @return string
*/
function get_date_by_time(?int $time = null)
{
return date('Y-m-d h:i:s', $time);
}
function get_start_and_end_time_by_day($day = '')
{
$date = $day ?: date('Y-m-d');
$day_start_time = strtotime($date);
//当天结束之间
$day_end_time = $day_start_time + 86400;
return [$day_start_time, $day_end_time];
}
/**
* 获取本周的 开始、结束时间
* @param data 日期
*/
function get_weekinfo_by_time($date)
{
$idx = strftime("%u", strtotime($date));
$mon_idx = $idx - 1;
$sun_idx = $idx - 7;
return array(
'week_start_day' => strftime('%Y-%m-%d', strtotime($date) - $mon_idx * 86400),
'week_end_day' => strftime('%Y-%m-%d', strtotime($date) - $sun_idx * 86400),
);
}
/**
* 路径转链接
* @param $path
* @return string
*/
function path_to_url($path)
{
return trim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '.');
}
/**
* 链接转化路径
* @param $url
* @return string
*/
function url_to_path($url)
{
if (str_contains($url, 'http://') || str_contains($url, 'https://')) return $url;//网络图片不必
return public_path() . trim(str_replace('/', DIRECTORY_SEPARATOR, $url));
}
/**
* 获取本地文件的对外网络路径
* @param string $path
* @return string
*/
function get_file_url(string $path)
{
if (!$path) return '';
if (!str_contains($path, 'http://') && !str_contains($path, 'https://')) {
return request()->domain() . '/' . path_to_url($path);
} else {
return path_to_url($path);
}
}
/**
* 获取图片文件的对外网络路径(get_file_url的别名)
* @param string $path
* @return string
*/
function get_image_url(string $path)
{
return get_file_url($path);
}
/**
* 新增队列工作
* @param $job
* @param $data
* @param $delay
* @param $queue
* @return bool
*/
function create_job($job, $data = '', $delay = 0, $queue = null)
{
if ($delay > 0) {
$is_success = Queue::later($delay, $job, $data, $queue);
} else {
$is_success = Queue::push($job, $data, $queue);
}
if ($is_success !== false) {
return true;
} else {
return false;
}
}
/**
* 获取插件对应资源文件(插件安装后获取)
* @param $addon //插件名称
* @param $file_name //文件名称(包含resource文件路径)
*/
function addon_resource($addon, $file_name)
{
return "addon/" . $addon . "/" . $file_name;
}
/**
* 判断 文件/目录 是否可写(取代系统自带的 is_writeable 函数)
*
* @param string $file 文件/目录
* @return boolean
*/
function is_write($file)
{
if (is_dir($file)) {
$dir = $file;
if ($fp = @fopen("$dir/test.txt", 'wb')) {
@fclose($fp);
@unlink("$dir/test.txt");
$writeable = true;
} else {
$writeable = false;
}
} else {
if ($fp = @fopen($file, 'ab+')) {
@fclose($fp);
$writeable = true;
} else {
$writeable = false;
}
}
return $writeable;
}
/**
* 主要用于金额格式化(用于显示)
* @param $number
* @return int|mixed|string
*/
function format_money($number)
{
if ($number == intval($number)) {
return intval($number);
} elseif ($number == sprintf('%.2f', $number)) {
return sprintf('%.2f', $number);
}
return $number;
}
/**
* 金额浮点数格式化
* @param $number
* @param $precision
* @return float|int
*/
function format_float_money($number, $precision = 2)
{
if ($precision > 0) {
return sprintf('%.' . $precision . 'f', floor($number * (10 ** $precision)) / (10 ** $precision));
} else {
return sprintf('%.' . $precision . 'f', floor($number));
}
}
/**
* 金额保留小数点后*位
* @param $number
* @return float
*/
function format_round_money($number)
{
return round($number, 2);
}
/**
* 基础属性过滤(特殊字符..)
* @param $string
* @return void
*/
function filter($string)
{
return $string;
}
/**
* 生成编号
* @param string $prefix
* @param string $tag 业务标识 例如member_id ...
* @return string
* @throws Exception
*/
function create_no(string $prefix = '', string $tag = '')
{
$data_center_id = 1;
$machine_id = 2;
$snowflake = new Snowflake($data_center_id, $machine_id);
$id = $snowflake->generateId();
return $prefix . date('Ymd') . $tag . $id;
}
/**
* 多级目录不存在则创建
* @param $dir
* @param $mode
* @return bool
*/
function mkdirs($dir, $mode = 0777)
{
if (str_contains($dir, '.')) $dir = dirname($dir);
if (is_dir($dir) || @mkdir($dir, $mode)) return true;
if (!mkdirs(dirname($dir), $mode)) return false;
return @mkdir($dir, $mode);
}
/**
* 创建文件夹
* @param $dir
* @param $mode
* @return true
*/
function mkdirs_or_notexist($dir, $mode = 0777)
{
if (!is_dir($dir) && !mkdir($dir, $mode, true) && !is_dir($dir)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir));
}
return true;
}
/**
* 删除缓存文件使用
* @param $dir
*/
function rmdirs($dir)
{
$dh = opendir($dir);
while ($file = readdir($dh)) {
if ($file != "." && $file != "..") {
$fullpath = $dir . "/" . $file;
if (is_dir($fullpath)) {
rmdirs($fullpath);
} else {
unlink($fullpath);
}
}
}
closedir($dh);
}
/**
* 获取唯一随机字符串
* @param int $len
* @return string
*/
function unique_random($len = 10)
{
$str = 'qwertyuiopasdfghjklzxcvbnmasdfgh';
str_shuffle($str);
return substr(str_shuffle($str), 0, $len);
}
/**
* 校验事件结果
* @param $result
* @return bool
*/
function check_event_result($result)
{
if (empty($result) || is_array($result)) {
return true;
}
foreach ($result as $v) {
if (!$v) return false;
}
return true;
}
/**
* 二维数组合并
* @param array $array1
* @param array $array2
* @return array
*/
function array_merge2(array $array1, array $array2)
{
foreach ($array2 as $array2_k => $array2_v) {
if (array_key_exists($array2_k, $array1)) {
if (is_array($array2_v)) {
foreach ($array2_v as $array2_kk => $array2_vv) {
if (array_key_exists($array2_kk, $array1[$array2_k])) {
if (is_array($array2_vv)) {
$array1[$array2_k][$array2_kk] = array_merge($array1[$array2_k][$array2_kk], $array2_vv);
}
} else {
$array1[$array2_k][$array2_kk] = $array2_vv;
}
}
} else {
$array1[$array2_k] = $array2_v;
}
} else {
$array1[$array2_k] = $array2_v;
}
}
return $array1;
}
/**
* 通过目录获取文件结构1
* @param $dir
* @return array
*/
function get_files_by_dir($dir)
{
$dh = @opendir($dir); //打开目录,返回一个目录流
$return = array();
while ($file = @readdir($dh)) { //循环读取目录下的文件
if ($file != '.' and $file != '..') {
$path = $dir . DIRECTORY_SEPARATOR . $file; //设置目录,用于含有子目录的情况
if (is_dir($path)) {
$return[] = $file;
}
}
}
@closedir($dh); //关闭目录流
return $return; //返回文件
}
/**
* 文件夹文件拷贝
* @param string $src 来源文件夹
* @param string $dst 目的地文件夹
* @param array $files 文件夹集合
* @param array $exclude_dirs 排除无需拷贝的文件夹
* @param array $exclude_files 排除无需拷贝的文件
* @return bool
*/
function dir_copy(string $src = '', string $dst = '', &$files = [], $exclude_dirs = [], $exclude_files = [])
{
if (empty($src) || empty($dst)) {
return false;
}
if (!file_exists($src)) {
return false;
}
$dir = opendir($src);
dir_mkdir($dst);
while (false !== ($file = readdir($dir))) {
if (($file != '.') && ($file != '..')) {
if (is_dir($src . '/' . $file)) {
// 排除目录
if (count($exclude_dirs) && in_array($file, $exclude_dirs)) continue;
dir_copy($src . '/' . $file, $dst . '/' . $file, $files);
} else {
// 排除文件
if (count($exclude_files) && in_array($file, $exclude_files)) continue;
copy($src . '/' . $file, $dst . '/' . $file);
$files[] = $dst . '/' . $file;
}
}
}
closedir($dir);
return true;
}
/**
* 删除文件
* @param string $dst
* @param array $dirs
* @return bool
*/
function dir_remove(string $dst = '', array $dirs = [])
{
if (empty($dirs) || empty($dst)) {
return false;
}
foreach ($dirs as $v) {
@unlink($dst . $v);
}
return true;
}
/**
* 创建文件夹
*
* @param string $path 文件夹路径
* @param int $mode 访问权限
* @param bool $recursive 是否递归创建
* @return bool
*/
function dir_mkdir($path = '', $mode = 0777, $recursive = true)
{
clearstatcache();
if (!is_dir($path)) {
if (mkdir($path, $mode, $recursive)) {
return chmod($path, $mode);
} else {
throw new \core\exception\CommonException("目录{$path}创建失败请检查是否有足够的权限");
}
}
return true;
}
/**
* 分割sql语句
* @param string $content sql内容
* @param bool $string 如果为真,则只返回一条sql语句,默认以数组形式返回
* @param array $replace 替换前缀,如:['my_' => 'me_'],表示将表前缀my_替换成me_
* @return array|string 除去注释之后的sql语句数组或一条语句
*/
function parse_sql($content = '', $string = false, $replace = [])
{
// 纯sql内容
$pure_sql = [];
// 被替换的前缀
$from = '';
// 要替换的前缀
$to = '';
// 替换表前缀
if (!empty($replace)) {
$to = current($replace);
$from = current(array_flip($replace));
}
if ($content != '') {
// 多行注释标记
$comment = false;
// 按行分割,兼容多个平台
$content = str_replace(["\r\n", "\r"], "\n", $content);
$content = explode("\n", trim($content));
// 循环处理每一行
foreach ($content as $line) {
// 跳过空行
if ($line == '') {
continue;
}
// 跳过以#或者--开头的单行注释
if (preg_match("/^(#|--)/", $line)) {
continue;
}
// 跳过以/**/包裹起来的单行注释
if (preg_match("/^\/\*(.*?)\*\//", $line)) {
continue;
}
// 多行注释开始
if (str_starts_with($line, '/*')) {
$comment = true;
continue;
}
// 多行注释结束
if (str_ends_with($line, '*/')) {
$comment = false;
continue;
}
// 多行注释没有结束,继续跳过
if ($comment) {
continue;
}
// 替换表前缀
if ($from != '') {
$line = str_replace('`' . $from, '`' . $to, $line);
}
// sql语句
$pure_sql[] = $line;
}
// 只返回一条语句
if ($string) {
return implode("", $pure_sql);
}
// 以数组形式返回sql语句
$pure_sql = implode("\n", $pure_sql);
$pure_sql = explode(";\n", $pure_sql);
}
return $pure_sql;
}
/**
* 递归查询目录下所有文件
* @param $path
* @param $data
* @param $search
* @return void
*/
function search_dir($path, &$data, $search = '')
{
if (is_dir($path)) {
$path .= DIRECTORY_SEPARATOR;
$fp = dir($path);
while ($file = $fp->read()) {
if ($file != '.' && $file != '..') {
search_dir($path . $file, $data, $search);
}
}
$fp->close();
}
if (is_file($path)) {
if ($search) $path = str_replace($search, '', $path);
$data[] = $path;
}
}
function remove_empty_dir($dirs)
{
}
/**
* 获取文件地图
* @param $path
* @param array $arr
* @return array
*/
function getFileMap($path, $arr = [])
{
if (is_dir($path)) {
$dir = scandir($path);
foreach ($dir as $file_path) {
if ($file_path != '.' && $file_path != '..') {
$temp_path = $path . '/' . $file_path;
if (is_dir($temp_path)) {
$arr[$temp_path] = $file_path;
$arr = getFileMap($temp_path, $arr);
} else {
$arr[$temp_path] = $file_path;
}
}
}
return $arr;
}
}
/**
* 如果不存在则写入缓存
* @param string|null $name
* @param $value
* @param $tag
* @param $options
* @return mixed|string
*/
function cache_remember(string $name = null, $value = '', $tag = null, $options = null)
{
if (!empty($hit = Cache::get($name)))//可以用has
return $hit;
if ($value instanceof Closure) {
// 获取缓存数据
$value = Container::getInstance()->invokeFunction($value);
}
if (is_null($tag)) {
Cache::set($name, $value, $options['expire'] ?? null);
} else {
Cache::tag($tag)->set($name, $value, $options['expire'] ?? null);
}
return $value;
}
/**
* 项目目录
* @return string
*/
function project_path()
{
return dirname(root_path()) . DIRECTORY_SEPARATOR;
}
/**
* 图片转base64
* @param string $path
* @param $is_delete 转换后是否删除原图
* @return string
*/
function image_to_base64(string $path, $is_delete = false)
{
if (!file_exists($path)) return 'image not exist';
$mime = getimagesize($path)['mime'];
$image_data = file_get_contents($path);
// 将图片转换为 base64
$base64_data = base64_encode($image_data);
if ($is_delete) @unlink($path);
return "data:$mime;base64,$base64_data";
}
/**
* 获取缩略图
* @param $image
* @param string $thumb_type
* @param bool $is_throw_exception
* @return mixed
* @throws Exception
*/
function get_thumb_images($image, $thumb_type = 'all', bool $is_throw_exception = false)
{
return (new CoreImageService())->thumb($image, $thumb_type, $is_throw_exception);
}
/**
* 版本号转整数 例如1.0.0=001.000.000=001000000=1000000
* @param $version
* @return int
*/
function version_to_int($version)
{
$version_array = explode(".", $version);
$v1 = sprintf('%03s', (int)$version_array[0] ?? 0);
$v2 = sprintf('%03s', (int)$version_array[1] ?? 0);
$v3 = sprintf('%03s', (int)$version_array[2] ?? 0);
return (int)"{$v1}{$v2}{$v3}";
}
/**
* 整数版本号转字符串例如 1000000=001000000=001.000.000=1.0.0
* @param int $ver
* @return string
*/
function version_to_string($ver)
{
if ($ver > 999) {
if ($ver > 999999) {
$ver .= "";
$v3 = (int)substr($ver, -3);
$v2 = (int)substr($ver, -6, 3);
$v1 = (int)substr($ver, 0, -6);
} else {
$ver .= "";
$v3 = (int)substr($ver, -3);
$v2 = (int)substr($ver, 0, -3);
$v1 = 0;
}
} else {
$v3 = $ver;
$v2 = 0;
$v1 = 0;
}
return "{$v1}.{$v2}.{$v3}";
}
/**
* 检测文件是否是本地图片
* @param string $file_path
* @return void
*/
function check_file_is_remote(string $file_path)
{
return str_contains($file_path, 'https://') || str_contains($file_path, 'http://') || str_contains($file_path, '.com');
}
/**
* 文件拷贝
* @param string $source_file
* @param string $to_file
* @return void
*/
function file_copy(string $source_file, string $to_file)
{
if (!file_exists($source_file)) return false;
// 检查目标文件是否存在
if (!file_exists($to_file)) {
// 创建目录
$directory = dirname($to_file);
if (!file_exists($directory)) {
mkdir($directory, 0777, true);
}
}
if (copy($source_file, $to_file)) {
return true;
} else {
return false;
}
}
/**
* 创建并生成二维码
* @param $url
* @param $dir
* @param $file_path
* @param $channel
* @param $size
* @return string
*/
function qrcode($url, $page, $data, $dir = '', $channel = 'h5', $style = ['is_transparent' => true], $outfile = true)
{
if ($outfile) {
$dir = $dir ?: 'upload' . '/' . 'qrcode/';//二维码默认存储位置
if (!is_dir($dir) && !mkdir($dir, 0777, true) && !is_dir($dir)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir));
}
$file_path = md5($url . $page . serialize($data) . serialize($style) . $channel);
$path = $dir . '/' . $file_path . '.png';
if (file_exists($path)) {
return $path;
}
}
$result = array_values(array_filter(event('GetQrcodeOfChannel', [
'filepath' => $path ?? '',
'url' => $url,
'page' => $page,
'data' => $data,
'channel' => $channel,
'outfile' => $outfile
])));
if (!empty($result[0])) {
$path = $result[0];
}
return $path;
}
/**
* 获取海报
* @param $id
* @param $type
* @param array $param
* @param string $channel
* @param bool $is_throw_exception
* @return string|void
*/
function poster($id, $type, array $param = [], string $channel = '', bool $is_throw_exception = true)
{
return (new \app\service\core\poster\CorePosterService())->get($id, $type, $param, $channel, $is_throw_exception);
}
/**
* 是否是url链接
* @param unknown $string
* @return boolean
*/
function is_url($string)
{
if (strstr($string, 'http://') === false && strstr($string, 'https://') === false) {
return false;
} else {
return true;
}
}
/**
* 获取站点插件
* @return array
*/
function get_site_addons(): array
{
$addons = Cache::get("local_install_addons");
return is_null($addons) ? [] : $addons;
}
function get_wap_domain()
{
$wap_url = (new CoreSysConfigService())->getSceneDomain()['wap_url'];
return $wap_url;
}
/**
* $str为要进行截取的字符串,$length为截取长度(汉字算一个字,字母算半个字
* @param $str
* @param int $length
* @param bool $is_need_apostrophe
* @return string
*/
function str_sub($str, $length = 10, $is_need_apostrophe = true)
{
return mb_substr($str, 0, $length, 'UTF-8') . ($is_need_apostrophe ? '...' : '');
}
/**
* 使用正则表达式匹配特殊字符
* @param $str
* @return bool
*/
function is_special_character($str)
{
$pattern = '/[!@#$%^&*()\[\]{}<>\|?:;"]/';
if (preg_match($pattern, $str)) {
return true;
} else {
return false;
}
}
/**
* 时间格式转换
* @param $time
* @return string
*/
function get_last_time($time = null)
{
$text = '';
$time = $time === null || $time > time() ? time() : intval($time);
$t = time() - $time; //时间差 (秒)
$y = date('Y', $time) - date('Y', time());//是否跨年
switch ($t) {
case 0:
$text = '刚刚';
break;
case $t < 60:
$text = $t . '秒前'; // 一分钟内
break;
case $t < 60 * 60:
$text = floor($t / 60) . '分钟前'; //一小时内
break;
case $t < 60 * 60 * 24:
$text = floor($t / (60 * 60)) . '小时前'; // 一天内
break;
case $t < 60 * 60 * 24 * 3:
$text = floor($time / (60 * 60 * 24)) == 1 ? '昨天' . date('H:i', $time) : '前天' . date('H:i', $time); //昨天和前天
break;
case $t < 60 * 60 * 24 * 30:
$text = date('m-d H:i', $time); //一个月内
break;
case $t < 60 * 60 * 24 * 365 && $y == 0:
$text = date('m-d', $time); //一年内
break;
default:
$text = date('Y-m-d', $time); //一年以前
break;
}
return $text;
}
function get_campus_where($user_id, $field = "campus_id")
{
$where = [];
$person = new Personnel();
$campusPersonRole = new CampusPersonRole();
if ($user_id > 1) {
$person_id = $person->where(['sys_user_id' => $user_id])->value("id");
$role_info = $campusPersonRole->where(['person_id' => $person_id])->find();
if (!in_array($role_info['role_id'], [1, 7, 8])) {
if ($role_info['campus_id']) {
$where[] = [$field, '=', $role_info['campus_id']];
}
}
}
return $where;
}
function getModifiedFields(array $oldData, array $newData): array
{
$modifiedFields = [];
$oldValues = [];
$newValues = [];
foreach ($newData as $key => $newValue) {
if (!array_key_exists($key, $oldData)) {
continue;
}
if ($oldData[$key] != $newValue) {
$modifiedFields[] = $key;
$oldValues[$key] = $oldData[$key];
$newValues[$key] = $newValue;
}
}
return [
'is_save' => !empty($modifiedFields),
'modified_fields' => json_encode($modifiedFields, JSON_UNESCAPED_UNICODE),
'old_values' => json_encode($oldValues, JSON_UNESCAPED_UNICODE),
'new_values' => json_encode($newValues, JSON_UNESCAPED_UNICODE),
];
}
// 获取员工编号
function getEmployeeNumber()
{
$personnel = new Personnel();
// 获取最新id
$max_id = $personnel->max('id') + 1;
$max_id = str_pad($max_id, 5, '0', STR_PAD_LEFT);
return date('Ymd') . $max_id;
}
function return_pay_config($campus_id, $order_id)
{
$campus_pay = new \app\model\campus_pay\CampusPay();
$pay_config = $campus_pay->where(['campus_id' => $campus_id])->find();
/**
* [
* "id" => 3
* "campus_id" => 3
* "mchid" => "1683015573"
* "pay_sign_key" => "eMxxEaxmzWo0FeMasMGPZDAC2vEZtVpE"
* "apiclient_key" => "upload/attachment/document/cert/202508/08/apiclient_cert.pem"
* "apiclient_cert" => "upload/attachment/document/cert/202508/08/apiclient_key.pem"
* "wx_pay_key" => "upload/attachment/document/cert/202508/09/pub_key.pem"
* "wx_pay_key_id" => "PUB_KEY_ID_0116830155732025080800112116001001"
* "created_at" => "2025-08-08 19:34:38"
* "updated_at" => "2025-08-09 00:02:13"
* ]
*/
$sysConfig = new SysConfig();
$vx_config = $sysConfig->where(['config_key' => 'weapp'])->value("value");
if (!$vx_config || !$pay_config) {
throw new \Exception('当前校区支付配置不存在');
}
$config = [
// 必填-商户号
'mch_id' => $pay_config['mchid'],
// 必填-商户私钥 字符串或路径
'mch_secret_cert' => $pay_config['apiclient_cert'],
// 必填-商户公钥证书路径
'mch_public_cert_path' => $pay_config['apiclient_key'],
// 必填
'notify_url' => 'https://api.hnhbty.cn/api/pay/qrcodenotify/order_id/' . $order_id,
// 选填-公众号 的 app_id
'mp_app_id' => $vx_config['app_id'],
// 选填-小程序 的 app_id
'mini_app_id' => '',
'mch_secret_key' => $pay_config['pay_sign_key'],
'wechat_public_cert_path' => $pay_config['wx_pay_key'] ?? '',
'wechat_public_cert_id' => $pay_config['wx_pay_key_id'] ?? '',
];
return $config;
}
function getCurrentDomain()
{
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ||
$_SERVER['SERVER_PORT'] == 443 ? "https://" : "http://";
$host = $_SERVER['HTTP_HOST']; // 包括域名和端口(如存在)
return $scheme . $host . '/';
}
function decryptWechatPayNotify($ciphertext, $nonce, $associatedData, $key)
{
$ciphertext = base64_decode($ciphertext);
$authTag = substr($ciphertext, -16);
$ciphertext = substr($ciphertext, 0, -16);
return openssl_decrypt(
$ciphertext,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$nonce,
$authTag,
$associatedData
);
}
/**
* 判断是否为手机号
*/
function isPhone($mobile)
{
return preg_match('/^1[3456789]\d{9}$/', $mobile);
}
/**
* 体测评分
* @param int $age年龄
* @param int $gender性别( 1:男,2:女)
* @param int $height身高
* @param int $weight体重
*/
function calculateChildHealthScore($age, $gender, $height, $weight)
{
// WHO 标准数据(简化版)——单位:cm/kg
// 示例使用中位数及上下限估算(实际可使用更精确的Z-score或百分位表)
$reference = [
'1' => [
3 => ['height_median' => 96.5, 'height_low' => 91.5, 'height_high' => 101.5, 'weight_median' => 14.2, 'weight_low' => 12.3, 'weight_high' => 16.5],
5 => ['height_median' => 110.0, 'height_low' => 104.5, 'height_high' => 115.5, 'weight_median' => 18.0, 'weight_low' => 15.6, 'weight_high' => 21.0],
7 => ['height_median' => 123.0, 'height_low' => 117.0, 'height_high' => 129.0, 'weight_median' => 22.5, 'weight_low' => 19.5, 'weight_high' => 26.0],
10 => ['height_median' => 138.0, 'height_low' => 131.0, 'height_high' => 145.0, 'weight_median' => 29.5, 'weight_low' => 25.5, 'weight_high' => 34.0],
12 => ['height_median' => 148.5, 'height_low' => 141.0, 'height_high' => 156.0, 'weight_median' => 36.0, 'weight_low' => 31.0, 'weight_high' => 42.0],
15 => ['height_median' => 164.0, 'height_low' => 155.0, 'height_high' => 173.0, 'weight_median' => 50.0, 'weight_low' => 43.0, 'weight_high' => 58.0],
18 => ['height_median' => 170.0, 'height_low' => 161.0, 'height_high' => 179.0, 'weight_median' => 58.0, 'weight_low' => 50.0, 'weight_high' => 67.0],
],
'2' => [
3 => ['height_median' => 95.5, 'height_low' => 90.5, 'height_high' => 100.5, 'weight_median' => 13.8, 'weight_low' => 12.0, 'weight_high' => 16.0],
5 => ['height_median' => 109.0, 'height_low' => 103.5, 'height_high' => 114.5, 'weight_median' => 17.8, 'weight_low' => 15.3, 'weight_high' => 20.5],
7 => ['height_median' => 122.0, 'height_low' => 116.0, 'height_high' => 128.0, 'weight_median' => 22.0, 'weight_low' => 19.0, 'weight_high' => 25.5],
10 => ['height_median' => 137.0, 'height_low' => 130.0, 'height_high' => 144.0, 'weight_median' => 29.0, 'weight_low' => 25.0, 'weight_high' => 33.5],
12 => ['height_median' => 150.0, 'height_low' => 142.0, 'height_high' => 158.0, 'weight_median' => 37.0, 'weight_low' => 31.5, 'weight_high' => 43.0],
15 => ['height_median' => 158.0, 'height_low' => 150.0, 'height_high' => 166.0, 'weight_median' => 48.0, 'weight_low' => 41.0, 'weight_high' => 56.0],
18 => ['height_median' => 159.0, 'height_low' => 152.0, 'height_high' => 166.0, 'weight_median' => 53.0, 'weight_low' => 46.0, 'weight_high' => 61.0],
]
];
if (!isset($reference[$gender][$age])) {
return 0; // 年龄不在范围内
}
$data = $reference[$gender][$age];
// 身高评分(线性插值)
$height_score = 100;
if ($height < $data['height_low']) {
$height_score = max(0, 50 * ($height - $data['height_low']) / ($data['height_median'] - $data['height_low']));
} elseif ($height > $data['height_high']) {
$height_score = max(0, 50 + 50 * ($data['height_high'] - $height) / ($data['height_high'] - $data['height_median']));
} else {
$height_score = 50 + 50 * (abs($height - $data['height_median']) / ($data['height_median'] - $data['height_low'])) * ($height > $data['height_median'] ? 1 : -1);
$height_score = max(0, min(100, $height_score));
}
// 体重评分
$weight_score = 100;
if ($weight < $data['weight_low']) {
$weight_score = max(0, 50 * ($weight - $data['weight_low']) / ($data['weight_median'] - $data['weight_low']));
} elseif ($weight > $data['weight_high']) {
$weight_score = max(0, 50 + 50 * ($data['weight_high'] - $weight) / ($data['weight_high'] - $data['weight_median']));
} else {
$weight_score = 50 + 50 * (abs($weight - $data['weight_median']) / ($data['weight_median'] - $data['weight_low'])) * ($weight > $data['weight_median'] ? 1 : -1);
$weight_score = max(0, min(100, $weight_score));
}
// 综合评分
$total_score = ($height_score + $weight_score) / 2;
// 四舍五入取整
return round($total_score);
}
function get_dict_value($key, $value)
{
$dict = new \app\model\dict\Dict();
$field = 'id,name,key,dictionary,memo,create_time,update_time';
$info = $dict->field($field)->where([['key', '=', $key]])->findOrEmpty()->toArray();
if ($info['dictionary'] == null) {
$info['dictionary'] = [];
}
// 如果dictionary是字符串,尝试解析为数组
if (is_string($info['dictionary'])) {
$info['dictionary'] = json_decode($info['dictionary'], true) ?: [];
}
$map = [];
if (is_array($info['dictionary'])) {
foreach ($info['dictionary'] as $item) {
$map[$item['value']] = $item['name'];
}
}
return $map[$value] ?? '';
}
function get_dict_name($key, $value)
{
$dict = new \app\model\dict\Dict();
$field = 'id,name,key,dictionary,memo,create_time,update_time';
$info = $dict->field($field)->where([['key', '=', $key]])->findOrEmpty()->toArray();
if ($info['dictionary'] == null) {
$info['dictionary'] = [];
}
$map = [];
foreach ($info['dictionary'] as $item) {
$map[$item['name']] = $item['value'];
}
return $map[$value] ?? '';
}
//$date = 年月 $campus_person_role_id 人员角色表id
//$field complete_num 完成数量 expire_num 到期数量 renew_num 续费数量 resource_num 分配的资源数量 visit_num 到访数量
//$num 增加数量 默认1
function set_summary($date, $campus_person_role_id, $field, $num = 1)
{
$personnel_summary = new PersonnelSummary();
$personnel_summary->where([
['task_date', '=', $date],
['campus_person_role_id', '=', $campus_person_role_id]
])->inc($field, $num)->update();
return true;
}
/**
* 根据角色id获取角色类型
*/
function get_role_type($role_id)
{
$role_type = [
1 => 'market',
2 => 'sale',
3 => 'teacher'
];
$dept = \app\model\sys\SysRole::find($role_id);
return $role_type[$dept->dept_id] ?? 'other';
}
/**
* 获取顶级部门
*/
function isDateInThisWeek($date)
{
$timestamp = strtotime($date);
// 获取本周开始和结束时间戳(周一到周日)
$monday = strtotime('monday this week');
$sunday = strtotime('sunday this week 23:59:59');
return $timestamp >= $monday && $timestamp <= $sunday;
}
function isDateInLastWeek($date)
{
$timestamp = strtotime($date);
// 获取上周的周一(开始)和周日(结束)
$startOfLastWeek = strtotime('monday last week');
$endOfLastWeek = strtotime('sunday last week 23:59:59');
return $timestamp >= $startOfLastWeek && $timestamp <= $endOfLastWeek;
}
function getChineseWeekday($date)
{
$weekdays = ['日', '一', '二', '三', '四', '五', '六'];
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;
}
}
function httpGet($url)
{
$curl = curl_init();
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 500);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_URL, $url);
$res = curl_exec($curl);
curl_close($curl);
return $res;
}
function getAccessToken()
{
$appId = 'wxe48c268ac1c97ab9';
$appSecret = 'fc355f42f55e693595d31e0e644bd11c';
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$appId&secret=$appSecret";
$res = json_decode(httpGet($url));
$access_token = $res->access_token;
return $access_token;
}
//$touser 用户的openid
//$template_id 模板id
//$value 发送的 消息 数组
function sendMessage($touser, $template_id, $value)
{
// 构建请求参数
$accessToken = getAccessToken();
$url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" . $accessToken;
if ($template_id == 'K5YS7UBbD18PZvqUmnBKfX_j8tKP_AUAKa75mwUsX0A') {
$data = [
'touser' => $touser,
'template_id' => $template_id,
'data' => [
'thing9' => ['value' => $value['thing9'], 'color' => '#173177'], //课程名称
'time8' => ['value' => $value['time8'], 'color' => '#173177'],//课程周期 例子 2023年10月11日~2023年11月30日
'number4' => ['value' => $value['number4'], 'color' => '#173177'],//课程数量
'amount5' => ['value' => $value['amount5'], 'color' => '#173177']//课程码 课程金额
],
'miniprogram' => [
"appid" => "wxaee2df4a4b31df05",
"pagepath" => $value['pagepath']
]
];
}
if ($template_id == 'FrAKIn9zs0qdxWFW1R9vnR2WDJgil2KCg-9cLFaHFWM') {
$data = [
'touser' => $touser,
'template_id' => $template_id,
'data' => [
'thing5' => ['value' => $value['thing5'], 'color' => '#173177'],
'thing19' => ['value' => $value['thing19'], 'color' => '#173177']
],
'miniprogram' => [
"appid" => "wxaee2df4a4b31df05",
"pagepath" => $value['pagepath']
]
];
}
// 发送 POST 请求
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$response = curl_exec($ch);
curl_close($ch);
}
/**
* 通过 id 获取员工姓名
*/
function getStaffNameById($id)
{
//获取全部员工的 id 和 name
$staffs = Personnel::column('id,name');
$staffs = array_column($staffs, 'name', 'id');
return $staffs[$id] ?? '';
}
/**
* 获取学员有效的课程
*/
function getValidCourseByStudentId($studentId)
{
$course = \app\model\student_courses\StudentCourses::where('student_id', $studentId)
->where('status', 1)
->order('id', 'desc')
->find();
return $course ?? [];
}
/**
* 根据校区ID和部门ID获取对应角色的人员列表
* @param int $campus_id 校区ID(必填)
* @param array $dept_ids 部门ID数组,例如:[1, 2, 23] 对应市场、教务、教练部门
* @return array 人员列表,包含person_id、name、phone等信息
*
* 使用示例:
* // 获取校区1的市场人员
* $market_staff = get_personnel_by_campus_dept(1, [1]);
*
* // 获取校区2的教练和教务人员
* $coaches_and_educators = get_personnel_by_campus_dept(2, [24, 2]);
*/
function get_personnel_by_campus_dept($campus_id, $dept_ids = [])
{
if (empty($campus_id) || empty($dept_ids)) {
return [];
}
try {
// 1. 根据部门ID获取对应的角色ID,使用正确的表名
$role_ids = \think\facade\Db::table('school_sys_role')->where([
['dept_id', 'in', $dept_ids],
['status', '=', 1]
])->column('role_id');
if (empty($role_ids)) {
return [];
}
// 2. 根据校区ID和角色ID获取人员
$personnel_list = \think\facade\Db::table('school_campus_person_role')
->alias('cpr')
->leftJoin('school_personnel p', 'cpr.person_id = p.id')
->where([
['cpr.campus_id', '=', $campus_id],
['cpr.role_id', 'in', $role_ids],
['cpr.deleted_at', '=', 0],
['p.status', '=', 2]
])
->field('cpr.person_id, p.name, p.phone, cpr.role_id, cpr.dept_id')
->select()
->toArray();
return $personnel_list;
} catch (\Exception $e) {
\think\facade\Log::write('获取校区部门人员失败:' . $e->getMessage());
return [];
}
}
/**
* 根据校区ID和角色ID获取人员列表
* @param int $campus_id 校区ID
* @param array $role_ids 角色ID数组
* @return array 人员列表
*
* 使用示例:
* // 获取指定校区的教练主管和普通教练
* $coaches = get_personnel_by_campus_role(1, [1, 5]);
*/
function get_personnel_by_campus_role($campus_id, $role_ids = [])
{
if (empty($campus_id) || empty($role_ids)) {
return [];
}
try {
// 根据校区ID和角色ID直接获取人员
$personnel_list = \think\facade\Db::table('school_campus_person_role')
->alias('cpr')
->leftJoin('school_personnel p', 'cpr.person_id = p.id')
->where([
['cpr.campus_id', '=', $campus_id],
['cpr.role_id', 'in', $role_ids],
['cpr.deleted_at', '=', 0],
['p.status', '=', 2]
])
->field('cpr.person_id, p.name, p.phone, cpr.role_id, cpr.dept_id')
->select()
->toArray();
return $personnel_list;
} catch (\Exception $e) {
\think\facade\Log::write('获取校区角色人员失败:' . $e->getMessage());
return [];
}
}
// ==================== 合同系统函数 ====================
/**
* 获取当前日期
* @return string
*/
function get_current_date()
{
return date('Y-m-d');
}
/**
* 获取当前时间
* @return string
*/
function get_current_time()
{
return date('H:i:s');
}
/**
* 获取当前日期时间
* @return string
*/
function get_current_datetime()
{
return date('Y-m-d H:i:s');
}
/**
* 获取当前年份
* @return string
*/
function get_current_year()
{
return date('Y');
}
/**
* 获取当前月份
* @return string
*/
function get_current_month()
{
return date('m');
}
/**
* 获取当前日
* @return string
*/
function get_current_day()
{
return date('d');
}
/**
* 获取当前周
* @return string
*/
function get_current_week()
{
return date('W');
}
/**
* 获取当前季度
* @return string
*/
function get_current_quarter()
{
$month = (int)date('m');
return ceil($month / 3);
}
/**
* 获取当前校区名称
* @param int $campus_id 校区ID,为空时尝试从当前用户获取
* @return string
*/
function get_current_campus($campus_id = 0)
{
try {
if (empty($campus_id)) {
// 尝试从当前登录用户获取校区信息
// 这里需要根据实际的用户会话机制来获取
return '默认校区';
}
$campus = \think\facade\Db::table('school_campus')
->where('id', $campus_id)
->value('campus_name');
return $campus ?: '未知校区';
} catch (\Exception $e) {
return '获取校区失败';
}
}
/**
* 获取当前登录用户信息
* @return string
*/
function get_current_login_user()
{
try {
// 这里需要根据实际的用户会话机制来获取当前用户
// 可以从JWT token或session中获取
return '当前用户';
} catch (\Exception $e) {
return '获取用户失败';
}
}
/**
* 获取当前员工信息
* @return string
*/
function get_current_personnel()
{
try {
// 这里需要根据实际的员工会话机制来获取当前员工
return '当前员工';
} catch (\Exception $e) {
return '获取员工失败';
}
}
/**
* 获取合同生成时间
* @return string
*/
function get_contract_generate_time()
{
return date('Y-m-d H:i:s');
}
/**
* 获取合同签署时间
* @return string
*/
function get_contract_sign_time()
{
return date('Y-m-d H:i:s');
}
/**
* 获取系统名称
* @return string
*/
function get_system_name()
{
return '智慧教务系统';
}
/**
* 获取系统版本
* @return string
*/
function get_system_version()
{
return '1.0.0';
}
/**
* 生成随机数字
* @param int $length 数字长度,默认6位
* @return string
*/
function get_random_number($length = 6)
{
$min = pow(10, $length - 1);
$max = pow(10, $length) - 1;
return str_pad(rand($min, $max), $length, '0', STR_PAD_LEFT);
}
/**
* 生成合同序号
* @param string $prefix 前缀,默认为HT
* @return string
*/
function get_contract_sequence($prefix = 'HT')
{
return $prefix . date('Ymd') . get_random_number(4);
}
/**
* 格式化货币
* @param float $amount 金额
* @param string $currency 货币符号,默认为¥
* @return string
*/
function format_currency($amount, $currency = '¥')
{
return $currency . number_format($amount, 2);
}
/**
* 上课规则配置函数
* @param array $params 配置参数
* @return array
*/
function get_class_schedule_rules($params = [])
{
// 默认上课规则配置
$default_rules = [
'class_duration' => 45, // 课时长度(分钟)
'break_duration' => 15, // 课间休息时间(分钟)
'start_time' => '08:00', // 开始时间
'end_time' => '18:00', // 结束时间
'lunch_break_start' => '12:00', // 午休开始时间
'lunch_break_end' => '14:00', // 午休结束时间
'weekend_enabled' => true, // 是否允许周末上课
'holiday_enabled' => false, // 是否允许节假日上课
'max_students_per_class' => 20, // 每班最大学生数
'min_students_per_class' => 5, // 每班最小学生数
'advance_booking_days' => 7, // 提前预约天数
'cancel_deadline_hours' => 24, // 取消课程截止时间(小时)
];
// 合并自定义参数
return array_merge($default_rules, $params);
}
/**
* 生成上课时间表
* @param string $start_date 开始日期
* @param string $end_date 结束日期
* @param array $rules 上课规则
* @return array
*/
function generate_class_schedule($start_date, $end_date, $rules = [])
{
$rules = get_class_schedule_rules($rules);
$schedule = [];
$current_date = strtotime($start_date);
$end_timestamp = strtotime($end_date);
while ($current_date <= $end_timestamp) {
$date_str = date('Y-m-d', $current_date);
$day_of_week = date('w', $current_date);
// 检查是否为周末
if (!$rules['weekend_enabled'] && ($day_of_week == 0 || $day_of_week == 6)) {
$current_date = strtotime('+1 day', $current_date);
continue;
}
// 生成当天的课程时间段
$daily_schedule = generate_daily_time_slots($date_str, $rules);
if (!empty($daily_schedule)) {
$schedule[$date_str] = $daily_schedule;
}
$current_date = strtotime('+1 day', $current_date);
}
return $schedule;
}
/**
* 生成单日课程时间段
* @param string $date 日期
* @param array $rules 规则配置
* @return array
*/
function generate_daily_time_slots($date, $rules)
{
$slots = [];
$current_time = strtotime($date . ' ' . $rules['start_time']);
$end_time = strtotime($date . ' ' . $rules['end_time']);
$lunch_start = strtotime($date . ' ' . $rules['lunch_break_start']);
$lunch_end = strtotime($date . ' ' . $rules['lunch_break_end']);
$slot_number = 1;
while ($current_time < $end_time) {
$slot_end = $current_time + ($rules['class_duration'] * 60);
// 检查是否与午休时间冲突
if ($current_time < $lunch_end && $slot_end > $lunch_start) {
$current_time = $lunch_end;
continue;
}
$slots[] = [
'slot_number' => $slot_number,
'start_time' => date('H:i', $current_time),
'end_time' => date('H:i', $slot_end),
'duration' => $rules['class_duration'],
'available' => true
];
$current_time = $slot_end + ($rules['break_duration'] * 60);
$slot_number++;
}
return $slots;
}
/**
* 验证上课时间冲突
* @param string $date 日期
* @param string $start_time 开始时间
* @param string $end_time 结束时间
* @param int $teacher_id 教师ID
* @param int $classroom_id 教室ID
* @return bool
*/
function check_class_time_conflict($date, $start_time, $end_time, $teacher_id = 0, $classroom_id = 0)
{
try {
$where = [
['date', '=', $date],
['status', '=', 1], // 正常状态
];
// 时间冲突检查:新课程的开始时间在已有课程时间范围内,或新课程的结束时间在已有课程时间范围内
$time_conflict = [
['start_time', '<', $end_time],
['end_time', '>', $start_time]
];
$where = array_merge($where, $time_conflict);
// 检查教师冲突
if ($teacher_id > 0) {
$teacher_conflict = \think\facade\Db::table('school_class_schedule')
->where($where)
->where('teacher_id', $teacher_id)
->count();
if ($teacher_conflict > 0) {
return true; // 有冲突
}
}
// 检查教室冲突
if ($classroom_id > 0) {
$classroom_conflict = \think\facade\Db::table('school_class_schedule')
->where($where)
->where('classroom_id', $classroom_id)
->count();
if ($classroom_conflict > 0) {
return true; // 有冲突
}
}
return false; // 无冲突
} catch (\Exception $e) {
\think\facade\Log::write('检查上课时间冲突失败:' . $e->getMessage());
return true; // 异常情况下认为有冲突,确保安全
}
}
// ==================== 签名图片处理函数 ====================
/**
* 获取校区印章图片URL
* @param int $campus_id 校区ID
* @return string 印章图片URL
*/
function get_campus_seal_image($campus_id)
{
try {
if (empty($campus_id)) {
return '';
}
$seal_image = \think\facade\Db::table('school_campus')
->where('id', $campus_id)
->value('seal_image');
if (empty($seal_image)) {
return '';
}
// 如果已经是完整URL,直接返回
if (str_contains($seal_image, 'http://') || str_contains($seal_image, 'https://')) {
return $seal_image;
}
// 转换为完整的URL路径
return get_file_url($seal_image);
} catch (\Exception $e) {
\think\facade\Log::write('获取校区印章图片失败:' . $e->getMessage());
return '';
}
}
/**
* 验证并处理上传的签名图片
* @param array $file 上传的文件信息
* @param array $options 配置选项
* @return array 处理结果
*/
function process_signature_image($file, $options = [])
{
try {
// 默认配置
$default_options = [
'max_size' => 2 * 1024 * 1024, // 2MB
'allowed_types' => ['jpg', 'jpeg', 'png', 'gif'],
'save_path' => 'upload/signature/',
'compress_quality' => 80, // 图片压缩质量
'max_width' => 800, // 最大宽度
'max_height' => 600, // 最大高度
];
$config = array_merge($default_options, $options);
// 验证文件大小
if ($file['size'] > $config['max_size']) {
return [
'success' => false,
'message' => '图片文件大小不能超过' . ($config['max_size'] / 1024 / 1024) . 'MB'
];
}
// 验证文件类型
$file_ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($file_ext, $config['allowed_types'])) {
return [
'success' => false,
'message' => '只支持' . implode('、', $config['allowed_types']) . '格式的图片'
];
}
// 创建保存目录
$save_dir = $config['save_path'] . date('Y/m/d') . '/';
if (!is_dir($save_dir) && !mkdir($save_dir, 0755, true)) {
return [
'success' => false,
'message' => '创建保存目录失败'
];
}
// 生成文件名
$filename = 'signature_' . date('YmdHis') . '_' . uniqid() . '.' . $file_ext;
$save_path = $save_dir . $filename;
// 移动文件
if (!move_uploaded_file($file['tmp_name'], $save_path)) {
return [
'success' => false,
'message' => '文件保存失败'
];
}
// 压缩图片(如果需要)
$compressed_path = compress_signature_image($save_path, $config);
if ($compressed_path) {
$save_path = $compressed_path;
}
return [
'success' => true,
'message' => '签名图片上传成功',
'data' => [
'file_path' => $save_path,
'file_url' => get_file_url($save_path),
'file_size' => filesize($save_path),
'original_name' => $file['name']
]
];
} catch (\Exception $e) {
\think\facade\Log::write('处理签名图片失败:' . $e->getMessage());
return [
'success' => false,
'message' => '处理签名图片失败:' . $e->getMessage()
];
}
}
/**
* 压缩签名图片
* @param string $source_path 源文件路径
* @param array $config 压缩配置
* @return string|false 压缩后的文件路径
*/
function compress_signature_image($source_path, $config)
{
try {
// 获取图片信息
$image_info = getimagesize($source_path);
if (!$image_info) {
return false;
}
[$width, $height, $type] = $image_info;
// 如果图片尺寸已经符合要求,不需要压缩
if ($width <= $config['max_width'] && $height <= $config['max_height']) {
return $source_path;
}
// 计算新尺寸
$ratio = min($config['max_width'] / $width, $config['max_height'] / $height);
$new_width = intval($width * $ratio);
$new_height = intval($height * $ratio);
// 创建源图像资源
switch ($type) {
case IMAGETYPE_JPEG:
$source_image = imagecreatefromjpeg($source_path);
break;
case IMAGETYPE_PNG:
$source_image = imagecreatefrompng($source_path);
break;
case IMAGETYPE_GIF:
$source_image = imagecreatefromgif($source_path);
break;
default:
return false;
}
if (!$source_image) {
return false;
}
// 创建新图像
$new_image = imagecreatetruecolor($new_width, $new_height);
// 保持透明度(PNG/GIF)
if ($type == IMAGETYPE_PNG || $type == IMAGETYPE_GIF) {
imagealphablending($new_image, false);
imagesavealpha($new_image, true);
$transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127);
imagefill($new_image, 0, 0, $transparent);
}
// 重新采样
imagecopyresampled($new_image, $source_image, 0, 0, 0, 0,
$new_width, $new_height, $width, $height);
// 生成压缩后的文件路径
$compressed_path = str_replace('.', '_compressed.', $source_path);
// 保存压缩后的图片
$saved = false;
switch ($type) {
case IMAGETYPE_JPEG:
$saved = imagejpeg($new_image, $compressed_path, $config['compress_quality']);
break;
case IMAGETYPE_PNG:
$saved = imagepng($new_image, $compressed_path);
break;
case IMAGETYPE_GIF:
$saved = imagegif($new_image, $compressed_path);
break;
}
// 释放资源
imagedestroy($source_image);
imagedestroy($new_image);
if ($saved) {
// 删除原文件
unlink($source_path);
return $compressed_path;
}
return false;
} catch (\Exception $e) {
\think\facade\Log::write('压缩签名图片失败:' . $e->getMessage());
return false;
}
}
/**
* 获取用户签名记录
* @param int $user_id 用户ID
* @param string $user_type 用户类型(staff/student/member)
* @return array 签名记录
*/
function get_user_signature_records($user_id, $user_type = 'staff')
{
try {
if (empty($user_id)) {
return [];
}
$signatures = \think\facade\Db::table('school_user_signatures')
->where([
['user_id', '=', $user_id],
['user_type', '=', $user_type],
['status', '=', 1], // 有效状态
['deleted_at', '=', 0]
])
->order('created_at', 'desc')
->select()
->toArray();
// 处理签名图片URL
foreach ($signatures as &$signature) {
if (!empty($signature['signature_image'])) {
$signature['signature_url'] = get_file_url($signature['signature_image']);
}
}
return $signatures;
} catch (\Exception $e) {
\think\facade\Log::write('获取用户签名记录失败:' . $e->getMessage());
return [];
}
}
/**
* 保存用户签名记录
* @param array $data 签名数据
* @return int|false 签名记录ID
*/
function save_user_signature($data)
{
try {
$required_fields = ['user_id', 'user_type', 'signature_image'];
foreach ($required_fields as $field) {
if (empty($data[$field])) {
throw new \Exception("缺少必要字段:{$field}");
}
}
$insert_data = [
'user_id' => $data['user_id'],
'user_type' => $data['user_type'],
'signature_image' => $data['signature_image'],
'signature_name' => $data['signature_name'] ?? '用户签名',
'signature_type' => $data['signature_type'] ?? 'image', // image/canvas/handwrite
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'status' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
'deleted_at' => 0
];
$signature_id = \think\facade\Db::table('school_user_signatures')
->insertGetId($insert_data);
if ($signature_id) {
\think\facade\Log::write('保存用户签名记录成功:' . json_encode($insert_data));
return $signature_id;
}
return false;
} catch (\Exception $e) {
\think\facade\Log::write('保存用户签名记录失败:' . $e->getMessage());
return false;
}
}