1225 lines
41 KiB
PHP
1225 lines
41 KiB
PHP
<?php
|
||||
|
|
declare(strict_types=1);
|
|||
|
|
/**
|
|||
|
|
*+------------------
|
|||
|
|
* madong
|
|||
|
|
*+------------------
|
|||
|
|
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
|||
|
|
*+------------------
|
|||
|
|
* Author: Mr. April (405784684@qq.com)
|
|||
|
|
*+------------------
|
|||
|
|
* Official Website: http://www.madong.tech
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
namespace app\adminapi\controller;
|
|||
|
|
|
|||
|
|
use core\excel\ExcelExportService;
|
|||
|
|
use core\exception\handler\AdminException;
|
|||
|
|
use core\tool\Json;
|
|||
|
|
use Illuminate\Database\Eloquent\Model;
|
|||
|
|
use madong\helper\Arr;
|
|||
|
|
use madong\helper\DateTime;
|
|||
|
|
use madong\helper\Tree;
|
|||
|
|
use madong\query\QueryBuilderHelper;
|
|||
|
|
use madong\query\QuerySelectConfig;
|
|||
|
|
use madong\query\QuerySelectHelper;
|
|||
|
|
use support\Request;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @author Mr.April
|
|||
|
|
* @since 1.0
|
|||
|
|
*/
|
|||
|
|
class Crud extends Base
|
|||
|
|
{
|
|||
|
|
/**
|
|||
|
|
* SelectInput 配置
|
|||
|
|
* @var array|null
|
|||
|
|
*/
|
|||
|
|
protected ?array $selectInputConfig = null;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 列表
|
|||
|
|
*
|
|||
|
|
* @param \support\Request $request
|
|||
|
|
*
|
|||
|
|
* @return \support\Response
|
|||
|
|
*/
|
|||
|
|
public function index(Request $request): \support\Response
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
[$where, $format, $limit, $field, $order, $page] = $this->selectInput($request);
|
|||
|
|
$methods = [
|
|||
|
|
'select' => 'formatSelect',
|
|||
|
|
'tree' => 'formatTree',
|
|||
|
|
'table_tree' => 'formatTableTree',
|
|||
|
|
'normal' => 'formatNormal',
|
|||
|
|
];
|
|||
|
|
$format_function = $methods[$format] ?? 'formatNormal';
|
|||
|
|
$total = $this->service->getCount($where);
|
|||
|
|
$list = $this->service->selectList($where, $field, $page, $limit, $order, [], false);
|
|||
|
|
return call_user_func([$this, $format_function], $list, $total);
|
|||
|
|
} catch (\Throwable $e) {
|
|||
|
|
return Json::fail($e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param \support\Request $request
|
|||
|
|
*
|
|||
|
|
* @return \support\Response
|
|||
|
|
*/
|
|||
|
|
public function select(Request $request): \support\Response
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
[$where, $format, $limit, $field, $order, $page] = $this->selectInput($request);
|
|||
|
|
|
|||
|
|
$data = $this->service->selectList($where, $field, 0, 99999, $order, [], true);
|
|||
|
|
return Json::success('ok', $data);
|
|||
|
|
} catch (\Throwable $e) {
|
|||
|
|
return Json::fail($e->getMessage());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param \support\Request $request
|
|||
|
|
*
|
|||
|
|
* @return \support\Response
|
|||
|
|
*/
|
|||
|
|
public function create(Request $request): \support\Response
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$id = $request->get('id');
|
|||
|
|
throw new AdminException('表单不存在');
|
|||
|
|
} catch (\Throwable $e) {
|
|||
|
|
return Json::fail($e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 插入数据
|
|||
|
|
*
|
|||
|
|
* @param \support\Request $request
|
|||
|
|
*
|
|||
|
|
* @return \support\Response
|
|||
|
|
*/
|
|||
|
|
public function store(Request $request): \support\Response
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$data = $this->insertInput($request);
|
|||
|
|
if (isset($this->validate) && $this->validate) {
|
|||
|
|
if (!$this->validate->scene('store')->check($data)) {
|
|||
|
|
|
|||
|
|
throw new \Exception($this->validate->getError());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
$model = $this->service->save($data);
|
|||
|
|
if (empty($model)) {
|
|||
|
|
throw new AdminException('插入失败');
|
|||
|
|
}
|
|||
|
|
$pk = $model->getPk();
|
|||
|
|
return Json::success('ok', [$pk => $model->getAttribute($pk)]);
|
|||
|
|
} catch (\Throwable $e) {
|
|||
|
|
return Json::fail($e->getMessage());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 查询详情
|
|||
|
|
*
|
|||
|
|
* @param \support\Request $request
|
|||
|
|
*
|
|||
|
|
* @return \support\Response
|
|||
|
|
*/
|
|||
|
|
public function show(Request $request): \support\Response
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$id = $request->route->param('id');
|
|||
|
|
$data = $this->service->get($id);
|
|||
|
|
if (empty($data)) {
|
|||
|
|
throw new AdminException('数据未找到', ['errorCode' => 400]);
|
|||
|
|
}
|
|||
|
|
return Json::success('ok', $data->toArray());
|
|||
|
|
} catch (\Throwable $e) {
|
|||
|
|
return Json::fail($e->getMessage(), [], $e->getCode());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function edit(Request $request): \support\Response
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$id = $request->route->param('id');
|
|||
|
|
throw new AdminException('表单不存在');
|
|||
|
|
} catch (\Throwable $e) {
|
|||
|
|
return Json::fail($e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新
|
|||
|
|
*
|
|||
|
|
* @param \support\Request $request
|
|||
|
|
*
|
|||
|
|
* @return \support\Response
|
|||
|
|
*/
|
|||
|
|
public function update(Request $request): \support\Response
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$id = $request->route->param('id');
|
|||
|
|
$data = $this->insertInput($request);
|
|||
|
|
if (isset($this->validate) && $this->validate) {
|
|||
|
|
if (!$this->validate->scene('update')->check($data)) {
|
|||
|
|
throw new \Exception($this->validate->getError());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
//路由模式兼容
|
|||
|
|
if (empty($id)) {
|
|||
|
|
$model = $this->service->getModel();
|
|||
|
|
$primaryKey = $model->getKeyName();
|
|||
|
|
if (!array_key_exists($primaryKey, $data)) {
|
|||
|
|
throw new \Exception('参数异常缺少参数:' . $primaryKey);
|
|||
|
|
}
|
|||
|
|
$id = $data[$primaryKey];
|
|||
|
|
}
|
|||
|
|
$this->service->update($id, $data);
|
|||
|
|
return Json::success('ok', []);
|
|||
|
|
} catch (\Throwable $e) {
|
|||
|
|
return Json::fail($e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新状态
|
|||
|
|
*
|
|||
|
|
* @param \support\Request $request
|
|||
|
|
*
|
|||
|
|
* @return \support\Response
|
|||
|
|
*/
|
|||
|
|
public function changeStatus(Request $request): \support\Response
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$data = $this->insertInput($request);
|
|||
|
|
$model = $this->service->getModel();
|
|||
|
|
$primaryKey = $model->getKeyName();
|
|||
|
|
if (!array_key_exists($primaryKey, $data)) {
|
|||
|
|
throw new \Exception('参数异常缺少主键');
|
|||
|
|
}
|
|||
|
|
$targetModel = $model->findOrFail($data[$primaryKey]);
|
|||
|
|
if (empty($targetModel)) {
|
|||
|
|
throw new \Exception('资源不存在' . $primaryKey . '=', $data[$primaryKey]);
|
|||
|
|
}
|
|||
|
|
$targetModel->fill($data);
|
|||
|
|
if (!$targetModel->save()) {
|
|||
|
|
throw new \RuntimeException('数据保存失败');
|
|||
|
|
}
|
|||
|
|
return Json::success('ok', []);
|
|||
|
|
} catch (\Throwable $e) {
|
|||
|
|
return Json::fail($e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Excel导出
|
|||
|
|
*
|
|||
|
|
* @param \support\Request $request
|
|||
|
|
*
|
|||
|
|
* @return \support\Response
|
|||
|
|
*/
|
|||
|
|
public function export(Request $request): \support\Response
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$params = $request->all();
|
|||
|
|
$args = (array)$params['query'] ?? [];
|
|||
|
|
$scope = $params['scope'] ?? 'all';
|
|||
|
|
$exportPage = max(1, (int)($args['__export_page'] ?? 1));
|
|||
|
|
$exportLimit = min(1000, max(1, (int)($args['__export_limit'] ?? 20)));
|
|||
|
|
$exportIds = array_values(array_filter((array)($args['__export_ids'] ?? []), static fn ($id) => $id !== null && $id !== ''));
|
|||
|
|
unset($args['__export_page'], $args['__export_limit'], $args['__export_ids']);
|
|||
|
|
if ($scope === 'selected' && empty($exportIds)) {
|
|||
|
|
return Json::fail('请先选择需要导出的数据');
|
|||
|
|
}
|
|||
|
|
$result = ExcelExportService::export($params, function ($chunkHandler) use ($args, $scope, $exportPage, $exportLimit, $exportIds) {
|
|||
|
|
$model = $this->service->getModel();
|
|||
|
|
$allow_column = $model->getFields();
|
|||
|
|
$query = $model->query();
|
|||
|
|
$where = $this->convertToStandardFormat($args, $allow_column);
|
|||
|
|
if (!empty($where)) {
|
|||
|
|
$whereInConditions = [];
|
|||
|
|
foreach ($where as $key => $condition) {
|
|||
|
|
if (is_array($condition) && count($condition) === 3 && ($condition[1] === 'in' || $condition[1] === 'IN')) {
|
|||
|
|
$whereInConditions[] = $condition;
|
|||
|
|
unset($where[$key]);//移除分离后的条件
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
//普通条件格式直接传入构建
|
|||
|
|
if (!empty($where)) {
|
|||
|
|
$query->where($where);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//特殊条件格式额外处理
|
|||
|
|
foreach ($whereInConditions as $condition) {
|
|||
|
|
if (empty($condition[2])) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
$value = Arr::normalize($condition[2]);
|
|||
|
|
$query->whereIn($condition[0], $value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if ($scope === 'selected') {
|
|||
|
|
$query->whereIn($model->getKeyName(), $exportIds);
|
|||
|
|
}
|
|||
|
|
$query->orderBy($model->getKeyName(), 'desc');
|
|||
|
|
$handleChunk = function ($items) use ($chunkHandler) {
|
|||
|
|
$chunkHandler($this->expandExportRows($items));
|
|||
|
|
};
|
|||
|
|
if ($scope === 'current') {
|
|||
|
|
$query->forPage($exportPage, $exportLimit)->get()->chunk(1000)->each($handleChunk);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
//切片查询数据
|
|||
|
|
$query->chunk(1000, $handleChunk);
|
|||
|
|
});
|
|||
|
|
return Json::success('导出成功', $result);
|
|||
|
|
} catch (\Throwable $e) {
|
|||
|
|
return Json::fail($e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 展开导出行;子控制器可覆盖此方法将一条记录转换为多条导出明细。
|
|||
|
|
*/
|
|||
|
|
protected function expandExportRows(iterable $items): iterable
|
|||
|
|
{
|
|||
|
|
return $items;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 删除资源
|
|||
|
|
*
|
|||
|
|
* @param \support\Request $request
|
|||
|
|
*
|
|||
|
|
* @return \support\Response
|
|||
|
|
*/
|
|||
|
|
public function destroy(Request $request): \support\Response
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
// 获取要删除的ID集合
|
|||
|
|
$data = $this->getDeleteIds($request);
|
|||
|
|
|
|||
|
|
if (empty($data)) {
|
|||
|
|
throw new AdminException('删除参数不能为空');
|
|||
|
|
}
|
|||
|
|
$result = $this->service->transaction(function () use ($data) {
|
|||
|
|
$data = is_array($data) ? $data : explode(',', $data);
|
|||
|
|
$deletedIds = [];
|
|||
|
|
foreach ($data as $id) {
|
|||
|
|
$item = $this->service->get($id);
|
|||
|
|
if (!$item) {
|
|||
|
|
continue; // 如果找不到项,跳过
|
|||
|
|
}
|
|||
|
|
$item->delete();
|
|||
|
|
$primaryKey = $item->getPk();
|
|||
|
|
$deletedIds[] = $item->{$primaryKey};
|
|||
|
|
}
|
|||
|
|
return $deletedIds;
|
|||
|
|
});
|
|||
|
|
return Json::success('ok', $result);
|
|||
|
|
} catch (\Throwable $e) {
|
|||
|
|
return Json::fail($e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取要删除的ID集合
|
|||
|
|
* 支持多种参数来源:
|
|||
|
|
* 1. 路由参数中的 'id'
|
|||
|
|
* 2. 请求体中的 'ids' 数组
|
|||
|
|
* 3. 请求体中的 'data' 数组(兼容旧版)
|
|||
|
|
*
|
|||
|
|
* @param Request $request
|
|||
|
|
*
|
|||
|
|
* @return array
|
|||
|
|
*/
|
|||
|
|
protected function getDeleteIds(Request $request): array
|
|||
|
|
{
|
|||
|
|
// 优先从路由参数获取单个ID
|
|||
|
|
$routeId = $request->route->param('id');
|
|||
|
|
|
|||
|
|
if (!empty($routeId) && $routeId !== '0') {
|
|||
|
|
return [$routeId];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 尝试从请求体中获取 'ids' 参数
|
|||
|
|
$ids = $request->input('ids', []);
|
|||
|
|
|
|||
|
|
// 如果 'ids' 不存在,尝试兼容旧版的 'data' 参数
|
|||
|
|
if (empty($ids)) {
|
|||
|
|
$ids = $request->input('data', []);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 确保返回数组格式
|
|||
|
|
if (is_array($ids)) {
|
|||
|
|
return $ids;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理逗号分隔的字符串
|
|||
|
|
if (is_string($ids) && !empty($ids)) {
|
|||
|
|
return explode(',', $ids);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 数据恢复
|
|||
|
|
*
|
|||
|
|
* @param \support\Request $request
|
|||
|
|
*
|
|||
|
|
* @return \support\Response
|
|||
|
|
*/
|
|||
|
|
public function recovery(Request $request): \support\Response
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$id = $request->route->param('id');
|
|||
|
|
$data = $request->input('data', []);
|
|||
|
|
$data = !empty($id) && $id !== '0' ? $id : $data;
|
|||
|
|
if (empty($data)) {
|
|||
|
|
throw new AdminException('参数错误');
|
|||
|
|
}
|
|||
|
|
$this->service->transaction(function () use ($data) {
|
|||
|
|
$data = is_array($data) ? $data : explode(',', $data);
|
|||
|
|
foreach ($data as $id) {
|
|||
|
|
$this->service->update($id, ['delete' => null]);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
return Json::success('操作成功', []);
|
|||
|
|
} catch (\Throwable $e) {
|
|||
|
|
return Json::fail($e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将各种格式的查询条件转换为统一格式
|
|||
|
|
* 支持大写前缀、三元素数组、普通字段等格式
|
|||
|
|
* 自动过滤空值参数(null、空字符串、空数组)
|
|||
|
|
*
|
|||
|
|
* @param array $params
|
|||
|
|
* @param array $allowColumns
|
|||
|
|
* @return array
|
|||
|
|
*/
|
|||
|
|
private function convertToStandardFormat(array $params, array $allowColumns): array
|
|||
|
|
{
|
|||
|
|
$result = [];
|
|||
|
|
$hasTripleArray = false;
|
|||
|
|
|
|||
|
|
// 检查是否包含三元素数组格式
|
|||
|
|
foreach ($params as $key => $value) {
|
|||
|
|
if (is_numeric($key) && is_array($value) && count($value) === 3) {
|
|||
|
|
$hasTripleArray = true;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果有三元素数组格式,直接保留原格式
|
|||
|
|
if ($hasTripleArray) {
|
|||
|
|
foreach ($params as $key => $value) {
|
|||
|
|
// 跳过非查询参数
|
|||
|
|
if (in_array($key, ['field', 'order', 'orderBy', 'orderDirection', 'sort', 'format', 'limit', 'page'])) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 三元素数组直接添加(跳过空值条件)
|
|||
|
|
if (is_numeric($key) && is_array($value) && count($value) === 3) {
|
|||
|
|
if ($this->isValidCondition($value)) {
|
|||
|
|
$result[] = $value;
|
|||
|
|
}
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理大写前缀格式: OPERATOR_field
|
|||
|
|
if (preg_match('/^([A-Z_]+)_(.+)$/', $key, $matches)) {
|
|||
|
|
$operatorUpper = $matches[1];
|
|||
|
|
$field = $matches[2];
|
|||
|
|
|
|||
|
|
// 字段白名单验证
|
|||
|
|
if (in_array($field, $allowColumns, true)) {
|
|||
|
|
// 检查值是否为空
|
|||
|
|
if (!$this->isValidValue($value, $operatorUpper)) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 转换操作符
|
|||
|
|
$operatorMap = [
|
|||
|
|
'IN' => 'IN',
|
|||
|
|
'LIKE' => 'LIKE',
|
|||
|
|
'GT' => '>',
|
|||
|
|
'LT' => '<',
|
|||
|
|
'GTE' => '>=',
|
|||
|
|
'LTE' => '<=',
|
|||
|
|
'NE' => '!=',
|
|||
|
|
'BETWEEN' => 'BETWEEN',
|
|||
|
|
'EQ' => '=',
|
|||
|
|
];
|
|||
|
|
$operator = $operatorMap[$operatorUpper] ?? '=';
|
|||
|
|
|
|||
|
|
// 特殊处理 LIKE 操作符自动加 %
|
|||
|
|
if ($operatorUpper === 'LIKE') {
|
|||
|
|
$result[] = [$field, $operator, "%{$value}%"];
|
|||
|
|
} elseif ($operatorUpper === 'IN' && is_array($value)) {
|
|||
|
|
$result[] = [$field, 'IN', $value];
|
|||
|
|
} elseif ($operatorUpper === 'BETWEEN' && is_array($value) && count($value) === 2) {
|
|||
|
|
$start = DateTime::dateTimeStringToTimestamp($value[0]);
|
|||
|
|
$end = DateTime::dateTimeStringToTimestamp($value[1]);
|
|||
|
|
$result[] = [$field, '>', $start];
|
|||
|
|
$result[] = [$field, '<', $end];
|
|||
|
|
} else {
|
|||
|
|
$result[] = [$field, $operator, $value];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理普通字段 (默认使用 =)
|
|||
|
|
if (in_array($key, $allowColumns, true)) {
|
|||
|
|
// 检查值是否为空
|
|||
|
|
if (!$this->isValidValue($value)) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
$result[] = [$key, '=', $value];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 没有三元素数组,转换为标准三元组格式
|
|||
|
|
foreach ($params as $key => $value) {
|
|||
|
|
// 跳过非查询参数
|
|||
|
|
if (in_array($key, ['field', 'order', 'orderBy', 'orderDirection', 'sort', 'format', 'limit', 'page', 'filters'], true)) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理大写前缀格式: OPERATOR_field
|
|||
|
|
if (preg_match('/^([A-Z_]+)_(.+)$/', $key, $matches)) {
|
|||
|
|
$operatorUpper = $matches[1];
|
|||
|
|
$field = $matches[2];
|
|||
|
|
|
|||
|
|
// 字段白名单验证
|
|||
|
|
if (in_array($field, $allowColumns, true)) {
|
|||
|
|
// 检查值是否为空
|
|||
|
|
if (!$this->isValidValue($value, $operatorUpper)) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 转换为标准操作符
|
|||
|
|
$operatorMap = [
|
|||
|
|
'IN' => 'IN',
|
|||
|
|
'LIKE' => 'LIKE',
|
|||
|
|
'GT' => '>',
|
|||
|
|
'LT' => '<',
|
|||
|
|
'GTE' => '>=',
|
|||
|
|
'LTE' => '<=',
|
|||
|
|
'NE' => '!=',
|
|||
|
|
'BETWEEN' => 'BETWEEN',
|
|||
|
|
'EQ' => '=',
|
|||
|
|
];
|
|||
|
|
$operator = $operatorMap[$operatorUpper] ?? '=';
|
|||
|
|
|
|||
|
|
// 特殊处理 LIKE 操作符自动加 %
|
|||
|
|
if ($operatorUpper === 'LIKE') {
|
|||
|
|
$result[] = [$field, $operator, "%{$value}%"];
|
|||
|
|
} elseif ($operatorUpper === 'IN' && is_array($value)) {
|
|||
|
|
$result[] = [$field, 'IN', $value];
|
|||
|
|
} elseif ($operatorUpper === 'BETWEEN' && is_array($value) && count($value) === 2) {
|
|||
|
|
$start = DateTime::dateTimeStringToTimestamp($value[0]);
|
|||
|
|
$end = DateTime::dateTimeStringToTimestamp($value[1]);
|
|||
|
|
$result[] = [$field, '>', $start];
|
|||
|
|
$result[] = [$field, '<', $end];
|
|||
|
|
} else {
|
|||
|
|
$result[] = [$field, $operator, $value];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理普通字段 (默认使用 eq)
|
|||
|
|
if (in_array($key, $allowColumns, true)) {
|
|||
|
|
// 检查值是否为空
|
|||
|
|
if (!$this->isValidValue($value)) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
$result[] = [$key, '=', $value];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查值是否有效(非空)
|
|||
|
|
*
|
|||
|
|
* @param mixed $value
|
|||
|
|
* @param string $operator 操作符(可选)
|
|||
|
|
* @return bool
|
|||
|
|
*/
|
|||
|
|
private function isValidValue(mixed $value, string $operator = ''): bool
|
|||
|
|
{
|
|||
|
|
// null 值无效
|
|||
|
|
if ($value === null) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 空字符串无效
|
|||
|
|
if ($value === '') {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 空数组无效
|
|||
|
|
if (is_array($value) && empty($value)) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 对于 BETWEEN 操作,需要检查数组元素
|
|||
|
|
if ($operator === 'BETWEEN' && is_array($value) && count($value) === 2) {
|
|||
|
|
return $value[0] !== '' && $value[0] !== null && $value[1] !== '' && $value[1] !== null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查三元素数组条件是否有效
|
|||
|
|
*
|
|||
|
|
* @param array $condition [字段, 操作符, 值]
|
|||
|
|
* @return bool
|
|||
|
|
*/
|
|||
|
|
private function isValidCondition(array $condition): bool
|
|||
|
|
{
|
|||
|
|
if (count($condition) !== 3) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查值是否有效
|
|||
|
|
return $this->isValidValue($condition[2]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析排序参数为 SQL order 字符串 - 支持多种场景
|
|||
|
|
*
|
|||
|
|
* 场景1: order=id 或 order=id desc (单字段排序)
|
|||
|
|
* 场景2: sort=id:asc,create_time:desc 或 sort[]=id:asc&sort[]=create_time:desc (多字段排序)
|
|||
|
|
* 场景3: orderBy=id&orderDirection=desc (兼容旧版大写前缀)
|
|||
|
|
*
|
|||
|
|
* @param array $params
|
|||
|
|
* @param array $allowColumns
|
|||
|
|
* @return string SQL order 字符串,如 "id desc, create_time asc"
|
|||
|
|
*/
|
|||
|
|
private function parseSortToOrder(array $params, array $allowColumns): string
|
|||
|
|
{
|
|||
|
|
$orders = [];
|
|||
|
|
|
|||
|
|
// 场景1: order=id 或 order=id desc
|
|||
|
|
if (isset($params['order']) && !isset($params['sort']) && !isset($params['orderBy'])) {
|
|||
|
|
$orderValue = $params['order'];
|
|||
|
|
$parts = explode(' ', $orderValue, 2);
|
|||
|
|
$field = $parts[0];
|
|||
|
|
$direction = strtolower($parts[1] ?? 'desc');
|
|||
|
|
|
|||
|
|
if (in_array($field, $allowColumns)) {
|
|||
|
|
$orders[] = "$field $direction";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 场景2: sort=id:asc,create_time:desc 或 sort[]=id:asc&sort[]=create_time:desc
|
|||
|
|
if (isset($params['sort'])) {
|
|||
|
|
$sortValues = is_array($params['sort']) ? $params['sort'] : explode(',', $params['sort']);
|
|||
|
|
foreach ($sortValues as $sortValue) {
|
|||
|
|
$parts = explode(':', trim($sortValue), 2);
|
|||
|
|
$field = $parts[0];
|
|||
|
|
$direction = strtolower($parts[1] ?? 'desc');
|
|||
|
|
|
|||
|
|
if (in_array($field, $allowColumns)) {
|
|||
|
|
$orders[] = "$field $direction";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 场景3: orderBy=id&orderDirection=desc (兼容旧版)
|
|||
|
|
if (isset($params['orderBy']) && !isset($params['sort']) && empty($orders)) {
|
|||
|
|
$field = $params['orderBy'];
|
|||
|
|
$direction = strtolower($params['orderDirection'] ?? 'desc');
|
|||
|
|
|
|||
|
|
if (in_array($field, $allowColumns)) {
|
|||
|
|
$orders[] = "$field $direction";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return implode(', ', $orders);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理查询输入参数
|
|||
|
|
*
|
|||
|
|
* @param Request $request
|
|||
|
|
* @param array|null $config 临时配置(可选),会覆盖类属性配置
|
|||
|
|
* @return array [where, format, limit, field, order, page]
|
|||
|
|
*/
|
|||
|
|
protected function selectInput(Request $request, ?array $config = null): array
|
|||
|
|
{
|
|||
|
|
$model = $this->service->getModel();
|
|||
|
|
$baseConfig = $this->getSelectInputConfig($request);
|
|||
|
|
|
|||
|
|
// 扩展 allowed_fields 并处理配置
|
|||
|
|
$helperConfig = $this->prepareHelperConfig($config, $baseConfig, $model);
|
|||
|
|
|
|||
|
|
// 处理查询参数
|
|||
|
|
$result = $this->processQueryParams($request, $model, $helperConfig);
|
|||
|
|
|
|||
|
|
// 处理 custom_fields
|
|||
|
|
$where = $this->processCustomFields(
|
|||
|
|
$result['where'],
|
|||
|
|
$result,
|
|||
|
|
$request,
|
|||
|
|
$config,
|
|||
|
|
$baseConfig
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 兼容旧格式返回值
|
|||
|
|
return [
|
|||
|
|
$where,
|
|||
|
|
$result['format'] ?? $request->input('format', 'normal'),
|
|||
|
|
$result['limit'],
|
|||
|
|
$result['field'] ?? $this->validateField($request->input('field', '*'), $model->getFields()),
|
|||
|
|
$result['order'],
|
|||
|
|
$result['page'],
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 准备 QuerySelectHelper 配置
|
|||
|
|
*/
|
|||
|
|
private function prepareHelperConfig(?array $config, array $baseConfig, Model $model): QuerySelectConfig
|
|||
|
|
{
|
|||
|
|
$allowColumn = $model->getFields();
|
|||
|
|
|
|||
|
|
// 扩展 allowed_fields:自动包含所有数据库字段
|
|||
|
|
if (isset($baseConfig['allowed_fields']) && is_array($baseConfig['allowed_fields'])) {
|
|||
|
|
$baseConfig['allowed_fields'] = array_unique(array_merge($allowColumn, $baseConfig['allowed_fields']));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($config !== null) {
|
|||
|
|
if (isset($config['allowed_fields']) && is_array($config['allowed_fields'])) {
|
|||
|
|
$config['allowed_fields'] = array_unique(array_merge($allowColumn, $config['allowed_fields']));
|
|||
|
|
}
|
|||
|
|
return QuerySelectConfig::make(array_merge($baseConfig, $config));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return QuerySelectConfig::make($baseConfig);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理查询参数
|
|||
|
|
*/
|
|||
|
|
private function processQueryParams(Request $request, Model $model, QuerySelectConfig $helperConfig): array
|
|||
|
|
{
|
|||
|
|
$helper = new QuerySelectHelper($helperConfig, $model->getFields());
|
|||
|
|
$result = $helper->process($request->all(), $model);
|
|||
|
|
$keywordParam = $helperConfig->getKeywordParam();
|
|||
|
|
$keyword = trim((string)$request->input($keywordParam, ''));
|
|||
|
|
if ($helperConfig->isEnableKeywordSearch() && $keyword !== '') {
|
|||
|
|
foreach ($helperConfig->getKeywordFields() as $field) {
|
|||
|
|
unset($result['where']['filters'][$field]);
|
|||
|
|
}
|
|||
|
|
$result['where']['keyword'] = $keyword;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 提取 custom_fields(它们会被合并到 where 中)
|
|||
|
|
$customFields = array_filter($result, function ($key) {
|
|||
|
|
return !in_array($key, ['where', 'format', 'limit', 'field', 'order', 'page']);
|
|||
|
|
}, ARRAY_FILTER_USE_KEY);
|
|||
|
|
|
|||
|
|
return array_merge($result, ['custom_fields' => $customFields]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理 custom_fields
|
|||
|
|
*/
|
|||
|
|
private function processCustomFields(array $where, array $result, Request $request, ?array $config, array $baseConfig): array
|
|||
|
|
{
|
|||
|
|
$customFields = $result['custom_fields'] ?? [];
|
|||
|
|
|
|||
|
|
$filterFormat = $this->getFilterFormat($config, $baseConfig);
|
|||
|
|
$defaultOperator = $this->getDefaultOperator($config, $baseConfig);
|
|||
|
|
$excludeFilters = $this->getExcludeFilters($config, $baseConfig);
|
|||
|
|
|
|||
|
|
$hasTripleArray = $this->hasTripleArray($where);
|
|||
|
|
$hasFiltersKey = isset($where['filters']) && is_array($where['filters']);
|
|||
|
|
|
|||
|
|
if ($excludeFilters) {
|
|||
|
|
return $this->processCustomFieldsWithoutFilters($where, $customFields, $request, $filterFormat, $defaultOperator, $hasFiltersKey);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $this->processCustomFieldsWithFilters($where, $customFields, $request, $filterFormat, $defaultOperator, $hasFiltersKey, $hasTripleArray);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取过滤器格式
|
|||
|
|
*/
|
|||
|
|
private function getFilterFormat(?array $config, array $baseConfig): string
|
|||
|
|
{
|
|||
|
|
if ($config !== null && isset($config['filter_format'])) {
|
|||
|
|
return $config['filter_format'];
|
|||
|
|
}
|
|||
|
|
return $baseConfig['filter_format'] ?? 'array';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取默认操作符
|
|||
|
|
*/
|
|||
|
|
private function getDefaultOperator(?array $config, array $baseConfig): string
|
|||
|
|
{
|
|||
|
|
if ($config !== null && isset($config['default_operator'])) {
|
|||
|
|
return $config['default_operator'];
|
|||
|
|
}
|
|||
|
|
return $baseConfig['default_operator'] ?? 'eq';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取 exclude_filters 配置
|
|||
|
|
*/
|
|||
|
|
private function getExcludeFilters(?array $config, array $baseConfig): bool
|
|||
|
|
{
|
|||
|
|
if ($config !== null && isset($config['exclude_filters'])) {
|
|||
|
|
return $config['exclude_filters'];
|
|||
|
|
}
|
|||
|
|
return $baseConfig['exclude_filters'] ?? false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查是否有三元素数组格式
|
|||
|
|
*/
|
|||
|
|
private function hasTripleArray(array $where): bool
|
|||
|
|
{
|
|||
|
|
foreach ($where as $key => $value) {
|
|||
|
|
if (is_numeric($key) && is_array($value) && count($value) === 3) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理 custom_fields(exclude_filters = true)
|
|||
|
|
*/
|
|||
|
|
private function processCustomFieldsWithoutFilters(array $where, array $customFields, Request $request, string $filterFormat, string $defaultOperator, bool $hasFiltersKey): array
|
|||
|
|
{
|
|||
|
|
if ($hasFiltersKey) {
|
|||
|
|
unset($where['filters']);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach ($customFields as $key => $value) {
|
|||
|
|
if (is_numeric($key)) {
|
|||
|
|
$fieldName = $value;
|
|||
|
|
$fieldValue = $request->input($fieldName);
|
|||
|
|
if ($fieldValue !== null && $fieldValue !== '') {
|
|||
|
|
$this->addCustomFieldToWhere($where, $fieldName, $fieldValue, $filterFormat, $defaultOperator);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if ($value !== null && $value !== '') {
|
|||
|
|
$this->addCustomFieldToWhere($where, $key, $value, $filterFormat, $defaultOperator);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $where;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理 custom_fields(exclude_filters = false)
|
|||
|
|
*/
|
|||
|
|
private function processCustomFieldsWithFilters(array $where, array $customFields, Request $request, string $filterFormat, string $defaultOperator, bool $hasFiltersKey, bool $hasTripleArray): array
|
|||
|
|
{
|
|||
|
|
if (empty($customFields) && !$hasFiltersKey) {
|
|||
|
|
if (!isset($where['filters']) && !$hasTripleArray) {
|
|||
|
|
$where['filters'] = [];
|
|||
|
|
}
|
|||
|
|
return $where;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!isset($where['filters']) || !is_array($where['filters'])) {
|
|||
|
|
$where['filters'] = [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$normalizedFilters = $this->normalizeFilters($where['filters']);
|
|||
|
|
|
|||
|
|
foreach ($customFields as $key => $value) {
|
|||
|
|
if (is_numeric($key)) {
|
|||
|
|
$fieldName = $value;
|
|||
|
|
$fieldValue = $request->input($fieldName);
|
|||
|
|
if ($fieldValue !== null && $fieldValue !== '') {
|
|||
|
|
$parsed = $this->parseFieldPrefix($fieldName, $defaultOperator);
|
|||
|
|
$normalizedFilters[] = [$parsed['field'], $parsed['operator'], $fieldValue];
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if ($value !== null && $value !== '') {
|
|||
|
|
$parsed = $this->parseFieldPrefix($key, $defaultOperator);
|
|||
|
|
$normalizedFilters[] = [$parsed['field'], $parsed['operator'], $value];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($filterFormat === 'keyvalue') {
|
|||
|
|
$finalFilters = [];
|
|||
|
|
foreach ($normalizedFilters as $filter) {
|
|||
|
|
$finalFilters[$filter[0]] = $filter[1] . ':' . $filter[2];
|
|||
|
|
}
|
|||
|
|
$where['filters'] = $finalFilters;
|
|||
|
|
} else {
|
|||
|
|
$where['filters'] = $normalizedFilters;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $where;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 添加 custom_field 到 where
|
|||
|
|
*/
|
|||
|
|
private function addCustomFieldToWhere(array &$where, string $field, $value, string $filterFormat, string $defaultOperator): void
|
|||
|
|
{
|
|||
|
|
// 解析字段前缀(如 LIKE_user_name)
|
|||
|
|
$parsed = $this->parseFieldPrefix($field, $defaultOperator);
|
|||
|
|
$actualField = $parsed['field'];
|
|||
|
|
$operator = $parsed['operator'];
|
|||
|
|
|
|||
|
|
switch ($filterFormat) {
|
|||
|
|
case 'simple':
|
|||
|
|
$where[$actualField] = $value;
|
|||
|
|
break;
|
|||
|
|
case 'keyvalue':
|
|||
|
|
$where[$actualField] = $operator . ':' . $value;
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
$where[] = [$actualField, $operator, $value];
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析字段前缀,支持 LIKE_, EQ_, GT_, LT_, GTE_, LTE_, NEQ_ 等前缀
|
|||
|
|
*/
|
|||
|
|
private function parseFieldPrefix(string $field, string $defaultOperator): array
|
|||
|
|
{
|
|||
|
|
$prefixes = [
|
|||
|
|
'LIKE_' => 'like',
|
|||
|
|
'EQ_' => 'eq',
|
|||
|
|
'GT_' => 'gt',
|
|||
|
|
'LT_' => 'lt',
|
|||
|
|
'GTE_' => 'gte',
|
|||
|
|
'LTE_' => 'lte',
|
|||
|
|
'NEQ_' => 'neq',
|
|||
|
|
'IN_' => 'in',
|
|||
|
|
'NOTIN_' => 'notin',
|
|||
|
|
'BETWEEN_' => 'between',
|
|||
|
|
'NOTBETWEEN_' => 'notbetween',
|
|||
|
|
'NULL_' => 'null',
|
|||
|
|
'NOTNULL_' => 'notnull',
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
foreach ($prefixes as $prefix => $operator) {
|
|||
|
|
if (strpos($field, $prefix) === 0) {
|
|||
|
|
return [
|
|||
|
|
'field' => substr($field, strlen($prefix)),
|
|||
|
|
'operator' => $operator,
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return [
|
|||
|
|
'field' => $field,
|
|||
|
|
'operator' => $defaultOperator,
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 标准化 filters
|
|||
|
|
*/
|
|||
|
|
private function normalizeFilters(array $filters): array
|
|||
|
|
{
|
|||
|
|
$normalized = [];
|
|||
|
|
foreach ($filters as $key => $filter) {
|
|||
|
|
if (is_array($filter) && count($filter) === 3) {
|
|||
|
|
$normalized[] = $filter;
|
|||
|
|
} elseif (is_string($key) && is_string($filter)) {
|
|||
|
|
if (strpos($filter, ':') !== false) {
|
|||
|
|
[$op, $val] = explode(':', $filter, 2);
|
|||
|
|
$normalized[] = [$key, $op, $val];
|
|||
|
|
} else {
|
|||
|
|
$normalized[] = [$key, 'eq', $filter];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return $normalized;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将 where 条件应用到查询构建器
|
|||
|
|
*
|
|||
|
|
* @param \Illuminate\Database\Eloquent\Builder $query
|
|||
|
|
* @param array $where
|
|||
|
|
*
|
|||
|
|
* @return \Illuminate\Database\Eloquent\Builder
|
|||
|
|
*/
|
|||
|
|
protected function applyWhereToQuery(\Illuminate\Database\Eloquent\Builder $query, array $where): \Illuminate\Database\Eloquent\Builder
|
|||
|
|
{
|
|||
|
|
// 处理 filters 格式
|
|||
|
|
if (isset($where['filters']) && is_array($where['filters'])) {
|
|||
|
|
QueryBuilderHelper::applyFiltersToQuery($query, $where['filters']);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理三元素数组格式(直接应用到查询)
|
|||
|
|
foreach ($where as $key => $value) {
|
|||
|
|
if (is_numeric($key) && is_array($value) && count($value) === 3) {
|
|||
|
|
$field = $value[0];
|
|||
|
|
$operator = $value[1];
|
|||
|
|
$val = $value[2];
|
|||
|
|
$query->where($field, $operator, $val);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理普通字段键值对格式(exclude_filters = true 时的 custom_fields)
|
|||
|
|
foreach ($where as $key => $value) {
|
|||
|
|
// 跳过系统保留键
|
|||
|
|
if (in_array($key, ['filters', 'format', 'limit', 'field', 'order', 'page'])) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理普通字段(字符串键且非数组)
|
|||
|
|
if (is_string($key) && !is_array($value)) {
|
|||
|
|
// 检查是否是 'op:value' 格式
|
|||
|
|
if (strpos($value, ':') !== false) {
|
|||
|
|
[$op, $val] = explode(':', $value, 2);
|
|||
|
|
$query->where($key, $op, $val);
|
|||
|
|
} else {
|
|||
|
|
$query->where($key, '=', $value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $query;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取 SelectInput 配置
|
|||
|
|
* 保持原有行为,默认不启用新特性
|
|||
|
|
*
|
|||
|
|
* @param Request $request
|
|||
|
|
* @return array
|
|||
|
|
*/
|
|||
|
|
protected function getSelectInputConfig(Request $request): array
|
|||
|
|
{
|
|||
|
|
$format = $request->input('format', 'normal');
|
|||
|
|
$defaultConfig = $this->selectInputConfig ?? [];
|
|||
|
|
|
|||
|
|
// 如果子类没有配置自定义选项,使用完全兼容的默认配置
|
|||
|
|
if (empty($defaultConfig)) {
|
|||
|
|
return [
|
|||
|
|
'return_format' => 'array',
|
|||
|
|
'param_format' => 'auto', // 自动检测参数格式,兼容现有所有格式
|
|||
|
|
'skip_fields' => [], // 默认不跳过任何字段
|
|||
|
|
'allowed_fields' => null, // 默认不限制字段
|
|||
|
|
'sort_fields' => null, // 默认不限制排序字段
|
|||
|
|
'default_sort' => [], // 默认不设置默认排序
|
|||
|
|
'default_page_size' => $format === 'tree' ? 10000 : 10,
|
|||
|
|
'max_page_size' => 10000,
|
|||
|
|
'default_page' => 1,
|
|||
|
|
'keyword_fields' => [], // 默认不启用关键词搜索
|
|||
|
|
'enable_keyword_search' => false,
|
|||
|
|
'enable_range_query' => true, // 启用范围查询
|
|||
|
|
'enable_like_query' => true, // 启用模糊匹配
|
|||
|
|
'auto_convert_datetime' => true,
|
|||
|
|
'skip_empty_values' => true,
|
|||
|
|
'custom_fields' => [], // 默认无自定义字段
|
|||
|
|
'filter_format' => 'keyvalue', // 过滤器格式:array、keyvalue、simple
|
|||
|
|
'default_operator' => 'eq', // 默认操作符
|
|||
|
|
'exclude_filters' => false, // 是否排除 filters 键,默认为 false(包含 filters)
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 子类配置了自定义选项,合并默认值
|
|||
|
|
return array_merge([
|
|||
|
|
'return_format' => 'array',
|
|||
|
|
'param_format' => 'auto',
|
|||
|
|
'skip_fields' => ['password', 'token', 'deleted_at'],
|
|||
|
|
'allowed_fields' => null,
|
|||
|
|
'sort_fields' => null,
|
|||
|
|
'default_sort' => [],
|
|||
|
|
'default_page_size' => $format === 'tree' ? 10000 : 10,
|
|||
|
|
'max_page_size' => 10000,
|
|||
|
|
'default_page' => 1,
|
|||
|
|
'keyword_fields' => [],
|
|||
|
|
'enable_keyword_search' => false,
|
|||
|
|
'enable_range_query' => true,
|
|||
|
|
'enable_like_query' => true,
|
|||
|
|
'auto_convert_datetime' => true,
|
|||
|
|
'skip_empty_values' => true,
|
|||
|
|
'custom_fields' => [], // 默认无自定义字段
|
|||
|
|
'filter_format' => 'keyvalue', // 过滤器格式:array([[字段, 操作符, 值]])、keyvalue([字段 => 'op:value'])、simple([字段 => 值])
|
|||
|
|
'default_operator' => 'eq', // 默认操作符
|
|||
|
|
'exclude_filters' => false, // 是否排除 filters 键,默认为 false(包含 filters)
|
|||
|
|
], $defaultConfig);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 验证字段是否有效
|
|||
|
|
*
|
|||
|
|
* @param string $field
|
|||
|
|
* @param array $allow_column
|
|||
|
|
* @return string
|
|||
|
|
*/
|
|||
|
|
protected function validateField(string $field, array $allow_column): string
|
|||
|
|
{
|
|||
|
|
if ($field === '*') {
|
|||
|
|
return '*';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理多个字段(逗号分隔)
|
|||
|
|
$fields = array_map('trim', explode(',', $field));
|
|||
|
|
$validFields = [];
|
|||
|
|
|
|||
|
|
foreach ($fields as $f) {
|
|||
|
|
if (in_array($f, $allow_column)) {
|
|||
|
|
$validFields[] = $f;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
return empty($validFields) ? '*' : implode(', ', $validFields);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 插入前置方法
|
|||
|
|
*
|
|||
|
|
* @param Request $request
|
|||
|
|
*
|
|||
|
|
* @return array
|
|||
|
|
*/
|
|||
|
|
protected function insertInput(Request $request): array
|
|||
|
|
{
|
|||
|
|
$data = $this->inputFilter($request->all());
|
|||
|
|
$password_filed = 'password';
|
|||
|
|
if (isset($data[$password_filed])) {
|
|||
|
|
$data[$password_filed] = password_hash($data[$password_filed], PASSWORD_DEFAULT);
|
|||
|
|
}
|
|||
|
|
return $data;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 对用户输入表单过滤
|
|||
|
|
*
|
|||
|
|
* @param array $data
|
|||
|
|
* @param array $skipKeys
|
|||
|
|
*
|
|||
|
|
* @return array
|
|||
|
|
*/
|
|||
|
|
protected function inputFilter(array $data, array $skipKeys = []): array
|
|||
|
|
{
|
|||
|
|
$model = $this->service->getModel();
|
|||
|
|
$columns = $model->getFields();
|
|||
|
|
foreach ($data as $col => $item) {
|
|||
|
|
// 检查是否在跳过的键中,或者不在列中
|
|||
|
|
if (!in_array($col, $columns) && !in_array($col, $skipKeys)) {
|
|||
|
|
unset($data[$col]);
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理时间字段
|
|||
|
|
if (empty($data['created_at']) && !in_array('created_at', $skipKeys)) {
|
|||
|
|
unset($data['created_at']);
|
|||
|
|
}
|
|||
|
|
if (empty($data['updated_at']) && !in_array('updated_at', $skipKeys)) {
|
|||
|
|
unset($data['updated_at']);
|
|||
|
|
}
|
|||
|
|
return $data;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 格式化树
|
|||
|
|
*
|
|||
|
|
* @param $items
|
|||
|
|
*
|
|||
|
|
* @return \support\Response
|
|||
|
|
*/
|
|||
|
|
protected function formatTree($items): \support\Response
|
|||
|
|
{
|
|||
|
|
$format_items = [];
|
|||
|
|
foreach ($items as $item) {
|
|||
|
|
$format_items[] = [
|
|||
|
|
'name' => $item->title ?? $item->name ?? $item->id,
|
|||
|
|
'value' => (string)$item->id,
|
|||
|
|
'id' => $item->id,
|
|||
|
|
'pid' => $item->pid,
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
$tree = new Tree($format_items);
|
|||
|
|
return Json::success('ok', $tree->getTree());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 格式化表格树
|
|||
|
|
*
|
|||
|
|
* @param $data
|
|||
|
|
* @param $total
|
|||
|
|
*
|
|||
|
|
* @return \support\Response
|
|||
|
|
*/
|
|||
|
|
protected function formatTableTree($data, $total): \support\Response
|
|||
|
|
{
|
|||
|
|
$tree = new Tree($data->toArray());
|
|||
|
|
$items = $tree->getTree();
|
|||
|
|
return Json::success('ok', $items);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 格式化下拉列表
|
|||
|
|
*
|
|||
|
|
* @param $items
|
|||
|
|
*
|
|||
|
|
* @return \support\Response
|
|||
|
|
*/
|
|||
|
|
protected function formatSelect($items): \support\Response
|
|||
|
|
{
|
|||
|
|
$formatted_items = [];
|
|||
|
|
foreach ($items as $item) {
|
|||
|
|
$formatted_items[] = [
|
|||
|
|
'label' => $item->title ?? $item->name ?? $item->real_name ?? $item->id,
|
|||
|
|
'value' => $item->id,
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
return Json::success('ok', $formatted_items);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 通用格式化
|
|||
|
|
*
|
|||
|
|
* @param $items
|
|||
|
|
* @param $total
|
|||
|
|
*
|
|||
|
|
* @return \support\Response
|
|||
|
|
*/
|
|||
|
|
protected function formatNormal($items, $total): \support\Response
|
|||
|
|
{
|
|||
|
|
return Json::success('ok', compact('items', 'total'));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function dev(Request $request): \support\Response
|
|||
|
|
{
|
|||
|
|
return Json::fail('接口开发中');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|