htmlContent); } /** * 获取生成的js代码 * @param $indent * @return string */ public function js($indent = 0): string { return str_replace("\n", "\n" . str_repeat(' ', $indent), $this->jsContent); } /** * 获取控件及相关参数 * @param $options * @return array */ protected function options($options): array { array_walk_recursive($options, function(&$item, $key){ if (is_string($item)) { $item = htmlspecialchars($item); if ($key === 'url') { $item = str_replace('&', '&', $item); } } }); $field = $options['field']??''; $props = !empty($options['props']) ? $options['props'] : []; $verify_string = !empty($props['lay-verify']) ? ' lay-verify="'.$props['lay-verify'].'"' : ''; $required_string = strpos($verify_string, 'required') ? ' required' : ''; $label = !empty($options['label']) ? '' : ''; $value = $props['value'] ?? ''; $class = $props['class'] ?? 'layui-input-block'; return [$label, $field, $value, $props, $verify_string, $required_string, $class]; } /** * input输入框 * @param $options * @return void */ public function input($options) { [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options); $placeholder_string = !empty($props['placeholder']) ? ' placeholder="'.$props['placeholder'].'"' : ''; $autocomplete_string = !empty($props['autocomplete']) ? ' autocomplete="'.$props['autocomplete'].'"' : ''; $disabled_string = !empty($props['disabled']) ? ' disabled' : ''; $type = $props['type'] ?? 'text'; $this->htmlContent .= << $label
EOF; } /** * input数字输入框 * @param $options * @return void */ public function inputNumber($options) { $options['props']['type'] = 'number'; $this->input($options); } /** * 输入框范围 * @param $options * @return void */ public function inputRange($options) { [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options); $type = $props['type'] ?? 'text'; $this->htmlContent .= << $label
-
EOF; } /** * 输入框模糊查询 * @param $options * @return void */ public function inputLike($options) { [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options); $type = $props['type'] ?? 'text'; $this->htmlContent .= << $label
EOF; } /** * 数字输入框范围 * @param $options * @return void */ public function inputNumberRange($options) { $options['props']['type'] = 'number'; $this->inputRange($options); } /** * 数字输入框模糊查询 * @param $options * @return void */ public function inputNumberLike($options) { $options['props']['type'] = 'number'; $this->inputLike($options); } /** * 密码输入框 * @param $options * @return void */ public function inputPassword($options) { $options['props']['type'] = 'password'; $this->input($options); } /** * 文本域 * @param $options * @return void */ public function textArea($options) { [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options); $placeholder_string = !empty($props['placeholder']) ? ' placeholder="'.$props['placeholder'].'"' : ''; $disabled_string = !empty($props['disabled']) ? ' disabled' : ''; $this->htmlContent .= << $label
EOF; } /** * 富文本 * @param $options * @return void */ public function richText($options) { [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options); $placeholder_string = !empty($props['placeholder']) ? ' placeholder="'.$props['placeholder'].'"' : ''; $disabled_string = !empty($props['disabled']) ? ' disabled' : ''; $id = $field; $this->htmlContent .= << $label
EOF; $options_string = ''; if (!isset($props['images_upload_url'])) { $props['images_upload_url'] = '/app/admin/upload/image'; } $props = $this->prepareProps($props); $options_string .= "\n" . $this->preparePropsToJsObject($props, 1, true); $this->jsContent .= <<options($options); $placeholder_string = !empty($props['placeholder']) ? ' placeholder="'.$props['placeholder'].'"' : ''; $autocomplete_string = !empty($props['autocomplete']) ? ' autocomplete="'.$props['autocomplete'].'"' : ''; $disabled_string = !empty($props['disabled']) ? ' disabled' : ''; $type = $props['type'] ?? 'text'; if (empty($value)){ $value='{}'; } $this->htmlContent .= << $label
EOF; $this->jsContent .= <<options($options); $props['accept'] = $props['accept'] ?? 'file'; $props['url'] = $props['url'] ?? '/app/admin/upload/file'; $id = $this->createId($field); $props['field'] = $props['field'] ?? '__file__'; unset($props['lay-verify']); $options_string = ''; $props = $this->prepareProps($props); $options_string .= "\n" . $this->preparePropsToJsObject($props, 1, true); $this->htmlContent .= << $label
$value
EOF; $this->jsContent .= <<options($options); $props['acceptMime'] = $props['acceptMime'] ?? 'image/gif,image/jpeg,image/jpg,image/png'; $props['url'] = $props['url'] ?? '/app/admin/upload/image'; $id = $this->createId($field); unset($props['lay-verify']); $props['field'] = $props['field'] ?? '__file__'; $options_string = ''; $props = $this->prepareProps($props); $options_string .= "\n" . $this->preparePropsToJsObject($props, 1, true); $this->htmlContent .= << $label
EOF; $this->jsContent .= << 0) return layui.layer.msg(res.msg); this.item.prev().val(res.data.url).prev().attr("src", res.data.url); } }); }); EOF; } /** * 日期时间选择组件 * @param $options * @return void */ public function dateTimePicker($options) { $options['props']['type'] = 'datetime'; $this->datePicker($options); } /** * 日期选择组件 * @param $options * @return void */ public function datePicker($options) { [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options); $value_string = $value ? ' value="'.$value.'"' : ''; $options_string = ''; unset($props['required'], $props['lay-verify'], $props['value']); $props = $this->prepareProps($props); $options_string .= "\n" . $this->preparePropsToJsObject($props, 1, true); $id = $this->createId($field); $this->htmlContent .= << $label
EOF; $this->jsContent .= <<datePickerRange($options); } /** * 日期范围选择组件 * @param $options * @return void */ public function datePickerRange($options) { [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options); if (!isset($options['props']['type'])) { $options['props']['type'] = 'date'; } $options_string = ''; unset($props['required'], $props['lay-verify'], $props['value']); $props = $this->prepareProps($props); $options_string .= "\n" . $this->preparePropsToJsObject($props, 1, true); $id = $this->createId($field); $id_start = "$id-date-start"; $id_end = "$id-date-end"; $this->htmlContent .= << $label
-
EOF; $this->jsContent .= <<options($options); $value_string = $value ? ' value="'.$value.'"' : ''; $id = $this->createId($field); $options_string = ''; $props = $this->prepareProps($props); $options_string .= "\n" . $this->preparePropsToJsObject($props, 1, true); $this->htmlContent .= << $label
EOF; $this->jsContent .= <<options($options); $value = (int)$value; $disabled_string = !empty($props['disabled']) ? ' disabled' : ''; $lay_text = !empty($props['lay-text']) ? "lay-text=\"{$props['lay-text']}\"" : ''; $id = $this->createId($field); $this->htmlContent .= << $label
EOF; $this->jsContent .= << 'hidden', 'label' => [ 'type' => 'text', ] ], $options['props']['model'] ?? []); $options['props']['clickClose'] = $options['props']['clickClose'] ?? true; $options['props']['radio'] = $options['props']['radio'] ?? true; $this->apiSelect($options); } /** * 下拉多选组件 * @return void */ public function selectMulti($options) { $options['props']['toolbar'] = array_merge_recursive([ 'show' => true, 'list' => [ 'ALL', 'CLEAR', 'REVERSE' ] ], $options['props']['toolbar'] ?? []); $this->apiSelect($options); } /** * 树单选组件 * @return void */ public function treeSelect($options) { $options['props']['model'] = array_merge_recursive([ 'icon' => 'hidden', 'label' => [ 'type' => 'text', ] ], $options['props']['model'] ?? []); $options['props']['clickClose'] = $options['props']['clickClose'] ?? true; $options['props']['radio'] = $options['props']['radio'] ?? true; $options['props']['tree'] = array_merge_recursive([ '$show' => true, '$strict' => false, '$clickCheck' => true, '$clickExpand' => false, '$expandedKeys' => '$initValue' ], $options['props']['tree'] ?? []); $this->apiSelect($options); } /** * 树多选组件 * @return void */ public function treeSelectMulti($options) { $options['props']['tree'] = array_merge_recursive(['show' => true, '$expandedKeys' => '$initValue'], $options['props']['tree'] ?? []); $options['props']['toolbar'] = array_merge_recursive([ '$show' => true, '$list' => [ 'ALL', 'CLEAR', 'REVERSE' ] ], $options['props']['toolbar'] ?? []); $this->apiSelect($options); } /** * 选择框,支持单选、多选、树形选择 * @see https://maplemei.gitee.io/xm-select/ * @param $options * @return void */ public function apiSelect($options) { [$select_label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options); $default_value_string = isset($props['initValue']) && $props['initValue'] != '' ? $props['initValue'] : $value; $url = $props['url'] ?? ''; $options_string = ''; if (isset($props['lay-verify'])) { $props['layVerify'] = $props['lay-verify']; } unset($props['lay-verify'], $props['url']); foreach ($props as $key => $item) { if (is_array($item)) { $item = json_encode($item, JSON_UNESCAPED_UNICODE); $item = preg_replace('/"\$([^"]+)"/', '$1', $item); $options_string .= "\n".($url?' ':' ')."$key: $item,"; } else if (is_string($item)) { $options_string .= "\n".($url?' ':' ')."$key: \"$item\","; } else { $options_string .= "\n".($url?' ':' ')."$key: ".var_export($item, true).","; } } $id = $this->createId($field); if ($url) { $this->jsContent .= <<jsContent .= <<htmlContent .= << $select_label
EOF; } /** * 构建表单 * @param $table * @param string $type * @return Layui * @throws BusinessException */ public static function buildForm($table, string $type = 'insert'): Layui { if (!in_array($type, ['insert', 'update', 'search'])) { $type = 'insert'; } $filter = $type === 'search' ? 'searchable' : 'form_show'; $form = new Layui(); $schema = Util::getSchema($table); $forms = $schema['forms']; $columns = $schema['columns']; $primary_key = $schema['table']['primary_key'][0] ?? null; foreach ($forms as $key => $info) { if (empty($info[$filter])) { continue; } $field = $info['field']; $default = $columns[$key]['default']; $control = strtolower($info['control']); $auto_increment = $columns[$key]['auto_increment']; // 搜索框里上传组件替换为input if ($type == 'search' && in_array($control, ['upload', 'uploadimg'])) { $control = 'input'; $info['control_args'] = ''; } if ($type === 'search' && $control === 'switch') { $control = 'select'; if (preg_match('/lay-text:(.+?)\|([^;]+)/', $info['control_args'], $matches)) { $info['control_args'] = 'data:1:' . $matches[1] . ',0:' . $matches[2]; } else { $info['control_args'] = 'data:1:是,0:否'; } } $props = Util::getControlProps($control, $info['control_args']); // 增加修改记录验证必填项 if ($filter == 'form_show' && !$columns[$key]['nullable'] && $default === null && ($field !== 'password' || $type === 'insert')) { if (!isset($props['lay-verify'])) { $props['lay-verify'] = 'required'; // 非类似字符串类型不允许传空 } elseif (!in_array($columns[$key]['type'], ['string', 'text', 'mediumText', 'longText', 'char', 'binary', 'json']) && strpos($props['lay-verify'], 'required') === false) { $props['lay-verify'] = 'required|' . $props['lay-verify']; } } // 增加记录显示默认值 if ($type === 'insert' && !isset($props['value']) && $default !== null) { $props['value'] = $default; } // 主键是自增字段或者表单是更新类型不显示主键 if ($primary_key && $field == $primary_key && (($type == 'insert' && $auto_increment) || $type == 'update')) { continue; } // 查询类型 if ($type == 'search') { if ($info['search_type'] == 'between' && method_exists($form, "{$control}Range")) { $control = "{$control}Range"; } elseif ($info['search_type'] == 'like' && method_exists($form, "{$control}Like")) { $control = "{$control}Like"; } } // 查询类型移除lay-verify if ($type == 'search' && !empty($props['lay-verify'])) { $props['lay-verify'] = ''; } $options = [ 'label' => $info['comment'] ?: $field, 'field' => $field, 'props' => $props, ]; $form->{$control}($options); } return $form; } /** * 构建表格 * @param $table * @param int $indent * @return array|string|string[] * @throws BusinessException */ public static function buildTable($table, int $indent = 0) { $schema = Util::getSchema($table); $forms = $schema['forms']; $codes = ''; $cols = ''; $api = ''; $api_result = ''; foreach ($forms as $info) { $title = $info['comment'] ?: $info['field']; $hide_str = $info['list_show'] ? '' : "\n hide: true,"; $sort_str = $info['enable_sort'] ? "\n sort: true," : ''; $field = $info['field']; $templet = ''; $schema = <<'; } EOF; break; case 'iconpicker': $templet = <<'; } EOF; break; case 'upload': $templet = <<' + util.escape(d['$field']) + ''; } EOF; break; case 'uploadimage': $templet = <<' } EOF; break; } if (in_array($control, ['select', 'selectmulti', 'treeselect', 'treeselectmulti'])) { $props = Util::getControlProps($info['control'], $info['control_args']); if (isset($props['url'])) { $api .= "\napis.push([\"$field\", \"{$props['url']}\"]);"; $api_result .= "\napiResults[\"$field\"] = [];"; } else if (!empty($props['data'])) { $options = []; foreach ($props['data'] as $option) { if (isset($option['value']) && isset($option['name'])) { $options[$option['value']] = $option['name']; } } $api_result .= "\napiResults[\"$field\"] = " . json_encode($options, JSON_UNESCAPED_UNICODE) . ";"; } else { $api_result .= "\napiResults[\"$field\"] = [];"; } $templet = << $v) { if (is_array($v)) { $props[$k] = $this->prepareProps($v); } elseif (!in_array($v, $raw_list) && !is_numeric($v)) { if (strpos($v, "#") === 0){ $props[$k] = substr($v, 1); } else { $props[$k] = "\"$v\""; } } } return $props; } private function preparePropsToJsObject($props, $indent = 0, $sub = false) { $string = ''; $indent_string = str_repeat(' ', $indent); if (!$sub) { $string .= "$indent_string{\n"; } foreach ($props as $k => $v) { if (!preg_match("#^[a-zA-Z0-9_]+$#", $k)) { $k = "'$k'"; } if (is_array($v)) { $string .= "$indent_string $k: {\n{$this->preparePropsToJsObject($v, $indent + 1, true)}\n$indent_string },\n"; } else { $string .= "$indent_string $k: $v,\n"; } } if (!$sub) { $string .= "$indent_string}\n"; } return trim($string,"\n"); } }