Files
xk-api-yii/admin/components/UI/Table.php
2024-09-19 11:32:39 +08:00

848 lines
21 KiB
PHP

<?php
namespace admin\components\UI;
use admin\components\UI\yii\Str;
use admin\components\UI\Table\Node;
use admin\components\UI\Widget\Icon;
use admin\models\Config;
use Illuminate\Support\Collection;
use yii\data\ActiveDataProvider;
use yii\db\ActiveQuery;
use yii\db\ActiveRecord;
use admin\components\Tree;
use admin\components\UI\Table\Action;
use admin\components\UI\Table\Batch;
use admin\components\UI\Table\Column;
use admin\components\UI\Table\Filter;
use admin\components\UI\Table\FilterType;
/**
* 表格UI
* Class Table
* @package backend\components\UI
*/
class Table
{
public ?ActiveRecord $model = null;
public $query = null;
public array $fields = [];
protected ?Collection $columns = null;
protected ?Collection $filters = null;
protected ?Collection $filtersType = null;
protected ?Action $action = null;
protected ?Batch $batch = null;
protected array $expand = [];
protected array $class = [];
protected array $rows = [];
protected array $map = [];
protected array $filterParams = [];
protected string $url = '';
protected bool $urlBind = true;
protected string $key = '';
protected ?bool $dialog = null;
protected string $title = '';
private ?string $eventName = null;
protected array $headerNode = [];
protected array $footerNode = [];
protected array $sideNode = [];
protected array $pageNode = [];
protected bool $tree = false;
protected bool $back = false;
protected int $limit = 20;
protected array $attr = [];
protected $script = [];
protected $scriptReturn = [];
protected $scriptData = [];
protected ?\Closure $dataCallback = null;
protected $data;
public $filterLayout;//rows
/**
* Table constructor.
* @param ActiveRecord $data
*/
public function __construct($data)
{
if ($data instanceof ActiveRecord) {
$this->model = $data;
$this->query = $data::find();
$this->fields = $data::getTableSchema()->getColumnNames();
} else {
$this->data = $data;
}
$this->columns = Collection::make();
$this->filters = Collection::make();
$this->filtersType = Collection::make();
if (\Yii::$app->request->getHeaders()->has('x-dialog')) {
$this->dialog = true;
}
}
public function filterLayout($filterLayout): self
{
$this->filterLayout = $filterLayout;
return $this;
}
/**
* 设置列
* @param string $name
* @param string $label
* @param null $callback
* @return Column
*/
public function column(string $label = '', string $name = '', $callback = null): Column
{
////关联模型
//if ($this->model && Str::contains($name, '.')) {
// return $this->joinColumn($label, $name, $callback);
//}
//数组对象
//if (Str::contains($name, '->')) {
// $name = str_replace('->', '.', $name);
return $this->addColumn($label, $name, $callback);
//}
////是否关联模型
//if ($this->model && $this->hasRelationColumn($label)) {
// $this->model->has($label);
// return $this->addColumn($name, $label, $callback)->setRelation($label);
//}
return $this->addColumn($label, $name, $callback);
}
public function addColumns($fields){
$labels = $this->model->attributeLabels();
foreach ($fields as $field){
$this->column($labels[$field],$field);
}
return true;
}
/**
* 展开行
* @param string $title
* @param array $node
* @param int $width
* @return $this
*/
public function expand(string $title = '', array $node = [], int $width = 100): self
{
$this->expand = [
'title' => $title,
'width' => $width,
'vRender:expandedRowRender:rowData' => $node
];
return $this;
}
/**
* 添加列参数
* @param $name
* @param $label
* @param $callback
* @return Column
*/
protected function addColumn($name, $label, $callback): Column
{
$column = new Column($name, $label, $callback);
$column->setLayout($this);
return tap($column, function ($value) {
$this->columns->push($value);
});
}
/**
* 判断关联模型
* @param $relation
* @return bool
*/
protected function hasRelationColumn($relation): bool
{
if (!method_exists($this->model, $relation)) {
return false;
}
if (!$this->model->{$relation}() instanceof \Illuminate\Database\Eloquent\Relations\Relation) {
return false;
}
return true;
}
/**
* 关联模型
* @param $name
* @param $label
* @param $callback
* @return Column
*/
protected function joinColumn($name, $label, $callback): Column
{
[$relation, $field] = explode('.', $label, 2);
$this->query->with($relation);
return $this->addColumn($name, str_replace('->', '.', $field), $callback)->setRelation($relation);
}
/**
* 获取列集合
* @return Collection
*/
protected function getColumns(): Collection
{
return $this->columns;
}
/**
* 设置行数据
* @param \Closure $callback
* @return $this
*/
public function row(\Closure $callback): self
{
$this->rows[] = $callback;
return $this;
}
/**
* 设置字段映射
* @param array $map
* @return $this
*/
public function map(array $map): self
{
$this->map = array_merge($this->map, $map);
return $this;
}
/**
* 筛选参数
* @param $key
* @param $value
* @return $this
*/
public function filterParams($key, $value): self
{
$this->filterParams[$key] = $value;
return $this;
}
/**
* 自定义头
* @param string|callable|object $callback
* @return $this
*/
public function header($callback): self
{
$this->headerNode[] = $callback;
return $this;
}
/**
* 自定义底部
* @param string|callable|object $callback
* @return $this
*/
public function footer($callback): self
{
$this->footerNode[] = $callback;
return $this;
}
/**
* 自定义侧边
* @param $callback
* @param string $direction
* @param bool $resize
* @param string $width
* @return $this
*/
public function side($callback, string $direction = 'left', bool $resize = false, string $width = '100px'): self
{
$this->sideNode[] = [
'callback' => $callback,
'direction' => $direction,
'resize' => $resize,
'width' => $width
];
return $this;
}
/**
* 自定义page内容
* @param $callback
* @param string $direction
* @return $this
*/
public function page($callback, string $direction = 'left'): self
{
$this->pageNode[] = [
'callback' => $callback,
'direction' => $direction
];
return $this;
}
/**
* 设置样式class
* @param string $class
* @return $this
*/
public function class(string $class): self
{
$this->class[] = $class;
return $this;
}
/**
* 设置绑定方式
* @param $urlBind true 使用url绑定控件
* @return $this
*/
public function urlBind($urlBind): self
{
$this->urlBind = $urlBind;
return $this;
}
/**
* 设置请求事件绑定名称
* @param string|null $eventName
* @return $this
*/
public function eventName(?string $eventName): self
{
$this->eventName = $eventName;
return $this;
}
/**
* 设置筛选条件
* @param string $name
* @param string $field
* @param bool $where
* @param null $default
* @return Filter
*/
public function filter(string $name, string $field, $where = true, $default = null): Filter
{
$filter = new \backend\components\UI\Table\Filter($name, $field, $where, $default);
$filter->setLayout($this);
return tap($filter, function ($value) {
$this->filters->push($value);
});
}
/**
* 筛选类型
* @param string $name
* @param callable|null $where
* @return FilterType
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function filterType(string $name, callable $where = null): FilterType
{
if (!isset($this->filterParams['type'])) {
$this->filterParams('type', \Yii::$app->request->get('type', 0));
}
$filterType = new \backend\components\UI\Table\FilterType($name, $where, $this->filterParams['type']);
$filterType->setLayout($this);
return tap($filterType, function ($value) {
$this->filtersType->push($value);
});
}
/**
* 设置动作
* @return Action
*/
public function action(): Action
{
if (!$this->action) {
$this->action = new Action();
}
return $this->action;
}
/**
* 批量操作
* @return Batch
*/
public function batch(): Batch
{
if (!$this->batch) {
$this->batch = new Batch();
}
return $this->batch;
}
/**
* 树形表格
* @return $this
*/
public function tree(): self
{
$this->tree = true;
return $this;
}
/**
* 树形状态
* @return bool
*/
public function getTree(): bool
{
return $this->tree;
}
/**
* 表格标题
* @param string $title
* @return $this
*/
public function title(string $title): self
{
$this->title = $title;
return $this;
}
/**
* 分页数量
* @param int $num
* @return $this
*/
public function limit(int $num): self
{
$this->limit = $num;
return $this;
}
/**
* 模型对象
*/
public function model(): ActiveQuery
{
return $this->query;
}
/**
* 模型对象
* @return ActiveRecord
*/
public function modelElo(): ?ActiveRecord
{
return $this->model;
}
/**
* 设置附加属性
* @param $name
* @param $value
* @return $this
*/
public function attr($name, $value): self
{
$this->attr[$name] = $value;
return $this;
}
/**
* 窗口滚动参数
* @param $x
* @param $y
* @return $this
*/
public function scroll($x,$y){
return $this->attr('scroll',['x' => $x,'y' => $y]);
}
/**
* 设置表格主键
* @param $key
* @return $this
*/
public function key($key): self
{
$this->key = $key;
return $this;
}
/**
* 弹窗
* @param bool $status
* @return $this
*/
public function dialog(bool $status = true): self
{
$this->dialog = $status;
return $this;
}
public function isDialog(){
return $this->dialog;
}
/**
* url数据
* @param string $url
* @return $this
*/
public function url(string $url = ''): self
{
$this->url = $url;
return $this;
}
/**
* 获取Url
* @return string
*/
public function getUrl(): string
{
return $this->url;
}
/**
* @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 $data
* @return $this
*/
public function scriptData($data): self
{
$this->scriptData = array_merge($this->scriptData, $data);
return $this;
}
/**
* 数据导出
* @param callable $callback
*/
public function export(callable $callback): void
{
// 设置筛选信息
$this->filters->map(function ($filter) {
return $filter->render();
});
// 设置筛选类型
$this->filtersType->map(function ($filter, $key) {
return $filter->render($key);
});
// 查询导出数据
$data = $this->query->all();
// 执行渲染输出
$export = new \backend\components\UI\Table\Export();
$callback($export);
$export->render($data);
}
/**
* 数据回调
* @param callable $callback
* @return $this
*/
public function dataCallback(callable $callback): self
{
$this->dataCallback = $callback;
return $this;
}
/**
* 渲染列node
* @return array
*/
public function renderColumn(): array
{
return $this->getColumns()->map(function ($column, $key) {
$render = $column->getRender();
if (!empty($render)) {
$render['sort'] = $render['sort'] ?? $key;
return $render;
}
})->filter()->sortBy('sort')->values()->toArray();
}
/**
* 渲染组件
* @return Node
*/
private function renderNode()
{
// 扩展节点
$headerNode = [];
foreach ($this->headerNode as $vo) {
$headerNode[] = is_callable($vo) ? $vo() : $vo;
}
$footerNode = [];
foreach ($this->footerNode as $vo) {
$footerNode[] = is_callable($vo) ? $vo() : $vo;
}
// 动作节点
$actionNode = $this->action ? $this->action()->render() : [];
// 批处理节点
$batchNode = $this->batch ? $this->batch()->render() : [];
// 类型筛选
$typeNode = $this->filtersType->map(function ($filter, $key) {
return $filter->render($key);
})->toArray();
// 筛选数据
$filters = $this->filters->map(function ($filter) {
return $filter->render();
})->toArray();
$filterNode = [];
$quickNode = [];
foreach ($filters as $vo) {
if (isset($vo['quick']) && $vo['quick']) {
$quickNode[] = $vo['render'];
} else {
$filterNode[] = $vo['render'];
}
}
// 表格列节点
$columnNode = $this->getColumns()->map(function ($column, $key) {
$render = $column->getRender();
if (!empty($render)) {
$render['sort'] = $render['sort'] ?? $key;
return $render;
}
})->filter()->sortBy('sort')->values()->toArray();
$keyName = $this->key ?: ($this->model ? $this->model->getPrimaryKey() : null);
$node = new Node($this->url ?: \Yii::$app->urlManager->createUrl(\Yii::$app->request->getPathInfo() . '/ajax'), $keyName, $this->title);
$node->urlBind($this->urlBind);
$node->class(implode(' ', $this->class));
$node->params($this->attr);
$node->data($this->filterParams);
$node->columns($columnNode);
$node->expand($this->expand);
$node->eventName($this->eventName);
foreach ($this->script as $key => $value) {
$node->script($value, $this->scriptReturn[$key]);
}
if ($this->scriptData) {
$node->scriptData($this->scriptData);
}
$node->type($typeNode);
$node->quickFilter($quickNode);
$node->filter($filterNode);
$node->filterLayout($this->filterLayout);
foreach ($this->sideNode as $vo) {
$node->side($vo['callback'], $vo['direction'], $vo['resize'], $vo['width']);
}
foreach ($this->pageNode as $vo) {
$node->page($vo['callback'], $vo['direction']);
}
$node->header($headerNode);
$node->footer($footerNode);
if ($actionNode) {
$node->action($actionNode);
}
if ($batchNode) {
$node->bath($batchNode);
}
return $node;
}
/**
* 渲染表格(数组)
* @return array
*/
public function renderArray()
{
$node = $this->renderNode();
return $node->render();
}
/**
* 只渲染table
* @return array
*/
public function renderTableCore()
{
$node = $this->renderNode();
return $node->renderTableCore();
}
/**
* 数据渲染
* @return array
*/
public function renderAjax()
{
// 筛选数据
$this->filters->map(function ($filter) {
$filter->execute($this->query);
});
$this->filtersType->map(function ($filter, $key) {
$filter->execute($this->query, $key);
});
// 列筛选数据
if ($this->columns) {
$this->columns->map(function ($column) {
if (method_exists($column, 'execute')) {
$column->execute($this->query);
}
});
}
//主键
$key = $this->key ?: ($this->model ? $this->model->getPrimaryKey() : '');
$limit = \Yii::$app->request->get('limit', $this->limit);
// 查询列表
if ($this->query) {
$data = $this->query;
if ($this->tree) {
$data = $data->paginate(99999)->eloquent();
$data->setCollection($data->getCollection()->toTree());
} else {
$data = $data->paginate($limit)->eloquent();
}
} else {
$data = $this->paginateCollection($this->data, $limit);
if ($this->tree) {
$data->setCollection(collect(Tree::arr2table($data->getCollection()->toArray(), $key, 'parent_id')));
}
}
if ($this->dataCallback) {
$dataCallback = call_user_func($this->dataCallback, $data->getCollection());
$data->setCollection($dataCallback);
}
$totalPage = $data->lastPage();
$page = $data->currentPage();
$total = $data->total();
$columns = [];
if ($this->columns) {
$columns = $this->columns->map(function ($column) {
return $column;
})->filter();
}
// 设置行数据回调
$this->map[] = $key;
// 排序自动设置key
if ($this->tree) {
$this->map['key'] = $key;
}
$resetData = $this->formatData($data, $columns,$this->tree);
return app_success('ok', [
'data' => $resetData,
'total' => $total,
'pageSize' => $limit,
'totalPage' => $totalPage,
]);
}
/**
* 渲染行数据
* @param Collection $data
* @param bool $tree
* @return array
*/
public function renderRowData(Collection $data, bool $tree = true): array
{
if ($this->dataCallback) {
$data = call_user_func($this->dataCallback, $data);
}
$key = $this->key ?: ($this->model ? $this->model->getPrimaryKey() : '');
$columns = [];
if ($this->columns) {
$columns = $this->columns->map(function ($column) {
return $column;
})->filter();
}
// 设置行数据回调
$this->map[] = $key;
// 排序自动设置key
if ($this->tree) {
$this->map['key'] = $key;
}
return $this->formatData($data, $columns, $tree);
}
/**
* @param $data
* @param $columns
* @param bool $tree
* @return array
*/
private function formatData($data, $columns, bool $tree = true): array
{
$resetData = [];
foreach ($data as $vo) {
$rowData = [];
if ($this->rows) {
foreach ($this->rows as $row) {
if ($call = call_user_func($row, $vo)) {
$rowData = $call;
}
}
}
foreach ($columns as $column) {
if ($colData = $column->getData($vo)) {
foreach ($colData as $k => $v) {
$rowData[$k] = $v;
}
}
}
if ($this->map) {
foreach ($this->map as $k => $v) {
$rowData[is_int($k) ? str_replace(['.', '->'], '_', $v) : $k] = is_callable($v) ? call_user_func($v, $vo) : Tools::parsingArrData($vo, $v);
}
}
if ($vo['children'] && $tree) {
$rowData['children'] = $this->formatData($vo['children'], $columns, $tree);
}
$resetData[] = $rowData;
}
return $resetData;
}
/**
* 集合分页
* @param $collection
* @param $perPage
* @param string $pageName
* @param null $fragment
*/
protected function paginateCollection($collection, $perPage, $pageName = 'page', $fragment = null): ActiveDataProvider
{
parse_str(\Yii::$app->request->getQueryString(), $query);
unset($query[$pageName]);
return new ActiveDataProvider([
'query' => $query,
'pagination' => [
'pageSize' => 20,
],
]
);
}
}