HasQuickSearch.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. <?php
  2. namespace Dcat\Admin\Grid\Concerns;
  3. use Dcat\Admin\Grid\Column;
  4. use Dcat\Admin\Grid\Model;
  5. use Dcat\Admin\Grid\Tools;
  6. use Dcat\Admin\Support\Helper;
  7. use Illuminate\Support\Collection;
  8. use Illuminate\Support\Str;
  9. /**
  10. * @property Collection $columns
  11. * @property Tools $tools
  12. *
  13. * @method Model model()
  14. */
  15. trait HasQuickSearch
  16. {
  17. /**
  18. * @var array|string|\Closure
  19. */
  20. protected $search;
  21. /**
  22. * @var Tools\QuickSearch
  23. */
  24. protected $quickSearch;
  25. /**
  26. * @param array|string|\Closure
  27. *
  28. * @return Tools\QuickSearch
  29. */
  30. public function quickSearch($search = null)
  31. {
  32. if (func_num_args() > 1) {
  33. $this->search = func_get_args();
  34. } else {
  35. $this->search = $search;
  36. }
  37. if ($this->quickSearch) {
  38. return $this->quickSearch;
  39. }
  40. return tap(new Tools\QuickSearch(), function ($search) {
  41. $this->quickSearch = $search;
  42. $search->setGrid($this);
  43. });
  44. }
  45. /**
  46. * @return Tools\QuickSearch
  47. */
  48. public function getQuickSearch()
  49. {
  50. return $this->quickSearch;
  51. }
  52. /**
  53. * @return \Illuminate\View\View|string
  54. */
  55. public function renderQuickSearch()
  56. {
  57. if (! $this->quickSearch) {
  58. return '';
  59. }
  60. return $this->quickSearch->render();
  61. }
  62. /**
  63. * Apply the search query to the query.
  64. *
  65. * @return mixed|void
  66. */
  67. public function applyQuickSearch()
  68. {
  69. if (! $this->quickSearch) {
  70. return;
  71. }
  72. $query = request()->get($this->quickSearch->getQueryName());
  73. if ($query === '' || $query === null) {
  74. return;
  75. }
  76. // 树表格子节点忽略查询条件
  77. $this->model()
  78. ->disableBindTreeQuery()
  79. ->treeUrlWithoutQuery(
  80. $this->quickSearch->getQueryName()
  81. );
  82. if ($this->search instanceof \Closure) {
  83. return $this->model()->where(function ($q) use ($query) {
  84. return call_user_func($this->search, $q, $query);
  85. });
  86. }
  87. if (is_string($this->search)) {
  88. $this->search = [$this->search];
  89. }
  90. if (is_array($this->search)) {
  91. $this->model()->where(function ($q) use ($query) {
  92. $keyword = '%'.$query.'%';
  93. foreach ($this->search as $column) {
  94. $this->addWhereLikeBinding($q, $column, true, $keyword);
  95. }
  96. });
  97. } elseif (is_null($this->search)) {
  98. $this->addWhereBindings($query);
  99. }
  100. }
  101. /**
  102. * Add where bindings.
  103. *
  104. * @param string $query
  105. */
  106. protected function addWhereBindings($query)
  107. {
  108. $queries = preg_split('/\s(?=([^"]*"[^"]*")*[^"]*$)/', trim($query));
  109. if (! $queries = $this->parseQueryBindings($queries)) {
  110. $this->addWhereBasicBinding($this->model(), $this->getKeyName(), false, '=', '___');
  111. return;
  112. }
  113. $this->model()->where(function ($q) use ($queries) {
  114. foreach ($queries as [$column, $condition, $or]) {
  115. if (preg_match('/(?<not>!?)\((?<values>.+)\)/', $condition, $match) !== 0) {
  116. $this->addWhereInBinding($q, $column, $or, (bool) $match['not'], $match['values']);
  117. continue;
  118. }
  119. if (preg_match('/\[(?<start>.*?),(?<end>.*?)]/', $condition, $match) !== 0) {
  120. $this->addWhereBetweenBinding($q, $column, $or, $match['start'], $match['end']);
  121. continue;
  122. }
  123. if (preg_match('/(?<function>date|time|day|month|year),(?<value>.*)/', $condition, $match) !== 0) {
  124. $this->addWhereDatetimeBinding($q, $column, $or, $match['function'], $match['value']);
  125. continue;
  126. }
  127. if (preg_match('/(?<pattern>%[^%]+%)/', $condition, $match) !== 0) {
  128. $this->addWhereLikeBinding($q, $column, $or, $match['pattern']);
  129. continue;
  130. }
  131. if (preg_match('/(?<pattern>[^%]+%)/', $condition, $match) !== 0) {
  132. $this->addWhereLikeBinding($q, $column, $or, $match['pattern']);
  133. continue;
  134. }
  135. if (preg_match('/\/(?<value>.*)\//', $condition, $match) !== 0) {
  136. $this->addWhereBasicBinding($q, $column, $or, 'REGEXP', $match['value']);
  137. continue;
  138. }
  139. if (preg_match('/(?<operator>>=?|<=?|!=|%){0,1}(?<value>.*)/', $condition, $match) !== 0) {
  140. $this->addWhereBasicBinding($q, $column, $or, $match['operator'], $match['value']);
  141. continue;
  142. }
  143. }
  144. });
  145. }
  146. /**
  147. * Parse quick query bindings.
  148. *
  149. * @param array $queries
  150. *
  151. * @return array
  152. */
  153. protected function parseQueryBindings(array $queries)
  154. {
  155. $columnMap = $this->columns->mapWithKeys(function (Column $column) {
  156. $label = $column->getLabel();
  157. $name = $column->getName();
  158. return [$label => $name, $name => $name];
  159. });
  160. return collect($queries)->map(function ($query) use ($columnMap) {
  161. $segments = explode(':', $query, 2);
  162. if (count($segments) != 2) {
  163. return;
  164. }
  165. $or = false;
  166. [$column, $condition] = $segments;
  167. if (Str::startsWith($column, '|')) {
  168. $or = true;
  169. $column = substr($column, 1);
  170. }
  171. $column = $columnMap[$column] ?? null;
  172. if (! $column) {
  173. return;
  174. }
  175. return [$column, $condition, $or];
  176. })->filter()->toArray();
  177. }
  178. /**
  179. * Add where like binding to model query.
  180. *
  181. * @param mixed $query
  182. * @param string $column
  183. * @param bool $or
  184. * @param string $pattern
  185. */
  186. protected function addWhereLikeBinding($query, ?string $column, ?bool $or, ?string $pattern)
  187. {
  188. $likeOperator = 'like';
  189. $method = $or ? 'orWhere' : 'where';
  190. Helper::withQueryCondition($query, $column, $method, [$likeOperator, $pattern]);
  191. }
  192. /**
  193. * Add where date time function binding to model query.
  194. *
  195. * @param mixed $query
  196. * @param string $column
  197. * @param bool $or
  198. * @param string $function
  199. * @param string $value
  200. */
  201. protected function addWhereDatetimeBinding($query, ?string $column, ?bool $or, ?string $function, ?string $value)
  202. {
  203. $method = ($or ? 'orWhere' : 'where').ucfirst($function);
  204. Helper::withQueryCondition($query, $column, $method, [$value]);
  205. }
  206. /**
  207. * Add where in binding to the model query.
  208. *
  209. * @param mixed $query
  210. * @param string $column
  211. * @param bool $or
  212. * @param bool $not
  213. * @param string $values
  214. */
  215. protected function addWhereInBinding($query, ?string $column, ?bool $or, ?bool $not, ?string $values)
  216. {
  217. $values = explode(',', $values);
  218. foreach ($values as $key => $value) {
  219. if ($value === 'NULL') {
  220. $values[$key] = null;
  221. }
  222. }
  223. $where = $or ? 'orWhere' : 'where';
  224. $method = $where.($not ? 'NotIn' : 'In');
  225. Helper::withQueryCondition($query, $column, $method, [$values]);
  226. }
  227. /**
  228. * Add where between binding to the model query.
  229. *
  230. * @param mixed $query
  231. * @param string $column
  232. * @param bool $or
  233. * @param string $start
  234. * @param string $end
  235. */
  236. protected function addWhereBetweenBinding($query, ?string $column, ?bool $or, ?string $start, ?string $end)
  237. {
  238. $method = $or ? 'orWhereBetween' : 'whereBetween';
  239. Helper::withQueryCondition($query, $column, $method, [[$start, $end]]);
  240. }
  241. /**
  242. * Add where basic binding to the model query.
  243. *
  244. * @param mixed $query
  245. * @param string $column
  246. * @param bool $or
  247. * @param string $operator
  248. * @param string $value
  249. */
  250. protected function addWhereBasicBinding($query, ?string $column, ?bool $or, ?string $operator, ?string $value)
  251. {
  252. $method = $or ? 'orWhere' : 'where';
  253. $operator = $operator ?: '=';
  254. if ($operator == '%') {
  255. $operator = 'like';
  256. $value = "%{$value}%";
  257. }
  258. if ($value === 'NULL') {
  259. $value = null;
  260. }
  261. if (Str::startsWith($value, '"') && Str::endsWith($value, '"')) {
  262. $value = substr($value, 1, -1);
  263. }
  264. Helper::withQueryCondition($query, $column, $method, [$operator, $value]);
  265. }
  266. }