HasQuickSearch.php 9.4 KB

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