[],'right'=>[]]; protected bool $dialog = false; protected bool $vertical = true; protected array $map = []; public Collection $element; /** * Form constructor. * @param $data * @param bool $model */ public function __construct($data = null, bool $model = true) { if (!$model) { // 虚拟数据 $this->info = $data; } else { // 数据模型 if ($data instanceof ActiveRecord) { $this->model = $data; $this->modelElo = $data; $this->info = $data->getAttributes(); } else { $this->info = $data; } } $this->element = Collection::make(); if (\Yii::$app->request->getHeaders()->has('x-dialog')) { $this->dialog = true; } } /** * 设置条件主键 * @param $key * @param $value */ public function setKey($key, $value): void { if ($key && $value) { $this->keys[$key] = $value; } if (!$this->model) { return; } $this->setInfo(); } /** * 获取当前数据 * @return array|ActiveRecord */ public function info() { return $this->info; } /** * 模型对象 * @return ActiveRecord */ public function model(): ActiveRecord { return $this->model; } /** * 模型对象 * @return ActiveRecord */ public function modelElo(): ?ActiveRecord { return $this->modelElo; } /** * 获取元素集合 * @param null $class */ public function getElement($class = null, $num = 0): Collection { if ($class) { $i = 0; foreach ($this->element as $vo) { if ($vo instanceof $class) { if ($i === $num) { return $vo; } $i++; } } } return $this->element; } /** * 表单标题 * @param string $title * @param bool $back * @return $this */ public function title(string $title, bool $back = true): self { $this->title = $title; $this->back = $back; return $this; } /** * 附加脚本 * @param string $content * @param string $return * @return $this */ public function script(string $content = '', string $return = ''): self { $this->script[] = $content; $this->scriptReturn[] = $return; return $this; } /** * 附加属性 * @param $name * @param $value * @return $this */ public function attr($name, $value): Form { $this->attr[] = $name . '="' . $value . '"'; return $this; } /** * 多行组件 * @return Form\Row */ public function row(): Form\Row { $data = new Form\Row(); $data->dialog($this->dialog); $data->vertical($this->vertical); $this->element->push($data); return $data; } /** * 切换组件 * @return Form\Tab */ public function tab(): Form\Tab { $data = new Form\Tab(); $data->dialog($this->dialog); $data->vertical($this->vertical); $this->element->push($data); return $data; } /** * 卡片组件 * @param $callback * @return Form\Card */ public function card($callback): Form\Card { $data = new Form\Card($callback); $data->dialog($this->dialog); $data->vertical($this->vertical); $this->element->push($data); return $data; } /** * Html内容 * @param $name * @param $callback * @return Form\Html */ public function html($name, $callback): Form\Html { $data = new Form\Html($name, $callback); $data->dialog($this->dialog); $data->vertical($this->vertical); $this->element->push($data); return $data; } /** * 布局组件 * @param $callback * @return Form\Layout */ public function layout($callback): Form\Layout { $data = new Form\Layout($callback); $data->dialog($this->dialog); $this->element->push($data); return $data; } // 边栏元素 public function side($callback, string $direction = 'left'): self { $this->sideNode[] = [ 'callback' => $callback, 'direction' => $direction ]; return $this; } /** * 设置字段映射 * @param array $map * @return $this */ public function map(array $map): self { $this->map = array_merge($this->map, $map); return $this; } /** * 获取表单数据 */ public function renderData($info) { $collection = Collection::make(); $this->element->map(function ($item) use ($collection, $info) { $data = $item->getData($info); foreach ($data as $key => $vo) { $collection->put($key, $vo); } }); if ($this->map) { foreach ($this->map as $k => $v) { $key = is_int($k) ? str_replace(['.', '->'], '_', $v) : $k; $vo = is_callable($v) ? call_user_func($v, $info) : Tools::parsingArrData($info,$v); $collection->put($key, $vo); } } return $collection->toArray(); } /** * @return array */ public function renderForm(): array { return $this->element->map(function ($vo, $key) { $sort = $vo->getSort(); $sort = $sort ?? $key; $groupRule = $vo->getGroup(); $group = []; foreach ($groupRule as $rule) { if (is_array($rule['value'])) { $value = json_encode($rule['value']); $group[] = "{$value}.indexOf(data.{$rule['name']}) !== -1"; }else { $group[] = "data.{$rule['name']} == '{$rule['value']}'"; } } $group = $group ? implode(' || ', $group) : null; if ($vo instanceof Form\Composite) { $node = [ 'nodeName' => 'div', 'child' => $vo->getRender(), 'sort' => $sort, ]; if ($group) { $node['vIf'] = $group; } return array_merge($node, $vo->getLayoutAttr()); } $helpNode = []; $prompt = $vo->getPrompt(); $help = $vo->getHelp(); if ($prompt) { $helpNode = [ 'nodeName' => 'a-tooltip', 'class' => 'ml-3', 'position' => 'top', 'content' => $vo->getPrompt(), 'child' => [ 'nodeName' => 'span', 'child' => [ 'nodeName' => 'icon-question-circle' ] ], ]; } if ($help) { $helpNode = [ 'nodeName' => 'div', 'class' => 'text-gray-300 pt-2 pb-2 ml-3', 'child' => $help ]; } $helpLine = $vo->getHelpLine(); $must = $vo->getMust(); $item = [ 'nodeName' => 'a-form-item', 'label' => $vo->getName(), 'field' => $vo->getField(), 'vIf' => $group, 'sort' => $sort, 'child' => [ $vo->getRender(), $helpLine ? [ 'vSlot:help' => '', 'nodeName' => 'div', 'child' => $helpLine ] : [], $helpNode ? [ 'nodeName' => 'div', 'class' => 'ml-2', 'child' => $helpNode ] : [] ] ]; if ($must) { $item['rules'] = [ [ 'required' => true, 'message' => '请填写' . $vo->getName() ] ]; } return $item; })->filter()->sortBy('sort')->values()->toArray(); } /** * @return mixed|void|null * @throws \Exception */ public function setInfo() { if ($this->info) { return $this->info; } if ($this->keys) { $model = $this->model(); foreach ($this->keys as $key => $value) { $model->where($key, $value); } $info = $model->find()->one(); if (empty($info)) { throw new \Exception('内容不存在'); } } else { $info = []; } $this->info = $info; } /** * 提交类型 * @param string $name * @return $this */ public function method(string $name = 'post'): self { $this->method = $name; return $this; } /** * 指定模板变量 * @param string $name * @param null $value * @return $this */ public function assign(string $name, $value = null): self { $this->assign[$name] = $value; return $this; } /** * 是否弹窗 * @param bool $status * @return $this */ public function dialog(bool $status): self { $this->dialog = $status; $this->vertical = true; return $this; } /** * 纵向表单 * @param bool $status * @return $this */ public function vertical(bool $status): self { $this->vertical = $status; return $this; } /** * 获取弹窗状态 * @return bool */ public function getDialog(): bool { return $this->dialog; } /** * 保存链接 * @param $uri * @return $this */ public function action($uri): self { $this->action = $uri; return $this; } /** * 渲染表单(数组) * @return array */ public function renderArray() { $params = \Yii::$app->request->getQueryParams(); $action = route($this->action, $params); // 提交地址 //if ($this->action) { // $action = $this->action; //} else { // $params = \Yii::$app->request->getQueryParams(); // if ($this->modelElo) { // $key = $this->modelElo->getKeyName(); // $id = $this->info->$key; // $params['id'] = $id; // } // $action = route($this->action, $params); //} $node = new Node($action, $this->method, $this->title); $node->dialog($this->dialog); $node->vertical($this->vertical); $node->back($this->back); // 表单元素· $node->element($this->renderForm()); // 表单数据 $node->data($this->renderData($this->info)); // 边栏元素 foreach ($this->sideNode as $vo) { $node->side($vo['callback'], $vo['direction']); } // 处理附加js foreach ($this->script as $key => $value) { $node->script($value, $this->scriptReturn[$key]); } return $node->render(); } public function renderFormWithoutNode(){ $params = \Yii::$app->request->getQueryParams(); $action = route($this->action, $params); // 提交地址 //if ($this->action) { // $action = $this->action; //} else { // $params = \Yii::$app->request->getQueryParams(); // if ($this->modelElo) { // $key = $this->modelElo->getKeyName(); // $id = $this->info->$key; // $params['id'] = $id; // } // $action = route($this->action, $params); //} // 处理附加js $script = []; foreach ($this->script as $key => $value) { if ($value instanceof \Closure) { $script[] = $value(); } else { $script[] = $value; } } return [ 'nodeName' => 'app-form', 'url' => $action, 'method' => $this->method, 'value' => $this->renderData($this->info), 'layout' => $this->vertical ? 'vertical' : 'horizontal', 'back' => $this->back, 'child' => [ 'nodeName' => 'div', 'class' => 'flex', 'vSlot' => '{value: data, submitStatus: loading}', 'child' => $this->dialog ? $this->renderDialog() : $this->renderPage() ] ]; } /** * 获取提交数据 * @param $time * @return Collection */ public function getInput($time): Collection { // 获取提交数据 $data = \Yii::$app->request->post(); // 提交数据处理 if ($this->flow['submit']) { foreach ($this->flow['submit'] as $item) { $data = $item($data, $time); } } // 过滤数据 $collection = Collection::make(); $this->element->map(function ($item) use ($collection, $time) { $inputs = $item->getInput($time); foreach ($inputs as $key => $vo) { $collection->put($key, $vo); } }); //验证数据 $rules = []; $msgs = []; $collection->map(function ($item) use (&$rules, &$msgs) { if ($item['verify']['rule']) { $rules = $rules + $item['verify']['rule']; } if ($item['verify']['msg']) { $msgs = $msgs + $item['verify']['msg']; } }); $validator = \Validator::make($data, $rules, $msgs); if ($this->flow['validator']) { foreach ($this->flow['validator'] as $vo) { $vo($validator); } } $validator->validate(); // 格式化数据 return $collection->map(function ($item) { $value = $item['value']; if ($item['format']) { foreach ($item['format'] as $vo) { $value = call_user_func($vo, $item['value']); } } return ['value' => $value, 'has' => $item['has'], 'pivot' => $item['pivot']]; }); } /** * 流程时间 * @var array */ protected array $prepared = []; /** * 主键值 * @var null */ public $modelId = null; /** * 保存数据 * @return null $modelId */ public function save() { // 获取主键数据 $id = 0; if ($this->modelElo) { $id = $this->keys[$this->modelElo->getPrimaryKey()]; } // 保存类型 $type = $id ? 'edit' : 'add'; // 获取提交数据 $data = $this->getInput($type); // 提取提交数据 $formatData = []; foreach ($data as $key => $vo) { $formatData[$key] = $vo['value']; } $formatData = collect($formatData); // 非模型返回集合 if (!$this->modelElo) { return $formatData; } // 获取模型对象 if ($type === 'add') { $model = $this->modelElo; } else { $model = $this->modelElo->find($id); } // 保存数据库 \Yii::$app->db->transation(function () use ($model, $data, $type, $formatData) { // 保存前置回调 if ($this->flow['front']) { foreach ($this->flow['front'] as $item) { $ret = $item($formatData, $type, $model); if ($ret instanceof ActiveRecord) { $model = $ret; } } } // 树形处理 已废弃 先设置 scoped 数据 再设置上级数据 /*if ($model->parent_id) { if (method_exists($model, 'appendToNode')) { $model = $model->appendToNode($this->modelElo->find($formatData['parent_id'])); } }*/ $data->map(function ($item, $key) use ($model) { $has = $item['has']; // 查询关联对象 if (method_exists($model, $has) && !is_null($item['value'])) { $relation = $model->$has(); // 多对多 if ($relation instanceof \Illuminate\Database\Eloquent\Relations\BelongsToMany) { $this->prepared[] = static function ($model) use ($item) { $sync = is_array($item['value']) ? $item['value'] : [$item['value']]; $syncFormat = []; if ($item['pivot']) { foreach ($sync as $vo) { $syncFormat[$vo] = $item['pivot']; } $sync = $syncFormat; } $model->{$item['has']}()->sync($sync); }; } } else if ($model::getTableSchema()->getColumn($key)) { // 过滤无用字段 $model->$key = $item['value']; } }); // 保存前置回调 if ($this->flow['before']) { foreach ($this->flow['before'] as $item) { $ret = $item($formatData, $type, $model); if ($ret instanceof ActiveRecord) { $model = $ret; } } } $model->save(); // 同步关联数据 foreach ($this->prepared as $callback) { $callback($model); } // 保存后置回调 if ($this->flow['after']) { foreach ($this->flow['after'] as $item) { $item($formatData, $type, $model); } } }); $this->modelId = $model->getKey(); return $this->modelId; } /** * 处理数据之前提交 * @param $callback * @return $this */ public function front($callback): Form { $this->flow['front'][] = $callback; return $this; } /** * 验证表单扩展 * @param $callback * @return $this */ public function validator($callback): Form { $this->flow['validator'][] = $callback; return $this; } /** * 提交之前回调 * @param $callback * @return $this */ public function submit($callback): Form { $this->flow['submit'][] = $callback; return $this; } /** * 保存之前回调 * @param $callback * @return $this */ public function before($callback): Form { $this->flow['before'][] = $callback; return $this; } /** * 保存后回调 * @param $callback * @return $this */ public function after($callback): Form { $this->flow['after'][] = $callback; return $this; } /** * 扩展元素 * @param $method * @param $className */ public function extend($method, $className): void { $this->extend[$method] = $className; } /** * 前端事件 * @param $table * @param $name * @param $type * @return array|false */ public function callbackEvent($table, $name, $type, $data = null) { if (!$this->modelId) { return false; } $rowsData = $data ?: $this->modelElo->where($this->modelElo->getPrimaryKey(), $this->modelId)->get(); $list = $table->renderRowData($rowsData, false); $parentKey = null; if ($table->getTree()) { $parentKey = $this->modelElo->find($this->modelId)->parent_id; } $event = new Event($name); foreach ($list as $item) { $event->add($type, $this->modelId, $item, $parentKey !== false ? ['parentKey' => $parentKey] : []); } return $event->render(); } /** * 回调类库 * @param $method * @param $arguments * @return mixed * @throws \Exception */ public function __call($method, $arguments) { $class = 'backend\\components\\UI\\Form\\' . ucfirst($method); if (!class_exists($class)) { if (!$this->extend[$method]) { throw new \Exception('There is no form method "' . $method . '"'); } $class = $this->extend[$method]; } $object = new $class(...$arguments); $object->dialog($this->dialog); $this->element->push($object); return $object; } private function renderPage(): array { return [ $this->side['left'] ? [ 'nodeName' => 'div', 'class' => 'flex-none flex h-screen flex-col', 'child' => $this->side['left'] ] : [], [ 'nodeName' => 'div', 'class' => 'flex flex-col lg:h-screen flex-grow w-10', 'child' => [ [ 'nodeName' => 'div', 'child' => $this->renderForm() ], [ 'nodeName' => 'div', 'class' => 'flex items-center justify-end gap-2 flex-row ', 'child' => [ $this->back ? [ 'nodeName' => 'route', 'type' => 'back', 'child' => [ 'type' => "outline", 'nodeName' => 'a-button', 'child' => '返回', ] ] : [], [ 'nodeName' => 'a-button', 'html-type' => 'submit', 'vBind:loading' => "loading", 'type' => 'primary', 'child' => $this->back ? '提交' : '保存', ], ] ], ], ], $this->side['right']? [ 'nodeName' => 'div', 'class' => 'flex-none flex h-screen flex-col', 'child' => $this->side['right'] ] : [], ]; } /** * 渲染弹窗 * @return array */ private function renderDialog(): array { return [ 'nodeName' => 'app-dialog', 'title' => $this->title ?: '信息详情', 'class' => 'flex-grow', 'child' => [ [ 'nodeName' => 'div', 'vSlot:default' => '', 'class' => 'flex', 'child' => [ $this->side['left'] ? [ 'nodeName' => 'div', 'class' => 'flex-none', 'child' => $this->side['left'] ] : [], [ 'nodeName' => 'div', 'class' => 'flex-grow p-5 pb-0', 'child' => $this->renderForm() ], $this->side['right'] ? [ 'nodeName' => 'div', 'class' => 'flex-none', 'child' => $this->side['right'] ] : [] ] ], [ 'nodeName' => 'div', 'vSlot:footer' => '', 'class' => 'arco-modal-footer', 'child' => [ [ 'nodeName' => 'route', 'type' => 'back', 'child' => [ 'nodeName' => 'a-button', 'child' => '取消' ] ], [ 'nodeName' => 'a-button', 'type' => 'primary', 'html-type' => 'submit', 'vBind:loading' => "loading", 'child' => '提交' ], ] ] ] ]; } }