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");
}
}