HasQuickSearch.php 8.8 KB

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