CRMEB/backend/app/services/system/config/SystemConfigServices.php
From-wh 38c398c109 fix(系统设置): 文件上传控件显示文件图标并统一上传前后尺寸
- 新增 form-create 单文件上传组件 FileUpload:空态 80×80 上传图标卡片,
  上传后同为 80×80;图片类文件(含 ico)显示缩略图,其它文件显示
  文件图标 + 文件名,悬停可删除,含上传中与失败提示
- 后端 FormBuilder 新增 fileUpload() 规则,配置项 upload_type=3
  (微信校验文件、系统ICO图标)改用该组件
- 移除系统设置页失效的 picture-card 尺寸覆盖(原写法尺寸变量作用在
  列表项上,导致上传后 148×148 与空态 80×80 不一致且非图片破图)
- 同步 crmeb-admin-ui 技能组件索引
2026-10-28 10:24:25 +08:00

1516 lines
66 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\system\config;
use app\dao\system\config\SystemConfigDao;
use crmeb\basic\BaseService;
use crmeb\exceptions\AdminException;
use crmeb\services\CacheService;
use crmeb\utils\File;
use crmeb\utils\FormBuilder;
use think\facade\Log;
/**
* 系统配置
* Class SystemConfigServices
* @package app\services\system\config
* @method count(array $where = []) 获取指定条件下的count
* @method save(array $data) 保存数据
* @method get(int $id, ?array $field = []) 获取一条数据
* @method update($id, array $data, ?string $key = null) 修改数据
* @method delete(int $id, ?string $key = null) 删除数据
* @method getUploadTypeList(string $configName) 获取上传配置中的上传类型
*/
class SystemConfigServices extends BaseService
{
/**
* 系统配置数据访问层
* @var SystemConfigDao
*/
protected $dao;
/**
* form表单句柄
* @var FormBuilder
*/
protected $builder;
/**
* 表单数据切割符号
* @var string
*/
protected $cuttingStr = '=>';
/**
* 获取配置表单提交URL
* 根据当前请求URL自动推导:将 edit_basics 替换为 save_basics
* @param string $url 当前请求的 baseUrl
* @return string
*/
protected function getSaveUrl(string $url): string
{
if (!$url) {
return '/setting/config/save_basics';
}
// 替换 edit_basics → save_basics
$saveUrl = str_replace('edit_basics', 'save_basics', $url);
// 去掉多应用URL前缀(如 /admin/、/adminapi/ 等),得到路由路径
$adminPrefix = config('app.admin_prefix', 'admin');
// 多应用模式下,URL 可能包含应用名前缀(如 /adminapi/)
$prefixes = array_unique([
'/' . $adminPrefix . '/',
'/adminapi/',
]);
foreach ($prefixes as $prefix) {
if (strpos($saveUrl, $prefix) === 0) {
$saveUrl = substr($saveUrl, strlen($prefix) - 1);
break;
}
}
return $saveUrl;
}
/**
* SystemConfigServices constructor.
* @param SystemConfigDao $dao
* @param FormBuilder $builder
*/
public function __construct(SystemConfigDao $dao, FormBuilder $builder)
{
$this->dao = $dao;
$this->builder = $builder;
}
/**
* 获取单个系统配置
* @param string $configName
* @param null $default
* @return mixed|null
* @throws \ReflectionException
*/
public function getConfigValue(string $configName, $default = null)
{
$value = $this->dao->getConfigValue($configName);
return is_null($value) ? $default : json_decode($value, true);
}
/**
* 写入单个系统配置(按 menu_name 定位已有配置行)
* @param string $configName 配置项 menu_name
* @param mixed $value 配置值
* @return bool
*/
public function setConfig(string $configName, $value): bool
{
if (!$this->dao->getOne(['menu_name' => $configName])) {
return false;
}
$this->dao->update($configName, ['value' => json_encode($value)], 'menu_name');
CacheService::delete('system_config_' . $configName);
return true;
}
/**
* 获取全部配置
* @param array $configName
* @return array
* @throws \ReflectionException
*/
public function getConfigAll(array $configName = [])
{
return array_map(function ($item) {
return json_decode($item, true);
}, $this->dao->getConfigAll($configName));
}
/**
* 获取配置列表搜索并分页
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getConfigList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getConfigList($where, $page, $limit);
$count = $this->dao->count($where);
$tidy_srr = [];
$configTabList = app()->make(SystemConfigTabServices::class)->getColumn([], 'title', 'id');
foreach ($list as &$item) {
$item['value'] = $item['value'] ? (json_decode($item['value'], true) ?: '') : '';
if ($item['type'] == 'radio' || $item['type'] == 'checkbox') {
$item['value'] = $this->getRadioOrCheckboxValueInfo($item['menu_name'], $item['value']);
}
if ($item['type'] == 'upload' && !empty($item['value'])) {
if ($item['upload_type'] == 1 || $item['upload_type'] == 3) {
$item['value'] = [set_file_url($item['value'])];
} elseif ($item['upload_type'] == 2) {
$tempValue = set_file_url($item['value']);
$item['value'] = is_array($tempValue) ? $tempValue : [];
}
if (is_array($item['value']) && !empty($item['value'])) {
foreach ($item['value'] as $key => $value) {
$tidy_srr[$key]['filepath'] = $value;
$tidy_srr[$key]['filename'] = basename($value);
}
$item['value'] = $tidy_srr;
} else {
$item['value'] = [];
}
}
if ($item['level'] == 1) {
$item['link_data'] = $this->getLinkData($item['link_id'], $item['link_value']);
}
$item['config_tab_name'] = $configTabList[$item['config_tab_id']] ?? '';
}
return compact('count', 'list');
}
/**
* 配置列表页获取关联的值,展示:关联配置名称/关联值
* @param $id
* @param $value
* @return string
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author wuhaotian
* @email 442384644@qq.com
* @date 2024/5/31
*/
private function getLinkData($id, $value)
{
$info = $this->dao->get($id);
if (!$info) return '';
$parameter = explode("\n", $info['parameter']);
$result = [];
foreach ($parameter as $item) {
$parts = explode('=>', $item);
$result[$parts[0]] = $parts[1];
}
return $info['info'] . ':' . $result[$value].'(显示)';
}
/**
* 配置列表页获取单选按钮或者多选按钮的显示值
* @param $menu_name
* @param $value
* @return string
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
private function getRadioOrCheckboxValueInfo(string $menu_name, $value): string
{
$option = [];
$config_one = $this->dao->getOne(['menu_name' => $menu_name]);
if (!$config_one) {
return '';
}
$parameter = explode("\n", $config_one['parameter']);
foreach ($parameter as $k => $v) {
if (isset($v) && strlen($v) > 0) {
$data = explode('=>', $v);
$option[$data[0]] = $data[1];
}
}
$str = '';
if (is_array($value)) {
foreach ($value as $v) {
$str .= $option[$v] . ',';
}
} else {
$str .= !empty($value) ? $option[$value] ?? '' : $option[0] ?? '';
}
return $str;
}
/**
* 根据配置tab id获取系统配置信息
* @param int $tabId
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getReadList(int $tabId)
{
$info = $this->dao->getConfigTabAllList($tabId);
foreach ($info as $k => $v) {
if (!is_null(json_decode($v['value'])))
$info[$k]['value'] = json_decode($v['value'], true);
if ($v['type'] == 'upload' && !empty($v['value'])) {
if ($v['upload_type'] == 1 || $v['upload_type'] == 3) $info[$k]['value'] = explode(',', $v['value']);
}
}
return $info;
}
/**
* 修改字段获取form表单
* @param int $id
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function editFieldForm(int $id)
{
$menu = $this->dao->get($id)->getData();
if (!$menu) {
throw new AdminException('数据不存在');
}
/** @var SystemConfigTabServices $service */
$service = app()->make(SystemConfigTabServices::class);
$formbuider = [];
$linkData = $this->linkData($menu['config_tab_id']);
$formbuider[] = $this->builder->radio('level', '联动显示', $menu['level'])->options([['value' => 0, 'label' => '否'], ['value' => 1, 'label' => '是']])->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => '否:默认正常展示此配置;是:此配置默认隐藏,当选中下方对应配置的值时,此配置才会显示']
])->appendControl(1, [
// 默认值需与 linkData() 中 options 的 value 类型保持一致,否则级联选择器无法自动回显
$this->builder->cascader('link_data', '关联配置/值', [(int)$menu['link_id'], $this->normalizeOptionValue($menu['link_value'])])->options($linkData)->props(['props' => ['multiple' => false, 'checkStrictly' => false, 'emitPath' => true]])->style(['width' => '100%']),
])->requiredNum();
$formbuider[] = $this->builder->input('menu_name', '字段变量', $menu['menu_name'])->disabled(1);
$formbuider[] = $this->builder->hidden('type', $menu['type']);
[$configTabList, $data] = $service->getConfigTabListForm((int)($menu['config_tab_id'] ?? 0));
$formbuider[] = $this->builder->cascader('config_tab_id', '分类', $data)->options($configTabList)->filterable(true)->props(['props' => ['multiple' => false, 'checkStrictly' => true, 'emitPath' => true]])->style(['width' => '100%']);
$formbuider[] = $this->builder->input('info', '配置名称', $menu['info'])->required('配置名称不能为空')->autofocus(1);
$formbuider[] = $this->builder->input('desc', '配置简介', $menu['desc']);
switch ($menu['type']) {
case 'text':
$menu['value'] = json_decode($menu['value'], true);
$formbuider[] = $this->createTextInputTypeForm($menu, true);
break;
case 'textarea':
$menu['value'] = json_decode($menu['value'], true);
$formbuider[] = $this->builder->textarea('value', '默认值', $menu['value'])->rows(3);
$formbuider[] = $this->builder->number('width', '文本框宽', (int)$menu['width'])->min(1)->max(24);
$formbuider[] = $this->builder->number('high', '多行文本框高', (int)$menu['high'])->min(1);
break;
case 'radio':
$formbuider = array_merge($formbuider, $this->createRadioForm($menu));
$formbuider[] = $this->builder->textarea('parameter', '配置参数', $menu['parameter'] ?? '')->rows(3)->placeholder("参数方式例如:\n1=>白色\n2=>红色\n3=>黑色");
break;
case 'upload':
$formbuider = array_merge($formbuider, $this->createUploadForm((int)$menu['upload_type'], $menu, true));
break;
case 'checkbox':
$menu['label'] = '默认值';
$formbuider = array_merge($formbuider, $this->createCheckboxForm($menu));
$formbuider[] = $this->builder->textarea('parameter', '配置参数', $menu['parameter'] ?? '')->rows(3)->placeholder("参数方式例如:\n1=>白色\n2=>红色\n3=>黑色");
break;
case 'select':
$formbuider = array_merge($formbuider, $this->createSelectForm($menu));
$formbuider[] = $this->builder->textarea('parameter', '配置参数', $menu['parameter'] ?? '')->rows(3)->placeholder("参数方式例如:\n1=>白色\n2=>红色\n3=>黑色");
break;
case 'switch':
$formbuider = array_merge($formbuider, $this->createSwitchForm($menu));
break;
}
// 是否必填(支持JSON格式)
$requiredData = json_decode($menu['required'], true);
if ($requiredData && isset($requiredData['required'])) {
$menu['required'] = $requiredData['required'] ? 1 : 0;
} else {
$menu['required'] = $menu['required'] ? 1 : 0;
}
// 非switch类型,添加是否必填字段
if ($menu['type'] != 'switch') {
$formbuider[] = $this->builder->radio('required', '是否必填', $menu['required'] ?? 0)->options([
['value' => 0, 'label' => '否'],
['value' => 1, 'label' => '是'],
])->requiredNum();
}
$formbuider[] = $this->builder->number('sort', '排序', (int)$menu['sort']);
$formbuider[] = $this->builder->radio('status', '状态', $menu['status'] ?? 0)->options([
['value' => 1, 'label' => '显示'],
['value' => 0, 'label' => '隐藏'],
])->requiredNum();
return create_form('编辑字段', $formbuider, $this->url('/setting/config/' . $id), 'PUT');
}
/**
* 创建和编辑文本类型的输入类型表单
* @param array $data 配置数据
* @param bool $isEdit 是否为编辑模式
* @return BaseForm|BaseForm[]
*/
private function createTextInputTypeForm(array $data, bool $isEdit = false)
{
// 确保值为字符串或数值类型(兼容数据库中误存为JSON数组的情况)
if (isset($data['value']) && is_array($data['value'])) {
$data['value'] = implode(',', $data['value']);
}
// 拆分必填、格式规则和数字范围(支持JSON格式)
$requiredRules = $data['required'] ?? '';
$requiredData = [];
// 尝试解析JSON格式的验证规则
if ($requiredRules && $requiredData = json_decode($requiredRules, true)) {
$data['required'] = isset($requiredData['required']) && $requiredData['required'] ? 'required:true' : '';
$data['regex'] = $requiredData['regex'] ?? '';
$data['min'] = $requiredData['min'] ?? '';
$data['max'] = $requiredData['max'] ?? '';
} else {
// 兼容旧格式:逗号分隔的字符串格式
$data['required'] = strpos($requiredRules, 'required:true') !== false ? 'required:true' : '';
$data['regex'] = '';
$data['min'] = '';
$data['max'] = '';
// 提取 min 和 max
if (preg_match('/\bmin:(-?\d+)/', $requiredRules, $minMatch)) {
$data['min'] = (int)$minMatch[1];
}
if (preg_match('/\bmax:(-?\d+)/', $requiredRules, $maxMatch)) {
$data['max'] = (int)$maxMatch[1];
}
// 提取正则表达式
if (preg_match('/regex:(\/.+\/[gimsuy]*)/', $requiredRules, $patternMatch)) {
$data['regex'] = $patternMatch[1];
}
}
// 创建输入类型选择器
$inputTypeSelect = $this->builder->select('input_type', '类型', $data['input_type'] ?? 'input')->setOptions([
['value' => 'input', 'label' => '文本框'],
['value' => 'number', 'label' => '数字'],
['value' => 'dateTime', 'label' => '日期时间'],
['value' => 'date', 'label' => '日期'],
['value' => 'time', 'label' => '时间'],
['value' => 'color', 'label' => '颜色']
])->required();
// 正则表达式(输入框)
$regexField = $this->builder->input('regex', '表单验证', $data['regex'] ?? '')
->placeholder('输入正则表达式,如:/^\d+$/')
->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => '邮箱:/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/<br>手机号:/^1[3-9]\d{9}$/<br>URL:/^https?:\/\/.+/i<br>电话:/^1[3-9]\d{9}$|^0\d{2,3}-\d{7,8}$|^400-\d{3}-\d{3}$/<br>长度:/^.{6,20}$/']
]);
// 宽度字段
$col = isset($data['width']) && $data['width'] != 0 && $data['width'] >= 4 && $data['width'] <= 24 ? (int)$data['width'] : 13;
$widthField = $this->builder->number('width', '表单框宽', $col)->min(4)->max(24);
// 不同类型的默认值
$inputValue = $data['input_type'] == 'input' ? ($data['value'] ?? '') : '';
$inputValueField = $this->builder->input('value', '默认值', $inputValue);
$numberValue = $data['input_type'] == 'number' ? ($data['value'] ?? '0') : '';
$numberValueField = $this->builder->input('value', '默认值', $numberValue);
$dateTimeValue = $data['input_type'] == 'dateTime' ? ($data['value'] ?? date('Y-m-d H:i:s')) : '';
$dateTimeValueField = $this->builder->dateTime('value', '默认值', $dateTimeValue);
$dateValue = $data['input_type'] == 'date' ? ($data['value'] ?? date('Y-m-d')) : '';
$dateValueField = $this->builder->date('value', '默认值', $dateValue);
$timeValue = $data['input_type'] == 'time' ? ($data['value'] ?? date('H:i:s')) : '';
$timeValueField = $this->builder->time('value', '默认值', $timeValue);
$colorValue = $data['input_type'] == 'color' ? ($data['value'] ?? '#000000') : '#000000';
$colorValueField = $this->builder->color('value', '默认值', $colorValue);
// 数字类型的最小值和最大值
$numberMinField = $this->builder->input('min', '最小值', $data['min'] ?? '')->type('number');
$numberMaxField = $this->builder->input('max', '最大值', $data['max'] ?? '')->type('number');
// 根据类型联动显示不同的控件
$inputTypeSelect->appendControl('input', [$regexField, $inputValueField, $widthField]);
$inputTypeSelect->appendControl('number', [$numberMinField, $numberMaxField, $numberValueField, $widthField]);
$inputTypeSelect->appendControl('dateTime', [$dateTimeValueField]);
$inputTypeSelect->appendControl('date', [$dateValueField]);
$inputTypeSelect->appendControl('time', [$timeValueField]);
$inputTypeSelect->appendControl('color', [$colorValueField]);
return $inputTypeSelect;
}
/**
* 创建单行表单
* @param string $type
* @param array $data
* @return array
*/
public function createTextForm(string $type, array $data)
{
$formbuider = [];
$inputRule = '';
switch ($type) {
case 'number':
// 解析验证规则(支持 JSON 格式)
$requiredRules = $data['required'] ?? '';
$requiredData = json_decode($requiredRules, true);
if ($requiredData) {
// JSON 格式
$data['min'] = $requiredData['min'] ?? null;
$data['max'] = $requiredData['max'] ?? null;
} else {
// 兼容旧格式:提取 min 和 max
$data['min'] = null;
$data['max'] = null;
if (preg_match('/\bmin:(-?\d+)/', $requiredRules, $minMatch)) {
$data['min'] = (int)$minMatch[1];
}
if (preg_match('/\bmax:(-?\d+)/', $requiredRules, $maxMatch)) {
$data['max'] = (int)$maxMatch[1];
}
}
$data['value'] = isset($data['value']) ? json_decode($data['value'], true) : 0;
// 确保值为数值类型(兼容数据库中误存为JSON数组的情况)
if (is_array($data['value'])) {
$data['value'] = 0;
}
// 最小值和最大值提示(有值才显示)
$minMaxTip = '';
if (isset($data['min']) && $data['min'] !== null) {
$minMaxTip = '<br>最小值:' . $data['min'];
}
if (isset($data['max']) && $data['max'] !== null) {
$minMaxTip .= ($minMaxTip ? ',' : '<br>') . '最大值:' . $data['max'];
}
// 宽度字段
$col = isset($data['width']) && $data['width'] != 0 && $data['width'] >= 4 && $data['width'] <= 24 ? (int)$data['width'] : 13;
$inputRule = $this->builder->number($data['menu_name'], $data['info'], (float)$data['value'])->controls(false)->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => $data['desc'] . $minMaxTip]
])->col($col);
break;
case 'dateTime':
$inputRule = $this->builder->dateTime($data['menu_name'], $data['info'], $data['value'])->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => $data['desc']]
]);
break;
case 'date':
$data['value'] = json_decode($data['value'], true) ?: '';
if (is_array($data['value'])) $data['value'] = '';
$inputRule = $this->builder->date($data['menu_name'], $data['info'], $data['value'])->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => $data['desc']]
]);
break;
case 'time':
$data['value'] = json_decode($data['value'], true) ?: '';
if (is_array($data['value'])) $data['value'] = '';
$inputRule = $this->builder->time($data['menu_name'], $data['info'], $data['value'])->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => $data['desc']]
]);
break;
case 'color':
$data['value'] = isset($data['value']) ? json_decode($data['value'], true) : '';
if (is_array($data['value'])) $data['value'] = '';
$inputRule = $this->builder->color($data['menu_name'], $data['info'], $data['value'])->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => $data['desc']]
]);
break;
default:
$data['value'] = isset($data['value']) ? json_decode($data['value'], true) : '';
// 确保值为字符串类型(兼容数据库中误存为JSON数组的情况)
if (is_array($data['value'])) {
$data['value'] = implode(',', array_map(static function ($value) {
return is_array($value) ? json_encode($value, JSON_UNESCAPED_UNICODE) : (string)$value;
}, $data['value']));
}
// 系统生成的消息推送地址需要添加站点域名,并且设为只读。
if (in_array($data['menu_name'], ['api', 'routine_api', 'wechat_api'], true)) {
$inputRule = $this->builder->input($data['menu_name'], $data['info'], strpos($data['value'], 'http') === false ? sys_config('site_url') . $data['value'] : $data['value'])->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => $data['desc']]
])->col($data['width'] ?? 13)->readonly(true);
} else {
// 宽宽度设置
$col = isset($data['width']) && $data['width'] != 0 && $data['width'] >= 4 && $data['width'] <= 24 ? (int)$data['width'] : 13;
$inputRule = $this->builder->input($data['menu_name'], $data['info'], $data['value'])->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => $data['desc']]
])->col($col)->placeholder('请输入'.$data['info']);
}
break;
}
// 是否必填项
$this->isRequired($data['required']) && $inputRule = $inputRule->required($data['info'].'不能为空');
$formbuider[] = $inputRule;
return $formbuider;
}
/**
* 创建多行文本框
* @param array $data
* @return mixed
*/
private function createTextareaForm(array $data)
{
$formbuider = [];
$textareaRule = '';
$data['value'] = json_decode($data['value'], true) ?: '';
if ($data['menu_name'] == 'param_filter_data') $data['value'] = base64_decode($data['value']);
// 宽度设置
$col = isset($data['width']) && $data['width'] < 24 && $data['width'] > 4 ? $data['width'] : 13;
$textareaRule = $this->builder->textarea($data['menu_name'], $data['info'], $data['value'])->placeholder($data['desc'])->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => $data['desc']]
])->rows($data['high'] ?? 6)->col($col);
// 是否必填项
$this->isRequired($data['required']) && $textareaRule = $textareaRule->required($data['info'].'不能为空')->placeholder($data['desc']);
$formbuider[] = $textareaRule;
return $formbuider;
}
/**
* 创建单选表单
* @param array $data
* @param array $control
* @param array $control_two
* @return array
*/
private function createRadioForm(array $data, $control = [], $control_two = [], $control_three = [])
{
$formbuider = [];
$value = json_decode($data['value'], true);
$data['value'] = $this->normalizeOptionValue(($value === null || $value === '' || $value === false) ? '0' : $value);
$parameter = explode("\n", $data['parameter']);
$options = [];
if ($parameter) {
foreach ($parameter as $v) {
if (strstr($v, $this->cuttingStr) !== false) {
$pdata = explode($this->cuttingStr, $v, 2);
$options[] = ['label' => trim($pdata[1]), 'value' => $this->normalizeOptionValue($pdata[0])];
}
}
$formbuider[] = $radio = $this->builder->radio($data['menu_name'], $data['info'], $data['value'])->options($options)->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => $data['desc']]
])->appendValidate([
'required' => true,
'message' => $data['info'] . '必选项',
'trigger' => 'change',
])->col(13);
if ($control) {
$radio->appendControl($data['show_value'] ?? 1, is_array($control) ? $control : [$control]);
}
if ($control_two && isset($data['show_value2'])) {
$radio->appendControl($data['show_value2'] ?? 2, is_array($control_two) ? $control_two : [$control_two]);
}
if ($control_three && isset($data['show_value3'])) {
$radio->appendControl($data['show_value3'] ?? 3, is_array($control_three) ? $control_three : [$control_three]);
}
return $formbuider;
}
}
/**
* 创建上传组件表单
* @param int $type
* @param array $data
* @param bool $showTypeSelect 是否显示上传类型选择器
* @return array
*/
private function createUploadForm(int $type, array $data, bool $showTypeSelect = false)
{
$formbuider = [];
// 单图控件
if($type == 1){
$singleDataValue = json_decode($data['value'], true) ?: '';
if ($singleDataValue !== '') $singleDataValue = set_file_url($singleDataValue);
} else{
$singleDataValue = '';
}
$singleImageField = $this->builder->frameImage($data['menu_name'], $data['info'], $this->url(config('app.admin_prefix', 'admin') . '/widget.images/index', ['fodder' => $data['menu_name']], true), $singleDataValue)
->icon('el-icon-picture-outline')->width('950px')->height('560px')->Props(['footer' => false, 'modalTitle' => '预览'])->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => $data['desc'] ?? '']
])->col(13);
// 多图控件
if($type == 2){
$multiDataValue = json_decode($data['value'], true) ?: [];
if (!empty($multiDataValue)) {
$multiDataValue = set_file_url($multiDataValue);
}
} else{
$multiDataValue = [];
}
$multiImageField = $this->builder->frameImages($data['menu_name'], $data['info'], $this->url(config('app.admin_prefix', 'admin') . '/widget.images/index', ['fodder' => $data['menu_name'], 'type' => 'many', 'maxLength' => 5], true), $multiDataValue)
->maxLength(5)->icon('el-icon-picture-outline')->width('950px')->height('560px')->Props(['footer' => false, 'modalTitle' => '预览'])
->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => $data['desc'] ?? '']
])->col(13);
// 文件控件
if($type == 3){
$fileDataValue = json_decode($data['value'], true) ?: '';
if ($fileDataValue !== '') $fileDataValue = set_file_url($fileDataValue);
$fileDataValue = is_string($fileDataValue) ? $fileDataValue : '';
} else{
$fileDataValue = '';
}
// 文件上传使用前端 form-create 自定义组件 FileUpload:非图片文件显示文件图标,上传前后同为 80×80
$fileField = $this->builder->fileUpload($data['menu_name'], $data['info'], $this->url('/adminapi/file/upload/1', ['type' => 1], false, true), $fileDataValue, [
'data' => ['menu_name' => $data['menu_name']],
'headers' => [
'Authori-zation' => app()->request->header('Authori-zation'),
],
])->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => $data['desc'] ?? '']
])->col(13);
// 如果需要显示类型选择器(编辑模式)
if ($showTypeSelect) {
// 创建上传类型的radio选择器
$uploadTypeSelect = $this->builder->radio('upload_type', '上传类型', $type)->options($this->uploadType());
// 联动:切换类型时显示对应的上传控件
$uploadTypeSelect->appendControl(1, [$singleImageField]);
$uploadTypeSelect->appendControl(2, [$multiImageField]);
$uploadTypeSelect->appendControl(3, [$fileField]);
$uploadRule = $uploadTypeSelect;
} else {
// 新建模式:根据类型只显示对应的上传控件
switch ($type) {
case 1:
// 是否必填项
$this->isRequired($data['required']) && $singleImageField = $singleImageField->appendValidate((new \FormBuilder\UI\Elm\Validate(\FormBuilder\UI\Elm\Validate::TYPE_STRING))->required()->message($data['info'].'请选择图片'));
$uploadRule = $singleImageField;
break;
case 2:
// 是否必填项
$this->isRequired($data['required']) && $multiImageField = $multiImageField->appendValidate((new \FormBuilder\UI\Elm\Validate(\FormBuilder\UI\Elm\Validate::TYPE_ARRAY))->required()->message($data['info'].'请选择图片'));
$uploadRule = $multiImageField;
break;
case 3:
// 是否必填项
$this->isRequired($data['required']) && $fileField = $fileField->appendValidate((new \FormBuilder\UI\Elm\Validate(\FormBuilder\UI\Elm\Validate::TYPE_STRING))->required()->message($data['info'].'请选择文件'));
$uploadRule = $fileField;
break;
}
}
$formbuider[] = $uploadRule;
return $formbuider;
}
/**
* 创建单选框
* @param array $data
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
private function createCheckboxForm(array $data)
{
$formbuider = [];
$checkboxRule = null;
$data['value'] = json_decode($data['value'], true) ?: [];
$parameter = explode("\n", $data['parameter']);
$options = [];
if ($parameter) {
foreach ($parameter as $v) {
if (strstr($v, $this->cuttingStr) !== false) {
$pdata = explode($this->cuttingStr, $v);
$options[] = ['label' => $pdata[1], 'value' => $pdata[0]];
}
}
$checkboxRule = $this->builder->checkbox($data['menu_name'], $data['info'], $data['value'])->options($options)->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => $data['desc']]
])->col(13);
}
// 是否必填项
$isRequired = $this->isRequired($data['required']);
if ($isRequired) {
$checkboxRule = $checkboxRule->required($data['info'].'至少选择一个');
}
$formbuider[] = $checkboxRule;
return $formbuider;
}
/**
* 创建选择框表单
* @param array $data
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
private function createSelectForm(array $data)
{
$formbuider = [];
$selectRule = null;
$data['value'] = json_decode($data['value'], true) ?: [];
$parameter = explode("\n", $data['parameter']);
$options = [];
if ($parameter) {
foreach ($parameter as $v) {
if (strstr($v, $this->cuttingStr) !== false) {
$pdata = explode($this->cuttingStr, $v);
$options[] = ['label' => $pdata[1], 'value' => $pdata[0]];
}
}
$selectRule = $this->builder->select($data['menu_name'], $data['info'], $data['value'])->options($options)->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => $data['desc']]
])->col(13);
}
// 是否必填项
$this->isRequired($data['required']) && $selectRule = $selectRule->required($data['info'].'请选择');
$formbuider[] = $selectRule;
return $formbuider;
}
/**
* 开关选择
* @param $data
* @return array
* @author: 吴汐
* @email: 442384644@qq.com
* @date: 2023/9/6
*/
private function createSwitchForm($data)
{
$formbuider = [];
$switchRule = null;
$data['value'] = json_decode($data['value'], true) ?: '';
$switchRule = $this->builder->switches($data['menu_name'], $data['info'], $data['value'])->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => $data['desc']]
])->required($data['info'].'必选项')->col(13);
$formbuider[] = $switchRule;
return $formbuider;
}
/**
* 创建颜色选择器
* @param array $data
* @return mixed
*/
private function createColorForm(array $data)
{
$data['value'] = json_decode($data['value'], true) ?: '';
$formbuider[] = $this->builder->color($data['menu_name'], $data['info'], $data['value'])->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => $data['desc']]
])->col(13);
return $formbuider;
}
/**
* 统一配置项选项值类型:普通数字保持数字,字符串编码保持字符串。
*
* @param mixed $value
* @return int|string
*/
private function normalizeOptionValue($value)
{
if (is_int($value)) {
return $value;
}
if (is_string($value)) {
$value = trim($value);
if (preg_match('/^-?(0|[1-9]\d*)$/', $value)) {
return (int)$value;
}
}
return $value;
}
/**
* 上传类型
* @return array
*/
private function uploadType(): array
{
return [
['value' => 1, 'label' => '单图']
, ['value' => 2, 'label' => '多图']
, ['value' => 3, 'label' => '文件']
];
}
/**
* 根据验证规则判断是否必填项
* @param string $required
* @return bool
*/
private function isRequired(string $required)
{
// 支持 JSON 格式
$requiredData = json_decode($required, true);
if ($requiredData) {
return isset($requiredData['required']) && $requiredData['required'] ? true : false;
}
// 兼容旧格式:逗号分隔
return strpos($required, 'required:true') !== false;
}
/**
* 获取系统配置表单
* @param $data
* @param bool $control
* @param array $controle_two
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function formTypeShine($data, $control = false, $controle_two = [], $controle_three = [])
{
switch ($data['type']) {
case 'text'://文本框
return $this->createTextForm($data['input_type'], $data);
case 'radio'://单选框
return $this->createRadioForm($data, $control, $controle_two, $controle_three);
case 'textarea'://多行文本框
return $this->createTextareaForm($data);
case 'upload'://文件上传
return $this->createUploadForm((int)$data['upload_type'], $data,false);
case 'checkbox'://多选框
return $this->createCheckboxForm($data);
case 'select'://多选框
return $this->createSelectForm($data);
// case 'color':
// return $this->createColorForm($data);
case 'switch'://开关
return $this->createSwitchForm($data);
}
}
/**
* 根据系统多个配置自动生成form表单页面
* @param array $list
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
private function createForm(array $list)
{
if (!$list) return [];
$list = array_combine(array_column($list, 'menu_name'), $list);
$formbuider = [];
$sonConfig = $this->dao->getColumn(['level' => 1], 'menu_name');
foreach ($list as $key => $data) {
if (in_array($key, $sonConfig)) {
continue;
}
switch ($data['type']) {
case 'text'://文本框
$formbuider = array_merge($formbuider, $this->createTextForm($data['input_type'], $data));
break;
case 'radio'://单选框
$relateRule = $this->buildRelatedRuleFromDB($data);
$builder = [];
if (isset($relateRule[$key])) {
$role = $relateRule[$key];
$data['show_value'] = $role['show_value'];
foreach ($role['son_type'] as $sk => $sv) {
if (isset($list[$sk])) {
$son_data = $list[$sk];
$son_data['show_value'] = $role['show_value'];
$son_build = [];
if (isset($sv['son_type'])) {
foreach ($sv['son_type'] as $ssk => $ssv) {
$son_data['show_value'] = $sv['show_value'];
$son_build[] = $this->formTypeShine($list[$ssk])[0];
unset($list[$ssk]);
}
}
$son_build_two = [];
if (isset($role['son_type'][$sk . '@'])) {
$son_type_two = $role['son_type'][$sk . '@'];
$son_data['show_value2'] = $son_type_two['show_value'];
if (isset($son_type_two['son_type'])) {
foreach ($son_type_two['son_type'] as $ssk => $ssv) {
$son_build_two[] = $this->formTypeShine($list[$ssk])[0];
unset($list[$ssk]);
}
}
}
$son_build_three = [];
if (isset($role['son_type'][$sk . '#'])) {
$son_type_three = $role['son_type'][$sk . '#'];
$son_data['show_value3'] = $son_type_three['show_value'];
if (isset($son_type_three['son_type'])) {
foreach ($son_type_three['son_type'] as $ssk => $ssv) {
$son_build_three[] = $this->formTypeShine($list[$ssk])[0];
unset($list[$ssk]);
}
}
}
$builder[] = $this->formTypeShine($son_data, $son_build, $son_build_two, $son_build_three)[0];
unset($list[$sk]);
}
}
$data['show_value'] = $role['show_value'];
}
$builder_two = [];
if (isset($relateRule[$key . '@'])) {
$role = $relateRule[$key . '@'];
$data['show_value2'] = $role['show_value'];
foreach ($role['son_type'] as $sk => $sv) {
$son_data = $list[$sk];
$son_data['show_value'] = $role['show_value'];
$builder_two[] = $this->formTypeShine($son_data)[0];
}
}
$builder_three = [];
if (isset($relateRule[$key . '#'])) {
$role = $relateRule[$key . '#'];
$data['show_value3'] = $role['show_value'];
foreach ($role['son_type'] as $sk => $sv) {
$son_data = $list[$sk];
$son_data['show_value'] = $role['show_value'];
$builder_three[] = $this->formTypeShine($son_data)[0];
}
}
$formbuider = array_merge($formbuider, $this->createRadioForm($data, $builder, $builder_two, $builder_three));
break;
case 'textarea'://多行文本框
$formbuider = array_merge($formbuider, $this->createTextareaForm($data));
break;
case 'upload'://文件上传
$formbuider = array_merge($formbuider, $this->createUploadForm((int)$data['upload_type'], $data,false));
break;
case 'checkbox'://多选框
$formbuider = array_merge($formbuider, $this->createCheckboxForm($data));
break;
case 'select'://多选框
$formbuider = array_merge($formbuider, $this->createSelectForm($data));
break;
case 'switch'://开关
$formbuider = array_merge($formbuider, $this->createSwitchForm($data));
break;
}
}
return $formbuider;
}
/**
* 从数据库递归构建联动规则
* 替代旧的 $relatedRule 硬编码,从 eb_system_config 表的 level/link_id/link_value 字段读取联动关系
* @param array $parentConfig 父配置数据
* @return array 联动规则数组,结构与 $relatedRule 一致
*/
private function buildRelatedRuleFromDB(array $parentConfig): array
{
$relateRule = [];
$sonData = $this->dao->getColumn(['level' => 1, 'link_id' => $parentConfig['id']], 'menu_name,link_value');
if (!$sonData) return $relateRule;
// 按 link_value 分组(处理多值联动:@/# 后缀场景)
$grouped = [];
foreach ($sonData as $sv) {
$grouped[$sv['link_value']][] = $sv['menu_name'];
}
$i = 0;
$suffixes = ['', '@', '#'];
foreach ($grouped as $linkValue => $menuNames) {
$label = $parentConfig['menu_name'] . ($suffixes[$i] ?? '');
$relateRule[$label]['show_value'] = $this->normalizeOptionValue($linkValue);
foreach ($menuNames as $menuName) {
// 递归检查该子配置是否也有自己的子配置(二级联动)
$childConfig = $this->dao->getOne(['menu_name' => $menuName]);
if ($childConfig) {
$childConfig = $childConfig->toArray();
}
if ($childConfig && $this->hasRelatedChildren((int)$childConfig['id'])) {
// 递归结果中 key 为子配置 menu_name(可能带 @/# 后缀),需展平到父级 son_type
$nestedRule = $this->buildRelatedRuleFromDB($childConfig);
foreach ($nestedRule as $nestedKey => $nestedValue) {
$relateRule[$label]['son_type'][$nestedKey] = $nestedValue;
}
} else {
$relateRule[$label]['son_type'][$menuName] = '';
}
}
$i++;
}
return $relateRule;
}
/**
* 检查指定配置是否有联动的子配置
* @param int $configId 配置ID
* @return bool
*/
private function hasRelatedChildren(int $configId): bool
{
return $this->dao->count(['level' => 1, 'link_id' => $configId]) > 0;
}
/**
* 根据tabid获取系统配置form表单创建
* @param $url
* @param int $tabId
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getConfigForm($url, int $tabId)
{
/** @var SystemConfigTabServices $service */
$service = app()->make(SystemConfigTabServices::class);
$title = $service->value(['id' => $tabId], 'title');
// tab 不存在(如配置页面已被删除)时返回空表单,前端正常渲染不报错
if (!$title) {
return create_form('', [], $this->url($url ?? ''), 'POST');
}
$list = $this->dao->getConfigTabAllList($tabId);
$formbuider = $this->createForm($list);
$saveUrl = $this->getSaveUrl($url ?? '');
return create_form($title, $formbuider, $this->url($saveUrl), 'POST');
}
/**
* 新增路由增加设置项验证
* 注:原先的硬编码路由验证已移除,该方法保留为兼容占位
* @param $url
* @param $post
* @return bool
*/
public function checkParam($url, $post)
{
return true;
}
/**
* 添加配置字段
* @param int $type
* @param int $tab_id
* @retuarr@throws \FormBuilder\Exception\FormBuilderException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function addFieldForm(int $type, int $tab_id): array
{
/** @var SystemConfigTabServices $service */
$service = app()->make(SystemConfigTabServices::class);
$formbuider = [];
$form_type = '';
$info_type = [];
$parameter = [];
switch ($type) {
case 0://文本框
$form_type = 'text';
$parameter[] = $this->createTextInputTypeForm(['input_type' => 'input']);
break;
case 1://多行文本框
$form_type = 'textarea';
$parameter[] = $this->builder->textarea('value', '默认值');
$parameter[] = $this->builder->number('width', '文本框宽', 100);
$parameter[] = $this->builder->number('high', '多行文本框高', 5);
break;
case 2://单选框
$form_type = 'radio';
$parameter[] = $this->builder->textarea('parameter', '配置参数', "1=>男\n2=>女\n3=>保密")->rows(3)->placeholder("参数方式例如:\n1=>男\n2=>女\n3=>保密")->required('配置参数不能为空')->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => '参数说明:=>前面是参数值,后面是参数名称,每行一个参数,=>分割符不能换,例如:<br>1=>男<br>2=>女<br>3=>保密']
]);
$parameter[] = $this->builder->input('value', '默认值');
break;
case 3://文件上传
$form_type = 'upload';
$parameter = array_merge($parameter, $this->createUploadForm(1, ['menu_name' => 'value', 'info' => '默认值', 'value' => ''], true));
break;
case 4://多选框
$form_type = 'checkbox';
$parameter[] = $this->builder->textarea('parameter', '配置参数', "1=>白色\n2=>红色\n3=>黑色")->rows(3)->placeholder("参数方式例如:\n1=>白色\n2=>红色\n3=>黑色")->required('配置参数不能为空')->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => '参数说明:=>前面是参数值,后面是参数名称,每行一个参数,=>分割符不能换,例如:<br>1=>白色<br>2=>红色<br>3=>黑色']
]);
break;
case 5://下拉框
$form_type = 'select';
$parameter[] = $this->builder->textarea('parameter', '配置参数', "1=>分类一\n2=>分类二\n3=>分类三")->rows(3)->placeholder("参数方式例如:\n1=>分类一\n2=>分类二\n3=>分类三")->required('配置参数不能为空')->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => '参数说明:=>前面是参数值,后面是参数名称,每行一个参数,=>分割符不能换,例如:<br>1=>分类一<br>2=>分类二<br>3=>分类三']
]);
break;
case 6://开关
$form_type = 'switch';
$parameter[] = $this->builder->switches('value', '默认');
break;
}
if ($form_type) {
$formbuider[] = $this->builder->hidden('type', $form_type);
[$configTabList, $data] = $service->getConfigTabListForm((int)($tab_id ?? 0));
$linkData = $this->linkData($tab_id);
$formbuider[] = $this->builder->radio('level', '联动显示', 0)->options([['value' => 0, 'label' => '否'], ['value' => 1, 'label' => '是']])->requiredNum()->appendRule('suffix', [
'type' => 'div',
'class' => 'tips-info',
'style' => ['width' => '100%'], 'props' => ['innerHTML' => '否:默认正常展示此配置;是:此配置默认隐藏,当选中下方对应配置的值时,此配置才会显示']
])->appendControl(1, [
$this->builder->cascader('link_data', '关联配置/值')->options($linkData)->props(['props' => ['multiple' => false, 'checkStrictly' => false, 'emitPath' => true]])->style(['width' => '100%']),
]);
$formbuider[] = $this->builder->cascader('config_tab_id', '分类', $data)->options($configTabList)->filterable(true)->props(['props' => ['multiple' => false, 'checkStrictly' => true, 'emitPath' => false]])->style(['width' => '100%']);
if ($info_type) {
$formbuider[] = $info_type;
}
$formbuider[] = $this->builder->input('info', '配置名称')->required('配置名称不能为空')->autofocus(1);
$formbuider[] = $this->builder->input('menu_name', '字段变量')->required('字段变量不能为空')->placeholder('例如:site_url');
$formbuider[] = $this->builder->input('desc', '配置简介');
$formbuider = array_merge($formbuider, $parameter);
// 是否必填
if ($form_type != 'switch') {
$formbuider[] = $this->builder->radio('required', '是否必填', 0)->options([
['value' => 0, 'label' => '否'],
['value' => 1, 'label' => '是'],
])->requiredNum();
}
$formbuider[] = $this->builder->number('sort', '排序', 0);
$formbuider[] = $this->builder->radio('status', '状态', 1)->options([['value' => 1, 'label' => '显示'], ['value' => 0, 'label' => '隐藏']])->requiredNum();
}
return create_form('添加字段', $formbuider, $this->url('/setting/config'), 'POST');
}
/**
* 联动选择关联配置/值
* 根据指定的标签ID,链接数据并以特定格式返回。
* @param $tab_id
* @return array
* @author wuhaotian
* @email 442384644@qq.com
* @date 2024/5/30
*/
public function linkData($tab_id)
{
$linkData = $this->selectList(['config_tab_id' => $tab_id, 'type' => 'radio', 'level' => 0], 'info as label,id as value,parameter,sort', 0, 0, 'sort DESC')->toArray();
foreach ($linkData as &$item) {
// 父级 value 也需归一化,与 children 的 value 类型保持一致,否则级联选择器无法匹配回显
$item['value'] = $this->normalizeOptionValue($item['value']);
$parameter = [];
$parameter = explode("\n", $item['parameter']);
foreach ($parameter as $pv) {
$pvArr = explode('=>', $pv, 2);
if (count($pvArr) < 2) {
continue;
}
$item['children'][] = [
'label' => trim($pvArr[1]),
'value' => $this->normalizeOptionValue($pvArr[0])
];
}
}
return $linkData;
}
/**
* radio 和 checkbox规则的判断
* @param $data
* @return bool
*/
public function valiDateRadioAndCheckbox($data)
{
$option = [];
$option_new = [];
$data['parameter'] = str_replace("\r\n", "\n", $data['parameter']);//防止不兼容
$parameter = explode("\n", $data['parameter']);
if (count($parameter) < 2) {
throw new AdminException('请输入正确格式的配置参数');
}
foreach ($parameter as $k => $v) {
if (isset($v) && !empty($v)) {
$option[$k] = explode('=>', $v);
}
}
if (count($option) < 2) {
throw new AdminException('请输入正确格式的配置参数');
}
$bool = 1;
foreach ($option as $k => $v) {
$option_new[$k] = $option[$k][0];
foreach ($v as $kk => $vv) {
$vv_num = strlen($vv);
if (!$vv_num) {
$bool = 0;
}
}
}
if (!$bool) {
throw new AdminException('请输入正确格式的配置参数');
}
$num1 = count($option_new);//提取该数组的数目
$arr2 = array_unique($option_new);//合并相同的元素
$num2 = count($arr2);//提取合并后数组个数
if ($num1 > $num2) {
throw new AdminException('请输入正确格式的配置参数');
}
return true;
}
/**
* 验证参数
* @param $data
* @return bool
*/
public function valiDateValue($data)
{
// 检查数据和验证规则是否存在
if (!$data || !isset($data['required']) || !$data['required']) {
return true;
}
$name = $data['info'] ?? '';
$value = $data['value'] ?? '';
// 支持JSON格式的验证规则
$requiredData = json_decode($data['required'], true);
if ($requiredData) {
// JSON格式
if (isset($requiredData['required']) && $requiredData['required']) {
if ($value === '' || $value === null) {
throw new AdminException($name . '不能为空');
}
}
if (isset($requiredData['regex']) && $requiredData['regex']) {
$match = @preg_match($this->normalizeRegexPattern($requiredData['regex']), (string) $value);
if ($match === false) {
throw new AdminException($name . '的验证规则配置错误');
}
if ($value && !$match) {
throw new AdminException($name . '请输入正确的格式');
}
}
if (isset($requiredData['min']) && $value !== '' && $value !== null) {
if ((float)$value < $requiredData['min']) {
throw new AdminException($name . '不能小于' . $requiredData['min']);
}
}
if (isset($requiredData['gt']) && $value !== '' && $value !== null) {
if ((float)$value <= $requiredData['gt']) {
throw new AdminException($name . '必须大于' . $requiredData['gt']);
}
}
if (isset($requiredData['max']) && $value !== '' && $value !== null) {
if ((float)$value > $requiredData['max']) {
throw new AdminException($name . '不能大于' . $requiredData['max']);
}
}
} else {
// 兼容旧格式:逗号分隔的字符串
$valids = explode(',', $data['required']);
foreach ($valids as $valid) {
$valid = explode(':', $valid);
if (isset($valid[0]) && isset($valid[1])) {
$k = strtolower(trim($valid[0]));
$v = strtolower(trim($valid[1]));
if ($v != 'true') {
continue;
}
switch ($k) {
case 'required':
if ($value === '' || $value === null) {
throw new AdminException($name . '不能为空');
}
break;
case 'regex':
if ($value && !preg_match($data['regex'], $value)) {
throw new AdminException($name . '请输入正确的格式');
}
break;
}
}
}
}
return true;
}
/**
* 将配置中未转义的正则分隔符规范化。
*
* 历史配置中的 URL 规则使用 /^https?://.+/i;其中协议后的斜杠在 PHP
* PCRE 中需要转义。本方法仅补齐正则主体内未转义的分隔符,保持配置原值和
* 后台表单展示不变。
*/
private function normalizeRegexPattern(string $pattern): string
{
if (strlen($pattern) < 3) {
return $pattern;
}
$delimiter = $pattern[0];
if (ctype_alnum($delimiter) || $delimiter === '\\') {
return $pattern;
}
$closingPosition = strrpos($pattern, $delimiter);
if ($closingPosition === false || $closingPosition === 0) {
return $pattern;
}
$body = substr($pattern, 1, $closingPosition - 1);
$normalizedBody = '';
$bodyLength = strlen($body);
for ($index = 0; $index < $bodyLength; $index++) {
$character = $body[$index];
if ($character === $delimiter) {
$backslashCount = 0;
for ($cursor = $index - 1; $cursor >= 0 && $body[$cursor] === '\\'; $cursor--) {
$backslashCount++;
}
if ($backslashCount % 2 === 0) {
$normalizedBody .= '\\';
}
}
$normalizedBody .= $character;
}
return $delimiter . $normalizedBody . substr($pattern, $closingPosition);
}
/**
* 保存平台电子面单打印信息
* @param array $data
* @return bool
*/
public function saveExpressInfo(array $data)
{
if (!is_array($data) || !$data) return false;
foreach ($data as $key => $value) {
$this->dao->update(['menu_name' => 'config_export_' . $key], ['value' => json_encode($value)]);
}
CacheService::clear();
return true;
}
/**
* 获取分享海报 兼容方法
*/
public function getSpreadBanner()
{
//配置
$banner = sys_config('spread_banner', []);
if (!$banner) {
//组合数据
$banner = sys_data('routine_spread_banner');
if ($banner) {
$banner = array_column($banner, 'pic');
$this->dao->update(['menu_name' => 'spread_banner'], ['value' => json_encode($banner)]);
CacheService::clear();
}
}
return $banner;
}
/**
* 保存wss配置
* @param int $wssOpen
* @param string $wssLocalpk
* @param string $wssLocalCert
*/
public function saveSslFilePath(int $wssOpen, string $wssLocalpk, string $wssLocalCert)
{
$wssFile = root_path() . '.wss';
$content = <<<WSS
wssOpen = $wssOpen
wssLocalpk = $wssLocalpk
wssLocalCert = $wssLocalCert
WSS;
try {
file_put_contents($wssFile, $content);
} catch (\Throwable $e) {
throw new AdminException('保存wss证书失败');
}
}
/**
* 获取wss配置
* @param string $key
* @return array|false|mixed
*/
public function getSslFilePath(string $key = '')
{
$wssFile = root_path() . '.wss';
try {
$content = parse_ini_file($wssFile);
} catch (\Throwable $e) {
$content = [];
}
return $content[$key] ?? $content;
}
/**
* 变更分销绑定关系模式
* @param array $post
* @return bool
*/
public function checkBrokerageBinding(array $post)
{
try {
$config_data = $post['store_brokerage_binding_status'];
$config_one = $this->dao->getOne(['menu_name' => 'store_brokerage_binding_status']);
$config_old = json_decode($config_one['value'], true);
if ($config_old != 2 && $config_data == 2) {
event('BrokerageBindingStatusChange', ['status' => $config_data]);
}
} catch (\Throwable $e) {
Log::error('变更分销绑定模式重置绑定时间失败,失败原因:' . $e->getMessage());
return false;
}
return true;
}
}