Files
2026-08-31 11:33:59 +08:00

1225 lines
41 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
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('接口开发中');
}
}