Layui.php 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165
  1. <?php
  2. namespace plugin\admin\app\common;
  3. use plugin\admin\app\common\Util;
  4. use support\exception\BusinessException;
  5. class Layui
  6. {
  7. /**
  8. * 生成的html代码
  9. * @var string
  10. */
  11. protected $htmlContent = '';
  12. /**
  13. * 生成的js代码
  14. * @var string
  15. */
  16. protected $jsContent = '';
  17. /**
  18. * 获取生成的html代码
  19. * @param $indent
  20. * @return string
  21. */
  22. public function html($indent = 0): string
  23. {
  24. return str_replace("\n", "\n" . str_repeat(' ', $indent), $this->htmlContent);
  25. }
  26. /**
  27. * 获取生成的js代码
  28. * @param $indent
  29. * @return string
  30. */
  31. public function js($indent = 0): string
  32. {
  33. return str_replace("\n", "\n" . str_repeat(' ', $indent), $this->jsContent);
  34. }
  35. /**
  36. * 获取控件及相关参数
  37. * @param $options
  38. * @return array
  39. */
  40. protected function options($options): array
  41. {
  42. array_walk_recursive($options, function(&$item, $key){
  43. if (is_string($item)) {
  44. $item = htmlspecialchars($item);
  45. if ($key === 'url') {
  46. $item = str_replace('&amp;', '&', $item);
  47. }
  48. }
  49. });
  50. $field = $options['field']??'';
  51. $props = !empty($options['props']) ? $options['props'] : [];
  52. $verify_string = !empty($props['lay-verify']) ? ' lay-verify="'.$props['lay-verify'].'"' : '';
  53. $required_string = strpos($verify_string, 'required') ? ' required' : '';
  54. $label = !empty($options['label']) ? '<label class="layui-form-label'.$required_string.'">'.$options['label'].'</label>' : '';
  55. $value = $props['value'] ?? '';
  56. $class = $props['class'] ?? 'layui-input-block';
  57. return [$label, $field, $value, $props, $verify_string, $required_string, $class];
  58. }
  59. /**
  60. * input输入框
  61. * @param $options
  62. * @return void
  63. */
  64. public function input($options)
  65. {
  66. [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options);
  67. $placeholder_string = !empty($props['placeholder']) ? ' placeholder="'.$props['placeholder'].'"' : '';
  68. $autocomplete_string = !empty($props['autocomplete']) ? ' autocomplete="'.$props['autocomplete'].'"' : '';
  69. $disabled_string = !empty($props['disabled']) ? ' disabled' : '';
  70. $type = $props['type'] ?? 'text';
  71. $this->htmlContent .= <<<EOF
  72. <div class="layui-form-item">
  73. $label
  74. <div class="$class">
  75. <input type="$type" name="$field" value="$value"$disabled_string$required_string$verify_string$placeholder_string$autocomplete_string class="layui-input">
  76. </div>
  77. </div>
  78. EOF;
  79. }
  80. /**
  81. * input数字输入框
  82. * @param $options
  83. * @return void
  84. */
  85. public function inputNumber($options)
  86. {
  87. $options['props']['type'] = 'number';
  88. $this->input($options);
  89. }
  90. /**
  91. * 输入框范围
  92. * @param $options
  93. * @return void
  94. */
  95. public function inputRange($options)
  96. {
  97. [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options);
  98. $type = $props['type'] ?? 'text';
  99. $this->htmlContent .= <<<EOF
  100. <div class="layui-form-item">
  101. $label
  102. <div class="$class">
  103. <div class="layui-input-block">
  104. <input type="$type" autocomplete="off" name="{$field}[]" class="layui-input inline-block" placeholder="开始">
  105. -
  106. <input type="$type" autocomplete="off" name="{$field}[]" class="layui-input inline-block" placeholder="结束">
  107. </div>
  108. </div>
  109. </div>
  110. EOF;
  111. }
  112. /**
  113. * 输入框模糊查询
  114. * @param $options
  115. * @return void
  116. */
  117. public function inputLike($options)
  118. {
  119. [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options);
  120. $type = $props['type'] ?? 'text';
  121. $this->htmlContent .= <<<EOF
  122. <div class="layui-form-item">
  123. $label
  124. <div class="$class">
  125. <div class="layui-input-block">
  126. <input type="hidden" autocomplete="off" name="{$field}[]" value="like" class="layui-input inline-block">
  127. <input type="$type" autocomplete="off" name="{$field}[]" class="layui-input">
  128. </div>
  129. </div>
  130. </div>
  131. EOF;
  132. }
  133. /**
  134. * 数字输入框范围
  135. * @param $options
  136. * @return void
  137. */
  138. public function inputNumberRange($options)
  139. {
  140. $options['props']['type'] = 'number';
  141. $this->inputRange($options);
  142. }
  143. /**
  144. * 数字输入框模糊查询
  145. * @param $options
  146. * @return void
  147. */
  148. public function inputNumberLike($options)
  149. {
  150. $options['props']['type'] = 'number';
  151. $this->inputLike($options);
  152. }
  153. /**
  154. * 密码输入框
  155. * @param $options
  156. * @return void
  157. */
  158. public function inputPassword($options)
  159. {
  160. $options['props']['type'] = 'password';
  161. $this->input($options);
  162. }
  163. /**
  164. * 文本域
  165. * @param $options
  166. * @return void
  167. */
  168. public function textArea($options)
  169. {
  170. [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options);
  171. $placeholder_string = !empty($props['placeholder']) ? ' placeholder="'.$props['placeholder'].'"' : '';
  172. $disabled_string = !empty($props['disabled']) ? ' disabled' : '';
  173. $this->htmlContent .= <<<EOF
  174. <div class="layui-form-item">
  175. $label
  176. <div class="$class">
  177. <textarea name="$field"$required_string$verify_string$placeholder_string$disabled_string class="layui-textarea">$value</textarea>
  178. </div>
  179. </div>
  180. EOF;
  181. }
  182. /**
  183. * 富文本
  184. * @param $options
  185. * @return void
  186. */
  187. public function richText($options)
  188. {
  189. [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options);
  190. $placeholder_string = !empty($props['placeholder']) ? ' placeholder="'.$props['placeholder'].'"' : '';
  191. $disabled_string = !empty($props['disabled']) ? ' disabled' : '';
  192. $id = $field;
  193. $this->htmlContent .= <<<EOF
  194. <div class="layui-form-item">
  195. $label
  196. <div class="$class">
  197. <textarea id="$id" name="$field"$required_string$verify_string$placeholder_string$disabled_string class="layui-textarea">$value</textarea>
  198. </div>
  199. </div>
  200. EOF;
  201. $options_string = '';
  202. if (!isset($props['images_upload_url'])) {
  203. $props['images_upload_url'] = '/app/admin/upload/image';
  204. }
  205. $props = $this->prepareProps($props);
  206. $options_string .= "\n" . $this->preparePropsToJsObject($props, 1, true);
  207. $this->jsContent .= <<<EOF
  208. // 字段 {$options['label']} $field
  209. layui.use(["tinymce"], function() {
  210. var tinymce = layui.tinymce
  211. var edit = tinymce.render({
  212. elem: "#$id",$options_string
  213. });
  214. edit.on("blur", function(){
  215. layui.$("#$id").val(edit.getContent());
  216. });
  217. });
  218. EOF;
  219. }
  220. /**
  221. * json编辑框
  222. * @param $options
  223. * @return void
  224. */
  225. public function jsonEditor($options)
  226. {
  227. [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options);
  228. $placeholder_string = !empty($props['placeholder']) ? ' placeholder="'.$props['placeholder'].'"' : '';
  229. $autocomplete_string = !empty($props['autocomplete']) ? ' autocomplete="'.$props['autocomplete'].'"' : '';
  230. $disabled_string = !empty($props['disabled']) ? ' disabled' : '';
  231. $type = $props['type'] ?? 'text';
  232. if (empty($value)){
  233. $value='{}';
  234. }
  235. $this->htmlContent .= <<<EOF
  236. <div class="layui-form-item">
  237. $label
  238. <div class="$class">
  239. <input type="$type" name="$field"id="$field" value="$value"$disabled_string$required_string$verify_string$placeholder_string$autocomplete_string class="layui-input">
  240. </div>
  241. </div>
  242. EOF;
  243. $this->jsContent .= <<<EOF
  244. jsonArea({
  245. el: "#$field",
  246. change: function(data) {
  247. console.log(data);
  248. }
  249. });
  250. EOF;
  251. }
  252. /**
  253. * 上传组件
  254. * @param $options
  255. * @return void
  256. */
  257. public function upload($options)
  258. {
  259. [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options);
  260. $props['accept'] = $props['accept'] ?? 'file';
  261. $props['url'] = $props['url'] ?? '/app/admin/upload/file';
  262. $id = $this->createId($field);
  263. $props['field'] = $props['field'] ?? '__file__';
  264. unset($props['lay-verify']);
  265. $options_string = '';
  266. $props = $this->prepareProps($props);
  267. $options_string .= "\n" . $this->preparePropsToJsObject($props, 1, true);
  268. $this->htmlContent .= <<<EOF
  269. <div class="layui-form-item">
  270. $label
  271. <div class="$class">
  272. <span>$value</span>
  273. <input type="text" style="display:none" name="$field" value="$value" />
  274. <button type="button" class="pear-btn pear-btn-primary pear-btn-sm" id="$id" permission="app.admin.upload.file">
  275. <i class="layui-icon layui-icon-upload"></i>上传文件
  276. </button>
  277. <button type="button" class="pear-btn pear-btn-primary pear-btn-sm" id="attachment-choose-$id" permission="app.admin.upload.attachment">
  278. <i class="layui-icon layui-icon-align-left"></i>选择文件
  279. </button>
  280. </div>
  281. </div>
  282. EOF;
  283. $this->jsContent .= <<<EOF
  284. // 字段 {$options['label']} $field
  285. layui.use(["upload", "layer", "popup", "util"], function() {
  286. let input = layui.$("#$id").prev();
  287. input.prev().html(layui.util.escape(input.val()));
  288. layui.$("#attachment-choose-$id").on("click", function() {
  289. parent.layer.open({
  290. type: 2,
  291. title: "选择附件",
  292. content: "/app/admin/upload/attachment",
  293. area: ["95%", "90%"],
  294. success: function (layero, index) {
  295. parent.layui.$("#layui-layer" + index).data("callback", function (data) {
  296. input.val(data.url).prev().html(layui.util.escape(data.url));
  297. });
  298. }
  299. });
  300. });
  301. layui.upload.render({
  302. elem: "#$id",$options_string
  303. done: function (res) {
  304. if (res.code) return layui.popup.failure(res.msg);
  305. this.item.prev().val(res.data.url).prev().html(layui.util.escape(res.data.url));
  306. }
  307. });
  308. });
  309. EOF;
  310. }
  311. /**
  312. * 图片上传组件
  313. * @param $options
  314. * @return void
  315. */
  316. public function uploadImage($options)
  317. {
  318. [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options);
  319. $props['acceptMime'] = $props['acceptMime'] ?? 'image/gif,image/jpeg,image/jpg,image/png';
  320. $props['url'] = $props['url'] ?? '/app/admin/upload/image';
  321. $id = $this->createId($field);
  322. unset($props['lay-verify']);
  323. $props['field'] = $props['field'] ?? '__file__';
  324. $options_string = '';
  325. $props = $this->prepareProps($props);
  326. $options_string .= "\n" . $this->preparePropsToJsObject($props, 1, true);
  327. $this->htmlContent .= <<<EOF
  328. <div class="layui-form-item">
  329. $label
  330. <div class="$class">
  331. <img class="img-3" src=""/>
  332. <input type="text" style="display:none" name="$field" value="$value" />
  333. <button type="button" class="pear-btn pear-btn-primary pear-btn-sm" id="$id" permission="app.admin.upload.image">
  334. <i class="layui-icon layui-icon-upload"></i>上传图片
  335. </button>
  336. <button type="button" class="pear-btn pear-btn-primary pear-btn-sm" id="attachment-choose-$id" permission="app.admin.upload.attachment">
  337. <i class="layui-icon layui-icon-align-left"></i>选择图片
  338. </button>
  339. </div>
  340. </div>
  341. EOF;
  342. $this->jsContent .= <<<EOF
  343. // 字段 {$options['label']} $field
  344. layui.use(["upload", "layer"], function() {
  345. let input = layui.$("#$id").prev();
  346. input.prev().attr("src", input.val());
  347. layui.$("#attachment-choose-$id").on("click", function() {
  348. parent.layer.open({
  349. type: 2,
  350. title: "选择附件",
  351. content: "/app/admin/upload/attachment?ext=jpg,jpeg,png,gif,bmp",
  352. area: ["95%", "90%"],
  353. success: function (layero, index) {
  354. parent.layui.$("#layui-layer" + index).data("callback", function (data) {
  355. input.val(data.url).prev().attr("src", data.url);
  356. });
  357. }
  358. });
  359. });
  360. layui.upload.render({
  361. elem: "#$id",$options_string
  362. done: function (res) {
  363. if (res.code > 0) return layui.layer.msg(res.msg);
  364. this.item.prev().val(res.data.url).prev().attr("src", res.data.url);
  365. }
  366. });
  367. });
  368. EOF;
  369. }
  370. /**
  371. * 日期时间选择组件
  372. * @param $options
  373. * @return void
  374. */
  375. public function dateTimePicker($options)
  376. {
  377. $options['props']['type'] = 'datetime';
  378. $this->datePicker($options);
  379. }
  380. /**
  381. * 日期选择组件
  382. * @param $options
  383. * @return void
  384. */
  385. public function datePicker($options)
  386. {
  387. [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options);
  388. $value_string = $value ? ' value="'.$value.'"' : '';
  389. $options_string = '';
  390. unset($props['required'], $props['lay-verify'], $props['value']);
  391. $props = $this->prepareProps($props);
  392. $options_string .= "\n" . $this->preparePropsToJsObject($props, 1, true);
  393. $id = $this->createId($field);
  394. $this->htmlContent .= <<<EOF
  395. <div class="layui-form-item">
  396. $label
  397. <div class="$class">
  398. <input type="text" name="$field" id="$id"$value_string$required_string$verify_string autocomplete="off" class="layui-input">
  399. </div>
  400. </div>
  401. EOF;
  402. $this->jsContent .= <<<EOF
  403. // 字段 {$options["label"]} $field
  404. layui.use(["laydate"], function() {
  405. layui.laydate.render({
  406. elem: "#$id",$options_string
  407. });
  408. })
  409. EOF;
  410. }
  411. /**
  412. * 日期时间范围选择组件
  413. * @param $options
  414. * @return void
  415. */
  416. public function dateTimePickerRange($options)
  417. {
  418. $options['props']['type'] = 'datetime';
  419. $this->datePickerRange($options);
  420. }
  421. /**
  422. * 日期范围选择组件
  423. * @param $options
  424. * @return void
  425. */
  426. public function datePickerRange($options)
  427. {
  428. [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options);
  429. if (!isset($options['props']['type'])) {
  430. $options['props']['type'] = 'date';
  431. }
  432. $options_string = '';
  433. unset($props['required'], $props['lay-verify'], $props['value']);
  434. $props = $this->prepareProps($props);
  435. $options_string .= "\n" . $this->preparePropsToJsObject($props, 1, true);
  436. $id = $this->createId($field);
  437. $id_start = "$id-date-start";
  438. $id_end = "$id-date-end";
  439. $this->htmlContent .= <<<EOF
  440. <div class="layui-form-item">
  441. $label
  442. <div class="$class">
  443. <div class="layui-input-block" id="$id">
  444. <input type="text" autocomplete="off" name="{$field}[]" id="$id_start" class="layui-input inline-block" placeholder="开始时间">
  445. -
  446. <input type="text" autocomplete="off" name="{$field}[]" id="$id_end" class="layui-input inline-block" placeholder="结束时间">
  447. </div>
  448. </div>
  449. </div>
  450. EOF;
  451. $this->jsContent .= <<<EOF
  452. // 字段 {$options['label']} $field
  453. layui.use(["laydate"], function() {
  454. layui.laydate.render({
  455. elem: "#$id",
  456. range: ["#$id_start", "#$id_end"],$options_string
  457. });
  458. })
  459. EOF;
  460. }
  461. /**
  462. * 创建id
  463. * @param $field
  464. * @return mixed
  465. */
  466. protected function createId($field)
  467. {
  468. return $field;
  469. }
  470. /**
  471. * 图标选择组件
  472. * @param $options
  473. * @return void
  474. */
  475. public function iconPicker($options)
  476. {
  477. [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options);
  478. $value_string = $value ? ' value="'.$value.'"' : '';
  479. $id = $this->createId($field);
  480. $options_string = '';
  481. $props = $this->prepareProps($props);
  482. $options_string .= "\n" . $this->preparePropsToJsObject($props, 1, true);
  483. $this->htmlContent .= <<<EOF
  484. <div class="layui-form-item">
  485. $label
  486. <div class="$class">
  487. <input name="$field" id="$id"$value_string$required_string$verify_string />
  488. </div>
  489. </div>
  490. EOF;
  491. $this->jsContent .= <<<EOF
  492. // 字段 {$options['label']} $field
  493. layui.use(["iconPicker"], function() {
  494. layui.iconPicker.render({
  495. elem: "#$id",
  496. type: "fontClass",
  497. page: false,$options_string
  498. });
  499. });
  500. EOF;
  501. }
  502. /**
  503. * switch组件
  504. * @param $options
  505. * @return void
  506. */
  507. public function switch($options)
  508. {
  509. [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options);
  510. $value = (int)$value;
  511. $disabled_string = !empty($props['disabled']) ? ' disabled' : '';
  512. $lay_text = !empty($props['lay-text']) ? "lay-text=\"{$props['lay-text']}\"" : '';
  513. $id = $this->createId($field);
  514. $this->htmlContent .= <<<EOF
  515. <div class="layui-form-item">
  516. $label
  517. <div class="$class">
  518. <input type="checkbox" id="$id" lay-filter="$field"$disabled_string$required_string lay-skin="switch" $lay_text/>
  519. <input type="text" style="display:none" name="$field" value="$value"$required_string />
  520. </div>
  521. </div>
  522. EOF;
  523. $this->jsContent .= <<<EOF
  524. // 字段 {$options['label']} $field
  525. layui.use(["form"], function() {
  526. layui.$("#$id").attr("checked", layui.$('input[name="$field"]').val() != 0);
  527. layui.form.render();
  528. layui.form.on("switch($field)", function(data) {
  529. layui.$('input[name="$field"]').val(this.checked ? 1 : 0);
  530. });
  531. })
  532. EOF;
  533. }
  534. /**
  535. * 下拉选择组件
  536. * @return void
  537. */
  538. public function select($options)
  539. {
  540. $options['props']['model'] = array_merge_recursive([
  541. 'icon' => 'hidden',
  542. 'label' => [
  543. 'type' => 'text',
  544. ]
  545. ], $options['props']['model'] ?? []);
  546. $options['props']['clickClose'] = $options['props']['clickClose'] ?? true;
  547. $options['props']['radio'] = $options['props']['radio'] ?? true;
  548. $this->apiSelect($options);
  549. }
  550. /**
  551. * 下拉多选组件
  552. * @return void
  553. */
  554. public function selectMulti($options)
  555. {
  556. $options['props']['toolbar'] = array_merge_recursive([
  557. 'show' => true,
  558. 'list' => [ 'ALL', 'CLEAR', 'REVERSE' ]
  559. ], $options['props']['toolbar'] ?? []);
  560. $this->apiSelect($options);
  561. }
  562. /**
  563. * 树单选组件
  564. * @return void
  565. */
  566. public function treeSelect($options)
  567. {
  568. $options['props']['model'] = array_merge_recursive([
  569. 'icon' => 'hidden',
  570. 'label' => [
  571. 'type' => 'text',
  572. ]
  573. ], $options['props']['model'] ?? []);
  574. $options['props']['clickClose'] = $options['props']['clickClose'] ?? true;
  575. $options['props']['radio'] = $options['props']['radio'] ?? true;
  576. $options['props']['tree'] = array_merge_recursive([
  577. '$show' => true,
  578. '$strict' => false,
  579. '$clickCheck' => true,
  580. '$clickExpand' => false,
  581. '$expandedKeys' => '$initValue'
  582. ], $options['props']['tree'] ?? []);
  583. $this->apiSelect($options);
  584. }
  585. /**
  586. * 树多选组件
  587. * @return void
  588. */
  589. public function treeSelectMulti($options)
  590. {
  591. $options['props']['tree'] = array_merge_recursive(['show' => true,
  592. '$expandedKeys' => '$initValue'], $options['props']['tree'] ?? []);
  593. $options['props']['toolbar'] = array_merge_recursive([
  594. '$show' => true,
  595. '$list' => [ 'ALL', 'CLEAR', 'REVERSE' ]
  596. ], $options['props']['toolbar'] ?? []);
  597. $this->apiSelect($options);
  598. }
  599. /**
  600. * 选择框,支持单选、多选、树形选择
  601. * @see https://maplemei.gitee.io/xm-select/
  602. * @param $options
  603. * @return void
  604. */
  605. public function apiSelect($options)
  606. {
  607. [$select_label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options);
  608. $default_value_string = isset($props['initValue']) && $props['initValue'] != '' ? $props['initValue'] : $value;
  609. $url = $props['url'] ?? '';
  610. $options_string = '';
  611. if (isset($props['lay-verify'])) {
  612. $props['layVerify'] = $props['lay-verify'];
  613. }
  614. unset($props['lay-verify'], $props['url']);
  615. foreach ($props as $key => $item) {
  616. if (is_array($item)) {
  617. $item = json_encode($item, JSON_UNESCAPED_UNICODE);
  618. $item = preg_replace('/"\$([^"]+)"/', '$1', $item);
  619. $options_string .= "\n".($url?' ':' ')."$key: $item,";
  620. } else if (is_string($item)) {
  621. $options_string .= "\n".($url?' ':' ')."$key: \"$item\",";
  622. } else {
  623. $options_string .= "\n".($url?' ':' ')."$key: ".var_export($item, true).",";
  624. }
  625. }
  626. $id = $this->createId($field);
  627. if ($url) {
  628. $this->jsContent .= <<<EOF
  629. // 字段 {$options['label']} $field
  630. layui.use(["jquery", "xmSelect", "popup"], function() {
  631. layui.$.ajax({
  632. url: "$url",
  633. dataType: "json",
  634. success: function (res) {
  635. let value = layui.$("#$id").attr("value");
  636. let initValue = value ? value.split(",") : [];
  637. layui.xmSelect.render({
  638. el: "#$id",
  639. name: "$field",
  640. initValue: initValue,
  641. filterable: true,
  642. data: res.data, $options_string
  643. });
  644. if (res.code) {
  645. layui.popup.failure(res.msg);
  646. }
  647. }
  648. });
  649. });
  650. EOF;
  651. } else {
  652. $this->jsContent .= <<<EOF
  653. // 字段 {$options['label']} $field
  654. layui.use(["jquery", "xmSelect"], function() {
  655. let value = layui.$("#$id").attr("value");
  656. let initValue = value ? value.split(",") : [];
  657. layui.xmSelect.render({
  658. el: "#$id",
  659. name: "$field",
  660. filterable: true,
  661. initValue: initValue,$options_string
  662. })
  663. });
  664. EOF;
  665. }
  666. $this->htmlContent .= <<<EOF
  667. <div class="layui-form-item">
  668. $select_label
  669. <div class="$class">
  670. <div name="$field" id="$id"$required_string value="$default_value_string" ></div>
  671. </div>
  672. </div>
  673. EOF;
  674. }
  675. /**
  676. * 构建表单
  677. * @param $table
  678. * @param string $type
  679. * @return Layui
  680. * @throws BusinessException
  681. */
  682. public static function buildForm($table, string $type = 'insert'): Layui
  683. {
  684. if (!in_array($type, ['insert', 'update', 'search'])) {
  685. $type = 'insert';
  686. }
  687. $filter = $type === 'search' ? 'searchable' : 'form_show';
  688. $form = new Layui();
  689. $schema = Util::getSchema($table);
  690. $forms = $schema['forms'];
  691. $columns = $schema['columns'];
  692. $primary_key = $schema['table']['primary_key'][0] ?? null;
  693. foreach ($forms as $key => $info) {
  694. if (empty($info[$filter])) {
  695. continue;
  696. }
  697. $field = $info['field'];
  698. $default = $columns[$key]['default'];
  699. $control = strtolower($info['control']);
  700. $auto_increment = $columns[$key]['auto_increment'];
  701. // 搜索框里上传组件替换为input
  702. if ($type == 'search' && in_array($control, ['upload', 'uploadimg'])) {
  703. $control = 'input';
  704. $info['control_args'] = '';
  705. }
  706. if ($type === 'search' && $control === 'switch') {
  707. $control = 'select';
  708. if (preg_match('/lay-text:(.+?)\|([^;]+)/', $info['control_args'], $matches)) {
  709. $info['control_args'] = 'data:1:' . $matches[1] . ',0:' . $matches[2];
  710. } else {
  711. $info['control_args'] = 'data:1:是,0:否';
  712. }
  713. }
  714. $props = Util::getControlProps($control, $info['control_args']);
  715. // 增加修改记录验证必填项
  716. if ($filter == 'form_show' && !$columns[$key]['nullable'] && $default === null && ($field !== 'password' || $type === 'insert')) {
  717. if (!isset($props['lay-verify'])) {
  718. $props['lay-verify'] = 'required';
  719. // 非类似字符串类型不允许传空
  720. } elseif (!in_array($columns[$key]['type'], ['string', 'text', 'mediumText', 'longText', 'char', 'binary', 'json'])
  721. && strpos($props['lay-verify'], 'required') === false) {
  722. $props['lay-verify'] = 'required|' . $props['lay-verify'];
  723. }
  724. }
  725. // 增加记录显示默认值
  726. if ($type === 'insert' && !isset($props['value']) && $default !== null) {
  727. $props['value'] = $default;
  728. }
  729. // 主键是自增字段或者表单是更新类型不显示主键
  730. if ($primary_key && $field == $primary_key && (($type == 'insert' && $auto_increment) || $type == 'update')) {
  731. continue;
  732. }
  733. // 查询类型
  734. if ($type == 'search') {
  735. if ($info['search_type'] == 'between' && method_exists($form, "{$control}Range")) {
  736. $control = "{$control}Range";
  737. } elseif ($info['search_type'] == 'like' && method_exists($form, "{$control}Like")) {
  738. $control = "{$control}Like";
  739. }
  740. }
  741. // 查询类型移除lay-verify
  742. if ($type == 'search' && !empty($props['lay-verify'])) {
  743. $props['lay-verify'] = '';
  744. }
  745. $options = [
  746. 'label' => $info['comment'] ?: $field,
  747. 'field' => $field,
  748. 'props' => $props,
  749. ];
  750. $form->{$control}($options);
  751. }
  752. return $form;
  753. }
  754. /**
  755. * 构建表格
  756. * @param $table
  757. * @param int $indent
  758. * @return array|string|string[]
  759. * @throws BusinessException
  760. */
  761. public static function buildTable($table, int $indent = 0)
  762. {
  763. $schema = Util::getSchema($table);
  764. $forms = $schema['forms'];
  765. $codes = '';
  766. $cols = '';
  767. $api = '';
  768. $api_result = '';
  769. foreach ($forms as $info) {
  770. $title = $info['comment'] ?: $info['field'];
  771. $hide_str = $info['list_show'] ? '' : "\n hide: true,";
  772. $sort_str = $info['enable_sort'] ? "\n sort: true," : '';
  773. $field = $info['field'];
  774. $templet = '';
  775. $schema = <<<EOF
  776. title: "$title",align: "center",
  777. field: "$field",$hide_str$sort_str
  778. EOF;
  779. $control = strtolower($info['control']);
  780. switch ($control) {
  781. case 'switch':
  782. $props = Util::getControlProps($info['control'], $info['control_args']);
  783. $lay_text = $props['lay-text'] ?? '';
  784. $templet = <<<EOF
  785. templet: function (d) {
  786. let field = "$field";
  787. form.on("switch("+field+")", function (data) {
  788. let load = layer.load();
  789. let postData = {};
  790. postData[field] = data.elem.checked ? 1 : 0;
  791. postData[PRIMARY_KEY] = this.value;
  792. $.post(UPDATE_API, postData, function (res) {
  793. layer.close(load);
  794. if (res.code) {
  795. return layui.popup.failure(res.msg, function () {
  796. data.elem.checked = !data.elem.checked;
  797. form.render();
  798. });
  799. }
  800. return layui.popup.success("操作成功");
  801. })
  802. });
  803. let checked = d[field] === 1 ? "checked" : "";
  804. return '<input type="checkbox" value="'+util.escape(d[PRIMARY_KEY])+'" lay-filter="'+util.escape(field)+'" lay-skin="switch" lay-text="'+util.escape('$lay_text')+'" '+checked+'/>';
  805. }
  806. EOF;
  807. break;
  808. case 'iconpicker':
  809. $templet = <<<EOF
  810. templet: function (d) {
  811. return '<i class="layui-icon ' + util.escape(d['$field']) + '"></i>';
  812. }
  813. EOF;
  814. break;
  815. case 'upload':
  816. $templet = <<<EOF
  817. templet: function (d) {
  818. return '<a href="' + encodeURI(d['$field']) + '" target="_blank">' + util.escape(d['$field']) + '</a>';
  819. }
  820. EOF;
  821. break;
  822. case 'uploadimage':
  823. $templet = <<<EOF
  824. templet: function (d) {
  825. return '<img src="'+encodeURI(d['$field'])+'" style="max-width:32px;max-height:32px;" alt="" />'
  826. }
  827. EOF;
  828. break;
  829. }
  830. if (in_array($control, ['select', 'selectmulti', 'treeselect', 'treeselectmulti'])) {
  831. $props = Util::getControlProps($info['control'], $info['control_args']);
  832. if (isset($props['url'])) {
  833. $api .= "\napis.push([\"$field\", \"{$props['url']}\"]);";
  834. $api_result .= "\napiResults[\"$field\"] = [];";
  835. } else if (!empty($props['data'])) {
  836. $options = [];
  837. foreach ($props['data'] as $option) {
  838. if (isset($option['value']) && isset($option['name'])) {
  839. $options[$option['value']] = $option['name'];
  840. }
  841. }
  842. $api_result .= "\napiResults[\"$field\"] = " . json_encode($options, JSON_UNESCAPED_UNICODE) . ";";
  843. } else {
  844. $api_result .= "\napiResults[\"$field\"] = [];";
  845. }
  846. $templet = <<<EOF
  847. templet: function (d) {
  848. let field = "$field";
  849. if (typeof d[field] == "undefined") return "";
  850. let items = [];
  851. layui.each((d[field] + "").split(","), function (k , v) {
  852. items.push(apiResults[field][v] || v);
  853. });
  854. return util.escape(items.join(","));
  855. }
  856. EOF;
  857. }
  858. $cols .= <<<EOF
  859. ,{
  860. $schema$templet
  861. }
  862. EOF;
  863. }
  864. $cols = <<<EOF
  865. // 表头参数
  866. let cols = [
  867. {
  868. type: "checkbox",
  869. align: "center"
  870. }$cols,{
  871. title: "操作",
  872. toolbar: "#table-bar",
  873. align: "center",
  874. fixed: "right",
  875. width: 120,
  876. }
  877. ];
  878. EOF;
  879. if (!$api && $api_result) {
  880. $codes = <<<EOF
  881. // 获取表格中下拉或树形组件数据
  882. let apiResults = {};$api_result
  883. EOF;
  884. } else if ($api && !$api_result) {
  885. $codes = <<<EOF
  886. // 获取表格中下拉或树形组件数据
  887. let apis = [];$api
  888. EOF;
  889. } else if ($api && $api_result) {
  890. $codes = <<<EOF
  891. // 获取表格中下拉或树形组件数据
  892. let apis = [];$api
  893. let apiResults = {};$api_result
  894. EOF;
  895. }
  896. if ($api) {
  897. $codes = <<<EOF
  898. $cols
  899. // 渲染表格
  900. function render()
  901. {
  902. table.render({
  903. elem: "#data-table",
  904. url: SELECT_API,
  905. page: true,
  906. cols: [cols],
  907. skin: "line",
  908. size: "lg",
  909. toolbar: "#table-toolbar",
  910. autoSort: false,
  911. defaultToolbar: [{
  912. title: "刷新",
  913. layEvent: "refresh",
  914. icon: "layui-icon-refresh",
  915. }, "filter", "print", "exports"],
  916. done: function () {
  917. layer.photos({photos: 'div[lay-id="data-table"]', anim: 5});
  918. }
  919. });
  920. }
  921. $codes
  922. let count = apis.length;
  923. layui.each(apis, function (k, item) {
  924. let [field, url] = item;
  925. $.ajax({
  926. url: url,
  927. dateType: "json",
  928. success: function (res) {
  929. if (res.code) {
  930. return layui.popup.failure(res.msg);
  931. }
  932. function travel(items) {
  933. for (let k in items) {
  934. let item = items[k];
  935. apiResults[field][item.value] = item.name;
  936. if (item.children) {
  937. travel(item.children);
  938. }
  939. }
  940. }
  941. travel(res.data);
  942. },
  943. complete: function () {
  944. if (--count === 0) {
  945. render();
  946. }
  947. }
  948. });
  949. });
  950. if (!count) {
  951. render();
  952. }
  953. EOF;
  954. } else {
  955. $codes = <<<EOF
  956. $cols
  957. // 渲染表格
  958. table.render({
  959. elem: "#data-table",
  960. url: SELECT_API,
  961. page: true,
  962. cols: [cols],
  963. skin: "line",
  964. size: "lg",
  965. toolbar: "#table-toolbar",
  966. autoSort: false,
  967. defaultToolbar: [{
  968. title: "刷新",
  969. layEvent: "refresh",
  970. icon: "layui-icon-refresh",
  971. }, "filter", "print", "exports"],
  972. done: function () {
  973. layer.photos({photos: 'div[lay-id="data-table"]', anim: 5});
  974. }
  975. });
  976. $codes
  977. EOF;
  978. }
  979. return str_replace("\n", "\n" . str_repeat(' ', $indent), $codes);
  980. }
  981. /**
  982. * 预处理props
  983. */
  984. private function prepareProps($props)
  985. {
  986. $raw_list = ['true','false','null','undefined'];
  987. foreach ($props as $k => $v) {
  988. if (is_array($v)) {
  989. $props[$k] = $this->prepareProps($v);
  990. } elseif (!in_array($v, $raw_list) && !is_numeric($v)) {
  991. if (strpos($v, "#") === 0){
  992. $props[$k] = substr($v, 1);
  993. } else {
  994. $props[$k] = "\"$v\"";
  995. }
  996. }
  997. }
  998. return $props;
  999. }
  1000. private function preparePropsToJsObject($props, $indent = 0, $sub = false)
  1001. {
  1002. $string = '';
  1003. $indent_string = str_repeat(' ', $indent);
  1004. if (!$sub) {
  1005. $string .= "$indent_string{\n";
  1006. }
  1007. foreach ($props as $k => $v) {
  1008. if (!preg_match("#^[a-zA-Z0-9_]+$#", $k)) {
  1009. $k = "'$k'";
  1010. }
  1011. if (is_array($v)) {
  1012. $string .= "$indent_string $k: {\n{$this->preparePropsToJsObject($v, $indent + 1, true)}\n$indent_string },\n";
  1013. } else {
  1014. $string .= "$indent_string $k: $v,\n";
  1015. }
  1016. }
  1017. if (!$sub) {
  1018. $string .= "$indent_string}\n";
  1019. }
  1020. return trim($string,"\n");
  1021. }
  1022. }