You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
699 lines
21 KiB
699 lines
21 KiB
<?php
|
|
|
|
/*
|
|
* The MIT License
|
|
*
|
|
* Copyright 2019 Blobt.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
namespace iron\grid;
|
|
|
|
use Closure;
|
|
use Yii;
|
|
use yii\base\InvalidConfigException;
|
|
use yii\base\Model;
|
|
use yii\helpers\Html;
|
|
use yii\helpers\Json;
|
|
use yii\helpers\Url;
|
|
use yii\helpers\ArrayHelper;
|
|
use yii\i18n\Formatter;
|
|
use yii\widgets\BaseListView;
|
|
use iron\web\GridViewAsset;
|
|
use blobt\grid\DataColumn;
|
|
|
|
/**
|
|
* @author Blobt
|
|
* @email 380255922@qq.com
|
|
* @created Aug 13, 2019
|
|
*/
|
|
class GridView extends BaseListView
|
|
{
|
|
|
|
/**
|
|
* @var string 渲染列数据的类,默认是'yii\grid\DataColumn'
|
|
*/
|
|
public $dataColumnClass;
|
|
|
|
/**
|
|
* @var array 表格说明的html属性
|
|
*/
|
|
public $captionOptions = [];
|
|
|
|
/**
|
|
* @var array 表格外层div的属性
|
|
*/
|
|
public $options = ['class' => 'card'];
|
|
|
|
/**
|
|
* @var array table的html属性
|
|
*/
|
|
public $tableOptions = ['class' => 'table table-bordered table-striped dataTable'];
|
|
|
|
/**
|
|
* @var array 表格头部html属性
|
|
*/
|
|
public $headerRowOptions = [];
|
|
|
|
/**
|
|
* @var array 表格脚部html属性
|
|
*/
|
|
public $footerRowOptions = [];
|
|
|
|
/**
|
|
* @var array|Cloure 表格每一行的html属性
|
|
* 这个参数除了可以是一个options数组外,还可以是一个匿名函数,该函数必须返回一个options数组,
|
|
* 渲染每一行都会调用该函数
|
|
* 该函数必须遵循以下声明规则
|
|
* ```php
|
|
* function ($model, $key, $index, $grid)
|
|
* ```
|
|
*
|
|
* - `$model`: 每行的模型
|
|
* - `$key`: id值
|
|
* - `$index`: [[dataProvider]]提供的索引号
|
|
* - `$grid`: GridView 对象
|
|
*/
|
|
public $rowOptions = [];
|
|
|
|
/**
|
|
* @var Closure an 一个匿名函数(结构和[[rowOptions]]一样),每行渲染前后都会被调用
|
|
*/
|
|
public $beforeRow;
|
|
public $afterRow;
|
|
|
|
/**
|
|
* @var bool 是否显示表格头
|
|
*/
|
|
public $showHeader = true;
|
|
|
|
/**
|
|
* @var bool 是否显示表格脚
|
|
*/
|
|
public $showFooter = false;
|
|
|
|
/**
|
|
* @var bool 没有数据情况下是否显示
|
|
*/
|
|
public $showOnEmpty = true;
|
|
|
|
/**
|
|
* @var array|Formatter 用来格式化输出属性值
|
|
*/
|
|
public $formatter;
|
|
|
|
/**
|
|
* @var string 摘要的显示样式
|
|
*
|
|
*
|
|
* - `{begin}`: 开始条数
|
|
* - `{end}`: 结束条数
|
|
* - `{count}`: 显示条数
|
|
* - `{totalCount}`: 总记录条数
|
|
* - `{page}`: 显示分页
|
|
* - `{pageCount}`: 总分页数
|
|
* - `{select}`: 显示页数
|
|
*/
|
|
public $summary = "{select} 显示{begin}~{end}条 共{totalCount}条";
|
|
|
|
/**
|
|
* @var array 摘要的html属性
|
|
*/
|
|
public $summaryOptions = ['class' => 'summary'];
|
|
|
|
/**
|
|
* @var array 列配置数组. 数组每一项代表一个列,列数组可以包括class、attribute、format、label等。
|
|
* 例子:
|
|
* ```php
|
|
* [
|
|
* ['class' => SerialColumn::className()],
|
|
* [
|
|
* 'class' => DataColumn::className(), //渲染用到的类,没一列都默认使用[[DataColumn]]渲染,所以这里可以忽略
|
|
* 'attribute' => 'name', //代表每一行的数据原
|
|
* 'format' => 'text', //输出的格式
|
|
* 'label' => 'Name', //label
|
|
* '' => ''
|
|
* ],
|
|
* ['class' => CheckboxColumn::className()],
|
|
* ]
|
|
* ```
|
|
*
|
|
* 当然,也支持简写成这样:[[DataColumn::attribute|attribute]], [[DataColumn::format|format]],
|
|
* 或 [[DataColumn::label|label]] options: `"attribute:format:label"`.
|
|
* 所以上面例子的 "name" 列能简写成这样 : `"name:text:Name"`.
|
|
* 甚至"format"和"label"都是可以不制定的,因为它们都有默认值。
|
|
*
|
|
* 其实大多数情况下都可以使用简写:
|
|
*
|
|
* ```php
|
|
* [
|
|
* 'id',
|
|
* 'amount:currency:Total Amount',
|
|
* 'created_at:datetime',
|
|
* ]
|
|
* ```
|
|
*
|
|
* 当[[dataProvider]]提供active records, 且active record又和其它 active record建立了关联关系的,
|
|
* 例如 the `name` 属性是 和 `author` 关联,那么你可以这样制定数据:
|
|
*
|
|
* ```php
|
|
* // shortcut syntax
|
|
* 'author.name',
|
|
* // full syntax
|
|
* [
|
|
* 'attribute' => 'author.name',
|
|
* // ...
|
|
* ]
|
|
* ```
|
|
*/
|
|
public $columns = [];
|
|
|
|
/**
|
|
* @var string 当单元格数据为空时候显示的内容。
|
|
*/
|
|
public $emptyCell = ' ';
|
|
|
|
/**
|
|
* @var string TODO:这个目前用来做页数选择,具体原理没有研究清楚
|
|
*/
|
|
public $filterSelector = 'select[name="per-page"]';
|
|
|
|
/**
|
|
* @var type
|
|
*/
|
|
public $filter;
|
|
|
|
/**
|
|
* @var array 批量操作的选项
|
|
*/
|
|
public $batch;
|
|
|
|
/**
|
|
* @var string 表格的layout:
|
|
*
|
|
* - `{summary}`: 摘要.
|
|
* - `{items}`: 表格项.
|
|
* - `{pager}`: 分页.
|
|
* - `{batch}`: 批量处理
|
|
*/
|
|
public $layout = <<< HTML
|
|
<div class="card-body">
|
|
<div id="example2_wrapper" class="dataTables_wrapper dt-bootstrap4">
|
|
<div class="row">
|
|
<div class="col-sm-12 col-md-6">
|
|
{batch}
|
|
<a href="{url}create" class="btn btn-default"><i class="fas fa-plus-square mr-2"></i>添加</a>
|
|
<!-- <a href="#" data-url='export' class="export btn btn-default"><i class="fa fa-file-excel-o"></i>导出</a>-->
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><i class="fas fa-file-upload mr-2"></i>导出</button>
|
|
<ul class="dropdown-menu" role="menu">
|
|
<li> <a class="dropdown-item export-page" href="#" data-url="{url}export">本页</a></li>
|
|
<li> <a class="dropdown-item export-all" href="#" data-url="{url}export">全部</a></li>
|
|
</ul>
|
|
</div>
|
|
<!-- <button type="button" id="export" class="btn btn-default"><i class="fa fa-file-excel-o"></i>导出</button>-->
|
|
</div>
|
|
<div class="col-sm-12 col-md-6">
|
|
{filter}
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-sm-12">
|
|
{items}
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-sm-12 col-md-5">
|
|
<div class="dataTables_length" id="example2_info" role="status" aria-live="polite">
|
|
{summary}
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-12 col-md-7">
|
|
<div class="dataTables_paginate paging_simple_numbers">
|
|
{pager}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
HTML;
|
|
public $batchTemplate = <<< HTML
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-default btn checkbox-toggle"><i class="far fa-square"></i></button>
|
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false">批量操作</button>
|
|
<ul class="dropdown-menu" role="menu">
|
|
{items}
|
|
</ul>
|
|
</div>
|
|
HTML;
|
|
|
|
/**
|
|
* 初始化 grid view.
|
|
* 初始化必须的属性和每个列对象
|
|
* @return
|
|
*/
|
|
public function init()
|
|
{
|
|
parent::init();
|
|
if ($this->formatter === null) {
|
|
$this->formatter = Yii::$app->getFormatter();
|
|
} elseif (is_array($this->formatter)) {
|
|
$this->formatter = Yii::createObject($this->formatter);
|
|
}
|
|
if (!$this->formatter instanceof Formatter) {
|
|
throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.');
|
|
}
|
|
$this->pager = [
|
|
'options' => ['class' => ['justify-content-end', 'pagination']],
|
|
'linkOptions' => ['class' => 'page-link'],
|
|
'pageCssClass' => 'paginate_button page-item',
|
|
'disabledPageCssClass' => 'page-link disabled',
|
|
'firstPageLabel' => '«',
|
|
'prevPageLabel' => '‹',
|
|
'nextPageLabel' => '›',
|
|
'lastPageLabel' => '»',];
|
|
$this->initColumns();
|
|
}
|
|
|
|
public function run()
|
|
{
|
|
$view = $this->getView();
|
|
GridViewAsset::register($view);
|
|
$this->registerGridJs();
|
|
$this->registerIcheckJs();
|
|
$this->registerConfirmJs();
|
|
$this->registerExportJs();
|
|
parent::run();
|
|
}
|
|
|
|
/**
|
|
* 注册GridView Js
|
|
*/
|
|
protected function registerGridJs()
|
|
{
|
|
$options = Json::htmlEncode(['filterUrl' => Url::to(Yii::$app->request->url),
|
|
'filterSelector' => $this->filterSelector]);
|
|
$id = $this->options['id'];
|
|
$this->getView()->registerJs("jQuery('#$id').yiiGridView($options);");
|
|
}
|
|
|
|
/**
|
|
* 注册icheck Js
|
|
*/
|
|
protected function registerIcheckJs()
|
|
{
|
|
$js = <<<SCRIPT
|
|
$('.dataTable input[type="checkbox"]').iCheck({
|
|
checkboxClass: 'icheckbox_flat-blue',
|
|
radioClass: 'iradio_flat-blue'
|
|
});
|
|
$(".checkbox-toggle").click(function () {
|
|
var clicks = $(this).data('clicks');
|
|
if (clicks) {
|
|
//Uncheck all checkboxes
|
|
$(".dataTable input[type='checkbox']").iCheck("uncheck");
|
|
$(".far", this).removeClass("fa-check-square").addClass('fa-square');
|
|
} else {
|
|
//Check all checkboxes
|
|
$(".dataTable input[type='checkbox']").iCheck("check");
|
|
$(".far", this).removeClass("fa-square").addClass('fa-check-square');
|
|
}
|
|
$(this).data("clicks", !clicks);
|
|
});
|
|
SCRIPT;
|
|
$this->getView()->registerJs($js);
|
|
}
|
|
|
|
/**
|
|
* 注册批量操作js
|
|
*/
|
|
protected function registerBatchJs()
|
|
{
|
|
$js = <<<SCRIPT
|
|
$("a.batch_item").click(function(){
|
|
var url = $(this).data("url");
|
|
var act = $(this).text();
|
|
var selected = [];
|
|
|
|
$(".checked input").each(function(){
|
|
selected.push($(this).val());
|
|
});
|
|
|
|
if(selected.length > 0){
|
|
alertify.confirm('系统提示', "确定执行批量 '"+act+"' 操作?", function(){
|
|
$.ajax({
|
|
type: "POST",
|
|
url: url,
|
|
traditional:true,
|
|
data:{ 'ids[]':selected},
|
|
dataType: "json",
|
|
async:false
|
|
});
|
|
window.location.reload();
|
|
},function(){
|
|
|
|
});
|
|
}
|
|
return false;
|
|
})
|
|
SCRIPT;
|
|
$this->getView()->registerJs($js);
|
|
}
|
|
|
|
protected function registerConfirmJs()
|
|
{
|
|
$js = <<<SCRIPT
|
|
$("a[alertify-confirm]").click(function(){
|
|
var message = $(this).attr('alertify-confirm');
|
|
var url = $(this).attr('href');
|
|
var id = $(this).data('id');
|
|
alertify.confirm('系统提示', message,function(){
|
|
$.ajax({
|
|
type: "POST",
|
|
url: url,
|
|
traditional:true,
|
|
data:{ id:id },
|
|
dataType: "json",
|
|
async:false
|
|
});
|
|
window.location.reload();
|
|
},function(){
|
|
|
|
});
|
|
return false;
|
|
});
|
|
SCRIPT;
|
|
$this->getView()->registerJs($js);
|
|
}
|
|
|
|
protected function registerExportJs()
|
|
{
|
|
$js = <<<SCRIPT
|
|
$("a.export-all").click(function(url){
|
|
var url = $(this).data("url");
|
|
if(!location.search){
|
|
window.location.replace(url+"?page-type=all");
|
|
}else{
|
|
window.location.replace(url+location.search+"&page-type=all");
|
|
}
|
|
});
|
|
$("a.export-page").click(function(url){
|
|
var url = $(this).data("url")+location.search;
|
|
if(!location.search){
|
|
window.location.replace(url+"?page-type=page");
|
|
}else{
|
|
window.location.replace(url+location.search+"&page-type=page");
|
|
}
|
|
});
|
|
SCRIPT;
|
|
$this->getView()->registerJs($js);
|
|
}
|
|
|
|
/**
|
|
* 渲染局部
|
|
* @return string|bool
|
|
*/
|
|
public function renderSection($name)
|
|
{
|
|
switch ($name) {
|
|
case '{summary}':
|
|
return $this->renderSummary();
|
|
case '{items}':
|
|
return $this->renderItems();
|
|
case '{pager}':
|
|
return $this->renderPager();
|
|
case '{sorter}':
|
|
return $this->renderSorter();
|
|
case '{filter}':
|
|
return $this->renderFilter();
|
|
case '{batch}':
|
|
return $this->renderBatch();
|
|
case '{url}':
|
|
return strpos(Yii::$app->request->url,'index') ?'': Yii::$app->request->url.'/';
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 渲染表格的html真实table
|
|
* @return string
|
|
*/
|
|
public function renderItems()
|
|
{
|
|
$tableHeader = $this->showHeader ? $this->renderTableHeader() : false;
|
|
$tableBody = $this->renderTableBody();
|
|
|
|
$content = array_filter([
|
|
$tableHeader,
|
|
$tableBody
|
|
]);
|
|
|
|
return Html::tag('table', implode("\n", $content), $this->tableOptions);
|
|
}
|
|
|
|
/**
|
|
* 初始化每列
|
|
* @throws InvalidConfigException
|
|
*/
|
|
protected function initColumns()
|
|
{
|
|
if (empty($this->columns)) {
|
|
throw new InvalidConfigException('The "columns" property must be set.');
|
|
}
|
|
|
|
foreach ($this->columns as $i => $column) {
|
|
if (is_string($column)) {
|
|
$column = $this->createDataColumn($column);
|
|
} else {
|
|
$column = Yii::createObject(array_merge([
|
|
'class' => $this->dataColumnClass ?: DataColumn::className(),
|
|
'grid' => $this,
|
|
], $column));
|
|
}
|
|
if (!$column->visible) {
|
|
unset($this->columns[$i]);
|
|
continue;
|
|
}
|
|
|
|
$this->columns[$i] = $column;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 渲染表头
|
|
* @return string
|
|
*/
|
|
public function renderTableHeader()
|
|
{
|
|
$cells = [];
|
|
foreach ($this->columns as $column) {
|
|
/* @var $column Column */
|
|
$cells[] = $column->renderHeaderCell();
|
|
}
|
|
|
|
$content = Html::tag('tr', implode('', $cells), $this->headerRowOptions);
|
|
return "<thead>\n" . $content . "\n</thead>";
|
|
}
|
|
|
|
/**
|
|
* 渲染表格体
|
|
* @return string
|
|
*/
|
|
public function renderTableBody()
|
|
{
|
|
$models = $this->dataProvider->getModels();
|
|
$keys = $this->dataProvider->getKeys();
|
|
$rows = [];
|
|
|
|
foreach ($models as $index => $model) {
|
|
$key = $keys[$index];
|
|
|
|
if ($this->beforeRow !== null) {
|
|
$row = call_user_func($this->beforeRow, $model, $key, $index, $this);
|
|
if (!empty($row)) {
|
|
$rows[] = $row;
|
|
}
|
|
}
|
|
|
|
$rows[] = $this->renderTableRow($model, $key, $index);
|
|
|
|
if ($this->afterRow !== null) {
|
|
$row = call_user_func($this->afterRow, $model, $key, $index, $this);
|
|
if (!empty($row)) {
|
|
$rows[] = $row;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (empty($rows) && $this->emptyText !== false) {
|
|
$colspan = count($this->columns);
|
|
|
|
return "<tbody>\n<tr><td colspan=\"$colspan\">" . $this->renderEmpty() . "</td></tr>\n</tbody>";
|
|
}
|
|
|
|
return "<tbody>\n" . implode("\n", $rows) . "\n</tbody>";
|
|
}
|
|
|
|
/**
|
|
* 渲染表格的每行
|
|
* @param Objetc $model
|
|
* @param int $key
|
|
* @param int $index
|
|
* @return string
|
|
*/
|
|
public function renderTableRow($model, $key, $index)
|
|
{
|
|
$cells = [];
|
|
foreach ($this->columns as $column) {
|
|
$cells[] = $column->renderDataCell($model, $key, $index);
|
|
}
|
|
if ($this->rowOptions instanceof Closure) {
|
|
$options = call_user_func($this->rowOptions, $model, $key, $index, $this);
|
|
} else {
|
|
$options = $this->rowOptions;
|
|
}
|
|
$options['data-key'] = is_array($key) ? json_encode($key) : (string)$key;
|
|
|
|
//TODO 各行变色放到这里不合理
|
|
if ($index % 2 == 0) {
|
|
$oddEven = 'odd';
|
|
} else {
|
|
$oddEven = 'even';
|
|
}
|
|
|
|
if (isset($options['class'])) {
|
|
$options['class'] += " " . $oddEven;
|
|
} else {
|
|
$options['class'] = $oddEven;
|
|
}
|
|
|
|
return Html::tag('tr', implode('', $cells), $options);
|
|
}
|
|
|
|
/**
|
|
* 渲染摘要显示
|
|
* @return string
|
|
*/
|
|
public function renderSummary()
|
|
{
|
|
$count = $this->dataProvider->getCount();
|
|
if ($count <= 0) {
|
|
return '';
|
|
}
|
|
$summaryOptions = $this->summaryOptions;
|
|
$tag = ArrayHelper::remove($summaryOptions, 'tag', 'div');
|
|
if (($pagination = $this->dataProvider->getPagination()) !== false) {
|
|
$totalCount = $this->dataProvider->getTotalCount();
|
|
$begin = $pagination->getPage() * $pagination->pageSize + 1;
|
|
$end = $begin + $count - 1;
|
|
if ($begin > $end) {
|
|
$begin = $end;
|
|
}
|
|
$page = $pagination->getPage() + 1;
|
|
$pageCount = $pagination->pageCount;
|
|
}
|
|
|
|
return Yii::$app->getI18n()->format($this->summary, [
|
|
'begin' => $begin,
|
|
'end' => $end,
|
|
'count' => $count,
|
|
'totalCount' => $totalCount,
|
|
'page' => $page,
|
|
'pageCount' => $pageCount,
|
|
'select' => $this->renderCountSelect()
|
|
], Yii::$app->language);
|
|
}
|
|
|
|
/**
|
|
* 渲染批量操作
|
|
*/
|
|
public function renderBatch()
|
|
{
|
|
if (empty($this->batch) && !is_array($this->batch)) {
|
|
return "";
|
|
}
|
|
|
|
$this->registerBatchJs();
|
|
|
|
$items = "";
|
|
foreach ($this->batch as $item) {
|
|
$items .= Html::tag('li', Html::a(Html::encode($item['label']), '#', ["data-url" => Html::encode($item['url']), "class" => "batch_item dropdown-item"]));
|
|
}
|
|
|
|
return strtr($this->batchTemplate, [
|
|
"{items}" => $items
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 渲染表格的页数select
|
|
* @return string
|
|
*/
|
|
protected function renderCountSelect()
|
|
{
|
|
$items = [
|
|
"20" => 20,
|
|
"50" => 50,
|
|
"100" => 100
|
|
];
|
|
|
|
$per = "条/页";
|
|
|
|
$options = [];
|
|
foreach ($items as $key => $val) {
|
|
$options[$val] = "{$key}{$per}";
|
|
}
|
|
|
|
$perPage = !empty($_GET['per-page']) ? $_GET['per-page'] : 20;
|
|
return Html::dropDownList('per-page', $perPage, $options, ["class" => "custom-select"]);
|
|
}
|
|
|
|
/**
|
|
* 渲染表格的筛选部分
|
|
* @return string
|
|
*/
|
|
protected function renderFilter()
|
|
{
|
|
return $this->filter;
|
|
}
|
|
|
|
/**
|
|
* 根据给定格式,创建一个 [[DataColumn]] 对象
|
|
* @param string $text DataColumn 格式
|
|
* @return DataColumn 实例
|
|
* @throws InvalidConfigException
|
|
*/
|
|
protected function createDataColumn($text)
|
|
{
|
|
if (!preg_match('/^([^:]+)(:(\w*))?(:(.*))?$/', $text, $matches)) {
|
|
throw new InvalidConfigException('The column must be specified in the format of "attribute", "attribute:format" or "attribute:format:label"');
|
|
}
|
|
|
|
return Yii::createObject([
|
|
'class' => $this->dataColumnClass ?: DataColumn::className(),
|
|
'grid' => $this,
|
|
'attribute' => $matches[1],
|
|
'format' => isset($matches[3]) ? $matches[3] : 'text',
|
|
'label' => isset($matches[5]) ? $matches[5] : null,
|
|
]);
|
|
}
|
|
|
|
}
|