<?php
namespace plugin\admin\app\common;

use support\exception\BusinessException;

class Layui
{

    /**
     * 生成的html代码
     * @var string
     */
    protected $htmlContent = '';

    /**
     * 生成的js代码
     * @var string
     */
    protected $jsContent = '';

    /**
     * 获取生成的html代码
     * @param $indent
     * @return string
     */
    public function html($indent = 0): string
    {
        return str_replace("\n", "\n" . str_repeat('    ', $indent), $this->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('&amp;', '&', $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']) ? '<label class="layui-form-label'.$required_string.'">'.$options['label'].'</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 .= <<<EOF

<div class="layui-form-item">
    $label
    <div class="$class">
        <input type="$type" name="$field" value="$value"$disabled_string$required_string$verify_string$placeholder_string$autocomplete_string class="layui-input">
    </div>
</div>

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 .= <<<EOF

<div class="layui-form-item">
    $label
    <div class="$class">
        <div class="layui-input-block">
            <input type="$type" autocomplete="off" name="{$field}[]" class="layui-input inline-block" placeholder="开始">
            -
            <input type="$type" autocomplete="off" name="{$field}[]" class="layui-input inline-block" placeholder="结束">
        </div>
    </div>
</div>

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 .= <<<EOF

<div class="layui-form-item">
    $label
    <div class="$class">
        <div class="layui-input-block">
            <input type="hidden" autocomplete="off" name="{$field}[]" value="like" class="layui-input inline-block">
            <input type="$type" autocomplete="off" name="{$field}[]" class="layui-input">
        </div>
    </div>
</div>

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 .= <<<EOF

<div class="layui-form-item">
    $label
    <div class="$class">
        <textarea name="$field"$required_string$verify_string$placeholder_string$disabled_string class="layui-textarea">$value</textarea>
    </div>
</div>

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 .= <<<EOF

<div class="layui-form-item">
    $label
    <div class="$class">
        <textarea id="$id" name="$field"$required_string$verify_string$placeholder_string$disabled_string class="layui-textarea">$value</textarea>
    </div>
</div>

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 .= <<<EOF

// 字段 {$options['label']} $field
layui.use(["tinymce"], function() {
    var tinymce = layui.tinymce
    var edit = tinymce.render({
        elem: "#$id",$options_string
    });
    edit.on("blur", function(){
        layui.$("#$id").val(edit.getContent());
    });
});

EOF;
    }

    /**
     * json编辑框
     * @param $options
     * @return void
     */
    public function jsonEditor($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';
        if (empty($value)){
            $value='{}';
        }
        $this->htmlContent .= <<<EOF

<div class="layui-form-item">
    $label
    <div class="$class">
        <input type="$type" name="$field"id="$field" value="$value"$disabled_string$required_string$verify_string$placeholder_string$autocomplete_string class="layui-input">
    </div>
</div>
EOF;
        $this->jsContent .= <<<EOF
jsonArea({
    el: "#$field",
    change: function(data) {
        console.log(data);
    }
});
EOF;

    }

    /**
     * 上传组件
     * @param $options
     * @return void
     */
    public function upload($options)
    {
        [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->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 .= <<<EOF

<div class="layui-form-item">
    $label
    <div class="$class">
        <span>$value</span>
        <input type="text" style="display:none" name="$field" value="$value" />
        <button type="button" class="pear-btn pear-btn-primary pear-btn-sm" id="$id" permission="app.admin.upload.file">
            <i class="layui-icon layui-icon-upload"></i>上传文件
        </button>
        <button type="button" class="pear-btn pear-btn-primary pear-btn-sm" id="attachment-choose-$id" permission="app.admin.upload.attachment">
            <i class="layui-icon layui-icon-align-left"></i>选择文件
        </button>
    </div>
</div>

EOF;
        $this->jsContent .= <<<EOF

// 字段 {$options['label']} $field
layui.use(["upload", "layer", "popup", "util"], function() {
    let input = layui.$("#$id").prev();
    input.prev().html(layui.util.escape(input.val()));
    layui.$("#attachment-choose-$id").on("click", function() {
        parent.layer.open({
            type: 2,
            title: "选择附件",
            content: "/app/admin/upload/attachment",
            area: ["95%", "90%"],
            success: function (layero, index) {
                parent.layui.$("#layui-layer" + index).data("callback", function (data) {
                    input.val(data.url).prev().html(layui.util.escape(data.url));
                });
            }
        });
    });
    layui.upload.render({
        elem: "#$id",$options_string
        done: function (res) {
            if (res.code) return layui.popup.failure(res.msg);
            this.item.prev().val(res.data.url).prev().html(layui.util.escape(res.data.url));
        }
    });
});

EOF;
    }

    /**
     * 图片上传组件
     * @param $options
     * @return void
     */
    public function uploadImage($options)
    {
        [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->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 .= <<<EOF

<div class="layui-form-item">
    $label
    <div class="$class">
        <img class="img-3" src=""/>
        <input type="text" style="display:none" name="$field" value="$value" />
        <button type="button" class="pear-btn pear-btn-primary pear-btn-sm" id="$id"  permission="app.admin.upload.image">
            <i class="layui-icon layui-icon-upload"></i>上传图片
        </button>
        <button type="button" class="pear-btn pear-btn-primary pear-btn-sm" id="attachment-choose-$id"  permission="app.admin.upload.attachment">
            <i class="layui-icon layui-icon-align-left"></i>选择图片
        </button>
    </div>
</div>

EOF;
        $this->jsContent .= <<<EOF

// 字段 {$options['label']} $field
layui.use(["upload", "layer"], function() {
    let input = layui.$("#$id").prev();
    input.prev().attr("src", input.val());
    layui.$("#attachment-choose-$id").on("click", function() {
        parent.layer.open({
            type: 2,
            title: "选择附件",
            content: "/app/admin/upload/attachment?ext=jpg,jpeg,png,gif,bmp",
            area: ["95%", "90%"],
            success: function (layero, index) {
                parent.layui.$("#layui-layer" + index).data("callback", function (data) {
                    input.val(data.url).prev().attr("src", data.url);
                });
            }
        });
    });
    layui.upload.render({
        elem: "#$id",$options_string
        done: function (res) {
            if (res.code > 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 .= <<<EOF

<div class="layui-form-item">
    $label
    <div class="$class">
        <input type="text" name="$field" id="$id"$value_string$required_string$verify_string autocomplete="off" class="layui-input">
    </div>
</div>

EOF;
        $this->jsContent .= <<<EOF

// 字段 {$options["label"]} $field
layui.use(["laydate"], function() {
    layui.laydate.render({
        elem: "#$id",$options_string
    });
})

EOF;
    }

    /**
     * 日期时间范围选择组件
     * @param $options
     * @return void
     */
    public function dateTimePickerRange($options)
    {
        $options['props']['type'] = 'datetime';
        $this->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 .= <<<EOF

<div class="layui-form-item">
    $label
    <div class="$class">
        <div class="layui-input-block" id="$id">
            <input type="text" autocomplete="off" name="{$field}[]" id="$id_start" class="layui-input inline-block" placeholder="开始时间">
            -
            <input type="text" autocomplete="off" name="{$field}[]" id="$id_end" class="layui-input inline-block" placeholder="结束时间">
        </div>
    </div>
</div>

EOF;
        $this->jsContent .= <<<EOF

// 字段 {$options['label']} $field
layui.use(["laydate"], function() {
    layui.laydate.render({
        elem: "#$id",
        range: ["#$id_start", "#$id_end"],$options_string
    });
})

EOF;
    }

    /**
     * 创建id
     * @param $field
     * @return mixed
     */
    protected function createId($field)
    {
        return $field;
    }

    /**
     * 图标选择组件
     * @param $options
     * @return void
     */
    public function iconPicker($options)
    {
        [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->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 .= <<<EOF

<div class="layui-form-item">
    $label
    <div class="$class">
        <input name="$field" id="$id"$value_string$required_string$verify_string />
    </div>
</div>

EOF;

        $this->jsContent .= <<<EOF

// 字段 {$options['label']} $field
layui.use(["iconPicker"], function() {
    layui.iconPicker.render({
        elem: "#$id",
        type: "fontClass",
        page: false,$options_string
    });
});

EOF;

    }

    /**
     * switch组件
     * @param $options
     * @return void
     */
    public function switch($options)
    {
        [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->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 .= <<<EOF

<div class="layui-form-item">
    $label
    <div class="$class">
        <input type="checkbox" id="$id" lay-filter="$field"$disabled_string$required_string lay-skin="switch" $lay_text/>
        <input type="text" style="display:none" name="$field" value="$value"$required_string />
    </div>
</div>

EOF;

        $this->jsContent .= <<<EOF

// 字段 {$options['label']} $field
layui.use(["form"], function() {
    layui.$("#$id").attr("checked", layui.$('input[name="$field"]').val() != 0);
    layui.form.render();
    layui.form.on("switch($field)", function(data) {
        layui.$('input[name="$field"]').val(this.checked ? 1 : 0);
    });
})

EOF;

    }

    /**
     * 下拉选择组件
     * @return void
     */
    public function select($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;
        $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 .= <<<EOF

// 字段 {$options['label']} $field
layui.use(["jquery", "xmSelect", "popup"], function() {
    layui.$.ajax({
        url: "$url",
        dataType: "json",
        success: function (res) {
            let value = layui.$("#$id").attr("value");
            let initValue = value ? value.split(",") : [];
            layui.xmSelect.render({
                el: "#$id",
                name: "$field",
                initValue: initValue,
                filterable: true,
                data: res.data, $options_string
            });
            if (res.code) {
                layui.popup.failure(res.msg);
            }
        }
    });
});

EOF;
        } else {
            $this->jsContent .= <<<EOF

// 字段 {$options['label']} $field
layui.use(["jquery", "xmSelect"], function() {
    let value = layui.$("#$id").attr("value");
    let initValue = value ? value.split(",") : [];
    layui.xmSelect.render({
        el: "#$id",
        name: "$field",
        filterable: true,
        initValue: initValue,$options_string
    })
});

EOF;
        }

        $this->htmlContent .= <<<EOF

<div class="layui-form-item">
    $select_label
    <div class="$class">
        <div name="$field" id="$id"$required_string value="$default_value_string" ></div>
    </div>
</div>

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
title: "$title",align: "center",
		field: "$field",$hide_str$sort_str
EOF;

            $control = strtolower($info['control']);
            switch ($control) {
                case 'switch':
                    $props = Util::getControlProps($info['control'], $info['control_args']);
                    $lay_text = $props['lay-text'] ?? '';
                    $templet = <<<EOF

		templet: function (d) {
			let field = "$field";
			form.on("switch("+field+")", function (data) {
				let load = layer.load();
				let postData = {};
				postData[field] = data.elem.checked ? 1 : 0;
				postData[PRIMARY_KEY] = this.value;
				$.post(UPDATE_API, postData, function (res) {
					layer.close(load);
					if (res.code) {
                        return layui.popup.failure(res.msg, function () {
                            data.elem.checked = !data.elem.checked;
                            form.render();
                        });
                    }
					return layui.popup.success("操作成功");
				})
			});
			let checked = d[field] === 1 ? "checked" : "";
			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+'/>';
		}
EOF;
                    break;

                case 'iconpicker':
                    $templet = <<<EOF

		templet: function (d) {
			return '<i class="layui-icon ' + util.escape(d['$field']) + '"></i>';
		}
EOF;
                    break;
                case 'upload':
                    $templet = <<<EOF

		templet: function (d) {
			return '<a href="' + encodeURI(d['$field']) + '" target="_blank">' + util.escape(d['$field']) + '</a>';
		}
EOF;
                    break;
                case 'uploadimage':
                    $templet = <<<EOF

		templet: function (d) {
			return '<img src="'+encodeURI(d['$field'])+'" style="max-width:32px;max-height:32px;" alt="" />'
		}
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 = <<<EOF

		templet: function (d) {
			let field = "$field";
			if (typeof d[field] == "undefined") return "";
			let items = [];
			layui.each((d[field] + "").split(","), function (k , v) {
				items.push(apiResults[field][v] || v);
			});
			return util.escape(items.join(","));
		}
EOF;

            }

            $cols .= <<<EOF
,{
		$schema$templet
	}
EOF;

        }

        $cols = <<<EOF

// 表头参数
let cols = [
	{
		type: "checkbox",
		align: "center"
	}$cols,{
		title: "操作",
		toolbar: "#table-bar",
		align: "center",
		fixed: "right",
		width: 120,
	}
];

EOF;
        if (!$api && $api_result) {
            $codes = <<<EOF
// 获取表格中下拉或树形组件数据
let apiResults = {};$api_result
EOF;
        } else if ($api && !$api_result) {
            $codes = <<<EOF
// 获取表格中下拉或树形组件数据
let apis = [];$api
EOF;
        } else if ($api && $api_result) {
            $codes = <<<EOF
// 获取表格中下拉或树形组件数据
let apis = [];$api
let apiResults = {};$api_result
EOF;
        }

        if ($api) {
            $codes = <<<EOF
$cols
// 渲染表格
function render()
{
    table.render({
        elem: "#data-table",
        url: SELECT_API,
        page: true,
        cols: [cols],
        skin: "line",
        size: "lg",
        toolbar: "#table-toolbar",
        autoSort: false,
        defaultToolbar: [{
            title: "刷新",
            layEvent: "refresh",
            icon: "layui-icon-refresh",
        }, "filter", "print", "exports"],
        done: function () {
            layer.photos({photos: 'div[lay-id="data-table"]', anim: 5});
        }
    });
}

$codes
let count = apis.length;
layui.each(apis, function (k, item) {
    let [field, url] = item;
    $.ajax({
        url: url,
        dateType: "json",
        success: function (res) {
            if (res.code) {
                return layui.popup.failure(res.msg);
            }
            function travel(items) {
                for (let k in items) {
                    let item = items[k];
                    apiResults[field][item.value] = item.name;
                    if (item.children) {
                        travel(item.children);
                    }
                }
            }
            travel(res.data);
        },
        complete: function () {
            if (--count === 0) {
                render();
            }
        }
    });
});
if (!count) {
    render();
}

EOF;

        } else {
            $codes = <<<EOF
$cols
// 渲染表格
table.render({
    elem: "#data-table",
    url: SELECT_API,
    page: true,
    cols: [cols],
    skin: "line",
    size: "lg",
    toolbar: "#table-toolbar",
    autoSort: false,
    defaultToolbar: [{
        title: "刷新",
        layEvent: "refresh",
        icon: "layui-icon-refresh",
    }, "filter", "print", "exports"],
    done: function () {
        layer.photos({photos: 'div[lay-id="data-table"]', anim: 5});
    }
});

$codes
EOF;

        }

        return str_replace("\n", "\n" . str_repeat('	', $indent), $codes);

    }

    /**
     * 预处理props
     */
    private function prepareProps($props)
    {
        $raw_list = ['true','false','null','undefined'];
        foreach ($props as $k => $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");
    }


}