初始化仓库

This commit is contained in:
2024-09-19 11:32:39 +08:00
commit 8f5315e936
921 changed files with 76455 additions and 0 deletions

55
.gitignore vendored Normal file
View File

@@ -0,0 +1,55 @@
# yii console command
#/yii
/admin/runtime/
/member/runtime/
/service/runtime/
/console/runtime/
/platform/runtime/
# phpstorm project files
/.idea
/.vscode
# netbeans project files
nbproject
# zend studio for eclipse project files
.buildpath
.project
.settings
# windows thumbnail cache
Thumbs.db
# composer vendor dir
# composer itself is not needed
composer.phar
# Mac DS_Store Files
.DS_Store
# phpunit itself is not needed
phpunit.phar
# local phpunit config
/phpunit.xml
composer.lock
.env
.env.dev
.env.example
.htaccess
nginx.htaccess
web/.htaccess
web/nginx.htaccess
web/admin
web/service/uploads
common/config/codeception-local.php
common/config/main-local.php
common/config/params-local.php
yii_test
yii_test.bat
/vendor/

29
LICENSE.md Normal file
View File

@@ -0,0 +1,29 @@
Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Yii Software LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

31
README.md Normal file
View File

@@ -0,0 +1,31 @@
### 3 分钟了解如何进入开发
欢迎使用云效 Codeup通过阅读以下内容你可以快速熟悉 Codeup ,并立即开始今天的工作。
### 提交**文件**
首先,你需要了解在 Codeup 中如何提交代码文件,跟着文档「[__提交第一行代码__](https://help.aliyun.com/document_detail/153708.html)」一起操作试试看吧。
### 开启代码检测
开发过程中为了更好的管理你的代码资产Codeup 内置了「[__代码检测服务__](https://help.aliyun.com/document_detail/434321.html),可设置提交或合并请求的变更自动触发扫描,并及时提供结果反馈。
![](https://img.alicdn.com/tfs/TB1nRDatoz1gK0jSZLeXXb9kVXa-1122-380.png "")
![](https://img.alicdn.com/tfs/TB1PrPatXY7gK0jSZKzXXaikpXa-1122-709.png "")
### 发起代码评审
功能开发完毕后,通常你需要发起「[__代码合并和评审__](https://help.aliyun.com/document_detail/153872.html)」Codeup 支持多人协作的代码评审服务,你可以通过「[__保护分支__](https://help.aliyun.com/document_detail/153873.html)」策略及「[__合并请求设置__](https://help.aliyun.com/document_detail/153874.html)」对合并过程进行流程化管控,同时提供 WebIDE 在线代码评审及冲突解决能力,让你的评审过程更加流畅。
![](https://img.alicdn.com/tfs/TB1XHrctkP2gK0jSZPxXXacQpXa-1432-887.png "")
![](https://img.alicdn.com/tfs/TB1V3fctoY1gK0jSZFMXXaWcVXa-1432-600.png "")
### 查看代码贡献
代码库提供了图形化报表帮助企业查看团队的代码提交和代码行贡献情况,此外还支持查看提交评审率、千行代码评论数等指标以衡量成员的代码评审活动参与度。
### 成员协作
是时候邀请成员一起编写卓越的代码工程了,请点击右上角「成员」邀请你的小伙伴开始协作吧!
### 更多
Git 使用教学、高级功能指引等更多说明,参见[__Codeup帮助文档__](https://help.aliyun.com/document_detail/153784.html)。

88
Vagrantfile vendored Normal file
View File

@@ -0,0 +1,88 @@
require 'yaml'
require 'fileutils'
required_plugins_installed = nil
required_plugins = %w( vagrant-hostmanager vagrant-vbguest )
required_plugins.each do |plugin|
unless Vagrant.has_plugin? plugin
system "vagrant plugin install #{plugin}"
required_plugins_installed = true
end
end
# IF plugin[s] was just installed - restart required
if required_plugins_installed
# Get CLI command[s] and call again
system 'vagrant' + ARGV.to_s.gsub(/\[\"|\", \"|\"\]/, ' ')
exit
end
domains = {
app: 'y2aa-app.test',
admin: 'y2aa-admin.test'
}
config = {
local: './vagrant/config/vagrant-local.yml',
example: './vagrant/config/vagrant-local.example.yml'
}
# copy config from example if local config not exists
FileUtils.cp config[:example], config[:local] unless File.exist?(config[:local])
# read config
options = YAML.load_file config[:local]
# check github token
if options['github_token'].nil? || options['github_token'].to_s.length != 40
puts "You must place REAL GitHub token into configuration:\n/yii2-app-advanced/vagrant/config/vagrant-local.yml"
exit
end
# vagrant configurate
Vagrant.configure(2) do |config|
# select the box
config.vm.box = 'bento/ubuntu-18.04'
# should we ask about box updates?
config.vm.box_check_update = options['box_check_update']
config.vm.provider 'virtualbox' do |vb|
# machine cpus count
vb.cpus = options['cpus']
# machine memory size
vb.memory = options['memory']
# machine name (for VirtualBox UI)
vb.name = options['machine_name']
end
# machine name (for vagrant console)
config.vm.define options['machine_name']
# machine name (for guest machine console)
config.vm.hostname = options['machine_name']
# network settings
config.vm.network 'private_network', ip: options['ip']
# sync: folder 'yii2-app-advanced' (host machine) -> folder '/app' (guest machine)
config.vm.synced_folder './', '/app', owner: 'vagrant', group: 'vagrant'
# disable folder '/vagrant' (guest machine)
config.vm.synced_folder '.', '/vagrant', disabled: true
# hosts settings (host machine)
config.vm.provision :hostmanager
config.hostmanager.enabled = true
config.hostmanager.manage_host = true
config.hostmanager.ignore_private_ip = false
config.hostmanager.include_offline = true
config.hostmanager.aliases = domains.values
# provisioners
config.vm.provision 'shell', path: './vagrant/provision/once-as-root.sh', args: [options['timezone'], options['ip']]
config.vm.provision 'shell', path: './vagrant/provision/once-as-vagrant.sh', args: [options['github_token']], privileged: false
config.vm.provision 'shell', path: './vagrant/provision/always-as-root.sh', run: 'always'
# post-install message (vagrant console)
config.vm.post_up_message = "Frontend URL: http://#{domains[:frontend]}\nBackend URL: http://#{domains[:backend]}"
end

4
admin/Dockerfile Normal file
View File

@@ -0,0 +1,4 @@
FROM yiisoftware/yii2-php:8.1-apache
# Change document root for Apache
RUN sed -i -e 's|/app/web|/app/admin/web|g' /etc/apache2/sites-available/000-default.conf

View File

@@ -0,0 +1,69 @@
<?php
namespace admin\behaviors;
use Yii;
use yii\base\ActionEvent;
use yii\base\Controller;
use admin\models\Menu;
use yii\web\ForbiddenHttpException;
use yii\web\Response;
/**
* 用于请求如果是页面的话跳过action执行
*/
class PageFilterBehavior extends \yii\base\Behavior
{
public $allowActions = [];
public function events()
{
return [
Controller::EVENT_BEFORE_ACTION => 'filter',
];
}
/**
* @param ActionEvent $event
* @return mixed
*/
public function filter($event){
$event->isValid = true; // 继续执行action
//echo 'Access Denied';
$rule = $event->action->getUniqueId();
if($result = $this->allowdCheck($rule)){
return $result;
};
if(!Yii::$app->request->isAjax
&& Yii::$app->request->getHeaders()->get('Accept')!=='application/json'
&& !Yii::$app->request->getHeaders()->has('x-dux-sfc')
){
$event->isValid = false; // 终止执行action
Yii::$app->response->format = Response::FORMAT_HTML;
Yii::$app->response->content = $event->action->controller->renderContent('');
Yii::$app->end();
return false;
}
return true;
}
public function allowdCheck($rule){
foreach ($this->allowActions as $allow) {
//dump($rule); dump(rtrim($allow,'*')); echo '<br>';
if (substr($allow, -1) == '*') {
if (strpos($rule, rtrim($allow,'*')) === 0) {
return true;
}
} else {
if ($rule == $allow) {
return true;
}
}
}
}
}

View File

@@ -0,0 +1,113 @@
<?php
namespace admin\behaviors;
use Yii;
use yii\base\Controller;
use admin\models\Menu;
use yii\web\ForbiddenHttpException;
/**
* RbacBehavior是用于权限检查这个更简单的是继承\yii\filters\AccessControl为了练习写行为这里就继承了\yii\base\Behavior
*
* To use RbacBehavior, declare it in the application config as behavior.
* For example.
*
* ~~~
* 'as rbac' => [
* 'class' => 'backend\behaviors\RbacBehavior',
* 'allowActions' => ['site/login', 'site/error']
* ]
* ~~~
*
*/
class RbacBehavior extends \yii\base\Behavior
{
/**
* @var array 无需权限检查的action
*/
public $allowActions = [];
/**
* ---------------------------------------
* 功能说明
* @return array
* ---------------------------------------
*/
public function events()
{
return [
Controller::EVENT_BEFORE_ACTION => 'rbacAction',
];
}
/**
* ---------------------------------------
* 控制器执行前的rbac处理
* @param $event \yii\base\ActionEvent 为什么是ActionEvent而不是Event
* 因为yii/base/Controller第269行事件参数是$event = new ActionEvent($action)
*
* 注意ActionEvent::$isValid参数true/false分别表示继续执行或终止执行action
* 所以验证成功后要$event->isValid = true参考代码yii/base/Controller第152、270行
* @return boolean
* ---------------------------------------
*/
public function rbacAction($event){
$event->isValid = true; // 继续执行action
$action = $event->action;
$rule = $action->getUniqueId();
if($result = $this->commonCheck($rule)){
return $result;
};
//echo 'Access Denied';
$event->isValid = false; // 终止执行action
$this->denyAccess();
}
/**
* Denies the access of the user. HTTP 403 您没有执行此操作的权限
* The default implementation will redirect the user to the login page if he is a guest;
* if the user is already logged, a 403 HTTP exception will be thrown.
* @throws ForbiddenHttpException if the user is already logged in.
*/
protected function denyAccess()
{
if (\Yii::$app->user->getIsGuest()) {
\Yii::$app->user->loginRequired();
} else {
Yii::$app->user->logout();
throw new ForbiddenHttpException(Yii::t('yii', 'You are not allowed to perform this action.'));
}
}
public function commonCheck($rule)
{
foreach ($this->allowActions as $allow) {
//dump($rule); dump(rtrim($allow,'*')); echo '<br>';
if (substr($allow, -1) == '*') {
if (strpos($rule, rtrim($allow,'*')) === 0) {
return true;
}
} else {
if ($rule == $allow) {
return true;
}
}
}
if($rule == 'index/index'){
if(!\Yii::$app->user->getIsGuest()){
return true;
}
}else{
/* 权限检查 */
if ( Menu::checkRule($rule) ){
return true;
}
}
return false;
}
}

15
admin/codeception.yml Normal file
View File

@@ -0,0 +1,15 @@
namespace: admin\tests
actor_suffix: Tester
paths:
tests: tests
output: tests/_output
data: tests/_data
support: tests/_support
bootstrap: _bootstrap.php
settings:
colors: true
memory_limit: 1024M
modules:
config:
Yii2:
configFile: 'config/codeception-local.php'

View File

@@ -0,0 +1,380 @@
<?php
namespace admin\components;
use admin\behaviors\PageFilterBehavior;
use admin\components\UI\Event;
use admin\foundation\Cors;
use admin\models\Admin;
use admin\models\Config;
use common\helpers\FakeId;
use common\models\AuthRole;
use common\models\AuthRule;
use Yii;
use yii\base\Arrayable;
use yii\base\DynamicModel;
use yii\base\InvalidArgumentException;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use yii\data\DataProviderInterface;
use yii\data\Pagination;
use yii\db\ActiveRecord;
use yii\db\Exception;
use yii\filters\auth\HttpBearerAuth;
use yii\helpers\ArrayHelper;
use yii\rest\ActiveController;
use common\core\TokenAuth;
use yii\base\Controller;
use yii\web\Response;
use function Symfony\Component\String\b;
/**
* 这里注意是继承 yii\rest\ActiveController 因为源码中已经帮我们实现了index/update等方法
* 以及其访问规则verbs()等,
* 其他可参考http://www.yiichina.com/doc/guide/2.0/rest-controllers
*
* 权限采用最简单的QueryParamAuth方式
* 用户角色权限比较复杂,这里没有做
*
* @package api\modules\v1\controllers
*/
class BaseAdminController extends Controller
{
public $field = [];//重定义格式
public $listkey = 'list';
public $pagekey = 'pagination';
public $extend_result = [];
public $layout = 'arco.layout.php';
public $enableCsrfValidation = false;
//beforeAction 注入
protected $mallId;//商城ID
protected $supplierId;//供应商ID
// 不需进行token权限认证的方法
public $optional = [];
public $user;
/**
* ---------------------------------------
* 构造方法
*
* @throws \Throwable
* @throws \yii\base\InvalidConfigException
* @author hlf <phphome@qq.com> 2020/5/21
* ---------------------------------------
*/
public function init()
{
parent::init();
// 多语言需要在http header中设置 app-language:zh-CN
//Yii::$app->language = Yii::$app->request->getHeaders()->get('app-language'); //'en-US';
Yii::$app->params['web'] = Config::lists();
}
/**
* ---------------------------------------
* 行为
*
* @return array
*
* @author hlf <phphome@qq.com> 2020/5/21
* ---------------------------------------
*/
final public function behaviors()
{
$behaviors = parent::behaviors();
$newBehaviors = [];
$newBehaviors['corsFilter'] = [
'class' => Cors::class,
];
// $newBehaviors['pageFilter'] = [
// 'class' => PageFilterBehavior::class,//如果不是接口请求直接返回布局页面
// 'allowActions'=>[
// 'debug/*','gii/*'
// ]
// ];
foreach ($behaviors as $k => $v) {
$newBehaviors[$k] = $v;
}
//unset($behaviors['authenticator']); //删掉保持先cors,后authenticator
// 设置认证方式,接口才认证
$newBehaviors['authenticator'] = [
'class' => HttpBearerAuth::class,
'optional' => $this->optional,
];
return $newBehaviors;
}
public function beforeAction($action)
{
$ret = parent::beforeAction($action); // TODO: Change the autogenerated stub
//不同角色权限
if (!Yii::$app->user->isGuest) {
/** @var Admin $admin */
$admin = Yii::$app->user->identity;
$this->mallId = $this->supplierId = 0;
switch ($admin->role) {
//角色1为官方管理2为市场专员3为客服4为供应商管理5为业务员6为门店管理
case 1:
//官方管理
$this->mallId = FakeId::decodeId($this->get("mallId", 0));
$this->supplierId = FakeId::decodeId($this->get("supplierId", 0));
break;
case 2:
case 3:
//@todo 根据绑定关系
$this->mallId = 0;
$this->supplierId = 0;
break;
case 4:
$this->mallId = 0;
$this->supplierId = $admin->supplier_id;
break;
case 5:
$this->mallId = 0;
$this->supplierId = $admin->supplier_id;
break;
case 6:
$this->supplierId = 0;
$this->mallId = $admin->mall_id;
break;
}
Yii::$app->mallId = $this->mallId;
Yii::$app->supplierId = $this->supplierId;
}
return $ret;
}
public function create($query, $post)
{
$pagination = [];
$defaultPageSize = 10;
if (isset($post['page'])) {
$pagination['page'] = (int)$post['page'];
}
if (isset($post['limit'])) {
$defaultPageSize = (int)$post['limit'];
}
return new ActiveDataProvider([
'query' => $query,
'pagination' => [
'defaultPageSize' => $defaultPageSize,
'params' => $pagination
]
]);
}
/**
* 当前登陆用户ID
* @return int|string
*/
public function getCurrentUserId()
{
return Yii::$app->user->identity->getId();
}
/**
* 取post参数
* @param $name
* @param $defaultValue
* @return array|mixed
*/
public function post($name = null, $defaultValue = null)
{
return Yii::$app->request->post($name, $defaultValue);
}
/**
* 取get参数
* @param $name
* @param $defaultValue
* @return array|mixed
*/
public function get($name = null, $defaultValue = null)
{
return Yii::$app->request->get($name, $defaultValue);
}
/**
* 参数验证
* @param $data
* @param $rules
* @return DynamicModel|\stdClass
* @throws
*/
public function requestValidate($data, $rules)
{
foreach ($rules as $rule) {
if (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
foreach ((array)$rule[0] as $r) {
if (!isset($data[$r])) {
$data[$r] = null;
}
}
}
}
$validate = DynamicModel::validateData($data, $rules);
if ($validate->hasErrors()) {
throw new InvalidArgumentException(current($validate->getFirstErrors()));
}
return $validate;
}
public function serializeData($data)
{
if (Yii::$app->response->format == Response::FORMAT_RAW) {
return $data;
}
if ($data instanceof Model && $data->hasErrors()) {
throw new InvalidArgumentException('参数错误。' . implode('', $data->getFirstErrors()));
// $data = $this->serializeModelErrors($data);
} elseif ($data instanceof Arrayable) {
$data = $this->serializeModel($data);
} elseif ($data instanceof DataProviderInterface) {
$data = $this->serializeDataProvider($data);
} elseif ($data === null) {
$data = [];
}
return array_merge($this->extend_result, $data);
}
protected function serializeDataProvider($dataProvider)
{
if (!empty($this->field)) {
$models = ArrayHelper::toArray($dataProvider->getModels(), $this->field);
} else {
$models = array_values($dataProvider->getModels());
$models = $this->serializeModels($models);
}
$pagination = $dataProvider->getPagination();
$result = [
$this->listkey => $models,
];
if ($pagination !== false) {
return array_merge($result, $this->serializePagination($pagination));
}
return $result;
}
/**
* Serializes a model object.
* @param Arrayable $model
* @return array the array representation of the model
*/
protected function serializeModel($model)
{
return $model->toArray();
}
protected function serializeModels(array $models)
{
foreach ($models as $i => $model) {
if ($model instanceof Arrayable) {
$models[$i] = $model->toArray();
} elseif (is_array($model)) {
$models[$i] = ArrayHelper::toArray($model);
}
}
return $models;
}
protected function serializePagination($pagination)
{
return [
//$this->pagekey => [
'total' => $pagination->totalCount,
'totalPage' => $pagination->getPageCount(),
'pageSize' => $pagination->getPageSize(),
//],
];
}
final public function afterAction($action, $result)
{
$result = parent::afterAction($action, $result);
return $this->serializeData($result);
}
public function createUrl($params)
{
return Yii::$app->urlManager->createUrl($params);
}
/**
* 前端事件
* @param array $model
* @param $type
* @param $name
* @return false|void
*/
public function callbackEvent($models, $key, $type)
{
$event = new Event(get_called_class());
if (!empty($models)) {
foreach ($models as $model) {
$event->add($type, $model[$key], $model);
}
} else {
//没有的话默认刷新事件
$event->add($type, $key, []);
}
$this->extend_result['__event'] = $event->render()['__event'];
}
public function render($view, $params = [])
{
Yii::$app->response->format = Response::FORMAT_RAW;
$this->layout = false;//去掉布局
return parent::render($view, $params); // TODO: Change the autogenerated stub
}
public function getErrorMsg($model = null)
{
if (!$model) {
$model = $this;
}
$msg = isset($model->errors) ? current($model->errors)[0] : '数据异常!';
return $msg;
}
public function actionCheckAuth()
{
$user_id = Yii::$app->user->identity;
$path = 'Admin/' . Yii::$app->requestedRoute;
$path = explode('/', $path);
foreach ($path as $value) {
$v = ucfirst($value);
$arr[] = $v;
}
$new_path = implode('/', $arr);
$AuthRule = AuthRule::find()->where(['path' => $new_path])->one();
if (!$AuthRule) throw new Exception('权限不存在!!!');
if ($AuthRule->status == 0) {
throw new Exception('该权限已被禁用');
}
$AuthRole = AuthRole::find()->where(['rule_id' => $AuthRule->id, 'role_id' => $user_id->role])->one();
if (!$AuthRole) {
throw new Exception('您没有:' . $AuthRule->title . '的权限');
}
if ($AuthRole->status == 0) {
throw new Exception('您的'.$AuthRule->title.'权限已被禁用');
}
return $AuthRule;
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace admin\components;
class HigherOrderTapProxy
{
/**
* The target being tapped.
*
* @var mixed
*/
public $target;
/**
* Create a new tap proxy instance.
*
* @param mixed $target
* @return void
*/
public function __construct($target)
{
$this->target = $target;
}
/**
* Dynamically pass method calls to the target.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
$this->target->{$method}(...$parameters);
return $this->target;
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace admin\components;
use yii\db\ActiveRecord;
class ModelAgent
{
public $model;
public function __construct(ActiveRecord $model)
{
$this->model = $model;
}
public function __call($method, $arguments)
{
$this->model = $this->model->$method(...$arguments);
return $this;
}
}

121
admin/components/Tree.php Normal file
View File

@@ -0,0 +1,121 @@
<?php
namespace admin\components;
/**
* Class Tree
*/
class Tree
{
public static function ruleTree($data){
foreach($data as $v){
if($v['pid'] == 0){
$parent[] = $v;
}else{
$child[$v['pid']][] = $v;
}
}
foreach($parent as $key => $value){
if(isset($child[$value['id']])){
$children = $child[$value['id']];
}else{
$children = [];
}
$parent[$key]['children'] = $children;
}
return $parent;
}
/**
* 数组转树形
* @param array $list
* @param string $id
* @param string $pid
* @param string $son
* @return array
*/
public static function arr2tree(array $list, string $id = 'id', string $pid = 'pid', string $son = 'sub'): array
{
[$tree, $map] = [[], []];
foreach ($list as $item) {
$map[$item[$id]] = $item;
}
foreach ($list as $item) {
if (isset($item[$pid], $map[$item[$pid]])) {
$map[$item[$pid]][$son][] = &$map[$item[$id]];
} else {
$tree[] = &$map[$item[$id]];
}
}
unset($map);
return $tree;
}
/**
* 数组转表格树
* @param array $list 数据列表
* @param string $id ID Key
* @param string $pid 父ID Key
* @param string $name 名称 Key
* @param string $path
* @param string $ppath
* @return array
*/
public static function arr2table(array $list, string $id = 'id', string $pid = 'pid', string $name = '', string $path = 'path', string $ppath = ''): array
{
$tree = [];
foreach (self::arr2tree($list, $id, $pid) as $attr) {
$attr[$path] = "{$ppath}-{$attr[$id]}";
$attr['sub'] = $attr['sub'] ?? [];
$attr['spt'] = substr_count($ppath, '-');
$attr['spl'] = str_repeat(" ├ ", $attr['spt']);
$attr['spl_' . $name] = $attr['spl'] . $attr[$name];
$sub = $attr['sub'];
unset($attr['sub']);
$tree[] = $attr;
if (!empty($sub)) {
$tree = array_merge($tree, self::arr2table($sub, $id, $pid, $name, $path, $attr[$path]));
}
}
return $tree;
}
/**
* 数组转路径
* @param array $data
* @param int $parentId
* @param string $id
* @param string $pid
* @param array $categories
* @return array
*/
public static function arr2path(array $data, int $parentId, string $id = 'id', string $pid = 'pid', array &$categories = []): array
{
if ($data && is_array($data)) {
foreach ($data as $item) {
if ($item[$id] == $parentId) {
$categories[] = $item;
self::arr2path($data, $item[$pid], $id, $pid, $categories);
}
}
}
return $categories;
}
/**
* 获取子id
* @param $data
* @param string $id
* @return array
*/
public static function allIds($data, string $id = 'id'): array
{
$arr = [];
array_walk_recursive($data, static function ($v, $k) use (&$arr, $id) {
if ($k == $id)
$arr[] = $v;
});
return $arr;
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* Created by PhpStorm.
* User: chatfeed
* Date: 2022/5/18
* Time: 4:13 PM
*/
namespace admin\components\UI\Components;
class ApexData
{
public $type;
public $config;
public function __construct(string $type = 'day', array $config = [])
{
$this->type = $type;
$this->config = $config;
}
/**
* @param $startdate
* @param $enddate
* @return array
*/
private function getDateFromRange($startdate, $enddate): array
{
$stimestamp = strtotime($startdate);
$etimestamp = strtotime($enddate);
// 计算日期段内有多少天
$days = ($etimestamp - $stimestamp) / 86400 + 1;
// 保存每天日期
$date = array();
for ($i = 0; $i < $days; $i++) {
$date[] = date('Y-m-d', $stimestamp + (86400 * $i));
}
return $date;
}
public function data($data): array
{
$series = [];
$labels = [];
$group = [];
$names = [];
foreach ($data as $vo) {
if ($this->type === 'day') {
$vo['label'] = date('Y-m-d', strtotime($vo['label']));
}
$group[$vo['name']][$vo['label']] += $vo['value'];
$labels[] = $vo['label'];
$names[] = $vo['name'];
}
$labels = array_unique($labels);
if ($this->type === 'day') {
$labels = $this->getDateFromRange($this->config['start'] ?? $labels[0], $this->config['stop'] ?? end($labels));
}
$date = array();
foreach ($labels as $key => $vo) {
$date[] = strtotime($vo);
}
array_multisort($date, SORT_ASC, $labels);
$names = array_unique($names);
$tmpArr = [];
foreach ($names as $name) {
foreach ($labels as $label) {
$tmpArr[$name][] = $group[$name][$label] ?: 0;
}
}
foreach ($tmpArr as $name => $vo) {
$series[] = [
'name' => $name,
'data' => $vo
];
}
return [$labels, $series];
}
}

View File

@@ -0,0 +1,458 @@
<?php
/**
* Created by PhpStorm.
* User: chatfeed
* Date: 2022/5/18
* Time: 4:20 PM
*/
namespace admin\components\UI\Components;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
/**
* Class Charts
* @package Duxravel\Core\Util
*/
class Charts
{
private string $type = '';
private ?\Closure $option = null;
private int $height = 200;
private $width = '100%';
private bool $zoom = false;
private bool $toolbar = false;
private bool $datatime = false;
private array $legend = [
'status' => false,
'x' => 'right',
'y' => 'top'
];
private array $date = [
'start' => '',
'stop' => '',
'interval' => '1 days',
'format' => 'Y-m-d'
];
private array $series = [];
private array $labels = [];
private array $data = [];
private array $title = [];
private array $subtitle = [];
/**
* @param int|string $width
* @return $this
*/
public function width($width): self
{
$this->width = $width;
return $this;
}
/**
* @param int $height
* @return $this
*/
public function height(int $height): self
{
$this->height = $height;
return $this;
}
/**
* @param string $title
* @param string $align
* @return $this
*/
public function title(string $title, string $align = 'left'): self
{
$this->title = [
'title' => $title,
'align' => $align
];
return $this;
}
/**
* @param string $title
* @param string $align
* @return $this
*/
public function subtitle(string $title, string $align = 'left'): self
{
$this->subtitle = [
'title' => $title,
'align' => $align
];
return $this;
}
/**
* @param false $zoom
* @return $this
*/
public function zoom(bool $zoom = false): self
{
$this->zoom = $zoom;
return $this;
}
/**
* @param false $toolbar
* @return $this
*/
public function toolbar(bool $toolbar = false): self
{
$this->toolbar = $toolbar;
return $this;
}
/**
* @param $status
* @param string $x
* @param string $y
* @return $this
*/
public function legend($status, string $x = "right", string $y = 'top'): self
{
$this->legend = [
'status' => $status,
'x' => $x,
'y' => $y
];
return $this;
}
/**
* 时间轴
* @param bool $status
* @return $this
*/
public function datetime(bool $status = true): self
{
$this->datatime = $status;
return $this;
}
/**
* @param string $start
* @param string $stop
* @param string $interval
* @param string $format
* @return $this
*/
public function date(string $start, string $stop, string $interval = '1 days', string $format = 'Y-m-d'): self
{
$this->date = [
'start' => $start,
'stop' => $stop,
'interval' => $interval,
'format' => $format
];
return $this;
}
/**
* @param string $name
* @param array $data
* @param string $format
* @return $this
*/
public function data(string $name, array $data = [], string $format = 'Ymd'): self
{
$this->data[] = [
'name' => $name,
'data' => $data,
'format' => $format
];
return $this;
}
/**
* 线型图
* @return $this
*/
public function line(): self
{
$this->type = 'line';
$this->option = function () {
return [
'dataLabels' => [
'enabled' => false,
],
'fill' => [
'opacity' => 1,
],
'stroke' => [
'curve' => "straight",
],
'xaxis' => [
'type' => $this->datatime ? 'datetime' : 'category',
'categories' => $this->labels
],
];
};
return $this;
}
/**
* 区域图
* @return $this
*/
public function area(): self
{
$this->type = 'area';
$this->option = function () {
return [
'dataLabels' => [
'enabled' => false,
],
'fill' => [
'opacity' => .2,
'type' => 'solid'
],
'xaxis' => [
'type' => $this->datatime ? 'datetime' : 'category',
'categories' => $this->labels
],
];
};
return $this;
}
/**
* 柱状图
* @return $this
*/
public function column(): self
{
$this->type = 'bar';
$this->option = function () {
return [
'plotOptions' => [
'bar' => [
'columnWidth' => '50%',
]
],
'dataLabels' => [
'enabled' => false,
],
'fill' => [
'opacity' => 1,
],
'xaxis' => [
'type' => $this->datatime ? 'datetime' : 'category',
'categories' => $this->labels
],
];
};
return $this;
}
public function get_uuid($len = 0): string
{
$int = '';
while (strlen($int) != $len) {
$int .= mt_rand(0, 9);
}
return date('Ymd') . substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8) . $int;
}
/**
* @param bool $html
* @return array|string
*/
public function render(bool $html = false)
{
$this->renderData();
$option = call_user_func($this->option);
$option['chart'] = [
'id' => 'vuechart-' . $this->get_uuid(10),
];
$option['grid'] = [
'strokeDashArray' => 4,
];
if ($this->title) {
$option['title'] = [
'text' => $this->title['title'],
'align' => $this->title['align'],
'style' => [
'fontSize' => '16px',
'fontWeight' => 'normal',
]
];
}
if ($this->subtitle) {
$option['subtitle'] = [
'text' => $this->title['title'],
'align' => $this->title['align'],
'style' => [
'fontSize' => '14px',
'fontWeight' => 'normal',
]
];
}
if ($this->toolbar) {
$option['chart']['toolbar'] = [
'show' => true,
'autoSelected' => true
];
} else {
$option['chart']['toolbar'] = [
'show' => false
];
}
if ($this->zoom) {
$option['chart']['zoom'] = [
'enabled' => true,
'type' => 'x',
'autoScaleYaxis' => false
];
} else {
$option['chart']['zoom'] = [
'enabled' => false
];
}
if ($this->legend['status']) {
$option['legend'] = [
'show' => true,
'position' => $this->legend['y'],
'horizontalAlign' => $this->legend['x'],
'floating' => true,
'offsetY' => 0,
'offsetX' => -5
];
} else {
$option['legend'] = [
'show' => false
];
}
if ($html) {
return $this->renderHtml($option);
} else {
return $this->renderNode($option);
}
}
/**
* @param array$option
* @return string
*/
private function renderHtml(array $option): string
{
$option = json_encode($option);
$series = json_encode($this->series);
return <<<HTML
<apexchart
ref="chart"
width="$this->width"
height="$this->height"
type="$this->type"
:options='$option'
:series='$series'
></apexchart>
HTML;
}
/**
* @param array $option
* @return array
*/
private function renderNode(array $option): array
{
return [
'nodeName' => 'apexchart',
'ref' => 'chart',
'width' => $this->width,
'height' => $this->height,
'type' => $this->type,
'options' => $option,
'series' => $this->series
];
}
/**
* @return void
*/
private function renderData(): void
{
$labels = $this->getDateFromRange($this->date['start'] ?: date($this->data['format'], strtotime('-7 day')), $this->date['stop'] ?: date($this->data['format']), $this->date['interval'], $this->date['format']);
$this->labels = $labels;
foreach ($this->data as $data) {
$group = [];
foreach ($data['data'] as $vo) {
$vo['label'] = date_format(date_create_from_format($data['format'], $vo['label']), $this->date['format']);
$group[$vo['label']] += $vo['value'];
}
$tmpArr = [];
foreach ($labels as $label) {
$tmpArr[] = $group[$label] ?: 0;
}
$this->series[] = [
'name' => $data['name'],
'data' => $tmpArr
];
}
}
/**
* @param string $startdate
* @param string $enddate
* @param string $interval
* @param string $format
* @return array
*/
private function getDateFromRange(string $startdate, string $enddate, $interval = '1 days', $format = 'Y-m-d'): array
{
$period = CarbonPeriod::create($startdate, $interval, $enddate)->toArray();
$data = [];
foreach ($period as $date) {
$data[] = $date->format($format);
}
return $data;
}
/**
* @param $method
* @param $arguments
* @return $this
*/
public function __call($method, $arguments)
{
$this->type = $method;
$this->option = function () use ($arguments) {
$option = [
'dataLabels' => [
'enabled' => false,
],
];
if ($arguments && $arguments[0] instanceof \Closure) {
call_user_func($arguments[0], $option, $this);
}
};
return $this;
}
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* Created by PhpStorm.
* User: chatfeed
* Date: 2022/5/19
* Time: 6:40 PM
*/
namespace admin\components\UI\Components;
class Component
{
}

View File

@@ -0,0 +1,29 @@
<?php
namespace admin\components\UI\Components;
use Illuminate\View\Component;
/**
* 加载数据中
* @package backend\components\UI\Components
*/
class Loading extends Component
{
public $title;
public $content;
public function __construct($title = '加载数据中,请稍等...', $content = '如果长时间未加载您可以尝试重新加载')
{
$this->title = $title;
$this->content = $content;
}
/**
* @return mixed
*/
public function render()
{
return view('vendor.duxphp.duxravel-app.src.core.UI.View.Components.loading');
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace admin\components\UI\Components;
use Illuminate\View\Component;
/**
* 数据不存在
* Class NoData
* @package backend\components\UI\Components
*/
class NoData extends Component
{
public $title;
public $content;
public $reload;
public function __construct($title = '未找到数据', $content = '暂时未找到数据,您可以尝试刷新页面', $reload = true)
{
$this->title = $title;
$this->content = $content;
$this->reload = $reload;
}
/**
* @return mixed
*/
public function render()
{
return view('vendor.duxphp.duxravel-app.src.core.UI.View.Components.nodata');
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace admin\components\UI\Components;
use Illuminate\View\Component;
/**
* 趋势图标
* Class Trend
* @package backend\components\UI\Components
*/
class Trend extends Component
{
public $type;
/**
* @param $type
*/
public function __construct($type)
{
$this->type = $type;
}
/**
* @return mixed
*/
public function render()
{
return view('vendor.duxphp.duxravel-app.src.core.UI.View.Components.trend');
}
}

View File

@@ -0,0 +1,15 @@
<?php
/**
* Created by PhpStorm.
* User: chatfeed
* Date: 2022/5/19
* Time: 6:40 PM
*/
namespace admin\components\UI\Components;
abstract class VueComponent
{
abstract function setup(string $script);
abstract function render();
}

View File

@@ -0,0 +1,17 @@
<?php
namespace admin\components\UI\Elements;
/**
* Created by PhpStorm.
* User: chatfeed
* Date: 2022/5/19
* Time: 7:40 PM
*/
class Button extends Element
{
public string $nodeName = 'a-button';
public string $child = '提交';
public string $type='primary';
public string $htmlType = 'submit';
public string $vbindLoading = 'loading';
}

View File

@@ -0,0 +1,50 @@
<?php
namespace admin\components\UI\Elements;
/**
* Created by PhpStorm.
* User: chatfeed
* Date: 2022/5/19
* Time: 7:40 PM
*/
abstract class Element implements \ArrayAccess
{
private array $_attrs = [];
public static function create(array $arr)
{
$el = new static();
foreach ($arr as $k => $v) {
$el->{$k} = $v;
}
return $el;
}
public function toArray(){
return $this->_attrs;
}
public function offsetGet($offset)
{
return $this->_attrs[$offset];
// TODO: Implement offsetGet() method.
}
public function offsetSet($offset, $value)
{
$this->_attrs[$offset] = $value;
return true;
// TODO: Implement offsetSet() method.
}
public function offsetUnset($offset)
{
unset($this->_attrs[$offset]);
return true;
// TODO: Implement offsetUnset() method.
}
public function offsetExists($offset)
{
return isset($this->_attrs[$offset]);
// TODO: Implement offsetExists() method.
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace admin\components\UI;
/**
* 前端事件触发器
* Class Table
* @package backend\components\UI
*/
class Event
{
public string $name = '';
public array $data = [];
/**
* @param $name
*/
public function __construct($name)
{
$this->name = md5($name);
}
/**
* 增加动作
* @param $type
* @param string $key
* @param array $data
* @param array $attr
* @return $this
*/
public function add($type, string $key = '', array $data = [], array $attr = []): self
{
$this->data[] = array_filter(array_merge([
'type' => $type,
'key' => $key,
'data' => $data
], $attr));
return $this;
}
/**
* 渲染数据
* @param false $inner
* @return array
*/
public function render(bool $inner = false): array
{
if ($inner) {
return [
'name' => $this->name,
'data' => $this->data
];
}
return [
'__event' => [
'name' => $this->name,
'data' => $this->data
]
];
}
}

View File

@@ -0,0 +1,986 @@
<?php
namespace admin\components\UI;
use admin\components\UI\Form\Node;
use admin\components\UI\Widget\Icon;
use admin\components\UI\yii\Str;
use Dotenv\Validator;
use yii\db\ActiveRecord;
use Illuminate\Support\Collection;
/**
* 表单UI
* Class Form
* @package backend\components\UI
* @method Form\Cascader cascader(string $name, string $field, $data = null, string $has = '')
* @method Form\CheckBox checkbox(string $name, string $field, $data = null, string $has = '')
* @method Form\Choice choice(string $name, string $field, string $has = '')
* @method Form\Color color(string $name, string $field, string $has = '')
* @method Form\Data data(string $name, string $field, string $has = '')
* @method Form\Date date(string $name, string $field, string $has = '')
* @method Form\Daterange daterange(string $name, string $field, string $has = '')
* @method Form\Datetime datetime(string $name, string $field, string $has = '')
* @method Form\Editor editor(string $name, string $field, string $has = '')
* @method Form\Email email(string $name, string $field, string $has = '')
* @method Form\File file(string $name, string $field, string $has = '')
* @method Form\Image image(string $name, string $field, string $has = '')
* @method Form\Images images(string $name, string $field, string $has = '')
* @method Form\Ip ip(string $name, string $field, string $has = '')
* @method Form\Password password(string $name, string $field, string $has = '')
* @method Form\Radio radio(string $name, string $field, $data = null, string $has = '')
* @method Form\Select select(string $name, string $field, $data = null, string $has = '')
* @method Form\Tree tree(string $name, string $field, $data = null, string $has = '')
* @method Form\Toggle toggle(string $name, string $field, string $has = '')
* @method Form\Tags tags(string $name, string $field, string $has = '')
* @method Form\Tel tel(string $name, string $field, string $has = '')
* @method Form\Text text(string $name, string $field, string $has = '')
* @method Form\Number number(string $name, string $field, string $has = '')
* @method Form\Time time(string $name, string $field, string $has = '')
* @method Form\Url url(string $name, string $field, string $has = '')
* @method Form\Textarea textarea(string $name, string $field, string $has = '')
* @method Form\Location location(string $name, string $field, string $has = '')
*/
class Form
{
public $model;
public $modelElo;
public $info;
protected string $title = '';
protected bool $back = true;
protected array $attr = [];
protected array $extend = [];
protected string $method = 'post';
protected string $action = '';
protected array $keys = [];
protected array $row = [];
protected array $flow = [];
protected array $assign = [];
protected array $script = [];
protected array $scriptReturn = [];
protected array $sideNode = [];
protected array $side = ['left'=>[],'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' => '提交'
],
]
]
]
];
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace admin\components\UI\Form;
/**
* 地区选择器
* @package backend\components\UI\Form
*/
class Area extends Element implements Component
{
protected bool $multi = false;
protected array $map = [
'province' => 'province',
'city' => 'city',
'region' => 'region',
'street' => 'street',
];
/**
* @param string $name
* @param array $map
* @param string $has
*/
public function __construct(string $name, array $map = [], string $has = '')
{
if ($map) {
$this->map = $map;
}
$this->name = $name;
$this->field = end($this->map);
$this->has = $has;
$this->attr['placeholder'] = null;
}
/**
* @return $this
*/
public function multi(): self
{
$this->multi = true;
return $this;
}
/**
* @return array
*/
public function render(): array
{
$data = [
'nodeName' => 'app-cascader',
'nParams' => [
'cascade' => true,
'show-path' => true,
'filterable' => false,
'clearable' => true,
'leaf-only' => true,
'multiple' => $this->multi,
'placeholder' => $this->attr['placeholder'] ?: '请选择' . $this->name,
],
'dataUrl' => route('service.area', ['level' => count($this->map)]),
];
if ($this->model) {
$data['vModel:value'] = $this->getModelField();
}
return $data;
}
/**
* @param $data
* @return array
*/
public function appendInput($data): array
{
$info = \backend\components\Model\Area::where(['code' => $data])->first();
$code = $info->parent_code;
if ($info->level > 3) {
$region = \backend\components\Model\Area::where(['code' => $code])->first();
$code = $region['parent_code'];
}
if ($info->level > 2) {
$city = \backend\components\Model\Area::where(['code' => $code])->first();
$code = $city['parent_code'];
}
if ($info->level > 1) {
$province = \backend\components\Model\Area::where(['code' => $code])->first();
}
$data = [];
if ($region) {
$data[$this->map['region']] = $region->code;
}
if ($city) {
$data[$this->map['city']] = $city->code;
}
if ($province) {
$data[$this->map['province']] = $province->code;
}
return $data;
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace admin\components\UI\Form;
use admin\components\UI\Form;
/**
* 卡片布局
* @package backend\components\UI\Table
*/
class Card extends Composite implements Component
{
protected \Closure $callback;
public function __construct($callback)
{
$this->callback = $callback;
$form = new Form();
$form->dialog($this->dialog);
$form->vertical($this->vertical);
$callback($form);
$this->column[] = [
'object' => $form,
];
}
/**
* @return array
*/
public function render(): array
{
$inner = [];
foreach ($this->column as $vo) {
$inner = $vo['object']->renderForm();
}
if (!$this->dialog) {
$this->class('mb-4 bg-white dark:bg-blackgray-4 rounded shadow p-7 pb-2');
}
return [
'nodeName' => 'div',
'class' => implode(' ', $this->class),
'child' => $inner
];
}
}

View File

@@ -0,0 +1,200 @@
<?php
namespace admin\components\UI\Form;
/**
* 级联选择器
* @package backend\components\UI\Form
*/
class Cascader extends Element implements Component
{
protected bool $tip = false;
protected bool $multi = false;
protected bool $leaf = true;
private array $params = [];
protected bool $treeData = false;
protected string $url = '';
protected string $route = '';
protected $data;
/**
* @param string $name
* @param string $field
* @param null $data
* @param string $has
*/
public function __construct(string $name, string $field, $data = null, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->data = $data;
$this->has = $has;
$this->attr['placeholder'] = null;
}
/**
* 添加选项
* @param $name
* @param $id
* @param $pid
* @return $this
*/
public function add($name, $id, $pid): self
{
$this->data[] = [
'name' => $name,
'id' => $id,
'pid' => $pid
];
return $this;
}
/**
* @param $url
* @return $this
*/
public function url($url): self
{
$this->url = $url;
return $this;
}
/**
* @param string $route
* @param array $params
* @return $this
*/
public function route(string $route, array $params = []): self
{
$this->route = $route;
$this->url = app_route($route, $params);
return $this;
}
/**
* @return $this
*/
public function multi(): self
{
$this->multi = true;
return $this;
}
/**
* leaf choice
* @param bool $leaf
* @return $this
*/
public function leaf(bool $leaf): self
{
$this->leaf = $leaf;
return $this;
}
/**
* tree data
* @param bool $bool
* @return $this
*/
public function tree(bool $bool = true): self
{
$this->treeData = $bool;
return $this;
}
/**
* 标签显示最大条数
* @param int $num
* @return $this
*/
public function maxTagCount(int $num){
return $this->nParams('maxTagCount',$num);
}
/**
* 附加参数
* @param $key
* @param $value
* @return $this
*/
public function nParams($key,$value){
$this->params[$key] = $value;
return $this;
}
/**
* @return array
*/
public function render(): array
{
$data = [];
if ($this->data instanceof \Closure) {
$data = call_user_func($this->data, $this);
}
if (is_array($this->data)) {
$data = $this->data;
}
if (!$this->treeData) {
$options = [];
foreach ($data as $vo) {
$options[] = [
'id' => $vo['id'],
'pid' => $vo['parent_id'],
'value' => $vo['id'],
'label' => $vo['name'],
];
}
$options = \backend\components\Tree::arr2tree($options, 'id', 'pid', 'children');
} else {
$options = $data;
}
$data = [
'nodeName' => 'app-cascader',
'nParams' => array_merge($this->params, [
'check-strictly' => !$this->leaf,
'multiple' => $this->multi,
'options' => $options,
'placeholder' => $this->attr['placeholder'] ?: '请选择' . $this->name,
])
];
if ($this->route) {
$data['vBind:dataUrl'] = $this->url;
} else {
$data['dataUrl'] = $this->url;
}
if ($this->model) {
$data['vModel:value'] = $this->getModelField();
}
return $data;
}
/**
* @param $value
* @return array|mixed
*/
public function dataValue($value)
{
return $this->multi ? array_values(array_filter((array)$this->getValueArray($value))) : $this->getValue($value);
}
/**
* @param $data
* @return string
*/
public function dataInput($data): ?string
{
return is_array($data) ? implode(',', $data) : $data;
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace admin\components\UI\Form;
/**
* 多选框
* @package backend\components\UI\Form
*/
class Checkbox extends Element implements Component
{
protected $data;
/**
* Select constructor.
* @param string $name
* @param string $field
* @param null|array|callable $data
* @param string $has
*/
public function __construct(string $name, string $field, $data = null, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->data = $data;
$this->has = $has;
}
/**
* add data
* @param $name
* @param $value
* @return $this
*/
public function add($name, $value): self
{
$this->data[] = [
'name' => $name,
'value' => $value
];
return $this;
}
/**
* @return array
*/
public function render(): array
{
$data = [];
if ($this->data instanceof \Closure) {
$data = call_user_func($this->data);
}
if (is_array($this->data)) {
$data = $this->data;
}
$child = [];
foreach ($data as $key => $vo) {
$child[] = [
'nodeName' => 'a-checkbox',
'value' => $key,
'child' => $vo,
];
}
$data = [
'nodeName' => 'a-checkbox-group',
'child' => $child
];
if ($this->model) {
$data['vModel:model-value'] = $this->getModelField();
}
return $data;
}
/**
* @param $value
* @return array
*/
public function dataValue($value): array
{
return array_values(array_filter((array)$this->getValueArray($value)));
}
/**
* @param $data
* @return string
*/
public function dataInput($data): ?string
{
return is_array($data) ? implode(',', $data) : $data;
}
}

View File

@@ -0,0 +1,184 @@
<?php
namespace admin\components\UI\Form;
use admin\components\Exceptions\ErrorException;
/**
* 表格管理选择器
* @package backend\components\UI\Form
*/
class Choice extends Element implements Component
{
protected array $column = [];
protected array $ajax = [];
protected bool $option = true;
protected int $number = 0;
/**
* Text constructor.
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
}
/**
* @param string $url
* @param string $key
* @param callable $column
* @param array $types
* @return $this
*/
public function ajax(string $url, string $key, callable $column, array $types = []): self
{
$this->ajax = [
'url' => $url,
'key' => $key,
'column' => $column,
'type' => $types
];
return $this;
}
/**
* text column
* @param string $name
* @param string $field
* @return $this
*/
public function text(string $name, string $field): self
{
$this->column[] = [
'name' => $name,
'key' => $field,
'type' => 'text'
];
return $this;
}
/**
* image column
* @param string $name
* @param string $field
* @return $this
*/
public function image(string $name, string $field): self
{
$this->column[] = [
'name' => $name,
'key' => $field,
'type' => 'image'
];
return $this;
}
/**
* show column
* @param string $name
* @param string $field
* @return $this
*/
public function show(string $name, string $field): self
{
$this->column[] = [
'name' => $name,
'key' => $field,
'type' => 'show'
];
return $this;
}
/**
* hidden column
* @param string $name
* @param string $field
* @return $this
*/
public function hidden(string $name, string $field): self
{
$this->column[] = [
'name' => $name,
'key' => $field,
'type' => 'hidden'
];
return $this;
}
/**
* option status
* @param bool $status
* @return $this
*/
public function option(bool $status = true): self
{
$this->option = $status;
return $this;
}
/**
* maximum number
* @param int $num
* @return $this
*/
public function num(int $num = 0): self
{
$this->number = $num;
return $this;
}
/**
* @return array
* @throws ErrorException
*/
public function render(): array
{
$url = route('service.image.placeholder', ['w' => 64, 'h' => 64, 't' => $this->attr['placeholder'] ?: '图片']);
$ajaxColumn = $this->ajax['column'] ?: [];
if (is_callable($ajaxColumn)) {
$ajaxColumn = $ajaxColumn(new ChoiceColumn());
if (!$ajaxColumn instanceof ChoiceColumn) {
app_error('Choice component configuration error');
}
$ajaxColumn = $ajaxColumn->getData();
}
return [
'nodeName' => 'app-choice',
'vModel:value' => $this->getModelField(),
'column' => $this->column,
'ajaxColumn' => $ajaxColumn,
'ajaxType' => $this->ajax['type'],
'key' => $this->ajax['key'],
'url' => $this->ajax['url'],
'number' => $this->number,
'option' => $this->option,
];
}
/**
* @param $value
* @return array|null
*/
public function dataValue($value): ?array
{
$value = $this->getValue($value);
if ($value instanceof \Illuminate\Database\Eloquent\Collection && $value->count()) {
$values = $value->toArray();
} else if (is_array($value)) {
$values = $value;
} else {
$values = [];
}
return $values;
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace admin\components\UI\Form;
/**
* 表格关联选择器列
* @package backend\components\UI\Form
*/
class ChoiceColumn
{
/**
* @var array
*/
private array $column;
/**
* @param string $name
* @param string $field
* @return $this
*/
public function text(string $name, string $field): self
{
$this->column[] = [
'name' => $name,
'key' => $field,
'type' => 'text'
];
return $this;
}
/**
* @param string $name
* @param string $field
* @return $this
*/
public function image(string $name, string $field): self
{
$this->column[] = [
'name' => $name,
'key' => $field,
'type' => 'image'
];
return $this;
}
/**
* @return array
*/
public function getData(): array
{
return $this->column;
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace admin\components\UI\Form;
/**
* 颜色选择器
* @package backend\components\UI\Form
*/
class Color extends Element implements Component
{
protected array $color = [];
protected bool $picker = false;
/**
* Text constructor.
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
$this->attr['placeholder'] = null;
}
/**
* @return $this
*/
public function picker(): self
{
$this->picker = true;
return $this;
}
/**
* @param array $data
* @return $this
*/
public function color(array $data): self
{
$this->color = $data;
return $this;
}
/**
* @return array
*/
public function render(): array
{
if ($this->picker) {
// 暂无组件
$data = [
];
}else {
$data = [
'nodeName' => 'app-color',
'colors' => $this->color,
'placeholder' => $this->attr['placeholder'] ?: '请选择' . $this->name,
];
}
if ($this->model) {
$data['vModel:value'] = $this->getModelField();
}
return $data;
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace admin\components\UI\Form;
/**
* 组件接口
* Class Component
* @package backend\components\UI
*/
interface Component
{
}

View File

@@ -0,0 +1,62 @@
<?php
namespace admin\components\UI\Form;
use admin\components\UI\Tools;
/**
* 复合组件
* @package backend\components\UI
*/
class Composite extends Element
{
public array $column = [];
public bool $component = true;
public object $form;
/**
* @param null $key
* @param string $item
* @return array|object
*/
public function getColumn($key = null, string $item = '')
{
return $key !== null ? ($item ? $this->column[$key][$item] : $this->column[$key]) : $this->column;
}
/**
* 获取值
* @param string $time
* @return array
*/
public function getInput(string $time = 'add'): array
{
$data = [];
foreach ($this->column as $vo) {
$vo['object']->getElement()->map(function ($item) use (&$data, $time) {
foreach ($item->getInput($time) as $k => $v) {
$data[$k] = $v;
}
});
}
return $data;
}
/**
* @param $info
* @return array
*/
public function getData($info): array
{
$data = [];
foreach ($this->column as $vo) {
$vo['object']->getElement()->map(function ($item) use (&$data, $info) {
foreach ($item->getData($info) as $k => $v) {
$data[$k] = $v;
}
});
}
return $data;
}
}

View File

@@ -0,0 +1,213 @@
<?php
namespace admin\components\UI\Form;
use admin\components\UI\Widget\Icon;
/**
* 动态数据编辑器
* @package backend\components\UI\Form
*/
class Data extends Element implements Component
{
protected array $column = [];
protected bool $option = true;
protected ?int $numberMax = null;
protected ?int $numberMin = null;
/**
* Text constructor.
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
}
/**
* 文本列
* @param string $name
* @param string $field
* @param null $width
* @return $this
*/
public function text(string $name, string $field, $width = null): self
{
$this->column[] = [
'name' => $name,
'key' => $field,
'type' => 'text',
'width' => $width,
];
return $this;
}
/**
* 图片列
* @param string $name
* @param string $field
* @param null $width
* @return $this
*/
public function image(string $name, string $field, $width = null): self
{
$this->column[] = [
'name' => $name,
'key' => $field,
'type' => 'image',
'width' => $width,
];
return $this;
}
/**
* 展示列
* @param string $name
* @param string $field
* @param null $width
* @return $this
*/
public function show(string $name, string $field, $width = null): self
{
$this->column[] = [
'name' => $name,
'key' => $field,
'type' => 'show',
'width' => $width,
];
return $this;
}
/**
* 隐藏列
* @param string $name
* @param string $field
* @return $this
*/
public function hidden(string $name, string $field): self
{
$this->column[] = [
'name' => $name,
'key' => $field,
'type' => 'hidden'
];
return $this;
}
/**
* 操作状态
* @param bool $status
* @return $this
*/
public function option(bool $status = true): self
{
$this->option = $status;
return $this;
}
/**
* 最大数量
* @param int $num
* @return $this
*/
public function max(int $num = 0): self
{
$this->numberMax = $num;
return $this;
}
/**
* 最小数量
* @param int $num
* @return $this
*/
public function min(int $num = 0): self
{
$this->numberMin = $num;
return $this;
}
/**
* @return array
*/
public function render(): array
{
$url = route('service.image.placeholder', ['w' => 64, 'h' => 64, 't' => $this->attr['placeholder'] ?: '图片']);
$inner = [];
$default = [];
foreach ($this->column as $column) {
$default[$column['key']] = '';
$field = "value['{$column['key']}']";
if ($column['type'] === 'text') {
$inner[] = [
'nodeName' => 'div',
'class' => 'flex-grow',
'child' => [
'nodeName' => 'a-input',
'placeholder' => '请输入' . $column['name'],
'vModel:model-value' => $field
]
];
}
if ($column['type'] === 'image') {
$inner[] = [
'nodeName' => 'div',
'class' => 'flex-none',
'child' => [
'nodeName' => 'app-file',
'image' => 'true',
'mini' => true,
'size' => 8,
'vModel:value' => $field
]
];
}
if ($column['type'] === 'show') {
$inner[] = [
'nodeName' => 'div',
'class' => 'flex-grow',
'child' => "{{ $field || '-'}}"
];
}
}
$create = json_encode($default);
$data = [
'nodeName' => 'app-dynamic-data',
'vModel:value' => $this->getModelField(),
'vBind:on-create' => "() => { return $create }",
'child' => [
'vSlot' => '{ index, value }',
'nodeName' => 'div',
'class' => 'flex flex-grow gap-4 items-center',
'child' => $inner
]
];
if ($this->numberMax) {
$data['max'] = $this->numberMax;
}
if ($this->numberMin) {
$data['min'] = $this->numberMin;
}
return $data;
}
/**
* @param $data
* @return array|null
*/
public function dataValue($data): ?array
{
$data = $this->getValue($data);
return $data ?: [];
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace admin\components\UI\Form;
/**
* 时间选择器
* @package backend\components\UI\Form
*/
class Date extends Element implements Component
{
protected ?string $type = null;
private array $types = [
'year',
'month',
];
/**
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
$this->attr['placeholder'] = null;
}
/**
* @param string $type
* @return $this
*/
public function type(string $type): self
{
if (!in_array($type, $this->types)) {
throw new \RuntimeException('There is no type "' . $type . '"');
}
$this->type = $type;
return $this;
}
/**
* @return array
*/
public function render(): array
{
$data = [
'nodeName' => 'a-date-picker',
'allowClear' => true,
'placeholder' => $this->attr['placeholder'] ?: '请选择' . $this->name,
'vModel:modelValue' => $this->getModelField()
];
if ($this->type) {
$data['nodeName'] = 'a-' . $this->type . '-picker';
}
if($this->replace != ''){
$data['vStringReplace'] = $this->replace;
}
return $data;
}
/**
* @param $data
* @return string|null
*/
public function dataInput($data): ?string
{
return $data ? date('Y-m-d', strtotime($data)) : null;
}
/**
* @param $data
* @return string|null
*/
public function dataValue($data): ?string
{
$data = $this->getValue($data);
return $data ?: null;
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace admin\components\UI\Form;
use admin\components\UI\Tools;
/**
* 时间范围选择器
* @package backend\components\UI\Form
*/
class Daterange extends Element implements Component
{
private string $stopField;
/**
* Text constructor.
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
}
/**
* 设置结束字段
* @param $field
* @return $this
*/
public function stopField($field): self
{
$this->stopField = $field;
return $this;
}
/**
* @return array
*/
public function render(): array
{
$data = [
'nodeName' => 'a-range-picker',
'allowClear' => true,
'vModel:modelValue' => $this->getModelField()
];
if($this->replace != ''){
$data['vStringReplace'] = $this->replace;
}
return $data;
}
/**
* @param $value
* @return array
*/
public function appendInput($value): array
{
if (!$this->stopField) {
return [];
}
$data = [];
$data[$this->stopField] = is_array($value) && $value[1] ? date('Y-m-d H:i:s', strtotime($value[1])) : null;
return $data;
}
/**
* @param $value
* @return string|null
*/
public function dataInput($value): ?string
{
if ($this->stopField) {
return is_array($value) && $value[0] ? date('Y-m-d H:i:s',strtotime($value[0])) : null;
}
return is_array($value) ? date('Y-m-d H:i:s', strtotime($value[0])) . ',' . date('Y-m-d H:i:s', strtotime($value[1])) : null;
}
/**
* @param $value
* @param $info
* @return array|null
*/
public function dataValue($value, $info): ?array
{
$value = $this->getValue($value);
$data = [];
if ($this->stopField) {
$data[] = $value ?: null;
$stopValue = Tools::parsingArrData($info, $this->stopField);
$data[] = $stopValue ?: null;
return $data;
}
if ($value) {
$data = explode(',', $value);
}
return $data ? [$data[0], $data[1]] : null;
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace admin\components\UI\Form;
/**
* 日期时间选择器
* @package backend\components\UI\Form
*/
class Datetime extends Element implements Component
{
protected string $string = 'YYYY-MM-DD HH:mm';
/**
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
}
/**
* 日期格式
* @param string $format
* @return $this
*/
public function string(string $format): self
{
$this->string = $format;
return $this;
}
/**
* 渲染组件
* @return array
*/
public function render(): array
{
$data = [
'nodeName' => 'a-date-picker',
'vModel:value' => $this->getModelField(),
'showTime' => true,
'allowClear' => true,
'format' => $this->string,
'placeholder' => $this->attr['placeholder'] ?: '请选择' . $this->name,
'vModel:modelValue' => $this->getModelField()
];
if($this->replace != ''){
$data['vStringReplace'] = $this->replace;
}
return $data;
}
/**
* @param $data
* @return string|null
*/
public function dataInput($data): ?string
{
return $data ? date('Y-m-d H:i:s', strtotime($data)) : null;
}
/**
* @param $data
* @return string|null
*/
public function dataValue($data): ?string
{
$data = $this->getValue($data);
return $data ?: null;
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace admin\components\UI\Form;
/**
* 编辑器
* @package backend\components\UI\Form
*/
class Editor extends Element implements Component
{
/**
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
}
/**
* @return array
*/
public function render(): array
{
$data = [
'nodeName' => 'app-editor'
];
if ($this->model) {
$data['vModel:value'] = $this->getModelField();
}
return $data;
}
}

View File

@@ -0,0 +1,536 @@
<?php
namespace admin\components\UI\Form;
use admin\components\UI\Tools;
/**
* 单元素组件
* Class Component
* @package backend\components\UI
*/
class Element
{
protected string $name = '';
protected string $field = '';
protected string $has = '';
protected $help = '';
protected $helpLine = '';
protected string $prompt = '';
protected string $model = 'data.';
protected array $class = [];
protected $replace = '';
protected array $attr = [];
protected array $layoutAttr = [];
protected array $style = [];
protected array $pivot = [];
protected array $verify = [];
protected array $verifyMsg = [];
protected array $format = [];
protected bool $dialog = false;
protected bool $vertical = false;
protected bool $label = true;
protected bool $component = false;
protected bool $must = false;
protected array $group = [];
protected ?int $sort = null;
protected $modelElo;
protected $value;
protected $default;
/**
* 设置弹窗
* @param $bool
* @return $this
*/
public function dialog($bool): self
{
$this->dialog = $bool;
return $this;
}
/**
* 设置方向
* @param $bool
* @return $this
*/
public function vertical($bool): self
{
$this->vertical = $bool;
return $this;
}
/**
* 设置数据模型
*/
public function modelElo($class)
{
$this->modelElo = $class;
return $this;
}
/**
* 设置数据前缀
*/
public function model($model)
{
$this->model = $model;
return $this;
}
/**
* 获取模型字段
* @return string
*/
public function getModelField()
{
return $this->model . $this->field;
}
/**
* 获取标签状态
* @return bool
*/
public function getLabel(): bool
{
return $this->label;
}
/**
* 获取字段名
* @return string
*/
public function getField(): string
{
return $this->field;
}
/**
* 获取关联模型
* @return string
*/
public function getHas(): string
{
return $this->has;
}
/**
* 获取名称
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* 获取值
* @param $value
* @return mixed
*/
public function getValue($value = null)
{
return ($this->value ?? $value) ?? $this->default;
}
/**
* 获取数组值
* @param $value
* @param bool $json
* @return array|null
*/
public function getValueArray($value, bool $json = false): ?array
{
if ($value instanceof \Illuminate\Database\Eloquent\Collection) {
if ($value->count()) {
$values = $value->pluck($value->first()->getKeyName())->toArray();
} else {
$values = $json ? [] : null;
}
} else if (is_array($value)) {
$values = $value;
} else if ($value !== null) {
$values = $json ? json_decode($value, true) : explode(',', $value);
} else {
$values = $json ? [] : null;
}
return $values;
}
/**
* 获取回调数据组
* @param $data
* @param $value
* @return array
*/
public function getCallbackArray($data, $value): array
{
if ($data instanceof \Closure) {
return call_user_func($data, [$value]);
}
if (is_array($data)) {
return $data;
}
return [];
}
/**
* 设置选项值
* @param $value
* @return $this
*/
public function value($value): self
{
$this->value = $value;
return $this;
}
/**
* 设置默认值
* @param $value
* @return $this
*/
public function default($value): self
{
$this->default = $value;
return $this;
}
/**
* 设置帮助信息
* @param string|array $value
* @param bool $line
* @return $this
*/
public function help($value, bool $line = false): self
{
if ($line) {
$this->helpLine = $value;
} else {
$this->help = $value;
}
return $this;
}
/**
* 属性数据
* @param string $name
* @param string|array $value
* @return $this
*/
public function attr(string $name, $value): self
{
$this->attr[$name] = $value;
return $this;
}
/**
* 布局树形
* @param string $name
* @param $value
* @return $this
*/
public function layoutAttr(string $name, $value): self
{
$this->layoutAttr[$name] = $value;
return $this;
}
/**
* 属性数组
* @param $attr
* @return $this
*/
public function attrArray($attr): self
{
$this->attr = $attr;
return $this;
}
/**
* class样式
* @param string $name
* @return $this
*/
public function class(string $name): self
{
$this->class[] = $name;
return $this;
}
/**
* 字符串替换标签(数字字符串处理使用)
* @param $replace
* @return $this
*/
public function replace($replace): self
{
$this->replace = $replace;
return $this;
}
/**
* 设置提示
* @param $name
* @return $this
*/
public function placeholder($name): self
{
if ($name) {
$this->attr['placeholder'] = $name;
}
return $this;
}
/**
* 元素分组
* @param $name
* @param $value
* @return $this
*/
public function group($name, $value): self
{
$this->group[] = [
'name' => $name,
'value' => $value
];
return $this;
}
/**
* 必填样式
* @return $this
*/
public function must(): self
{
$this->must = true;
$this->verify['all'][$this->field][] = 'required';
$this->verifyMsg['all'][$this->field . '.' . 'required'] = '请输入' . $this->name;
return $this;
}
/**
* 帮助信息
* @param $content
* @return $this
*/
public function prompt($content): self
{
$this->prompt = $content;
return $this;
}
/**
* 排序
* @param $num
* @return $this
*/
public function sort($num): self
{
$this->sort = $num;
return $this;
}
/**
* 获取分组
* @return array
*/
public function getGroup(): array
{
return $this->group;
}
/**
* 获取必须
* @return bool
*/
public function getMust(): bool
{
return $this->must;
}
/**
* 获取提醒
* @return string
*/
public function getPrompt(): string
{
return $this->prompt;
}
/**
* 获取帮助行
* @return string|array
*/
public function getHelpLine()
{
return $this->helpLine;
}
/**
* 获取层属性
* @return array
*/
public function getLayoutAttr()
{
return $this->layoutAttr;
}
/**
* 同步附加数据
* @param $data
* @return $this
*/
public function pivot($data)
{
$this->pivot = $data;
return $this;
}
/**
* 设置字段验证
* @param $rule
* @param array $msg
* @param string $time
* @return $this
*/
public function verify($rule, array $msg = [], string $time = 'all'): self
{
$this->verify[$time][$this->field] = $rule;
foreach ($msg as $key => $vo) {
$this->verifyMsg[$time][$this->field . '.' . $key] = $vo;
}
return $this;
}
/**
* 获取验证规则
* @param string $time
* @return array
*/
public function getVerify(string $time = 'add'): array
{
return [
'rule' => (array)$this->verify['all'] + (array)$this->verify[$time],
'msg' => (array)$this->verifyMsg['all'] + (array)$this->verifyMsg[$time]
];
}
/**
* 设置表单格式化
* @param string|callable $rule
* @param string $time
* @return $this
*/
public function format($rule, string $time = 'all'): self
{
$this->format[$time][] = $rule;
return $this;
}
/**
* 获取表单格式化
* @param string $time
* @return array
*/
public function getFormat(string $time = 'add'): array
{
return (array)$this->format['all'] + (array)$this->format[$time];
}
/**
* 获取提交数据
* @param string $time
* @return mixed
*/
public function getInput(string $time = 'add'): array
{
$data = \Yii::$app->request->get($this->field);
$inputs = [];
if (method_exists($this, 'appendInput') && !$this->has) {
$appendData = $this->appendInput($data);
foreach ($appendData as $key => $vo) {
$inputs[$key] = ['value' => $vo];
}
}
if (method_exists($this, 'dataInput') && !$this->has) {
$data = $this->dataInput($data);
}
$inputs[$this->field] = ['value' => $data, 'has' => $this->has, 'format' => $this->getFormat($time), 'verify' => $this->getVerify($time), 'pivot' => $this->pivot];
return $inputs;
}
/**
* 获取帮助信息
* @return string|array
*/
public function getHelp()
{
return $this->help;
}
/**
* 获取顺序
* @return null
*/
public function getSort(): ?int
{
return $this->sort;
}
/**
* 复合组件
* @return bool
*/
public function getComponent(): bool
{
return $this->component;
}
/**
* 获取渲染组件
* @return array
*/
public function getRender(): array
{
if ($this->class) {
$this->attr['class'] = implode(' ', $this->class);
}
return array_merge($this->render(), $this->attr);
}
/**
* 获取数据值
* @param $info
* @return array
*/
public function getData($info): array
{
$field = $this->getHas() ?: $this->getField();
$value = Tools::parsingArrData($info, $field);
$data = [];
if (method_exists($this, 'appendValue')) {
$appendValue = $this->appendValue($info);
foreach ($appendValue as $key => $vo) {
$data[$key] = $vo;
}
}
if (method_exists($this, 'dataValue')) {
$value = $this->dataValue($value, $info);
} else {
$value = $this->getValue($value);
}
$data[$this->getField()] = $value;
return $data;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace admin\components\UI\Form;
/**
* 邮箱输入
* @package backend\components\UI\Form
*/
class Email extends Element implements Component
{
protected Text $object;
/**
* Text constructor.
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
$this->object = new Text($this->name, $this->field, $this->has);
$this->object->afterIcon('email');
}
/**
* @return array
*/
public function render(): array
{
return $this->object->getRender();
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace admin\components\UI\Form;
/**
* 文件上传
* @package backend\components\UI\Form
*/
class File extends Element implements Component
{
protected string $type = 'upload';
protected string $url = '';
protected string $fileUrl = '';
/**
* File constructor.
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
}
/**
* 上传方式
* @param string $type
* @return $this
*/
public function type(string $type = 'upload'): self
{
$this->type = $type;
return $this;
}
/**
* 上传地址
* @param string $url
* @return $this
*/
public function url(string $url): self
{
$this->url = $url;
return $this;
}
/**
* 文件地址
* @param string $url
* @return $this
*/
public function fileUrl(string $url): self
{
$this->fileUrl = $url;
return $this;
}
/**
* @return array
*/
public function render(): array
{
$data = [
'nodeName' => 'app-file',
];
if ($this->url) {
$data['upload'] = $this->url;
}
if ($this->fileUrl) {
$data['fileUrl'] = $this->fileUrl;
}
if ($this->type) {
$data['type'] = $this->type;
}
if ($this->model) {
$data['vModel:value'] = $this->getModelField();
}
return $data;
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace admin\components\UI\Form;
/**
* 多文件上传
* @package backend\components\UI\Form
*/
class Files extends Element implements Component
{
protected string $type = 'manage';
protected string $url = '';
protected string $fileUrl = '';
/**
* Text constructor.
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
}
/**
* 上传方式
* @param string $type
* @return $this
*/
public function type(string $type = 'manage'): self
{
$this->type = $type;
return $this;
}
/**
* 上传地址
* @param string $url
* @return $this
*/
public function url(string $url): self
{
$this->url = $url;
return $this;
}
/**
* 文件地址
* @param string $url
* @return $this
*/
public function fileUrl(string $url): self
{
$this->fileUrl = $url;
return $this;
}
/**
* @return string[]
*/
public function render(): array
{
$data = [
'nodeName' => 'app-files',
];
if ($this->type) {
$data['type'] = $this->type;
}
if ($this->url) {
$data['upload'] = $this->url;
}
if ($this->fileUrl) {
$data['fileUrl'] = $this->fileUrl;
}
if ($this->model) {
$data['vModel:value'] = $this->getModelField();
}
return $data;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace admin\components\UI\Form;
/**
* 自定义Html
* @package backend\components\UI\Table
*/
class Html extends Element implements Component
{
protected \Closure $callback;
/**
* @param string $name
* @param \Closure $callback
*/
public function __construct(string $name, \Closure $callback)
{
$this->name = $name;
$this->callback = $callback;
}
/**
* @return array
*/
public function render(): array
{
$callback = is_callable($this->callback) ? call_user_func($this->callback) : $this->callback;
if (is_array($callback)) {
return $callback;
}
return [
'nodeName' => 'rich-text',
'nodes' => $callback
];
}
}

View File

@@ -0,0 +1,124 @@
<?php
namespace admin\components\UI\Form;
/**
* Class Image
* 图片上传
* @package backend\components\UI\Form
*/
class Image extends Element implements Component
{
private array $thumb = [];
private array $water = [];
protected string $type = 'manage';
protected string $url = '';
protected string $fileUrl = '';
/**
* Text constructor.
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
}
/**
* 缩图
* @param int $width
* @param int $height
* @param string $type
* @return $this
*/
public function thumb(int $width, int $height, string $type = 'scale'): self
{
$this->thumb = [
'width' => $width,
'height' => $height,
'thumb' => $type
];
return $this;
}
/**
* 水印
* @param string $position
* @param int $alpha
* @return $this
*/
public function water(string $position = 'center', int $alpha = 80): self
{
$this->water = [
'alpha' => $alpha,
'water' => $position
];
return $this;
}
/**
* @param string $type
* @return $this
*/
public function type(string $type = 'manage'): self
{
$this->type = $type;
return $this;
}
/**
* 上传地址
* @param string $url
* @return $this
*/
public function url(string $url): self
{
$this->url = $url;
return $this;
}
/**
* 文件地址
* @param string $url
* @return $this
*/
public function fileUrl(string $url): self
{
$this->fileUrl = $url;
return $this;
}
/**
* 渲染组件
* @return array
*/
public function render(): array
{
$data = [
'nodeName' => 'app-file',
'format' => 'image',
'image' => true,
'size' => 125
];
if ($this->url) {
$data['upload'] = $this->url;
}
if ($this->fileUrl) {
$data['fileUrl'] = $this->fileUrl;
}
if ($this->type) {
$data['type'] = $this->type;
}
if ($this->model) {
$data['vModel:value'] = $this->getModelField();
}
return $data;
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace admin\components\UI\Form;
/**
* 组图上传
* @package backend\components\UI\Form
*/
class Images extends Element implements Component
{
protected string $type = 'manage';
protected string $url = '';
protected string $fileUrl = '';
/**
* Text constructor.
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
}
public function type($type = 'manage')
{
$this->type = $type;
return $this;
}
/**
* 上传地址
* @param string $url
* @return $this
*/
public function url(string $url): self
{
$this->url = $url;
return $this;
}
/**
* 文件地址
* @param string $url
* @return $this
*/
public function fileUrl(string $url): self
{
$this->fileUrl = $url;
return $this;
}
/**
* @return array
*/
public function render(): array
{
$data = [
'nodeName' => 'app-images',
];
if ($this->type) {
$data['type'] = $this->type;
}
if ($this->url) {
$data['upload'] = $this->url;
}
if ($this->fileUrl) {
$data['fileUrl'] = $this->fileUrl;
}
if ($this->model) {
$data['vModel:value'] = $this->getModelField();
}
return $data;
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace admin\components\UI\Form;
/**
* IP地址
* @package backend\components\UI\Form
*/
class Ip extends Element implements Component
{
protected Text $object;
/**
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
$this->object = new Text($this->name, $this->field, $this->has);
$this->object->afterIcon('desktop');
}
/**
* @return array
*/
public function render(): array
{
return $this->object->getRender();
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace admin\components\UI\Form;
/**
* Class Row
* @package backend\components\UI\Table
*/
class Layout extends Composite implements Component
{
protected $callback;
/**
* @param callable|array $callback
*/
public function __construct($callback)
{
$this->callback = $callback;
}
/**
* @return array
*/
public function render(): array
{
$callback = is_callable($this->callback) ? call_user_func($this->callback) : $this->callback;
return [
'nodeName' => 'div',
'class' => 'mb-4',
'child' => $callback
];
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace admin\components\UI\Form;
/**
* 地图
* @package backend\components\UI\Form
*/
class Location extends Element implements Component
{
protected array $map = [
'province' => 'province',
'city' => 'city',
'district' => 'district',
'street' => 'street',
'streetNumber' => 'streetNumber',
'address' => 'address',
'lat' => 'lat',
'lng' => 'lng'
];
/**
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
}
/**
* @return array
*/
public function render(): array
{
$child = [];
return [
'nodeName' => 'app-map',
'vModel:value' => $this->getModelField(),
'child' => $child,
'placeholder' => '请输入' . $this->name,
];
}
/**
* @param $data
* @return array
*/
public function appendInput($data): array
{
$ret = [];
if ($data->province) {
$ret[$this->map['province']] = $data->province;
}
if ($data->city) {
$ret[$this->map['city']] = $data->city;
}
if ($data->district) {
$ret[$this->map['district']] = $data->district;
}
if ($data->street) {
$ret[$this->map['street']] = $data->street;
}
if ($data->streetNumber) {
$ret[$this->map['streetNumber']] = $data->streetNumber;
}
if ($data->address) {
$ret[$this->map['address']] = $data->address;
}
if ($data->lat) {
$ret[$this->map['lat']] = $data->lat;
}
if ($data->lng) {
$ret[$this->map['lng']] = $data->lng;
}
return $ret;
}
/**
* @param $data
* @return string|null
*/
public function dataValue($data): ?array
{
$data = $this->getValue($data);
if( empty($data) ){
return [];
}else{
return json_decode($data,true);
}
}
}

View File

@@ -0,0 +1,264 @@
<?php
namespace admin\components\UI\Form;
/**
* 表单节点
* @package backend\components\UI\Form
*/
class Node
{
private string $url;
private string $method;
private ?string $title;
private bool $back = false;
private bool $dialog = false;
private bool $vertical = false;
private array $element = [];
private array $data = [];
private array $side = ['left'=>[],'right'=>[]];
private array $script = [];
private array $scriptReturn = [];
/**
* Node constructor.
* @param string $action
* @param string $method
* @param string|null $title
*/
public function __construct(string $action, string $method, ?string $title = '')
{
$this->url = $action;
$this->method = $method;
$this->title = $title;
}
/**
* @param bool $bool
*/
public function back(bool $bool): void
{
$this->back = $bool;
}
/**
* @param Closure|string $content
* @param string $return
* @return $this
*/
public function script($content, string $return): self
{
if ($content instanceof \Closure) {
$this->script[] = $content();
} else {
$this->script[] = $content;
}
$this->scriptReturn[] = $return;
return $this;
}
/**
* @param bool $bool
* @return $this
*/
public function dialog(bool $bool): self
{
$this->dialog = $bool;
$this->vertical = true;
return $this;
}
/**
* @param bool $bool
* @return $this
*/
public function vertical(bool $bool): self
{
$this->vertical = $bool;
return $this;
}
/**
* @param array $node
* @return $this
*/
public function element(array $node): self
{
$this->element = $node;
return $this;
}
/**
* @param Closure|array $node
* @param string $type
* @return $this
*/
public function side($node, string $type = 'left'): self
{
$this->side[$type] = is_callable($node) ? $node() : $node;
return $this;
}
/**
* @param $data
* @return $this
*/
public function data($data): self
{
$this->data = $data;
return $this;
}
/**
* 渲染页面
* @return array[]
*/
private function renderPage(): array
{
return [
$this->side['left'] ? [
'nodeName' => 'div',
'class' => 'flex-none flex h-screen flex-col',
'child' => $this->side['left']
] : [],
[
'nodeName' => 'app-layout',
'class' => 'flex-grow w-10',
'title' => $this->title ?: '信息详情',
'form' => true,
'back' => $this->back,
'vBind:formLoading' => 'loading',
'child' => [
[
'nodeName' => 'div',
'class' => 'p-4',
'child' => [
[
'nodeName' => 'div',
'child' => $this->element
],
[
'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->element
],
$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' => '提交'
],
]
]
]
];
}
/**
* 渲染布局
* @return array
*/
public function render(): array
{
return [
'node' => [
'nodeName' => 'app-form',
'url' => $this->url,
'method' => $this->method,
'value' => $this->data,
'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()
]
],
'setupScript' => implode("\n", $this->script) . "\n" . ' return {' . implode(",", $this->scriptReturn) . '}'
];
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace admin\components\UI\Form;
/**
* 数字输入器
* @package backend\components\UI\Form
*/
class Number extends Element implements Component
{
protected Text $object;
/**
* @var int|float
*/
protected $step = 1;
/**
* @var ?int|float
*/
protected $max = null;
/**
* @var int|float
*/
protected $min = 0;
protected ?int $precision = null;
/**
* Text constructor.
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
$this->attr['placeholder'] = null;
}
/**
* 最大值
* @param int|float $default
* @return $this
*/
public function max($default = 0): self
{
$this->max = $default;
return $this;
}
/**
* 最小值
* @param int|float $default
* @return $this
*/
public function min($default = 0): self
{
$this->min = $default;
return $this;
}
/**
* 步进数值
* @param int|float $default
* @param int|null $precision
* @return $this
*/
public function step($default = 1, ?int $precision = null): self
{
$this->step = $default;
$this->precision = $precision;
return $this;
}
/**
* @return array
*/
public function render(): array
{
$data = [
'nodeName' => 'a-input-number',
'placeholder' => $this->attr['placeholder'] ?: '请输入' . $this->name,
'vModel:modelValue' => $this->getModelField(),
'step' => $this->step,
'min' => $this->min,
'mode' => 'button'
];
if ($this->max) {
$data['max'] = $this->max;
}
if ($this->precision) {
$data['precision'] = $this->precision;
}
if($this->replace != ''){
$data['vStringReplace'] = $this->replace;
}
return $data;
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace admin\components\UI\Form;
/**
* 密码输入器
* @package backend\components\UI\Form
*/
class Password extends Element implements Component
{
protected Text $object;
/**
* Text constructor.
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
$this->attr['placeholder'] = null;
}
/**
* @return array
*/
public function render(): array
{
$data = [
'nodeName' => 'a-input-password',
'vModel:modelValue' => $this->getModelField(),
'placeholder' => $this->attr['placeholder'] ?: '请输入' . $this->name,
'allowClear' => true
];
if($this->replace != ''){
$data['vStringReplace'] = $this->replace;
}
return $data;
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace admin\components\UI\Form;
/**
* 单选框
* @package backend\components\UI\Form
*/
class Radio extends Element implements Component
{
protected array $box = [];
protected $data = [];
protected string $switch = '';
/**
* Select constructor.
* @param string $name
* @param string $field
* @param null|array|callable $data
* @param string $has
*/
public function __construct(string $name, string $field, $data = [], string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->data = $data;
$this->has = $has;
}
/**
* 添加选项
* @param $name
* @param $value
* @return $this
*/
public function add($name, $value): self
{
$this->data[$name] = $value;
return $this;
}
/**
* 切换组件
* @param $group
* @return $this
*/
public function switch($group): self
{
$this->switch = $group;
return $this;
}
/**
* 类型选择
* @param array $data
* @return $this
*/
public function box(array $data): self
{
$this->box = $data;
return $this;
}
/**
* @return array
*/
public function render(): array
{
$data = [];
if ($this->data instanceof \Closure) {
$data = call_user_func($this->data);
}
if (is_array($this->data)) {
$data = $this->data;
}
$this->data = $data;
$child = [];
foreach ($data as $key => $vo) {
$child[] = [
'nodeName' => 'a-radio',
'child' => $vo,
'value' => $key,
];
}
$data = [
'nodeName' => 'a-radio-group',
'name' => $this->field,
'vModel:modelValue' => $this->getModelField(),
'child' => $child
];
if($this->replace != ''){
$data['vStringReplace'] = $this->replace;
}
return $data;
}
public function dataValue($value)
{
return $this->getValue($value) ?? array_key_first((array)$this->data);
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace admin\components\UI\Form;
use admin\components\UI\Form;
/**
* Class Row
* @package backend\components\UI\Table
*/
class Row extends Composite implements Component
{
/**
* 设置列
* @param callable $callback
* @param int $width
* @return $this
*/
public function column(callable $callback, int $width = 0): self
{
$form = new Form();
$callback($form);
$this->column[] = [
'width' => $width,
'object' => $form,
];
return $this;
}
/**
* @return array
*/
public function render(): array
{
$inner = [];
foreach ($this->column as $vo) {
$width = $vo['width'] ? "lg:row-span-{$vo['width']}" : '';
$form = $vo['object']->renderForm();
$inner[] = [
'nodeName' => 'div',
'class' => $width,
'child' => $form
];
}
return [
'nodeName' => 'div',
'class' => 'grid lg:grid-flow-col gap-4',
'child' => $inner
];
}
}

View File

@@ -0,0 +1,198 @@
<?php
namespace admin\components\UI\Form;
/**
* Class Select
* @package backend\components\UI\Form
*/
class Select extends Element implements Component
{
protected string $url = '';
protected string $route = '';
protected int $tagCount = 0;
protected bool $tip = false;
protected bool $search = false;
protected bool $multi = false;
protected array $optionRender = [];
protected $data;
/**
* Select constructor.
* @param string $name
* @param string $field
* @param null $data
* @param string $has
*/
public function __construct(string $name, string $field, $data = null, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->data = $data;
$this->has = $has;
$this->attr['placeholder'] = null;
}
/**
* 添加选项
* @param $name
* @param $value
* @return $this
*/
public function add($name, $value): self
{
$this->data[$value] = $name;
return $this;
}
/**
* 默认提示
* @param bool $tip
* @return $this
*/
public function tip(bool $tip = true): self
{
$this->tip = $tip;
return $this;
}
/**
* 搜索
* @param bool $search
* @return $this
*/
public function search(bool $search = true): self
{
$this->search = $search;
return $this;
}
/**
* 搜索地址
* @param string $url
* @return $this
*/
public function url(string $url): self
{
$this->url = $url;
return $this;
}
/**
* 路由地址
* @param string $route
* @param array $params
* @return $this
*/
public function route(string $route, array $params = []): self
{
$this->route = $route;
$this->url = app_route($route, $params);
return $this;
}
/**
* 多选
* @param int $count
* @return $this
*/
public function multi(int $count = 0): self
{
$this->multi = true;
$this->tagCount = $count;
return $this;
}
/**
* 选项渲染
* @param array $data JS Node
* @return $this
*/
public function optionRender(array $data): self
{
$this->optionRender = $data;
return $this;
}
/**
* @return array
*/
public function render(): array
{
$data = [];
if ($this->data instanceof \Closure) {
$data = call_user_func($this->data);
}
if (is_array($this->data)) {
$data = $this->data;
}
$options = [];
if(isset($data[0]['label'])){
$options=$data;
}else{
foreach ($data as $key => $vo) {
$options[] = [
'label' => $vo,
'value' => $key
];
}
}
$object = [
'nodeName' => 'app-select',
'nParams' => [
'placeholder' => $this->attr['placeholder'] ?: '请选择' . $this->name,
'options' => $options
],
];
if ($this->model) {
$object['vModel:value'] = $this->getModelField();
}
$object['nParams']['allowClear'] = true;
if ($this->multi) {
$object['nParams']['multiple'] = true;
}
if ($this->url) {
$object['nParams']['allowSearch'] = true;
$object['nParams']['filterOption'] = false;
if ($this->route) {
$object['vBind:dataUrl'] = $this->url;
} else {
$object['dataUrl'] = $this->url;
}
}
if ($this->search) {
$object['nParams']['allowSearch'] = true;
}
if ($this->tagCount) {
$object['nParams']['maxTagCount'] = $this->tagCount;
}
if ($this->optionRender) {
$object['vRender:optionRender:item'] = $this->optionRender;
}
return $object;
}
/**
* @param $value
* @return array|mixed
*/
public function dataValue($value)
{
return $this->multi ? array_values(array_filter((array)$this->getValueArray($value))) : $this->getValue($value);
}
/**
* @param $data
* @return string
*/
public function dataInput($data): ?string
{
return is_array($data) ? implode(',', $data) : $data;
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace admin\components\UI\Form;
use admin\components\UI\Form;
/**
* Class Tab
* 切换组件
* @package backend\components\UI\Table
*/
class Tab extends Composite implements Component
{
/**
* @param $name
* @param callable $callback
* @param int|null $order
* @param string $title
* @param string $desc
* @return $this
*/
public function column($name, callable $callback, int $order = null, string $title = '', string $desc = ''): self
{
$form = new Form();
$form->dialog($this->dialog);
$form->vertical($this->vertical);
$callback($form);
$this->column[] = [
'name' => $name,
'title' => $title,
'desc' => $desc,
'order' => $order ?? (count($this->column) + 1),
'object' => $form,
];
return $this;
}
/**
* @return array
*/
public function render(): array
{
$nodes = [];
$column = collect($this->column)->sortBy('order')->toArray();
foreach ($column as $key => $vo) {
$child = [];
if ($vo['title']) {
$child[] = [
'nodeName' => 'div',
'class' => 'py-4 flex flex-col gap-2',
'child' => [
[
'nodeName' => 'div',
'class' => 'text-xl',
'child' => $vo['title'],
],
[
'nodeName' => 'div',
'class' => 'text-gray-500',
'child' => $vo['desc'],
]
]
];
}
$child[] = [
'nodeName' => 'div',
'class' => 'pt-2',
'child' => $vo['object']->renderForm()
];
$nodes[] = [
'nodeName' => 'a-tab-pane',
'title' => $vo['name'],
'key' => $key,
'class' => !$this->dialog ? ' border-t border-gray-200 dark:border-blackgray-1 px-3 pt-4 pb-0' : '',
'child' => [
'nodeName' => 'div',
'class' => '',
'child' => $child
]
];
}
return [
'nodeName' => 'a-tabs',
'class' => !$this->dialog ? 'mb-4 bg-white dark:bg-blackgray-4 rounded shadow p-4 pb-1' : '',
'type' => 'rounded',
'child' => $nodes
];
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace admin\components\UI\Form;
/**
* Class Tags
* 标签输入框
* @package backend\components\UI\Form
*/
class Tags extends Element implements Component
{
protected int $limit = 0;
/**
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
$this->attr['placeholder'] = null;
}
/**
* @param $num
* @return $this
*/
public function limit($num): self
{
$this->limit = $num;
return $this;
}
/**
* @return array
*/
public function render(): array
{
$data = [
'nodeName' => 'a-input-tag',
'vModel:modelValue' => $this->getModelField(),
'placeholder' => $this->attr['placeholder'] ?: '请输入' . $this->name,
'allowClear' => true
];
if ($this->limit) {
$data['maxTagCount'] = $this->limit;
}
if($this->replace != ''){
$data['vStringReplace'] = $this->replace;
}
return $data;
}
/**
* @param $value
* @return array
*/
public function dataValue($value): array
{
return array_values(array_filter((array)$this->getValueArray($value)));
}
/**
* @param $data
* @return string
*/
public function dataInput($data): ?string
{
return is_array($data) ? implode(',', $data) : $data;
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace admin\components\UI\Form;
/**
* Class Tel
* @package backend\components\UI\Form
*/
class Tel extends Element implements Component
{
protected Text $object;
protected string $mask = '19999999999';
/**
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
$this->object = new Text($this->name, $this->field, $this->has);
$this->object->afterIcon('phone');
}
/**
* 设置掩码
* @param $value
* @return $this
*/
public function mask($value): self
{
$this->mask = (string) $value;
return $this;
}
/**
* @return array
*/
public function render(): array
{
$this->object->attrArray($this->attr);
return $this->object->getRender();
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace admin\components\UI\Form;
use admin\components\UI\Widget\Icon;
/**
* 输入框
* @package backend\components\UI\Form
*/
class Text extends Element implements Component
{
protected string $type = 'text';
protected array $before = [];
protected array $after = [];
/**
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
}
/**
* 文本类型
* @param $name
* @return $this
*/
public function type($name): self
{
$this->type = $name;
return $this;
}
/**
* 前置图标
* @param $content
* @return $this
*/
public function beforeIcon($content): self
{
$this->before = (new Icon($content))->attr('vSlot:prepend', '')->getRender();
return $this;
}
/**
* 后置图标
* @param $content
* @return $this
*/
public function afterIcon($content): self
{
$this->after = (new Icon($content))->attr('vSlot:append', '')->getRender();
return $this;
}
/**
* 前置文本
* @param $content
* @return $this
*/
public function beforeText($content): self
{
$this->before = [
'vSlot:prepend' => '',
'nodeName' => 'span',
'child' => $content
];
return $this;
}
/**
* 后置文本
* @param $content
* @return $this
*/
public function afterText($content): self
{
$this->after = [
'vSlot:append' => '',
'nodeName' => 'span',
'child' => $content
];
return $this;
}
/**
* @return array
*/
public function render(): array
{
$child = [];
if ($this->before || $this->after) {
$child = [
$this->before,
$this->after
];
}
$data = [
'nodeName' => 'a-input',
'vModel:modelValue' => $this->getModelField(),
'child' => $child,
'placeholder' => '请输入' . $this->name
];
if($this->replace != ''){
$data['vStringReplace'] = $this->replace;
}
return $data;
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace admin\components\UI\Form;
/**
* Class Textarea
* 多行输入框
* @package backend\components\UI\Form
*/
class Textarea extends Element implements Component
{
protected int $limit = 0;
/**
* Text constructor.
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
$this->attr['placeholder'] = null;
}
/**
* @param $num
* @return $this
*/
public function limit($num): self
{
$this->limit = $num;
return $this;
}
/**
* @return array
*/
public function render(): array
{
$data = [
'nodeName' => 'a-textarea',
'vModel:modelValue' => $this->getModelField(),
'placeholder' => $this->attr['placeholder'] ?: '请输入' . $this->name,
'allowClear' => true,
'showWordLimit' => true
];
if ($this->limit) {
$data['maxLength'] = $this->limit;
}
if($this->replace != ''){
$data['vStringReplace'] = $this->replace;
}
return $data;
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace admin\components\UI\Form;
/**
* Class Time
* 时间选择器
* @package backend\components\UI\Form
*/
class Time extends Element implements Component
{
protected string $string = 'HH:mm:ss';
/**
* Text constructor.
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
$this->attr['placeholder'] = null;
}
/**
* 时间格式
* @param string $format
* @return $this
*/
public function string(string $format): self
{
$this->string = $format;
return $this;
}
/**
* @return array
*/
public function render(): array
{
return [
'nodeName' => 'a-time-picker',
'allowClear' => true,
'format' => $this->string,
'placeholder' => $this->attr['placeholder'] ?: '请选择' . $this->name,
'vModel:model-value' => $this->getModelField()
];
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace admin\components\UI\Form;
/**
* 开关切换
* @package backend\components\UI\Form
*/
class Toggle extends Element implements Component
{
private $checkedValue = 1;
private $uncheckedValue = 0;
/**
* Toggle constructor.
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
}
/**
* @return array
*/
public function render(): array
{
$data = [
'nodeName' => 'a-switch',
'vModel:modelValue' => $this->getModelField(),
'checkedValue' => $this->checkedValue,
'uncheckedValue' => $this->uncheckedValue
];
if($this->replace != ''){
$data['vStringReplace'] = $this->replace;
}
return $data;
}
/**
* 开关数据
* @param string|number|boolean $checkedValue
* @param string|number|boolean $uncheckedValue
* @return $this
*/
public function data($checkedValue,$uncheckedValue){
$this->checkedValue = $checkedValue;
$this->uncheckedValue = $uncheckedValue;
return $this;
}
/**
* @param $data
* @return int
*/
public function dataInput($data): int
{
return $data ? 1 : 0;
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace admin\components\UI\Form;
/**
* Class Tree
* @package backend\components\UI\Table
*/
class Tree extends Element implements Component
{
protected $data;
/**
* Tree constructor.
* @param string $name
* @param string $field
* @param null $data
* @param string $has
*/
public function __construct(string $name, string $field, $data = null, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->data = $data;
$this->has = $has;
}
/**
* @return array
*/
public function render(): array
{
$data = $this->data;
if ($this->data instanceof \Closure) {
$data = call_user_func($this->data);
}
if ($data instanceof \Illuminate\Database\Eloquent\Collection) {
$data = $data->toArray();
}
$data = $this->loop($data);
$data = [
'nodeName' => 'div',
'class' => 'bg-gray-100 dark:bg-blackgray-2 p-2 rounded w-full h-56 overflow-y-auto app-scrollbar',
'child' => [
'nodeName' => 'a-tree',
'blockNode' => true,
'checkable' => true,
'showLine' => true,
'vModel:checked-keys' => $this->getModelField(),
'data' => $data,
]
];
return $data;
}
/**
* @param $value
* @return array|null
*/
public function dataValue($value): ?array
{
return $this->getValueArray($value);
}
/**
* @param $data
* @return string
*/
public function dataInput($data): ?string
{
return is_array($data) ? implode(',', $data) : '';
}
/**
* @param $data
* @return array
*/
protected function loop($data): array
{
$newData = [];
foreach ($data as $item) {
$tmpData = [
'title' => $item['name'],
'key' => $item['id'],
];
if ($item['children']) {
$tmpData['children'] = $this->loop($item['children']);
}
$newData[] = $tmpData;
}
return $newData;
}
}

View File

@@ -0,0 +1,183 @@
<?php
namespace admin\components\UI\Form;
/**
* Class TreeSelect
* @package backend\components\UI\Form
*/
class TreeSelect extends Element implements Component
{
protected bool $tip = false;
protected bool $multi = false;
protected bool $treeData = false;
protected string $strategy = 'all';
protected string $url = '';
protected string $route = '';
protected $data;
/**
* Select constructor.
* @param string $name
* @param string $field
* @param array|callback|null $data
* @param string $has
*/
public function __construct(string $name, string $field, $data = null, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->data = $data;
$this->has = $has;
$this->attr['placeholder'] = null;
}
/**
* 添加选项
* @param $name
* @param $id
* @param $pid
* @return $this
*/
public function add($name, $id, $pid): self
{
$this->data[] = [
'name' => $name,
'id' => $id,
'pid' => $pid
];
return $this;
}
/**
* 设置动态地址
* @param string $url
* @return $this
*/
public function url(string $url): self
{
$this->url = $url;
return $this;
}
/**
* @param string $route
* @param array $params
* @return $this
*/
public function route(string $route, array $params = []): self
{
$this->route = $route;
$this->url = app_route($route, $params);
return $this;
}
/**
* 多选组件
* @return $this
*/
public function multi(): self
{
$this->multi = true;
return $this;
}
/**
* 树形模式
* @param bool $bool
* @return $this
*/
public function tree(bool $bool = true): self
{
$this->treeData = $bool;
return $this;
}
/**
* 回填方式
* @param string $model
* @return $this
*/
public function strategy(string $model): self
{
$this->strategy = $model;
return $this;
}
/**
* @return array
*/
public function render(): array
{
$data = [];
if ($this->data instanceof \Closure) {
$data = call_user_func($this->data, $this);
}
if (is_array($this->data)) {
$data = $this->data;
}
if (!$this->treeData) {
$options = [];
foreach ($data as $vo) {
$options[] = [
'id' => $vo['id'],
'pid' => $vo['pid'],
'value' => $vo['id'],
'label' => $vo['name'],
];
}
$options = \backend\components\Util\Tree::arr2tree($options, 'id', 'pid', 'children');
} else {
$options = $data;
}
$data = [
'nodeName' => 'app-tree-select',
'nParams' => [
'multiple' => $this->multi,
'treeCheckable' => $this->multi,
'treeCheckedStrategy' => $this->strategy,
'data' => $options,
'placeholder' => $this->attr['placeholder'] ?: '请选择' . $this->name,
]
];
if ($this->route) {
$data['vBind:dataUrl'] = $this->url;
} else {
$data['dataUrl'] = $this->url;
}
if ($this->model) {
$data['vModel:value'] = $this->getModelField();
}
return $data;
}
/**
* @param $value
* @return array|mixed
*/
public function dataValue($value)
{
return $this->multi ? array_values(array_filter((array)$this->getValueArray($value))) : $this->getValue($value);
}
/**
* @param $data
* @return string
*/
public function dataInput($data): ?string
{
return is_array($data) ? implode(',', $data) : $data;
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace admin\components\UI\Form;
/**
* url输入
* @package backend\components\UI\Form
*/
class Url extends Element implements Component
{
protected Text $object;
/**
* Text constructor.
* @param string $name
* @param string $field
* @param string $has
*/
public function __construct(string $name, string $field, string $has = '')
{
$this->name = $name;
$this->field = $field;
$this->has = $has;
$this->object = new Text($this->name, $this->field, $this->has);
$this->object->beforeText('http(s)');
}
/**
* @return array
*/
public function render(): array
{
$this->object->attrArray($this->attr);
return $this->object->getRender();
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace admin\components\UI;
use admin\components\UI\Components\VueComponent;
class Layout extends VueComponent
{
private string $name='app-layout';
private string $class='flex-grow w-10';
private string $title;
public function __construct($title){
$this->title = $title;
}
public $child = [];
function setup(string $script)
{
// TODO: Implement setup() method.
}
public function addChild($node){
$this->child[] = $node;
}
function render()
{
return ['node'=>[
"nodeName" => "div",
"class" => "flex h-screen",
"vSlot" => "{value: data}",
"child" => [
[
'nodeName' => $this->name,
'class' => $this->class,
'title' => $this->title,
'child' => [
[
'nodeName' => 'div',
'class' => 'p-4',
'child' => $this->child
]
]
]
],
'setupScript'=>"\n return {}"
]];
// TODO: Implement render() method.
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace admin\components\UI;
use admin\components\UI\Node\NodeEl;
/**
* node 生成工具
*/
class Node
{
public array $nodes = [];
/**
* @return array
*/
public function render(): array
{
$data = [];
foreach ($this->nodes as $vo) {
$data[] = $vo->render();
}
return $data;
}
/**
* @param $method
* @param $arguments
* @return NodeEl
*/
public function __call($method, $arguments)
{
$nodeEl = new NodeEl($method, $arguments[0]);
$this->nodes[] = $nodeEl;
return $nodeEl;
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace admin\components\UI\Node;
use admin\components\UI\Node;
/**
* node 属性
*/
class NodeEl
{
public string $name = 'div';
public $callback;
public array $attr = [];
/**
* @param $name
* @param null $callback
*/
public function __construct($name, $callback = null)
{
$this->name = $name;
if ($callback instanceof \Closure) {
$node = new Node();
$callback($node);
$this->callback = $node;
}else {
$this->callback = $callback;
}
}
/**
* @return array
*/
public function render(): array
{
$data = [
'nodeName' => $this->name,
];
if ($this->callback instanceof Node) {
$data['child'] = $this->callback->render();
} else if ($this->callback) {
$data['child'] = $this->callback;
}
return array_merge($data, $this->attr);
}
public function __call($method, $arguments)
{
$this->attr[$method] = $arguments[0];
return $this;
}
}

View File

@@ -0,0 +1,847 @@
<?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,
],
]
);
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace admin\components\UI\Table;
use admin\components\UI\Tools;
use admin\components\UI\Widget\Link;
use admin\components\UI\Widget\Menu;
/**
* 操作动作
* Class Column
* @package backend\components\UI\Filter
*/
class Action
{
protected array $button = [];
protected array $menu = [];
/**
* 按钮
* @param string $name
* @param string $route
* @param array $params
* @param string $type
* @return Link
*/
public function button(string $name, string $route = '', array $params = [], string $type = 'primary'): Link
{
$link = new Link($name, $route, $params);
$link->button($type);
$this->button[] = $link;
return $link;
}
/**
* 菜单按钮
* @param string $name
* @param string $type
* @return Menu
*/
public function menu(string $name, string $type = 'default'): Menu
{
$menu = new Menu($name, $type);
$this->menu[] = $menu;
return $menu;
}
/**
* 渲染组件
* @return array
*/
public function render(): array
{
$node = [];
foreach ($this->menu as $menu) {
$node[] = $menu->getRender();
}
foreach ($this->button as $class) {
$node[] = $class->getRender();
}
return $node;
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace admin\components\UI\Table;
use admin\components\UI\Widget\Link;
use admin\components\UI\Form\Select;
/**
* 操作批量
* Class Column
* @package backend\components\UI\Filter
*/
class Batch
{
protected array $nodes = [];
protected array $select = [];
protected array $url = [];
/**
* 按钮
* @param string $name
* @param string $type
* @param string $route
* @param array $params
* @param string $btnType
* @return $this
*/
public function button(string $name, string $type = '', string $route = '', array $params = [], string $btnType = 'normal'): self
{
$params['bath_type'] = $type;
$url = route($route, $params);
$this->nodes[] = [
'nodeName' => 'a-button',
'type' => 'secondary',
'status' => $btnType,
'child' => $name,
'vOn:click' => "footer.checkAction('$url', '确定执行$name\操作?')"
];
return $this;
}
/**
* @param string $name
* @param string $route
* @param array $params
* @return $this
*/
public function select(string $name, string $route = '', array $params = []): self
{
$url = route($route, $params);
$this->select[] = [
'nodeName' => 'a-doption',
'child' => $name,
'vOn:click' => "footer.checkAction('$url' '确定执行$name\操作?')"
];
return $this;
}
/**
* 渲染组件
* @return array
*/
public function render(): array
{
if ($this->select) {
$this->nodes[] = [
'nodeName' => 'a-dropdown',
'child' => [
[
'nodeName' => 'a-button',
'type' => 'secondary',
'child' => '批量操作',
],
[
'vSlot:content' => '',
'nodeName' => 'div',
'child' => $this->select
]
],
];
}
return $this->nodes;
}
}

View File

@@ -0,0 +1,467 @@
<?php
namespace admin\components\UI\Table;
use Exception;
use Illuminate\Support\Collection;
use admin\components\UI\Widget\Link;
use admin\components\UI\Table;
use admin\components\UI\Tools;
/**
* 表格列
* Class Column
* @method static Column\Hidden hidden()
* @method static Column\Progress progress(string $color = 'default')
* @method static Column\Status status(array $map, array $color, string $type = 'badge')
* @method static Column\Chart chart(int $day = 7, string $has = 'viewsData', string $key = 'pv', string $name = '访问量', string $type = 'area')
* @method static Column\Tags tags(array $map, array $color)
* @method Column\Toggle toggle(string $field, string $url, array $params = [])
* @method static Column\Input input(string $field, $url, array $params = [])
* @package backend\components\UI\Table
*/
class Column
{
protected string $name;
protected string $label = '';
protected ?\Closure $callback = null;
protected array $node = [];
protected array $attr = [];
protected array $function = [];
protected array $children = [];
protected $width = '';
protected $align = '';
protected $fixed = '';
protected $class = [];
protected $replace = '';
protected ?int $colspan = null;
protected ?\Closure $show = null;
protected ?int $sort = null;
protected $sorter = null;
protected $layout;
protected $relation;
protected $model;
protected $element;
protected $extend;
/**
* Column constructor.
* @param string $name
* @param string $label
* @param null $callback
*/
public function __construct(string $name = '', string $label = '', $callback = null)
{
$this->name = $name;
$this->label = $label;
$this->callback = $callback;
}
/**
* 设置父级对象
* @param Table $layout
*/
public function setLayout(Table $layout): void
{
$this->layout = $layout;
}
/**
* 关联数据
* @param $relation
* @return $this
*/
public function setRelation($relation): self
{
$this->relation = $relation;
return $this;
}
/**
* 宽度
* @param $width
* @return $this
*/
public function width($width): self
{
$this->width = $width;
return $this;
}
/**
* 自定义节点数据
* @param $node
* @return $this
*/
public function node($node): self
{
$this->node = $node;
return $this;
}
/**
* 对齐
* @param string $align
* @return $this
* @throws Exception
*/
public function align(string $align): self
{
$this->align = $align;
return $this;
}
/**
* 固定列
* @param string $fixed
* @return $this
*/
public function fixed(string $fixed = 'right'): self
{
$this->fixed = $fixed;
return $this;
}
/**
* 设置样式类
* @param string $class
* @return $this
*/
public function class(string $class): self
{
$this->class[] = $class;
return $this;
}
/**
* 设置附加属性
* @param string $name
* @param $value
* @return $this
*/
public function attr(string $name, $value): self
{
$this->attr[$name] = $value;
return $this;
}
/**
* 设置颜色
* @param string $name
* @return $this
*/
public function color(string $name): self
{
$this->class[] = 'text-' . $name;
return $this;
}
/**
* 字符串替换标签(数字字符串处理使用)
* @param $replace
* @return $this
*/
public function replace($replace): self
{
$this->replace = $replace;
return $this;
}
/**
* 添加链接
* @param string $name
* @param string $route
* @param array $params
* @param bool $absolute
* @return Link
*/
public function link(string $name, string $route, array $params = [], bool $absolute = false): Link
{
if (!$this->element) {
$this->element = new Table\Column\Link();
$this->element->fields($this->layout->fields);
}
return $this->element->add($name, $route, $params, $absolute);
}
/**
* 添加菜单
* @param string $name
* @param string $route
* @param array $params
* @return Link
*/
public function menu(string $name, string $route, array $params = []): Link
{
if (!$this->element) {
$this->element = new Table\Column\Menu();
}
return $this->element->add($name, $route, $params);
}
/**
* 副标题
* @param string $label
* @param callable|null $callback
* @return $this
*/
public function desc(string $label, callable $callback = null): self
{
if (!$this->element && !$this->element instanceof Table\Column\RichText) {
$this->element = new Table\Column\RichText();
$this->element->setRelation($this->relation);
}
$this->element->desc($label, $callback);
return $this;
}
/**
* 图片显示
* @param string $label
* @param callable|null $callback
* @param int $width
* @param int $height
* @param string $placeholder
* @return $this
*/
public function image(string $label, callable $callback = null, int $width = 10, int $height = 10, string $placeholder = ''): self
{
if (!$this->element && !$this->element instanceof Table\Column\RichText) {
$this->element = new Table\Column\RichText();
}
$this->element->image($label, $width, $height, $placeholder, $callback);
return $this;
}
/**
* 格式化时间
* @param $format
* @return $this
*/
public function date($format): self
{
$this->function[] = [
'fun' => 'date',
'params' => $format
];
return $this;
}
/**
* 显示隐藏
* @param callable $callback
* @return $this
*/
public function show(callable $callback): self
{
$this->show = $callback;
return $this;
}
/**
* 列排序
* @param int $num
* @return $this
*/
public function sort(int $num): self
{
$this->sort = $num;
return $this;
}
/**
* 排序条件
*/
public function sorter($sorter = true): self
{
$this->sorter = $sorter;
return $this;
}
/**
* 列合并
* @param int $num
* @return $this
*/
public function colspan(int $num): self
{
$this->colspan = $num;
return $this;
}
// 分组表格
public function children(string $name = '', string $label = '', $callback = null): self
{
$this->children[] = new Column($name, $label, $callback);
return $this;
}
/**
* 获取字段名
* @return string
*/
public function getLabel(): string
{
return Tools::converLabel2($this->label, $this->relation);
}
/**
* 获取列配置
*/
public function getRender(): array
{
$render = $this->node;
if ($this->node instanceof \Closure) {
$render = call_user_func($this->node);
}
if ($this->element) {
$render = $this->element->render($this->getLabel());
}
$node = [
'title' => $this->name,
'dataIndex' => $this->label,
'width' => $this->width,
'className' => implode(' ', $this->class),
'colSpan' => $this->colspan,
'sort' => $this->sort,
'align' => $this->align,
];
if ($this->fixed) {
$node['fixed'] = $this->fixed;
}
if($this->replace){
$node['replace'] = $this->replace;
}
if ($this->children) {
$children = [];
foreach ($this->children as $item) {
$children[] = $item->getRender();
}
$node['children'] = $children;
}
if ($this->sorter) {
$node['vBind:sortable'] = 'colSortable';
}
if ($render) {
$node['render:rowData, rowIndex'] = $render;
}
return array_merge($node, $this->attr);
}
/**
* 行数据
* @param $rowData
* @return array
*/
public function getData($rowData): array
{
if ($this->relation) {
// 解析关联数组
$parsingData = Tools::parsingObjData($rowData, $this->relation, $this->label);
} else {
// 解析普通数组
$parsingData = Tools::parsingArrData($rowData, $this->label);
}
// 回调处理
if ($this->callback instanceof \Closure) {
$callback = call_user_func($this->callback, $parsingData, $rowData);
if ($callback) {
$parsingData = $callback;
}
} else {
$parsingData = $this->callback ?: $parsingData;
}
// 函数处理
if ($this->function) {
foreach ($this->function as $vo) {
if (function_exists($vo['fun'])) {
$parsingData = call_user_func($vo['fun'], $vo['params'], $parsingData);
}
}
}
if ($this->label) {
$data = [
$this->getLabel() => $parsingData
];
} else {
$data = [];
}
// 元素数据
if ($this->element && method_exists($this->element, 'getData')) {
$data = array_merge($data, $this->element->getData($rowData, $this->getLabel(), $parsingData));
}
if ($this->children) {
foreach ($this->children as $item) {
$data = array_merge($data, $item->getData($rowData));
}
}
return $data;
}
/**
* 列条件
* @param $query
* @return false|void
*/
public function execute($query)
{
$sort = \Yii::$app->request->get('_sort');
$value = $sort && $sort[$this->label] ? $sort[$this->label] : null;
if (!$this->sorter || $value === null) {
return false;
}
if ($this->sorter instanceof \Closure) {
call_user_func($this->sorter, $query, $value);
} else if ($this->sorter !== false) {
$query->orderBy(is_string($this->sorter) ? $this->sorter : $this->label, $value === 'desc' ? 'desc' : 'asc');
}
}
/**
* 执行元素处理
* @param callable $callback
* @return $this
*/
public function element(callable $callback){
$callback($this->element);
return $this;
}
/**
* @param $method
* @param $arguments
* @return $this
* @throws Exception
*/
public function __call($method, $arguments)
{
$class = 'backend\\components\\UI\\Table\\Column\\' . ucfirst($method);
if (!class_exists($class)) {
if (!$this->extend[$method]) {
throw new \Exception('There is no form method "' . $method . '"');
} else {
$class = $this->extend[$method];
}
}
$object = new $class(...$arguments);
if (method_exists($object, 'fields')) {
$object->fields($this->layout->fields);
}
$this->element = $object;
return $this;
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace admin\components\UI\Table\Column;
use admin\components\UI\Tools;
use admin\components\UI\yii\Arr;
use admin\components\UI\yii\Str;
/**
* Class Chart
*/
class Chart implements Component
{
private int $day;
private string $has;
private string $key;
private string $name;
private string $type;
/**
* Chart constructor.
* @param int $day
* @param string $has
* @param string $key
* @param string $name
* @param string $type
*/
public function __construct(int $day = 7, string $has = 'viewsData', string $key = 'pv', string $name = '访问量', string $type = 'area')
{
$this->day = $day;
$this->has = $has;
$this->key = $key;
$this->name = $name;
$this->type = $type;
}
/**
* @param $value
* @param $data
* @return string
*/
public function render($value, $data): string
{
$chartData = $data->{$this->has};
$tmpData = [];
$chartData->each(function ($item) use (&$tmpData) {
$tmpData[$item->date] += $item[$this->key];
});
$tmpChart = [];
foreach ($tmpData as $key => $vo) {
$tmpChart[] = [
"value" => $vo,
"label" => $key,
"name" => $this->name
];
}
$viewId = 'chart-chart-' . Str::random(5);
$chart = (new \backend\components\Util\ApexCharts)->{$this->type}($tmpChart)->type('day', [
'start' => date('Y-m-d', strtotime('-' . $this->day . ' day')),
])->render($viewId, function ($config) {
Arr::set($config, 'chart.height', 35);
Arr::set($config, 'chart.sparkline.enabled', true);
Arr::set($config, 'chart.animations.enabled', false);
Arr::set($config, 'tooltip.enabled', false);
return $config;
});
return "<div id='$viewId' class='p-1'></div> $chart";
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace admin\components\UI\Table\Column;
/**
* 组件接口
* Class Component
* @package backend\components\UI
*/
interface Component
{
public function render($label): array;
}

View File

@@ -0,0 +1,42 @@
<?php
namespace admin\components\UI\Table\Column;
/**
* Class Hidden
*/
class Hidden implements Component
{
/**
* @param $label
* @return array
*/
public function render($label): array
{
$name = $label . '_show';
return [
'nodeName' => 'div',
'child' => [
[
'nodeName' => 'span',
'class' => 'mr-2',
'vIf' => "rowData.record['$name']",
'child' => "{{rowData.record['$label']}}",
],
[
'nodeName' => 'a-button',
'shape' => 'round',
'size' => 'mini',
'type' => 'outline',
'vBind:status' => "rowData.record['$name'] ? 'warning' : ''",
'class' => 'mr-2',
'vOn:click' => "rowData.record['$name'] = !rowData.record['$name']",
'child' => "{{rowData.record['$name'] ? '隐藏' : '显示'}}",
],
]
];
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace admin\components\UI\Table\Column;
use admin\components\UI\Widget\Badge;
/**
* Class Images
*/
class Images implements Component
{
private int $size;
/**
* Tags constructor.
*/
public function __construct(int $size = 3)
{
$this->size = $size;
}
/**
* @param $label
* @return array
*/
public function render($label): array
{
$node = [
'nodeName' => 'div',
'vFor' => "item in rowData.record['$label']",
'class' => "flex-none bg-cover w-{$this->size} h-{$this->size}",
'vBind:style' => "{'background-image': 'url(' + item + ')'}"
];
return [
'nodeName' => 'div',
'class' => 'flex gap-2',
'child' => $node
];
}
public function getData($rowData, $field, $value): array
{
return [$field => !is_array($value) ? json_decode($value, true) : $value];
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace admin\components\UI\Table\Column;
use admin\components\UI\Tools;
/**
* Class Input
*/
class Input implements Component
{
private string $route;
private array $params;
private string $field;
private array $fields;
/**
* @param string $field
* @param string $route
* @param array $params
*/
public function __construct(string $field, string $route, array $params = [])
{
$this->field = $field;
$this->params = $params;
$this->route = $route;
}
/**
* @param array $fields
* @return $this
*/
public function fields(array $fields = []): Input
{
$this->fields = $fields;
return $this;
}
/**
* @param $label
* @return array
*/
public function render($label): array
{
$url = app_route($this->route, $this->params, false, 'rowData.record', $this->fields);
return [
'nodeName' => 'n-input',
'class' => 'shadow-sm',
'vModel:value' => "rowData.record['$label']",
'vOn:blur' => "editValue($url, {'field': '$this->field', '$this->field': rowData.record['$label']})",
];
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace admin\components\UI\Table\Column;
use admin\components\UI\Tools;
/**
* Class Link
*/
class Link implements Component
{
private array $link;
private array $fields;
/**
* @param array $fields
* @return $this
*/
public function fields(array $fields = []): Link
{
$this->fields = $fields;
return $this;
}
/**
* 添加条目
* @param string $name
* @param string $route
* @param array $params
* @param bool $absolute
* @return \backend\components\UI\Widget\Link
*/
public function add(string $name, string $route, array $params = [], bool $absolute = false): \backend\components\UI\Widget\Link
{
$link = new \backend\components\UI\Widget\Link($name, $route, $params, $absolute);
$link->fields($this->fields);
$link = $link->model('rowData.record');
$this->link[] = $link;
return $link;
}
/**
* @param $label
* @return array
*/
public function render($label): array
{
$link = [];
foreach ($this->link as $class) {
$type = $class->getType();
$data = $class->render();
if (($type === 'ajax' || $type === 'dialog') && (!isset($data['vBind:before']) || !isset($data['before']))) {
$data['vBind:before'] = "() => rowData.record.__loading = true";
}
if (($type === 'ajax' || $type === 'dialog') && (!isset($data['vBind:after']) || !isset($data['after']))) {
$data['vBind:after'] = "() => rowData.record.__loading = false";
}
$link[] = [
'nodeName' => 'span',
'child' => $data
];
}
$link = array_filter($link);
return [
'nodeName' => 'a-spin',
'vBind:loading' => 'rowData.record.__loading',
'child' => [
'nodeName' => 'div',
'class' => 'inline-flex gap-2',
'child' => $link
]
];
}
}

View File

@@ -0,0 +1,105 @@
<?php
namespace admin\components\UI\Table\Column;
use admin\components\UI\Tools;
/**
* Class Menu
*/
class Menu implements Component
{
private array $link;
private array $routes = [];
/**
* 添加条目
* @param string $name
* @param string $route
* @param array $params
* @return \backend\components\UI\Widget\Link
*/
public function add(string $name, string $route, array $params = []): \backend\components\UI\Widget\Link
{
$label = $route . '?' . http_build_query($params);
$link = new \backend\components\UI\Widget\Link($name, $route, $params);
$link = $link->model('rowData');
$this->link[] = $link;
$this->routes[$label] = [
'route' => $route,
'params' => $params
];
return $link;
}
/**
* 获取数据
* @param $rowData
* @return array
*/
public function getData($rowData): array
{
$urls = [];
foreach ($this->routes as $key => $vo) {
$params = [];
foreach ($vo['params'] as $k => $v) {
$params[$k] = Tools::parsingArrData($rowData, $v, true);
}
$urls[$key] = route($vo['route'], $params, false);
}
return $urls;
}
/**
* @param $label
* @return array
*/
public function render($label): array
{
$options = [];
foreach ($this->link as $key => $class) {
$data = $class->render();
$route = [
'nodeName' => 'route',
'type' => $data['type'],
'title' => $data['title'],
'href' => $class->getRoute(),
];
$options[] = [
'label' => $data['name'],
'key' => $key,
'route' =>$route,
];
}
$options = array_filter($options);
return [
'nodeName' => 'n-dropdown',
'width' => '80',
'placement' => 'right-start',
'overlap' => true,
'trigger' => 'click',
'options' => $options,
'render-label:option' => [
'nodeName' => 'route',
'class' => 'block',
'vBind:href' => 'rowData.record[option.route.href]',
'vBind:title' => 'option.route.title',
'vBind:type' => 'option.route.type',
'child' => '{{option.label}}'
],
'child' => [
'nodeName' => 'n-icon',
'class' => 'cursor-pointer',
'size' => 16,
'child' => [
'nodeName' => 'dots-vertical-icon'
]
],
];
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace admin\components\UI\Table\Column;
/**
* Class Progress
*/
class Progress implements Component
{
private string $color;
/**
* @param string $color
*/
public function __construct(string $color = 'default')
{
$this->color = $color;
}
/**
* @param $label
* @return array
*/
public function render($label): array
{
return (new \backend\components\UI\Widget\Progress("rowData.record['$label']"))->color($this->color)->render();
}
}

View File

@@ -0,0 +1,153 @@
<?php
namespace admin\components\UI\Table\Column;
use admin\components\UI\Tools;
/**
* Class RichText
*/
class RichText implements Component
{
private array $desc = [];
private array $image = [];
protected $relation;
/**
* 设置关系
* @param $relation
* @return $this
*/
public function setRelation($relation = null): self
{
$this->relation = $relation;
return $this;
}
/**
* @param string $label
* @param callable|null $callback
* @return $this
*/
public function desc(string $label, ?callable $callback = null): self
{
$this->desc[] = ['label' => $label, 'callback' => $callback];
return $this;
}
/**
* @param string $label
* @param int $width
* @param int $height
* @param string $placeholder
* @param callable|null $callback
* @return $this
*/
public function image(string $label, int $width = 10, int $height = 10, string $placeholder = '', ?callable $callback = null): self
{
$this->image[] = [
'label' => $label,
'width' => $width,
'height' => $height,
'placeholder' => $placeholder,
'callback' => $callback
];
return $this;
}
/**
* @param $rowData
* @return array
*/
public function getData($rowData): array
{
$data = [];
foreach ($this->image as $vo) {
if ($this->relation) {
// 解析关联数组
$url = Tools::parsingObjData($rowData, $this->relation, $vo['label']);
} else {
// 解析普通数组
$url = Tools::parsingArrData($rowData, $vo['label'], true);
}
if ($vo['callback'] instanceof \Closure) {
$url = call_user_func($vo['callback'], $url, $rowData);
}
if (filter_var($url, FILTER_VALIDATE_URL) === false) {
$url = route('service.image.placeholder', ['w' => 100, 'h' => 100, 't' => $vo['placeholder'] ?: '暂无']);
}
$data[Tools::converLabel($vo['label'], $this->relation)] = $url;
}
foreach ($this->desc as $key => $vo) {
if ($this->relation) {
// 解析关联数组
$var = Tools::parsingObjData($rowData, $this->relation, $vo['label']);
} else {
// 解析普通数组
$var = Tools::parsingArrData($rowData, $vo['label']);
}
if ($vo['callback'] instanceof \Closure) {
$var = call_user_func($vo['callback'], $var, $rowData);
}
$data[Tools::converLabel($vo['label'], $this->relation)] = $var;
}
return $data;
}
/**
* @param $label
* @return array
*/
public function render($label): array
{
$imageNode = [];
if ($this->image) {
foreach ($this->image as $vo) {
$itemLabel = Tools::converLabel2($vo['label']);
$imageNode[] = [
'nodeName' => 'div',
'class' => "flex-none bg-cover w-{$vo['width']} h-{$vo['height']}",
'vBind:style' => "{'background-image': 'url(' + rowData.record['$itemLabel'] + ')'}"
];
}
}
$descNode = [];
if ($this->desc) {
foreach ($this->desc as $vo) {
$itemLabel = Tools::converLabel2($vo['label']);
$descNode[] = [
'nodeName' => 'div',
'class' => "text-gray-500 overflow-ellipsis max-w-md",
'child' => "{{rowData.record['$itemLabel']}}"
];
}
}
return [
'nodeName' => 'div',
'class' => 'flex items-center gap-2',
'child' => [
...$imageNode,
[
'nodeName' => 'div',
'class' => 'flex-grow',
'child' => [
[//注释是因为图片显示会把url也显示
'nodeName' => 'div',
'class' => 'overflow-ellipsis max-w-md',
'child' => "{{rowData.record['$label']}}"
],
...$descNode
]
]
]
];
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace admin\components\UI\Table\Column;
use admin\components\UI\Widget\Badge;
/**
* Class Status
*/
class Status implements Component
{
private array $map;
private array $color;
private string $type;
/**
* @param array $map
* @param array $color
* @param string $type
*/
public function __construct(array $map, array $color, string $type = 'badge')
{
$this->map = $map;
$this->color = $color;
$this->type = $type;
}
/**
* @param $label
* @return array
*/
public function render($label): array
{
$statusArr = [];
foreach ($this->map as $key => $vo) {
$statusArr[$key]['name'] = $vo;
$statusArr[$key]['color'] = $this->color[$key] ?? 'blue';
}
$node = [];
foreach ($statusArr as $key => $vo) {
if ($this->type === 'badge') {
$item = (new Badge($vo['name']))->color($vo['color'])->render();
$item['vIf'] = "rowData.record['{$label}'] == " . (is_numeric($key) ? $key : "'$key'");
} else {
$item = [
'nodeName' => 'div',
'class' => 'text-' . $vo['color'] . '-900',
'child' => $vo['name']
];
}
$node[] = $item;
}
return $node;
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace admin\components\UI\Table\Column;
use admin\components\UI\Widget\Badge;
/**
* Class Tags
*/
class Tags implements Component
{
private array $map;
private array $color;
/**
* Tags constructor.
* @param array $map
* @param array $color
*/
public function __construct(array $map, array $color)
{
$this->map = $map;
$this->color = $color;
}
/**
* @param $label
* @return array
*/
public function render($label): array
{
$tagsArr = [];
foreach ($this->map as $key => $vo) {
$tagsArr[$key]['name'] = $vo;
}
foreach ($this->color as $key => $vo) {
$tagsArr[$key]['color'] = $vo;
}
$node = [];
foreach ($tagsArr as $key => $vo) {
$item = (new Badge($vo['name']))->color($vo['color'])->render();
$item['vIf'] = "~rowData.record['{$label}'].indexOf(". (is_numeric($key) ? $key : "'$key'") .")";
$node[] = $item;
}
return [
'nodeName' => 'div',
'class' => 'flex gap-2',
'child' => $node
];
}
public function getData($rowData, $field, $value): array
{
return [$field => !is_array($value) ? explode(',', $value) : $value];
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace admin\components\UI\Table\Column;
use admin\components\UI\Tools;
/**
* Class Toggle
*/
class Toggle implements Component
{
private string $route;
private array $params;
private string $field;
private array $fields;
private $checkedValue = 1;
private $uncheckedValue = 0;
/**
* @param string $field
* @param string $route
* @param array $params
*/
public function __construct(string $field, string $route, array $params = [])
{
$this->field = $field;
$this->params = $params;
$this->route = $route;
}
/**
* 设置列字段
* @param array $fields
* @return $this
*/
public function fields(Array $fields = []): self
{
$this->fields = $fields;
return $this;
}
/**
* 开关数据
* @param string|number|boolean $checkedValue
* @param string|number|boolean $uncheckedValue
* @return $this
*/
public function data($checkedValue,$uncheckedValue){
$this->checkedValue = $checkedValue;
$this->uncheckedValue = $uncheckedValue;
return $this;
}
/**
* @param $label
* @return string[]
*/
public function render($label): array
{
$url = app_route($this->route, $this->params, false, 'rowData.record', $this->fields);
return [
'nodeName' => 'a-switch',
'vModel:model-value' => "rowData.record['$label']",
'vOn:change' => "rowData.record['$label'] = \$event, editValue($url, {'field': '$this->field', '$this->field': rowData.record['$label']})",
'checkedValue' => $this->checkedValue,
'uncheckedValue' => $this->uncheckedValue
];
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace admin\components\UI\Table;
use Closure;
use admin\components\Util\Excel;
use admin\components\UI\Tools;
/**
* 数据导出
* Class Column
* @package backend\components\UI\Filter
*/
class Export
{
public array $column = [];
public string $title = '数据导出';
public string $subtitle = '';
/**
* 标题
* @param $name
* @return $this
*/
public function title($name): self
{
$this->title = $name;
return $this;
}
/**
* 副标题
* @param $name
* @return $this
*/
public function subtitle($name): self
{
$this->subtitle = $name;
return $this;
}
/**
* 列设置
* @param string $name
* @param string|Closure $value
* @param int $width
* @return $this
*/
public function column(string $name, $value, int $width = 10): self
{
$this->column[] = [
'name' => $name,
'value' => $value,
'width' => $width
];
return $this;
}
/**
* 输出表单
* @param $data
*/
public function render($data): void
{
$header = [];
$cellData = [];
foreach ($data as $vo) {
$tmp = [];
foreach ($this->column as $column) {
if (is_string($column['value'])) {
$tmp[] = Tools::parsingArrData($vo, $column['value']);
} else {
$tmp[] = call_user_func($column['value'], $vo);
}
}
$cellData[] = $tmp;
}
foreach ($this->column as $vo) {
$header[] = [
'name' => $vo['name'],
'width' => $vo['width']
];
}
Excel::export($this->title, $this->subtitle, $header, $cellData);
}
}

View File

@@ -0,0 +1,291 @@
<?php
namespace admin\components\UI\Table;
use admin\components\UI\Form\Cascader;
use admin\components\UI\Form\Date;
use admin\components\UI\Form\Daterange;
use admin\components\UI\Form\Datetime;
use admin\components\UI\Form\Select;
use admin\components\UI\Form\Text;
use admin\components\UI\Table;
/**
* 表格筛选
* Class Column
* @package backend\components\UI\Filter
*/
class Filter
{
protected string $name;
protected string $field;
protected $default;
protected $where = true;
protected Table $layout;
protected string $type = '';
protected string $placeholder = '';
protected bool $quick = false;
protected string $condition = '';
protected ?\Closure $callback;
protected $data;
protected $model;
protected $value;
protected $filterLayout;//rows
/**
* Filter constructor.
* @param string $name
* @param string $field
* @param bool $where
* @param null $default
*/
public function __construct(string $name, string $field, $where = true, $default = null)
{
$this->name = $name;
$this->field = $field;
$this->where = $where;
$this->default = $default;
$this->value = \Yii::$app->request->get($field, $this->default);
}
/**
* 设置父级对象
* @param Table $layout
*/
public function setLayout(Table $layout): void
{
$this->layout = $layout;
$this->model = $layout->model();
}
/**
* 级联选择
* @param callable|array $data
* @param callable|null $callback
* @return $this
*/
public function cascader($data = [], callable $callback = NULL): self
{
$this->data = $data;
$this->type = 'cascader';
$this->callback = $callback;
return $this;
}
/**
* 下拉框
* @param callable|array $data
* @param callable|null $callback
* @return $this
*/
public function select($data = [], callable $callback = NULL): self
{
$this->data = $data;
$this->type = 'select';
$this->callback = $callback;
return $this;
}
/**
* 文本库
* @param string $placeholder
* @param callable|null $callback
* @return $this
*/
public function text(string $placeholder = '', callable $callback = NULL): self
{
$this->placeholder = $placeholder;
$this->type = 'text';
$this->callback = $callback;
return $this;
}
/**
* 日期
* @param string $placeholder
* @param callable|null $callback
* @return $this
*/
public function date(string $placeholder = '', callable $callback = NULL): self
{
$this->placeholder = $placeholder;
$this->type = 'date';
$this->callback = $callback;
return $this;
}
/**
* 日期时间
* @param string $placeholder
* @param callable|null $callback
* @return $this
*/
public function datetime(string $placeholder = '', callable $callback = NULL): self
{
$this->placeholder = $placeholder;
$this->type = 'datetime';
$this->callback = $callback;
return $this;
}
/**
* 日期范围
* @param string $placeholder
* @param callable|null $callback
* @return $this
*/
public function daterange(string $placeholder = '', callable $callback = NULL): self
{
$this->placeholder = $placeholder;
$this->type = 'daterange';
$this->callback = $callback;
return $this;
}
/**
* 快捷筛选
* @return $this
*/
public function quick(): self
{
$this->quick = true;
return $this;
}
/**
* 筛选条件
* @param $type
* @return $this
*/
public function condition($type): self
{
$this->condition = $type;
return $this;
}
/**
* 执行筛选
* @param $query
* @return false
*/
public function execute($query): bool
{
if ($this->value === null) {
return false;
}
if (is_array($this->value) && empty($this->value)) {
return false;
}
if ($this->where instanceof \Closure) {
call_user_func($this->where, $query, $this->value, $this->data);
} elseif ($this->where !== false) {
$field = is_string($this->where) ? $this->where : $this->field;
$condition = '=';
$value = $this->value;
if ($this->condition === 'like') {
$condition = 'like';
$value = '%' . $value . '%';
}
$query->where($field, $condition, $value);
}
return true;
}
/**
* 渲染组件
* @return array
*/
public function render(): array
{
if (!$this->type) {
$this->layout->filterParams($this->field, $this->value);
return [];
}
switch ($this->type) {
case 'select':
$object = new Select($this->name, $this->field, $this->data);
$object->tip(true);
break;
case 'cascader':
$object = new Cascader($this->name, $this->field, $this->data);
break;
case 'date':
$object = new Date($this->name, $this->field);
break;
case 'datetime':
$object = new Datetime($this->name, $this->field);
break;
case 'daterange':
$object = new Daterange($this->name, $this->field);
break;
case 'text':
default:
$object = new Text($this->name, $this->field);
}
$object->model('data.filter.');
$this->layout->filterParams($this->field, $this->value);
if ($this->callback instanceof \Closure) {
call_user_func($this->callback, $object);
}
$data = [
'status' => $this->value !== null,
'quick' => $this->quick,
'where' => $this->where,
'value' => $this->value,
'field' => $this->field,
'data' => $this->data,
'name' => $this->name
];
if($this->layout->filterLayout=='rows'){
$data['render'] = [
'nodeName'=>'a-col',
"span"=>"6",
'child'=>[
'nodeName'=>'a-form-item',
'field'=>$this->field,
'label'=>$this->name,
'child' => $object->placeholder($this->placeholder)->getRender()
]
];
return $data;
}
if ($this->quick) {
$data['render'] = [
'nodeName' => 'div',
'class' => 'lg:w-40',
'child' => $object->placeholder($this->placeholder)->getRender()
];
} else {
$data['render'] = [
'nodeName' => 'div',
'class' => 'my-2',
'child' => [
[
'nodeName' => 'div',
'child' => $this->name,
],
[
'nodeName' => 'div',
'class' => 'mt-2',
'child' => $object->placeholder($this->placeholder)->getRender()
]
],
];
}
return $data;
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace admin\components\UI\Table;
use admin\components\UI\Form\Date;
use admin\components\UI\Form\Daterange;
use admin\components\UI\Form\Datetime;
use admin\components\UI\Form\Select;
use admin\components\UI\Form\Text;
use admin\components\UI\Table;
use admin\components\UI\Widget\Icon;
/**
* 类型筛选
* Class Column
* @package backend\components\UI\Filter
*/
class FilterType
{
protected string $name;
protected ?\Closure $callback;
protected ?\Closure $where;
protected string $icon = '';
protected ?int $num = null;
protected ?Table $layout;
protected $model;
protected $value;
/**
* @param string $name
* @param callable|null $where
* @param int $value
*/
public function __construct(string $name, callable $where = null, int $value = 0)
{
$this->name = $name;
$this->where = $where;
$this->value = $value;
}
public function setLayout(Table $layout): void
{
$this->layout = $layout;
$this->model = $layout->model();
}
public function num($num = 0): self
{
$this->num = $num;
return $this;
}
public function icon($content): self
{
$this->icon = $content;
return $this;
}
public function execute($query, $key): void
{
if ($this->where instanceof \Closure && $this->value == $key) {
call_user_func($this->where, $this->model);
}
}
/**
* @param $key
* @return array
*/
public function render($key): array
{
return [
'nodeName' => 'a-radio',
'value' => $key,
'child' => [
$this->icon ? [
'nodeName' => $this->icon
]: [],
[
'nodeName' => 'span',
'child' => ' ' . $this->name
]
]
];
}
}

View File

@@ -0,0 +1,591 @@
<?php
namespace admin\components\UI\Table;
use admin\components\UI\Form\Cascader;
use admin\components\UI\Tools;
use admin\components\UI\Widget\Icon;
/**
* 表格节点
* @package backend\components\UI\Table
*/
class Node
{
private string $url;
private ?string $key;
private ?string $title;
private bool $tree = false;
private bool $urlBind = true;
private string $class = '';
private array $params = [];
private array $data = ['show'=>[],'filter'=>[]];
private array $columns = [];
private array $expand = [];
private array $filter = [];
private array $quickFilter = [];
private array $action = [];
private array $bath = [];
private array $page = ['left'=>[],'right'=>[]];
private array $type = [];
private array $side = ['left'=>[],'right'=>[]];
private array $sideSize = ['left'=>'','right'=>''];
private array $header = [];
private array $footer = [];
private array $script = [];
private array $scriptReturn = [];
private array $scriptData = [];
private ?string $eventName = null;
private $filterLayout =null;
/**
* Node constructor.
* @param string $url
* @param string $key
* @param string|null $title
*/
public function __construct(string $url, string $key, ?string $title = '')
{
$this->url = $url;
$this->key = $key;
$this->title = $title;
}
/**
* @param $urlBind
* @return $this
*/
public function urlBind($urlBind): self
{
$this->urlBind = $urlBind;
return $this;
}
/**
* @param $class
* @return $this
*/
public function class($class): self
{
$this->class .= ' ' . $class;
return $this;
}
/**
* @param $params
* @return $this
*/
public function params($params): self
{
$this->params = $params;
return $this;
}
/**
* 附加参数
* @param $key
* @param $value
* @return $this
*/
public function nParams($key,$value){
$this->params[$key] = $value;
return $this;
}
/**
* @param array $filter
* @return $this
*/
public function data(array $filter): self
{
$this->data['filter'] = $filter;
return $this;
}
/**
* @param $content
* @param $return
* @return $this
*/
public function script($content, $return): self
{
if ($content instanceof \Closure) {
$this->script[] = $content();
} else {
$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 $config
* @return $this
*/
public function tree($config): self
{
$this->tree = $config;
return $this;
}
/**
* @param array $node
* @return $this
*/
public function columns(array $node): self
{
$this->columns = $node;
return $this;
}
/**
* @param array $node
* @return $this
*/
public function expand(array $node): self
{
$this->expand = $node;
return $this;
}
/**
* @param array $node
* @return $this
*/
public function type(array $node): self
{
$this->type = $node;
return $this;
}
/**
* @param array $node
* @return $this
*/
public function filter(array $node): self
{
$this->filter = array_values(array_filter($node));
return $this;
}
public function filterLayout($l):self{
$this->filterLayout =$l;
return $this;
}
/**
* @param array $node
* @return $this
*/
public function quickFilter(array $node): self
{
if ($node) {
$this->quickFilter = $node;
}
return $this;
}
/**
* @param array $node
* @return $this
*/
public function action(array $node): self
{
$this->action = $node;
return $this;
}
/**
* @param array $node
* @return $this
*/
public function bath(array $node): self
{
$this->bath = $node;
return $this;
}
/**
* @param string|null $eventName
* @return $this
*/
public function eventName(?string $eventName): self
{
$this->eventName = $eventName;
return $this;
}
/**
* @param $node
* @param string $type
* @param false $resize
* @param string $width
* @return $this
*/
public function side($node, string $type = 'left', bool $resize = false, string $width = '100px'): self
{
$this->side[$type] = is_callable($node) ? $node() : $node;
if ($resize) {
$this->sideSize[$type] = $width;
}
return $this;
}
/**
* @param $node
* @param string $type
* @return $this
*/
public function page($node, string $type = 'left'): self
{
$this->page[$type] = is_callable($node) ? $node() : $node;
return $this;
}
/**
* @param $node
* @return $this
*/
public function header($node): self
{
$this->header = $node;
return $this;
}
/**
* @param $node
* @return $this
*/
public function footer($node): self
{
$this->footer = $node;
return $this;
}
/**
* @return array[]
*/
private function headNode(): array
{
if($this->filterLayout=='rows'){
$ret[] = [
'nodeName'=>'a-row',
'child'=>[
[
'nodeName'=>'a-col',
'child'=>[
'nodeName' => 'a-form',
"label-align"=>"left",
"vBind:label-col-props"=>"{'span':'8'}",
"vBind:wrapper-col-props"=>"{'span':'16'}",
'child'=>[
[
'nodeName'=>'a-row',
"vBind:gutter"=>"16",
'child' => $this->filter
],
]
]
],
]
];
if(!empty($this->action)){
if(!empty($this->filter)){
$ret[] = [
'nodeName'=>'a-divider',
'style'=>'margin-top: 0',
];
}
$ret[] = [
'nodeName'=>'a-row',
"style"=>"margin-bottom: 16px",
'child'=>[
'nodeName' => 'a-col',
'vBind:span'=>'16',
'child'=>[
'nodeName' => 'a-space',
'child'=>$this->action
]
]
];
}
return $ret;
}
if ($this->filter) {
$this->quickFilter[] = [
'nodeName' => 'a-trigger',
'position' => 'br',
'trigger' => 'click',
'child' => [
[
'nodeName' => 'a-button',
'type' => 'secondary',
'child' => [
'筛选',
[
'vSlot:icon' => '',
'nodeName' => 'icon-filter',
]
]
],
[
'vSlot:content' => '',
'nodeName' => 'div',
'class' => 'flex flex-col rounded shadow bg-white dark:bg-blackgray-1 dark:text-gray-400 p-2 w-56',
'child' => $this->filter
]
]
];
}
$header=[];
$ret = [];
if ($this->type) {
$header = [
'nodeName' => 'a-radio-group',
'name' => 'type',
'type' => 'button',
'vModel:modelValue' => 'data.filter.type',
'child' => $this->type
];
$ret[] = [
'nodeName' => 'div',
'class' => 'flex-grow lg:w-10 flex justify-center lg:justify-start',
'child' => $header
];
}
$ret[] = [
'nodeName' => 'div',
'class' => 'flex-none flex gap-2',
'child' => array_filter(array_merge($this->quickFilter, $this->action))
];
return $ret;
}
/**
* @return array
*/
public function tableNode(): array
{
// 指定行key
$this->params['row-key'] = $this->key;
// 设置扩展行
if ($this->expand) {
$this->params['vChild:expandable'] = $this->expand;
}
// 分组表头线
$children = false;
foreach ($this->columns as $col) {
if (isset($col['children']) && $col['children']) {
$children = true;
break;
}
}
if ($children) {
$this->params['bordered'] = [
'headerCell' => true
];
}
return [
'nodeName' => 'app-table',
'requestEventName' => $this->eventName,
'class' => $this->class,
'url' => $this->url,
'urlBind' => $this->urlBind,
'n-params' => $this->params,
'columns' => $this->columns,
'vBind:filter' => 'data.filter',
'select' => (bool)$this->bath,
'table-layout-fixed' => true,
'child' => [
'vSlot:footer' => 'footer',
'nodeName' => 'div',
'class' => 'flex gap-2',
'child' => $this->bath
]
];
}
/**
* @return array
*/
public function render(): array
{
/*$this->script[] = <<<JS
JS;
$this->scriptReturn[] = 'dataRef';*/
$value = [
'filter' => $this->data['filter'] ?: [],
'show' => $this->data['show'] ?: []
];
$value = array_merge($this->scriptData, $value);
return [
'node' => [
'nodeName' => 'app-form',
'value' => $value,
'child' => [
'nodeName' => 'div',
'class' => 'flex h-screen',
'vSlot' => '{value: data}',
'child' => [
$this->side['left'] ? [
'nodeName' => isset($this->sideSize['left']) ? 'a-resize-box' : 'div',
'style' => $this->sideSize['left'] ? 'width:' . $this->sideSize['left'] : '',
'directions' => ['right'],
'class' => 'border-r border-gray-200 dark:border-gray-700 flex-none bg-white dark:bg-blackgray-4 h-screen',
'child' => $this->side['left']
] : [],
[
'nodeName' => 'app-layout',
'class' => 'flex-grow w-10',
'title' => $this->title ?: '列表数据',
'child' => [
[
'vSlot' => '',
'nodeName' => 'div',
'class' => 'flex flex-row items-start gap-4 p-4',
'child' => [
$this->page['left'] ?: [],
[
'nodeName' => 'div',
'class' => 'flex-grow lg:w-10 p-4 bg-white dark:bg-blackgray-4 rounded shadow',
'child' => [
$this->header ? [
'nodeName' => 'div',
'class' => 'pb-4',
'child' => $this->header
] : [],
[
'nodeName' => 'div',
'class' => 'flex-none flex-row gap-2 items-center pb-4',
'child' => $this->headNode()
],
$this->tableNode(),
$this->footer ? [
'nodeName' => 'div',
'class' => 'pb-4',
'child' => $this->footer
] : [],
],
],
$this->page['right'] ?: [],
]
]
]
],
$this->side['right'] ? [
'nodeName' => $this->sideSize['right'] ? 'a-resize-box' : 'div',
'style' => $this->sideSize['right'] ? 'width:' . $this->sideSize['left'] : '',
'directions' => ['left'],
'class' => 'border-l border-gray-200 dark:border-gray-700 flex-none bg-white dark:bg-blackgray-4 h-screen',
'child' => $this->side['right']
] : [],
]
],
],
'setupScript' => implode("\n", $this->script) . "\n" . ' return {' . implode(",", $this->scriptReturn) . '}'
];
}
/**
* 渲染table核心
* @return array[]
*/
public function renderTableCore(): array
{
$value = [
'filter' => $this->data['filter'] ?: [],
'show' => $this->data['show'] ?: []
];
$value = array_merge($this->scriptData, $value);
return [
'node' => [
'nodeName' => 'app-form',
'value' => $value,
'child' => [
'nodeName' => 'app-dialog',
'vSlot' => '{value: data}',
'title' => $this->title ?: '信息详情',
'class' => 'flex-grow',
'child' => [
[
'nodeName' => 'div',
'class' => 'flex',
'vSlot' => '{value: data}',
'child' => [
$this->side['left'] ? [
'nodeName' => 'div',
'class' => 'flex-none',
'child' => $this->side['left']
] : [],
[
'nodeName' => 'div',
'class' => 'flex-grow lg:w-10 p-4 bg-white dark:bg-blackgray-4 rounded shadow',
'child' => [
$this->header ? [
'nodeName' => 'div',
'class' => 'pb-4',
'child' => $this->header
] : [],
[
'nodeName' => 'div',
'class' => 'flex-none flex flex-row gap-2 items-center pb-4',
'child' => $this->headNode()
],
$this->tableNode(),
$this->footer ? [
'nodeName' => 'div',
'class' => 'pb-4',
'child' => $this->footer
] : [],
],
],
$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' => '关闭'
]
]
]
]
]
]
]
];
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace admin\components\UI\Table;
use admin\components\UI\Widget\Link;
/**
* 树形列表
* @package backend\components\UI\Filter
*/
class Tree
{
protected string $label = '';
protected array $node = [];
protected array $prefix = [];
protected array $suffix = [];
protected Column\Link $link;
/**
* @param string $label
* @param array $node
*/
public function __construct(string $label, array $node = [])
{
$this->label = $label;
$this->node = $node;
}
/**
* @param $node
* @return $this
*/
public function prefix($node): self
{
$this->prefix = $node;
return $this;
}
/**
* @param $node
* @return $this
*/
public function suffix($node): self
{
$this->suffix = $node;
return $this;
}
/**
* 添加链接
* @param string $name
* @param string $route
* @param array $params
* @return Link
*/
public function link(string $name, string $route, array $params = []): Link
{
if (!$this->link) {
$this->link = new Column\Link();
}
return $this->link->add($name, $route, $params);
}
/**
* 渲染组件
* @return array
*/
public function render(): array
{
$suffix = $this->suffix;
if ($this->link) {
$suffix = $this->link->render('');
}
return [
'node' => $this->node ?: ['nodeName' => 'div', 'child' => "{{rowData.record['$this->label']}}"],
'prefix' => $this->prefix,
'suffix' => $suffix
];
}
/**
* 组件行数据
* @param $rowData
* @return array
*/
public function getData($rowData): array
{
$data = [];
// 元素数据
if ($this->link) {
$data = array_merge($data, $this->link->getData($rowData));
}
return $data;
}
}

View File

@@ -0,0 +1,73 @@
<?php
/**
* Created by PhpStorm.
* User: chatfeed
* Date: 2022/5/19
* Time: 6:40 PM
*/
namespace admin\components\UI;
use admin\components\UI\Components\VueComponent;
use phpDocumentor\Reflection\Types\Boolean;
class Tabs extends VueComponent
{
public $dialog =false;
public bool $lazyLoad = false;
public $nodes = [];
public function setup(string $script)
{
return <<<JS
JS;
// TODO: Implement setup() method.
}
public function addTab($title,$key,$desc,$child=null){
$this->nodes[] = ['title'=>$title,'name'=>$title,'key'=>$key,'order'=>0,'desc'=>$desc,'child'=>$child?[$child]:[]];
}
public function render(): array
{
$panelNodes = [];
$nodes = collect($this->nodes)->sortBy('order')->toArray();
foreach ($nodes as $key => $node) {
$child = [];
if ($node['title']) {
$child[] = [
'nodeName' => 'div',
'class' => 'pt-2',
'child' => $node['child']
];
}
//$child[] = [
// 'nodeName' => 'div',
// 'class' => 'pt-2',
// 'child' => $node['object']->render()
//];
$panelNodes[] = [
'nodeName' => 'a-tab-pane',
'title' => $node['name'],
'key' => $key,
'class' => !$this->dialog ? ' border-t border-gray-200 dark:border-blackgray-1 px-3 pt-4 pb-0' : '',
'child' => [
'nodeName' => 'div',
'class' => '',
'child' => $child
]
];
}
return [
'nodeName' => 'a-tabs',
'lazy-load'=>$this->lazyLoad,
'class' => !$this->dialog ? 'mb-4 bg-white dark:bg-blackgray-4 rounded shadow p-4 pb-1' : '',
'type' => 'rounded',
'child' => $panelNodes
];
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace admin\components\UI;
use admin\components\UI\yii\Arr;
/**
* UI工具
* Class Table
* @package backend\components\UI
*/
class Tools
{
/**
* 解析对象数据
* @param object $data
* @param string $relation
* @param string|null $field
* @return string|string[]|null
*/
public static function parsingObjData(object $data, string $relation, string $field = null)
{
$relationData = $data->$relation;
if ($field) {
if ($relationData instanceof \Illuminate\Support\Collection) {
$tmp = [];
foreach ($relationData as $vo) {
$tmp[] = self::parsingArrData($vo, $field);
}
$data = implode(',', $tmp);
} else {
$data = self::parsingArrData($relationData, $field);
}
} else {
$data = '';
}
return $data;
}
/**
* 解析数组数据
* @param $data
* @param string|null $field
* @param bool $source
* @return string|string[]|null
*/
public static function parsingArrData($data, string $field = null, bool $source = false)
{
//$field = str_replace('->', '.', $field);
if (!$source) {
return $field ? Arr::get($data, $field) : '';
}
return Arr::has($data, $field) ? Arr::get($data, $field) : $field;
}
/**
* 标签转换
* @param $label
* @param null $relation
* @return string
*/
public static function converLabel($label, $relation = null): string
{
return str_replace(['.', '->'], "_", $relation ? $relation . '_' . $label : $label);
}
public static function converLabel2($label): string
{
return str_replace(['.', '->'], "']['",$label);
}
}

View File

@@ -0,0 +1,11 @@
<div {{ $attributes }}>
<div class="empty">
<div class="spinner-border mb-3" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="empty-title" style="font-size: 1rem;">{{$title}}</p>
<p class="empty-subtitle text-muted">
{{$content}}
</p>
</div>
</div>

View File

@@ -0,0 +1,39 @@
<div {{ $attributes }}>
<div class="flex flex-col items-center">
<div class="w-20 h-20">
<svg class="w-full h-full" viewBox="0 0 140 140" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="06.-Design-Variations" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="02-example-products-empty" transform="translate(-776.000000, -284.000000)">
<g id="empty-state" transform="translate(290.000000, 164.000000)">
<g id="illustration" transform="translate(486.000000, 120.000000)">
<rect id="illustration-bg" fill="#FFFFFF" opacity="0" x="0" y="0" width="140" height="140"></rect>
<g id="receipt" transform="translate(28.000000, 16.000000)">
<path d="M78,0.979732163 L6,1 L6,61.6706477 C6,62.220134 78,61.9838101 78,61.6706477 L78,0.979732163 Z" id="Path" fill="#D7DBEC"></path>
<polygon id="Path" fill="#336DFF" points="84 9 0 9 0 109 5.5 103 10.5 109 16 103 21 109 26.5 103 31.5 109 37 103 42 109 47.5 103 52.5 109 58 103 63 109 68.5 103 73.5 109 79 103 84 109"></polygon>
<polygon id="Path" fill="#FFFFFF" fill-rule="nonzero" points="21.6465315 28.5275452 24.3534685 31.4724548 15.3588167 39.740266 10.6190485 35.2159419 13.3809515 32.3225197 15.41 34.26"></polygon>
<rect id="Rectangle" fill="#FFFFFF" x="32" y="32" width="38" height="4"></rect>
<polygon id="Path" fill="#FFFFFF" fill-rule="nonzero" points="21.6465315 45.5275452 24.3534685 48.4724548 15.3588167 56.740266 10.6190485 52.2159419 13.3809515 49.3225197 15.41 51.26"></polygon>
<rect id="Rectangle" fill="#FFFFFF" x="32" y="49" width="38" height="4"></rect>
<polygon id="Path" fill="#FFFFFF" fill-rule="nonzero" points="21.6465315 62.5275452 24.3534685 65.4724548 15.3588167 73.740266 10.6190485 69.2159419 13.3809515 66.3225197 15.41 68.26"></polygon>
<rect id="Rectangle" fill="#FFFFFF" x="32" y="66" width="38" height="4"></rect>
</g>
</g>
</g>
</g>
</g>
</svg>
</div>
<p class="text-lg mb-2 mt-4">{{$title}}</p>
<p class="text-gray-500">
{{$content}}
</p>
@if($reload)
<div class="mt-5">
<a href="javascript:location.reload();" class="btn-blue flex items-center space-x-2">
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M19.95 11a8 8 0 1 0 -.5 4m.5 5v-5h-5" /></svg>
<span>刷新页面</span>
</a>
</div>
@endif
</div>
</div>

View File

@@ -0,0 +1,22 @@
@if($type == 2)
<svg xmlns="http://www.w3.org/2000/svg" class="icon ml-1" width="24" height="24"
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"
fill="none"/><polyline
points="3 17 9 11 13 15 21 7"/><polyline points="14 7 21 7 21 14"/></svg>
@elseif($type == 1)
<svg xmlns="http://www.w3.org/2000/svg" class="icon ms-1" width="24" height="24"
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"
fill="none"></path><line x1="5"
y1="12"
x2="19"
y2="12"></line></svg>
@else
<svg xmlns="http://www.w3.org/2000/svg" class="icon ms-1" width="24" height="24"
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"
fill="none"></path><polyline
points="3 7 9 13 13 9 21 17"></polyline><polyline
points="21 10 21 17 14 17"></polyline></svg>
@endif

View File

@@ -0,0 +1 @@
@include($layout)

View File

@@ -0,0 +1 @@
@include($layout)

View File

@@ -0,0 +1,39 @@
<?php
namespace admin\components\UI;
/**
* 通用部件
* Class Composite
* @package backend\components\UI
* @method static \backend\components\UI\Widget\Alert alert(string $content, string $title = null, callable $callback = NULL)
* @method static \backend\components\UI\Widget\Badge badge($content, callable $callback = NULL)
* @method static \backend\components\UI\Widget\Images images($list, callable $callback = NULL)
* @method static \backend\components\UI\Widget\Form form($data, callable $callback = NULL)
* @method static \backend\components\UI\Widget\Icon icon($content, callable $callback = NULL)
* @method static \backend\components\UI\Widget\Link link($data, callable $callback = NULL)
* @method static \backend\components\UI\Widget\Menu menu(string $name, string $type = 'default', callable $callback = NULL)
* @method static \backend\components\UI\Widget\Table table($data, callable $callback = NULL)
* @method static \backend\components\UI\Widget\Text text($content, callable $callback = NULL)
* @method static \backend\components\UI\Widget\Row row(callable $callback = NULL)
* @method static \backend\components\UI\Widget\StatsCard statsCard(callable $callback = NULL)
*/
class Widget
{
private static $extend;
public static function __callStatic($method, $arguments)
{
$class = 'backend\\components\\UI\\Widget\\' . ucfirst($method);
if (!class_exists($class)) {
if (!self::$extend[$method]) {
throw new \RuntimeException('There is no widget method "' . $method . '"');
} else {
$class = self::$extend[$method];
}
}
$object = new $class(...$arguments);
return $object->next()->getRender();
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace admin\components\UI\Widget;
/**
* 警告信息
* @package backend\components\UI\Widget
*/
class Alert extends Widget
{
protected string $type = 'info';
private ?string $title;
private $content;
/**
* @param string|array $content
* @param string|null $title
* @param callable|null $callback
*/
public function __construct($content, string $title = null, callable $callback = NULL)
{
$this->title = $title;
$this->content = $content;
$this->callback = $callback;
}
/**
* 文本类型
* @param $name
* @return $this
*/
public function type($name): self
{
$this->type = $name;
return $this;
}
/**
* @return array
*/
public function render(): array
{
return [
'nodeName' => 'a-alert',
'title' => $this->title,
'type' => $this->type,
'child' => $this->content
];
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace admin\components\UI\Widget\Append;
/**
* Class Element
* @package backend\components\UI\Widget\Append
*/
trait Element
{
}

View File

@@ -0,0 +1,63 @@
<?php
namespace admin\components\UI\Widget;
/**
* Class Badges
* @package backend\components\UI\Widget
*/
class Badge extends Widget
{
private string $content;
private string $size = 'medium';
private string $type;
/**
* Badges constructor.
* @param $content
* @param callable|null $callback
*/
public function __construct($content, callable $callback = NULL)
{
$this->content = $content;
$this->callback = $callback;
}
/**
* 颜色
* @param $value
* @return $this
*/
public function color($value): self
{
$this->type = $value;
return $this;
}
/**
* 大小
* @param string $size
* @return $this
*/
public function size(string $size = ''): self
{
$this->size = $size;
return $this;
}
/**
* @return array
*/
public function render(): array
{
return [
'nodeName' => 'a-tag',
'color' => $this->type,
'size' => $this->size,
'child' => $this->content
];
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace admin\components\UI\Widget;
/**
* Class Form
* @package backend\components\UI\Widget
*/
class Form extends Widget
{
private \backend\components\UI\Form $form;
public function __construct($data, callable $callback = null)
{
$this->callback = $callback;
$this->form = new \backend\components\UI\Form($data, false);
}
/**
* @return string
*/
public function render(): string
{
return $this->form->render();
}
/**
* @param $method
* @param $arguments
* @return mixed
*/
public function __call($method, $arguments)
{
return $this->form->$method(...$arguments);
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace admin\components\UI\Widget;
use admin\components\UI\Widget\Append\Element;
/**
*图标组件
* @package backend\components\UI\Widget
*/
class Icon extends Widget
{
use Element;
private string $content;
private bool $layout = false;
private int $size;
/**
* @param string $content
* @param callable|null $callback
*/
public function __construct(string $content, ?callable $callback = null)
{
$this->content = $content;
$this->callback = $callback;
}
/**
* 设置大小
* @param int $size
* @return $this
*/
public function size(int $size): self
{
$this->size = $size;
return $this;
}
/**
* @return array
*/
public function render(): array
{
$icon = $this->content;
if (strpos($icon, '<svg') !== false) {
return [
'nodeName' => 'n-icon',
'size' => $this->size,
'child' => [
'nodeName' => 'rich-text',
'class' => implode(' ', $this->class),
'nodes' => $icon
]
];
}
return [
'nodeName' => 'icon-' . $icon,
'class' => implode(' ', $this->class),
];
}
}

Some files were not shown because too many files have changed in this diff Show More