commit e78454540f1ebdf53700c57bc8b4650d23501af7 Author: xuanchen <122383162@qq.com> Date: Wed Mar 8 09:16:04 2023 +0800 first diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6537ca4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0490e8d --- /dev/null +++ b/.env.example @@ -0,0 +1,48 @@ +APP_NAME=Laravel +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_URL=http://localhost + +SERVER_ID=001 + +RATE_LIMITER=300 + +ADMIN_HTTPS=false +ADMIN_NAME="Uz.Tech 后台系统" +ADMIN_TITLE='Uz.Tech' +ADMIN_LOGO="UZ Tech" +ADMIN_LOGO_MINI="U.T" + +LOG_CHANNEL=stack +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=laravel +DB_USERNAME=root +DB_PASSWORD= + +BROADCAST_DRIVER=log +CACHE_DRIVER=file +QUEUE_CONNECTION=database +SESSION_DRIVER=file +SESSION_LIFETIME=120 + +MEMCACHED_HOST=127.0.0.1 + +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 +REDIS_DB=1 +REDIS_CACHE_DB=1 + +FILESYSTEM_DRIVER=public +OSS_ACCESS_KEY= +OSS_SECRET_KEY= +OSS_ENDPOINT= +OSS_BUCKET= +OSS_IS_CNAME=false +OSS_CDN_HOST= diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..967315d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +* text=auto +*.css linguist-vendored +*.scss linguist-vendored +*.js linguist-vendored +CHANGELOG.md export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c407190 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +/node_modules +/public/hot +/public/storage +/storage/*.key +/vendor +.env +.env.backup +.phpunit.result.cache +docker-compose.override.yml +Homestead.json +Homestead.yaml +npm-debug.log +yarn-error.log +.idea +.DS_Store +.vscode/ \ No newline at end of file diff --git a/.styleci.yml b/.styleci.yml new file mode 100644 index 0000000..9231873 --- /dev/null +++ b/.styleci.yml @@ -0,0 +1,13 @@ +php: + preset: laravel + disabled: + - no_unused_imports + finder: + not-name: + - index.php + - server.php +js: + finder: + not-name: + - webpack.mix.js +css: true diff --git a/README.md b/README.md new file mode 100644 index 0000000..d9ab369 --- /dev/null +++ b/README.md @@ -0,0 +1,123 @@ +

+ +# UzTech.Laravel + +> Jason.Chen , 为了构建一个健壮的底层。 + +[TOC] + +## 1.安装 + +```shell +git pull http://git.yuzhankeji.cn/UzTech/laravel.git +``` + +```shell +# 生成 laravel 密钥 +php artisan key:generate --ansi + +# 数据库迁移 +php artisan migrate + +# 数据填充(初始化基础数据) +php artisan db:seed --class=AdminPanelSeeder +``` + +手动导入 initial.sql 后台数据基础文件 + +## 包含的基础组件 + +> OSS文件管理包 +> +> "jasonc/laravel-filesystem-oss": "^3.0" +> +> 模块化工具 +> +> "nwidart/laravel-modules": "^8.2" +> +> 模块安装工具 +> +> "joshbrw/laravel-module-installer": "^2.0" +> +> API 管理工具 +> +> "jasonc/api": "^3.3" +> +> 后台模板 +> +> "encore/laravel-admin": "^1.8" + +## 模块安装 + +> 模块安装完毕之后,要执行 composer dump-autoload + +### 1. + +## 模块开发 + +### 1. 创建新模块 + +```shell +php artisan module:make ModuleName +``` + +### 2. 模块目录的一些规范 + +``` +modules/ + ├── Blog/ + ├── Config/ 配置目录,使用 Config::get('module_name.') 调用 + ├── Console/ 控制台命令 + ├── Kernel.php 执行定时任务 + ├── Database/ 数据库 + ├── Migrations/ 数据表单迁移 + ├── Seeders/ 数据填充文件 + ├── Events/ 事件目录 + ├── Http/ + ├── Controllers/ 控制器 + ├── Admin/ + ├── Api/ + ├── Middleware/ 中间件目录 + ├── Requests/ 请求验证 + ├── Resources API接口资源 + ├── Jobs/ 队列 + ├── Listeners/ 监听器 + ├── Models/ 模型 + ├── Traits/ 模块内部模型使用的traits + ├── Providers/ + ├── BlogServiceProvider.php + ├── RouteServiceProvider.php + ├── Resources/ 静态资源目录 + ├── assets/ + ├── js/ + ├── app.js + ├── sass/ + ├── app.scss + ├── lang/ + ├── views/ + ├── Routes/ 路由 + ├── admin.php + ├── api.php + ├── Service/ 内部服务 + ├── Traits/ 对外的traits + ├── composer.json + ├── module.json + ├── README.md +``` +### 3. 定时任务相关说明 + +```php +// 定时任务命令在ServiceProvider中加载 +if ($this->app->runningInConsole()) { + $this->commands([]); +} +// 定时任务的执行 +// 在模型文件夹 Console 建立 Kernel 类,类中 runCommand 执行定时任务 +$schedule->command(**)->everyMinute(); +``` + +### 4. 查看全部路由,优化组件 + +```shell +php artisan route:pretty +``` diff --git a/app/Admin/Controllers/AuthController.php b/app/Admin/Controllers/AuthController.php new file mode 100644 index 0000000..3488577 --- /dev/null +++ b/app/Admin/Controllers/AuthController.php @@ -0,0 +1,10 @@ + 'PHP version', 'value' => 'PHP/'.PHP_VERSION], + ['name' => 'Laravel version', 'value' => app()->version()], + ['name' => 'CGI', 'value' => php_sapi_name()], + ['name' => 'Uname', 'value' => php_uname()], + ['name' => 'Server', 'value' => Arr::get($_SERVER, 'SERVER_SOFTWARE')], + + ['name' => 'Cache driver', 'value' => config('cache.default')], + ['name' => 'Session driver', 'value' => config('session.driver')], + ['name' => 'Queue driver', 'value' => config('queue.default')], + + ['name' => 'Timezone', 'value' => config('app.timezone')], + ['name' => 'Locale', 'value' => config('app.locale')], + ['name' => 'Env', 'value' => config('app.env')], + ['name' => 'URL', 'value' => config('app.url')], + ]; + + return view('admin.dashboard.environment', compact('envs')); + } + + /** + * @return Factory|View + */ + public static function extensions() + { + $extensions = [ + 'user' => [ + 'name' => '用户模块', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-user-module', + 'icon' => 'users', + ], + 'chain' => [ + 'name' => '区块链管理', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-chain-module', + 'icon' => 'chain', + ], + 'lottery' => [ + 'name' => '抽奖模块', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-lottery-module', + 'icon' => 'gavel', + ], + 'task' => [ + 'name' => '任务模块', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-task-module', + 'icon' => 'hourglass-end', + ], + 'cms' => [ + 'name' => '内容管理', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-cms-module', + 'icon' => 'book', + ], + 'appversion' => [ + 'name' => 'App版本', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-appversion-module', + 'icon' => 'apple', + ], + 'mall' => [ + 'name' => '多用户商城', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-mall-module', + 'icon' => 'shopping-cart', + ], + 'payment' => [ + 'name' => '支付模块', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-payment-module', + 'icon' => 'paypal', + ], + 'company' => [ + 'name' => '企业管理', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-company-module', + 'icon' => 'black-tie', + ], + 'coupon' => [ + 'name' => '优惠券模块', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-coupon-module', + 'icon' => 'qrcode', + ], + 'settlement' => [ + 'name' => '结算模块', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-settlement-module', + 'icon' => 'sliders', + ], + 'configuration' => [ + 'name' => '参数配置', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-configuration-module', + 'icon' => 'cogs', + ], + 'withdraw' => [ + 'name' => '提现管理', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-withdraw-module', + 'icon' => 'clock-o', + ], + 'notification' => [ + 'name' => '消息中心', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-notification-module', + 'icon' => 'envelope', + ], + 'linker' => [ + 'name' => '链接管理', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-linker-module', + 'icon' => 'link', + ], + 'storage' => [ + 'name' => '文件存储', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-storage-module', + 'icon' => 'file', + ], + 'omniform' => [ + 'name' => '万能表单', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-omni-form-module', + 'icon' => 'wpforms', + ], + 'tao' => [ + 'name' => '淘宝客', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-tao-module', + 'icon' => 'simplybuilt', + ], + 'market' => [ + 'name' => '交易市场', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-market-module', + 'icon' => 'asl-interpreting', + ], + 'tokenmall' => [ + 'name' => '区块链商城', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-token-mall-module', + 'icon' => 'flask', + ], + 'acme' => [ + 'name' => 'SSL免费证书', + 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-acme-module', + 'icon' => 'html5', + ], + ]; + + foreach ($extensions as $key => &$extension) { + $module = Module::find($key); + + if ($module) { + $extension['installed'] = (int) $module->isEnabled(); + } else { + $extension['installed'] = 2; + } + } + + return view('admin.dashboard.extensions', compact('extensions')); + } + + /** + * @return string + */ + public static function dependencies(): string + { + $json = file_get_contents(base_path('composer.json')); + + $dependencies = json_decode($json, true)['require']; + + return Admin::component('admin.dashboard.dependencies', compact('dependencies')); + } +} diff --git a/app/Admin/Controllers/HomeController.php b/app/Admin/Controllers/HomeController.php new file mode 100644 index 0000000..65b757d --- /dev/null +++ b/app/Admin/Controllers/HomeController.php @@ -0,0 +1,46 @@ + + * @param Content $content + * @return Content + */ + public function index(Content $content): Content + { + if (config('app.debug')) { + return $content + ->title(__('admin.menu_titles.dashboard')) + ->description('Description...') + ->row(Dashboard::title()) + ->row(function (Row $row) { + $row->column(4, function (Column $column) { + $column->append(Dashboard::environment()); + }); + + $row->column(4, function (Column $column) { + $column->append(Dashboard::dependencies()); + }); + + $row->column(4, function (Column $column) { + $column->append(Dashboard::extensions()); + }); + }); + } else { + return $content + ->title(__('admin.menu_titles.dashboard')) + ->description('Description...'); + } + } +} diff --git a/app/Admin/Controllers/ModuleController.php b/app/Admin/Controllers/ModuleController.php new file mode 100644 index 0000000..3986f85 --- /dev/null +++ b/app/Admin/Controllers/ModuleController.php @@ -0,0 +1,105 @@ + + * @return Grid + */ + protected function grid(): Grid + { + $grid = new Grid(new Module()); + + $grid->disableBatchActions(); + $grid->disableFilter(); + $grid->disableCreateButton(); + $grid->disableActions(); + + $grid->column('name', '模块名称'); + $grid->column('alias', '别名'); + $grid->column('version', '版本'); + $grid->column('author', '作者'); + $grid->column('description', '模块简介'); + $grid->column('enabled', '状态')->bool(); + $grid->column('id', '操作')->display(function () { + if ($this->enabled) { + return sprintf('%s', route('admin.module.disable', $this->name), '禁用'); + } else { + return sprintf('%s', route('admin.module.enable', $this->name), '启用'); + } + }); + + return $grid; + } + + /** + * Notes : 禁用模块 + * + * @Date : 2021/3/11 1:13 下午 + * @Author : + * @param string $name + * @return RedirectResponse + */ + public function disable(string $name): RedirectResponse + { + try { + $module = ModuleManager::find($name); + + $module->disable(); + + $class = sprintf('\\%s\\%s\\%s', config('modules.namespace'), $module->getName(), $module->getName()); + + if (class_exists($class)) { + call_user_func([$class, 'uninstall']); + } + + admin_success('Success', $name . '模块禁用成功'); + } catch (\Exception $exception) { + admin_error('Error', $exception->getMessage()); + } + + return back(); + } + + /** + * Notes : 启用模块 + * + * @Date : 2021/3/11 1:13 下午 + * @Author : + * @param string $name + * @return RedirectResponse + */ + public function enable(string $name): RedirectResponse + { + try { + $module = ModuleManager::find($name); + + $module->enable(); + + $class = sprintf('\\%s\\%s\\%s', config('modules.namespace'), $module->getName(), $module->getName()); + + if (class_exists($class)) { + call_user_func([$class, 'install']); + } + + admin_success('Success', $name . '模块启用成功'); + } catch (\Exception $exception) { + admin_error('Error', $exception->getMessage()); + } + + return back(); + } +} diff --git a/app/Admin/Extensions/CleanCache.php b/app/Admin/Extensions/CleanCache.php new file mode 100644 index 0000000..2ec3bbb --- /dev/null +++ b/app/Admin/Extensions/CleanCache.php @@ -0,0 +1,35 @@ +response()->success('清理完成')->refresh(); + } + + public function dialog() + { + $this->confirm('确认清除缓存'); + } + + public function html(): string + { + return << + + + + +HTML; + } +} diff --git a/app/Admin/Extensions/FormQrCode.php b/app/Admin/Extensions/FormQrCode.php new file mode 100644 index 0000000..73a4c2d --- /dev/null +++ b/app/Admin/Extensions/FormQrCode.php @@ -0,0 +1,21 @@ +value = $google2fa->getQRCodeUrl( + $this->data['username'], + $this->data['name'], + $this->data['g2fa_secret'] + ); + return parent::render(); + } +} \ No newline at end of file diff --git a/app/Admin/Routes/modules.php b/app/Admin/Routes/modules.php new file mode 100644 index 0000000..92206a0 --- /dev/null +++ b/app/Admin/Routes/modules.php @@ -0,0 +1,12 @@ + 'modules', +], function (Router $router) { + $router->get('', 'ModuleController@index'); + $router->get('{name}/disable', 'ModuleController@disable')->name('module.disable'); + $router->get('{name}/enable', 'ModuleController@enable')->name('module.enable'); +}); diff --git a/app/Admin/Traits/WithUploads.php b/app/Admin/Traits/WithUploads.php new file mode 100644 index 0000000..bd6d14d --- /dev/null +++ b/app/Admin/Traits/WithUploads.php @@ -0,0 +1,94 @@ + + * @param Form $form + * @param string $filed + * @param string $label + * @return Image + */ + public function cover(Renderable $form, string $filed = 'cover', string $label = '封面图片'): Image + { + $cover = $form->image($filed, $label) + ->move('images/'.date('Y/m/d')) + ->uniqueName() + ->removable() + ->retainable(); + + $waterConfig = config('admin.image_water'); + + if (! empty($waterConfig)) { + $cover->insert(...$waterConfig); + } + + $coverThumb = config('admin.cover_thumb'); + + if (! empty($coverThumb)) { + $cover->thumbnail($coverThumb); + } + return $cover; + } + + /** + * Notes : 统一的多图上传 + * + * @Date : 2021/4/25 2:06 下午 + * @Author : + * @param Form $form + * @param string $filed + * @param string $label + * @return MultipleImage + */ + public function pictures(Renderable $form, string $filed = 'pictures', string $label = '多图轮播'): MultipleImage + { + $pictures = $form->multipleImage($filed, $label) + ->move('images/'.date('Y/m/d')) + ->uniqueName() + ->removable() + ->retainable(); + + // 多图如果开启排序的话,会报错,暂时没由解决办法 ->sortable() + $waterConfig = config('admin.image_water'); + + if (! empty($waterConfig)) { + $pictures->insert(...$waterConfig); + } + return $pictures; + } + + /** + * Notes : 统一的附件上传 + * + * @Date : 2021/4/25 3:03 下午 + * @Author : + * @param Renderable $form + * @param string $filed + * @param string $label + * @return MultipleFile + */ + public function attachments( + Renderable $form, + string $filed = 'attachments', + string $label = '内容附件' + ): MultipleFile { + return $form->multipleFile($filed, $label) + ->move('attachments/'.date('Y/m/d')) + ->uniqueName() + ->removable() + ->retainable() + ->sortable(); + } +} diff --git a/app/Admin/bootstrap.php b/app/Admin/bootstrap.php new file mode 100644 index 0000000..1061fc8 --- /dev/null +++ b/app/Admin/bootstrap.php @@ -0,0 +1,51 @@ +right(new CleanCache()); + $navbar->right(new Navbar\Fullscreen()); +}); + +Form::init(function (Form $form) { + $form->disableEditingCheck(); + $form->disableCreatingCheck(); + $form->disableViewCheck(); + + $form->tools(function (Form\Tools $tools) { + $tools->disableView(); + // $tools->disableDelete(); + // $tools->disableList(); + }); +}); + +Show::init(function (Show $show) { + $show->panel() + ->tools(function ($tools) { + // $tools->disableEdit(); + // $tools->disableList(); + $tools->disableDelete(); + });; +}); + +Grid::init(function (Grid $grid) { + $grid->disableExport(); + $grid->actions(function (Grid\Displayers\Actions $actions) { + $actions->disableView(); + }); + $grid->disableBatchActions(); + $grid->filter(function ($filter) { + $filter->disableIdFilter(); + }); + // $grid->expandFilter(); +}); + +Form::extend('qrCode', FormQrCode::class); diff --git a/app/Admin/routes.php b/app/Admin/routes.php new file mode 100644 index 0000000..8bb201f --- /dev/null +++ b/app/Admin/routes.php @@ -0,0 +1,20 @@ + config('admin.route.prefix'), + 'namespace' => config('admin.route.namespace'), + 'middleware' => config('admin.route.middleware'), + 'as' => config('admin.route.as'), +], function (Router $router) { + $router->get('/', 'HomeController@index')->name('home'); + + foreach (glob(admin_path('Routes') . '/*.php') as $routeFile) { + require $routeFile; + } +}); diff --git a/app/Api/Controllers/Controller.php b/app/Api/Controllers/Controller.php new file mode 100644 index 0000000..1f36c80 --- /dev/null +++ b/app/Api/Controllers/Controller.php @@ -0,0 +1,11 @@ +success('Json Api is ready'); + } +} diff --git a/app/Api/Resources/BaseCollection.php b/app/Api/Resources/BaseCollection.php new file mode 100644 index 0000000..ec7eb12 --- /dev/null +++ b/app/Api/Resources/BaseCollection.php @@ -0,0 +1,19 @@ + $this->currentPage(), + 'total_page' => $this->lastPage(), + 'per_page' => $this->perPage(), + 'has_more' => $this->hasMorePages(), + 'total' => $this->total(), + ]; + } +} diff --git a/app/Api/bootstrap.php b/app/Api/bootstrap.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/app/Api/bootstrap.php @@ -0,0 +1 @@ +name('home'); + +/** + * 分组的路由示例 + */ +Route::group([ +// 'as' => '', +// 'domain' => '', +// 'middleware' => '', +// 'namespace' => '', +// 'prefix' => '', +], function (Router $router) { + $router->get('/', 'IndexController@index')->name('home'); +}); + +/** + * 文件夹引入的示例 + */ +//foreach (glob(app_path('Api/Routes') . '/*.php') as $routeFile) { +// require $routeFile; +//} \ No newline at end of file diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php new file mode 100644 index 0000000..e538ff9 --- /dev/null +++ b/app/Console/Kernel.php @@ -0,0 +1,52 @@ +command('inspire')->hourly(); + $this->modules($schedule); + } + + /** + * 要执行任务的位置增加Console\Kernel类 + * 类中 runCommand(Schedule $schedule) + * 模型中的command在模型的ServiceProvider自行注册 + * + * @param Schedule $schedule + */ + protected function modules(Schedule $schedule) + { + $data = ModuleManager::toCollection(); + foreach ($data as $name => $module) { + $class = "\\Modules\\$name\\Console\\Kernel"; + if (class_exists($class)) { + $runKernel = resolve($class); + $runKernel->runCommand($schedule); + } + } + } + + /** + * Register the commands for the application. + * + * @return void + */ + protected function commands() + { + // $this->load(__DIR__.'/Commands'); + } + +} diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php new file mode 100644 index 0000000..39a19e0 --- /dev/null +++ b/app/Exceptions/Handler.php @@ -0,0 +1,44 @@ +> + */ + protected $dontReport = [ + // + ]; + + /** + * A list of the inputs that are never flashed for validation exceptions. + * + * @var array + */ + protected $dontFlash = [ + 'current_password', + 'password', + 'password_confirmation', + ]; + + /** + * Register the exception handling callbacks for the application. + * + * @return void + */ + public function register() + { + $this->reportable(function (Throwable $e) { + // + }); + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php new file mode 100644 index 0000000..0671c57 --- /dev/null +++ b/app/Http/Controllers/Controller.php @@ -0,0 +1,28 @@ + + * @param string $token + * @return array + */ + protected function respondWithToken(string $token): array + { + return [ + 'access_token' => $token, + 'token_type' => 'Bearer', + 'expires_in' => auth('api')->factory()->getTTL() * 60, + ]; + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php new file mode 100644 index 0000000..974243a --- /dev/null +++ b/app/Http/Kernel.php @@ -0,0 +1,68 @@ + + */ + protected $middleware = [ + // \App\Http\Middleware\TrustHosts::class, + \App\Http\Middleware\TrustProxies::class, + \Fruitcake\Cors\HandleCors::class, + \App\Http\Middleware\PreventRequestsDuringMaintenance::class, + \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, + \App\Http\Middleware\TrimStrings::class, + \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, + \App\Http\Middleware\SetServerId::class, + ]; + + /** + * The application's route middleware groups. + * + * @var array> + */ + protected $middlewareGroups = [ + 'web' => [ + \App\Http\Middleware\EncryptCookies::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \Illuminate\Session\Middleware\StartSession::class, + // \Illuminate\Session\Middleware\AuthenticateSession::class, + \Illuminate\View\Middleware\ShareErrorsFromSession::class, + \App\Http\Middleware\VerifyCsrfToken::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class, + ], + + 'api' => [ + // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, + 'throttle:api', + \Illuminate\Routing\Middleware\SubstituteBindings::class, + ], + ]; + + /** + * The application's route middleware. + * + * These middleware may be assigned to groups or used individually. + * + * @var array + */ + protected $routeMiddleware = [ + 'auth' => \App\Http\Middleware\Authenticate::class, + 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, + 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, + 'can' => \Illuminate\Auth\Middleware\Authorize::class, + 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, + 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, + 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, + 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + ]; +} diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php new file mode 100644 index 0000000..954e38e --- /dev/null +++ b/app/Http/Middleware/Authenticate.php @@ -0,0 +1,22 @@ +expectsJson()) { + return route('login'); + } + } +} diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php new file mode 100644 index 0000000..867695b --- /dev/null +++ b/app/Http/Middleware/EncryptCookies.php @@ -0,0 +1,17 @@ + + */ + protected $except = [ + // + ]; +} diff --git a/app/Http/Middleware/PreventRequestsDuringMaintenance.php b/app/Http/Middleware/PreventRequestsDuringMaintenance.php new file mode 100644 index 0000000..74cbd9a --- /dev/null +++ b/app/Http/Middleware/PreventRequestsDuringMaintenance.php @@ -0,0 +1,17 @@ + + */ + protected $except = [ + // + ]; +} diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php new file mode 100644 index 0000000..45bb1b7 --- /dev/null +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -0,0 +1,34 @@ +check()) { + return redirect(RouteServiceProvider::HOME); + } + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/SetServerId.php b/app/Http/Middleware/SetServerId.php new file mode 100644 index 0000000..ee67e8b --- /dev/null +++ b/app/Http/Middleware/SetServerId.php @@ -0,0 +1,29 @@ + + * @param Request $request + * @param Closure $next + * @return mixed + */ + public function handle(Request $request, Closure $next) + { + $response = $next($request); + $response->headers->add([ + 'X-Server-Id' => env('SERVER_ID'), + ]); + + return $response; + } + +} diff --git a/app/Http/Middleware/TrimStrings.php b/app/Http/Middleware/TrimStrings.php new file mode 100644 index 0000000..88cadca --- /dev/null +++ b/app/Http/Middleware/TrimStrings.php @@ -0,0 +1,19 @@ + + */ + protected $except = [ + 'current_password', + 'password', + 'password_confirmation', + ]; +} diff --git a/app/Http/Middleware/TrustHosts.php b/app/Http/Middleware/TrustHosts.php new file mode 100644 index 0000000..c9c58bd --- /dev/null +++ b/app/Http/Middleware/TrustHosts.php @@ -0,0 +1,20 @@ + + */ + public function hosts(): array + { + return [ + $this->allSubdomainsOfApplicationUrl(), + ]; + } +} diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php new file mode 100644 index 0000000..3391630 --- /dev/null +++ b/app/Http/Middleware/TrustProxies.php @@ -0,0 +1,28 @@ +|string|null + */ + protected $proxies; + + /** + * The headers that should be used to detect proxies. + * + * @var int + */ + protected $headers = + Request::HEADER_X_FORWARDED_FOR | + Request::HEADER_X_FORWARDED_HOST | + Request::HEADER_X_FORWARDED_PORT | + Request::HEADER_X_FORWARDED_PROTO | + Request::HEADER_X_FORWARDED_AWS_ELB; +} diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php new file mode 100644 index 0000000..9e86521 --- /dev/null +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -0,0 +1,17 @@ + + */ + protected $except = [ + // + ]; +} diff --git a/app/Models/Google2FA.php b/app/Models/Google2FA.php new file mode 100644 index 0000000..062db7c --- /dev/null +++ b/app/Models/Google2FA.php @@ -0,0 +1,45 @@ + 'boolean', + ]; + + public function subscriber(): MorphTo + { + return $this->morphTo(); + } + + /** + * Notes : 更新密钥 + * + * @Date : 2022/11/30 20:51 + * @Author : + */ + public function upgrade() + { + $google2fa = app('pragmarx.google2fa'); + + $this->secret = $google2fa->generateSecretKey(32); + $this->save(); + } + + /** + * Notes : 验证 + * + * @Date : 2022/11/30 20:49 + * @Author : + * @param string $value + * @return bool + */ + public function verify(string $value): bool + { + $google2fa = app('pragmarx.google2fa'); + return $google2fa->verifyGoogle2FA($this->secret, $value); + } +} diff --git a/app/Models/Model.php b/app/Models/Model.php new file mode 100644 index 0000000..052b614 --- /dev/null +++ b/app/Models/Model.php @@ -0,0 +1,27 @@ + + * @return LengthAwarePaginator + */ + public function paginate(): LengthAwarePaginator + { + $perPage = Request::get('per_page', 20); + $page = Request::get('page', 1); + + $data = ModuleManager::toCollection(); + + $chunk = $data->forPage($page, $perPage); + + $modules = $chunk->map(function ($module) { + return [ + 'id' => $module->getName(), + 'name' => $module->getName(), + 'alias' => $module->getAlias(), + 'description' => $module->getDescription(), + 'priority' => $module->getPriority(), + 'keywords' => $module->get('keywords'), + 'requires' => $module->getRequires(), + 'enabled' => $module->isEnabled(), + 'version' => $module->get('version'), + 'author' => $module->get('author'), + ]; + }); + + $modules = static::hydrate($modules->toArray()); + $paginator = new LengthAwarePaginator($modules, ModuleManager::count(), $perPage); + $paginator->setPath(url()->current()); + + return $paginator; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php new file mode 100644 index 0000000..ee8ca5b --- /dev/null +++ b/app/Providers/AppServiceProvider.php @@ -0,0 +1,28 @@ + + */ + protected $policies = [ + // 'App\Models\Model' => 'App\Policies\ModelPolicy', + ]; + + /** + * Register any authentication / authorization services. + * + * @return void + */ + public function boot() + { + $this->registerPolicies(); + + // + } +} diff --git a/app/Providers/BroadcastServiceProvider.php b/app/Providers/BroadcastServiceProvider.php new file mode 100644 index 0000000..395c518 --- /dev/null +++ b/app/Providers/BroadcastServiceProvider.php @@ -0,0 +1,21 @@ +> + */ + protected $listen = [ + Registered::class => [ + SendEmailVerificationNotification::class, + ], + ]; + + /** + * Register any events for your application. + * + * @return void + */ + public function boot() + { + // + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..8e5b91c --- /dev/null +++ b/app/Providers/RouteServiceProvider.php @@ -0,0 +1,63 @@ +configureRateLimiting(); + + $this->routes(function () { + Route::prefix('api') + ->middleware('api') + ->namespace($this->namespace) + ->group(base_path('routes/api.php')); + + Route::middleware('web') + ->namespace($this->namespace) + ->group(base_path('routes/web.php')); + }); + } + + /** + * Configure the rate limiters for the application. + * + * @return void + */ + protected function configureRateLimiting() + { + RateLimiter::for('api', function (Request $request) { + return Limit::perMinute(env('RATE_LIMITER', 60))->by(optional($request->user())->id ?: $request->ip()); + }); + } +} diff --git a/app/Rules/AdminG2FARule.php b/app/Rules/AdminG2FARule.php new file mode 100644 index 0000000..a0d2025 --- /dev/null +++ b/app/Rules/AdminG2FARule.php @@ -0,0 +1,43 @@ +formData = $data; + } + + public function passes($attribute, $value): bool + { + $user = Administrator::where('username', $this->formData['username'])->first(); + + if ($user && $user->use_g2fa) { + if (blank($value)) { + $this->errorMessage = '身份校验码 必须填写'; + return false; + } + if (strlen($value) != 6) { + $this->errorMessage = '身份校验码 必须是6位'; + return false; + } + + $google2fa = app('pragmarx.google2fa'); + return $google2fa->verifyGoogle2FA($user->g2fa_secret, $value); + } + return true; + } + + public function message(): string + { + return $this->errorMessage; + } +} \ No newline at end of file diff --git a/app/Rules/IdCardRule.php b/app/Rules/IdCardRule.php new file mode 100644 index 0000000..ac6f04e --- /dev/null +++ b/app/Rules/IdCardRule.php @@ -0,0 +1,69 @@ + + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function passes($attribute, $value): bool + { + if (strlen($value) !== 18) { + $this->errorMessage = '请输入18位身份证号码'; + return false; + } + + return $this->isIdCard($value); + } + + private function isIdCard($id): bool + { + $id = strtoupper($id); + $regx = "/(^\d{17}([0-9]|X)$)/"; + $arr_split = []; + if (! preg_match($regx, $id)) { + return false; + } + + $regx = "/^(\d{6})+(\d{4})+(\d{2})+(\d{2})+(\d{3})([0-9]|X)$/"; + @preg_match($regx, $id, $arr_split); + $dtm_birth = $arr_split[2].'/'.$arr_split[3].'/'.$arr_split[4]; + if (! strtotime($dtm_birth)) { + return false; + } + + $arr_int = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]; + $arr_ch = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']; + $sign = 0; + for ($i = 0; $i < 17; $i++) { + $b = (int) $id[$i]; + $w = $arr_int[$i]; + $sign += $b * $w; + } + $n = $sign % 11; + $val_num = $arr_ch[$n]; + + return ! ($val_num !== substr($id, 17, 1)); + } + + /** + * 获取校验错误信息 + * + * @return string + */ + public function message(): string + { + return $this->errorMessage; + } +} diff --git a/app/Scopes/OrderByIdDescScope.php b/app/Scopes/OrderByIdDescScope.php new file mode 100644 index 0000000..54f16cd --- /dev/null +++ b/app/Scopes/OrderByIdDescScope.php @@ -0,0 +1,23 @@ + + * @param Builder $builder + * @param Model $model + */ + public function apply(Builder $builder, Model $model): void + { + $builder->orderByDesc('id'); + } +} diff --git a/app/Scopes/OrderByOrderAscScope.php b/app/Scopes/OrderByOrderAscScope.php new file mode 100644 index 0000000..2836331 --- /dev/null +++ b/app/Scopes/OrderByOrderAscScope.php @@ -0,0 +1,23 @@ + + * @param Builder $builder + * @param Model $model + */ + public function apply(Builder $builder, Model $model): void + { + $builder->orderBy('order')->orderByDesc('id'); + } +} diff --git a/app/Traits/HasClicks.php b/app/Traits/HasClicks.php new file mode 100644 index 0000000..09598b2 --- /dev/null +++ b/app/Traits/HasClicks.php @@ -0,0 +1,87 @@ + + * @return string + */ + private function getClicksField(): string + { + return $this->clicks_filed ?? 'clicks'; + } + + /** + * Notes : 获取缓存前缀 + * + * @Date : 2021/3/16 5:52 下午 + * @Author : + * @return string + */ + private function getClickCachePrefix(): string + { + return $this->cachePrefix ?? class_basename(__CLASS__); + } + + /** + * Notes : 生成一个缓存KEY + * + * @Date : 2021/3/16 5:52 下午 + * @Author : + * @param string|null $appends + * @return string + */ + private function getCacheKey(string $appends = null): string + { + return $this->getClickCachePrefix().':'.$this->getKey().':'.$appends; + } + + /** + * Notes : 增加点击量 + * + * @Date : 2021/3/17 9:20 上午 + * @Author : + * @param int $step + */ + public function incrementClicks(int $step = 1): void + { + Cache::increment($this->getCacheKey('clicks'), $step); + + if (rand(1, $this->saveRate) === 1) { + $this->update([$this->getClicksField() => $this->clicks]); + } + } + + /** + * Notes : 获取缓存的浏览次数 + * + * @Date : 2021/3/16 5:52 下午 + * @Author : + * @return int + */ + public function getClicksAttribute(): int + { + $clicks = Cache::get($this->getCacheKey('clicks')); + + if (is_null($clicks)) { + return Cache::rememberForever($this->getCacheKey('clicks'), function () { + return $this->getAttributes()[$this->getClicksField()]; + }); + } else { + return $clicks; + } + } +} diff --git a/app/Traits/HasCovers.php b/app/Traits/HasCovers.php new file mode 100644 index 0000000..542852c --- /dev/null +++ b/app/Traits/HasCovers.php @@ -0,0 +1,86 @@ + + * @return string + */ + public function getCoverField(): string + { + return $this->cover_field ?? 'cover'; + } + + /** + * Notes : 获取图片字段(多图) + * + * @Date : 2021/3/16 4:35 下午 + * @Author : + * @return string + */ + public function getPicturesField(): string + { + return $this->pictures_field ?? 'pictures'; + } + + /** + * Notes : 解析单图地址 + * + * @Date : 2021/3/16 4:54 下午 + * @Author : + * @return string + */ + public function getCoverUrlAttribute(): string + { + $cover = $this->getAttribute($this->getCoverField()); + + return $this->parseImageUrl($cover); + } + + /** + * Notes : 解析多图地址 + * + * @Date : 2021/3/16 4:54 下午 + * @Author : + * @return array + */ + public function getPicturesUrlAttribute(): array + { + $pictures = $this->getAttribute($this->getPicturesField()); + + if (empty($pictures)) { + return []; + } + + return collect($pictures)->map(function ($picture) { + return $this->parseImageUrl($picture); + })->toArray(); + } + + /** + * Notes : 解析图片文件的实际展示地址 + * + * @Date : 2021/3/16 4:53 下午 + * @Author : + * @param string|null $image + * @return string + */ + protected function parseImageUrl(?string $image): string + { + if (empty($image)) { + return ''; + } elseif (Str::startsWith($image, 'http')) { + return $image; + } else { + return Storage::url($image); + } + } +} diff --git a/app/Traits/HasStatus.php b/app/Traits/HasStatus.php new file mode 100644 index 0000000..dbd5ce4 --- /dev/null +++ b/app/Traits/HasStatus.php @@ -0,0 +1,91 @@ + + * @return string + */ + protected function getStatusField(): string + { + return $this->status_field ?? 'status'; + } + + /** + * Notes : 获取各状态的名称 + * + * @Date : 2021/5/27 11:50 上午 + * @Author : + * @return string[] + */ + protected function getStatusMap(): array + { + return isset($this->status_map) && ! empty($this->status_map) ? $this->status_map : [ + 0 => '待审核', + 1 => '正常', + 2 => '驳回', + 3 => '关闭', + ]; + } + + /** + * 正常显示的数据 + * + * @Author: + * @Date :2021-04-09 + * @param Builder $query + * @return Builder + */ + public function scopeShown(Builder $query): Builder + { + return $query->where($this->getStatusField(), 1); + } + + /** + * 不显示的数据 + * + * @Author : + * @Date :2021-04-09 + * @param Builder $query + * @return Builder + */ + public function scopeHidden(Builder $query): Builder + { + return $query->where($this->getStatusField(), 0); + } + + /** + * Notes : 状态查询 + * + * @Date : 2021/6/28 10:25 上午 + * @Author : + * @param Builder $query + * @param int $status + * @return Builder + */ + public function scopeOfStatus(Builder $query, int $status): Builder + { + return $query->where($this->getStatusField(), $status); + } + + /** + * Notes : 获取状态的文本信息 + * + * @Date : 2021/4/25 2:10 下午 + * @Author : + * @return string + */ + public function getStatusTextAttribute(): string + { + $map = $this->getStatusMap(); + + return $map[$this->{$this->getStatusField()}] ?? '未知'; + } +} diff --git a/app/Traits/Macroable.php b/app/Traits/Macroable.php new file mode 100644 index 0000000..df9637f --- /dev/null +++ b/app/Traits/Macroable.php @@ -0,0 +1,48 @@ +getRelationshipFromMethod($key); + } + + return $relation; + } + + /** + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return $this->macroCall($method, $parameters); + } + + return parent::__call($method, $parameters); + } + + /** + * @param string $method + * @param array $parameters + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + return parent::__callStatic($method, $parameters); + } +} diff --git a/app/Traits/OrderByIdDesc.php b/app/Traits/OrderByIdDesc.php new file mode 100644 index 0000000..25fe399 --- /dev/null +++ b/app/Traits/OrderByIdDesc.php @@ -0,0 +1,21 @@ + + * @Date : 2020/1/19 1:42 下午 + */ + public static function bootOrderByIdDesc(): void + { + static::addGlobalScope(new OrderByIdDescScope); + } +} diff --git a/app/Traits/OrderByOrderAsc.php b/app/Traits/OrderByOrderAsc.php new file mode 100644 index 0000000..8ffd668 --- /dev/null +++ b/app/Traits/OrderByOrderAsc.php @@ -0,0 +1,22 @@ + + * @Date : 2020/1/19 1:42 下午 + */ + public static function bootOrderByOrderAsc(): void + { + static::addGlobalScope(new OrderByOrderAscScope()); + } +} diff --git a/app/Traits/WithGoogle2FA.php b/app/Traits/WithGoogle2FA.php new file mode 100644 index 0000000..3c01f85 --- /dev/null +++ b/app/Traits/WithGoogle2FA.php @@ -0,0 +1,24 @@ +google2fa()->create([ + 'secret' => $google2fa->generateSecretKey(32), + ]); + }); + } + + public function google2fa(): MorphOne + { + return $this->morphOne(Google2FA::class, 'subscriber'); + } +} \ No newline at end of file diff --git a/app/Traits/WithPosition.php b/app/Traits/WithPosition.php new file mode 100644 index 0000000..cd9e6c1 --- /dev/null +++ b/app/Traits/WithPosition.php @@ -0,0 +1,67 @@ + + */ + protected function getPositionMap(): array + { + return $this->position_map ?? []; + } + + /** + * Notes: 定位查询作用域 + * + * @Author: Mr.wang + * @Date : 2021/5/11 10:48 + * @param Builder $query + * @param int $pos + * @return Builder + */ + public function scopeOfPosition(Builder $query, int $pos): Builder + { + return $query->whereRaw('position & '.$pos); + } + + /** + * Notes: 设置定位 + * + * @Author: Mr.wang + * @Date : 2020/5/11 10:48 + * @param array $value + */ + protected function setPositionAttribute(array $value): void + { + if (! blank($value)) { + $this->attributes['position'] = array_sum($value); + } + } + + /** + * Notes: 获取定位数据 + * + * @Author: Mr.wang + * @Date : 2020/5/11 10:48 + * @param int $value + * @return array + */ + protected function getPositionAttribute(int $value): array + { + $position = []; + foreach ($this->getPositionMap() as $k => $v) { + if ($k & $value) { + $position[] = $k; + } + } + + return $position; + } +} diff --git a/artisan b/artisan new file mode 100644 index 0000000..5c23e2e --- /dev/null +++ b/artisan @@ -0,0 +1,53 @@ +#!/usr/bin/env php +make(Illuminate\Contracts\Console\Kernel::class); + +$status = $kernel->handle( + $input = new Symfony\Component\Console\Input\ArgvInput, + new Symfony\Component\Console\Output\ConsoleOutput +); + +/* +|-------------------------------------------------------------------------- +| Shutdown The Application +|-------------------------------------------------------------------------- +| +| Once Artisan has finished running, we will fire off the shutdown events +| so that any final work may be done by the application before we shut +| down the process. This is the last thing to happen to the request. +| +*/ + +$kernel->terminate($input, $status); + +exit($status); diff --git a/bootstrap/app.php b/bootstrap/app.php new file mode 100644 index 0000000..037e17d --- /dev/null +++ b/bootstrap/app.php @@ -0,0 +1,55 @@ +singleton( + Illuminate\Contracts\Http\Kernel::class, + App\Http\Kernel::class +); + +$app->singleton( + Illuminate\Contracts\Console\Kernel::class, + App\Console\Kernel::class +); + +$app->singleton( + Illuminate\Contracts\Debug\ExceptionHandler::class, + App\Exceptions\Handler::class +); + +/* +|-------------------------------------------------------------------------- +| Return The Application +|-------------------------------------------------------------------------- +| +| This script returns the application instance. The instance is given to +| the calling script so we can separate the building of the instances +| from the actual running of the application and sending responses. +| +*/ + +return $app; diff --git a/bootstrap/cache/.gitignore b/bootstrap/cache/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/bootstrap/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..402eb18 --- /dev/null +++ b/composer.json @@ -0,0 +1,75 @@ +{ + "name": "uztech/laravel", + "type": "project", + "description": "The Laravel Framework.", + "keywords": [ + "framework", + "laravel" + ], + "license": "MIT", + "require": { + "php": "^7.4|^8.0", + "ext-json": "*", + "encore/laravel-admin": "^1.8.16", + "fideloper/proxy": "^4.4.1", + "fruitcake/laravel-cors": "^2.0.4", + "genealabs/laravel-model-caching": "^0.11.3", + "guzzlehttp/guzzle": "^7.4.0", + "jasonc/api": "^5.0.4", + "laravel/framework": "^8.71.0", + "laravel/sanctum": "^2.12.2", + "nosun/ueditor": "^3.0.2", + "nwidart/laravel-modules": "^8.2.0", + "pragmarx/google2fa-laravel": "^2.0", + "simplesoftwareio/simple-qrcode": "^4.2" + }, + "require-dev": { + "facade/ignition": "^2.16.0", + "fakerphp/faker": "^1.16.0", + "laravel/sail": "^1.12.4", + "mockery/mockery": "^1.4.4", + "nunomaduro/collision": "^5.10.0", + "phpunit/phpunit": "^9.5.10", + "wulfheart/pretty_routes": "^0.3.0" + }, + "config": { + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true + }, + "extra": { + "laravel": { + "dont-discover": [] + } + }, + "autoload": { + "psr-4": { + "App\\": "app/", + "Modules\\": "modules/", + "Database\\Factories\\": "database/factories/", + "Database\\Seeders\\": "database/seeders/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "scripts": { + "post-autoload-dump": [ + "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", + "@php artisan package:discover --ansi" + ], + "post-update-cmd": [ + "@php artisan vendor:publish --tag=laravel-assets --ansi --force" + ], + "post-root-package-install": [ + "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" + ], + "post-create-project-cmd": [ + "@php artisan key:generate --ansi" + ] + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..d6b2d73 --- /dev/null +++ b/composer.lock @@ -0,0 +1,10220 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "8488287b99a0b6989664702075a20d61", + "packages": [ + { + "name": "asm89/stack-cors", + "version": "v2.0.3", + "source": { + "type": "git", + "url": "https://github.com/asm89/stack-cors.git", + "reference": "9cb795bf30988e8c96dd3c40623c48a877bc6714" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/asm89/stack-cors/zipball/9cb795bf30988e8c96dd3c40623c48a877bc6714", + "reference": "9cb795bf30988e8c96dd3c40623c48a877bc6714", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.0|^8.0", + "symfony/http-foundation": "~2.7|~3.0|~4.0|~5.0", + "symfony/http-kernel": "~2.7|~3.0|~4.0|~5.0" + }, + "require-dev": { + "phpunit/phpunit": "^6|^7|^8|^9", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Asm89\\Stack\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alexander", + "email": "iam.asm89@gmail.com" + } + ], + "description": "Cross-origin resource sharing library and stack middleware", + "homepage": "https://github.com/asm89/stack-cors", + "keywords": [ + "cors", + "stack" + ], + "support": { + "issues": "https://github.com/asm89/stack-cors/issues", + "source": "https://github.com/asm89/stack-cors/tree/v2.0.3" + }, + "time": "2021-03-11T06:42:03+00:00" + }, + { + "name": "bacon/bacon-qr-code", + "version": "2.0.7", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "d70c840f68657ce49094b8d91f9ee0cc07fbf66c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/d70c840f68657ce49094b8d91f9ee0cc07fbf66c", + "reference": "d70c840f68657ce49094b8d91f9ee0cc07fbf66c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "dasprid/enum": "^1.0.3", + "ext-iconv": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phly/keep-a-changelog": "^2.1", + "phpunit/phpunit": "^7 | ^8 | ^9", + "spatie/phpunit-snapshot-assertions": "^4.2.9", + "squizlabs/php_codesniffer": "^3.4" + }, + "suggest": { + "ext-imagick": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-4": { + "BaconQrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "support": { + "issues": "https://github.com/Bacon/BaconQrCode/issues", + "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.7" + }, + "time": "2022-03-14T02:02:36+00:00" + }, + { + "name": "brick/math", + "version": "0.9.3", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/ca57d18f028f84f777b2168cd1911b0dee2343ae", + "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0", + "vimeo/psalm": "4.9.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "brick", + "math" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.9.3" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/brick/math", + "type": "tidelift" + } + ], + "time": "2021-08-15T20:50:18+00:00" + }, + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99.4", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "b174585d1fe49ceed21928a945138948cb394600" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b174585d1fe49ceed21928a945138948cb394600", + "reference": "b174585d1fe49ceed21928a945138948cb394600", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-09-13T08:41:34+00:00" + }, + { + "name": "dasprid/enum", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/5abf82f213618696dda8e3bf6f64dd042d8542b2", + "reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require-dev": { + "phpunit/phpunit": "^7 | ^8 | ^9", + "squizlabs/php_codesniffer": "^3.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "DASPRiD\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "PHP 7.1 enum implementation", + "keywords": [ + "enum", + "map" + ], + "support": { + "issues": "https://github.com/DASPRiD/Enum/issues", + "source": "https://github.com/DASPRiD/Enum/tree/1.0.3" + }, + "time": "2020-10-02T16:03:48+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^3.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" + }, + "time": "2021-08-13T13:06:58+00:00" + }, + { + "name": "doctrine/cache", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce", + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "~7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "cache/integration-tests": "dev-master", + "doctrine/coding-standard": "^8.0", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "predis/predis": "~1.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev", + "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", + "keywords": [ + "abstraction", + "apcu", + "cache", + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" + ], + "support": { + "issues": "https://github.com/doctrine/cache/issues", + "source": "https://github.com/doctrine/cache/tree/2.1.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "time": "2021-07-17T14:49:29+00:00" + }, + { + "name": "doctrine/dbal", + "version": "3.1.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "821b4f01a36ce63ed36c090ea74767b72db367e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/821b4f01a36ce63ed36c090ea74767b72db367e9", + "reference": "821b4f01a36ce63ed36c090ea74767b72db367e9", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "composer/package-versions-deprecated": "^1.11.99", + "doctrine/cache": "^1.0|^2.0", + "doctrine/deprecations": "^0.5.3", + "doctrine/event-manager": "^1.0", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "9.0.0", + "jetbrains/phpstorm-stubs": "2021.1", + "phpstan/phpstan": "1.1.1", + "phpstan/phpstan-strict-rules": "^1", + "phpunit/phpunit": "9.5.10", + "psalm/plugin-phpunit": "0.16.1", + "squizlabs/php_codesniffer": "3.6.1", + "symfony/cache": "^5.2|^6.0", + "symfony/console": "^2.0.5|^3.0|^4.0|^5.0|^6.0", + "vimeo/psalm": "4.12.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", + "keywords": [ + "abstraction", + "database", + "db2", + "dbal", + "mariadb", + "mssql", + "mysql", + "oci8", + "oracle", + "pdo", + "pgsql", + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlite", + "sqlserver", + "sqlsrv" + ], + "support": { + "issues": "https://github.com/doctrine/dbal/issues", + "source": "https://github.com/doctrine/dbal/tree/3.1.4" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "type": "tidelift" + } + ], + "time": "2021-11-15T16:44:33+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v0.5.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0|^7.0|^8.0", + "phpunit/phpunit": "^7.0|^8.0|^9.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" + }, + "time": "2021-03-21T12:59:47+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": "<2.9@dev" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/1.1.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2020-05-29T18:28:51+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "vimeo/psalm": "^4.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:16:43+00:00" + }, + { + "name": "doctrine/lexer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2020-05-25T17:44:05+00:00" + }, + { + "name": "dragonmantank/cron-expression", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/dragonmantank/cron-expression.git", + "reference": "7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c", + "reference": "7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2|^8.0", + "webmozart/assert": "^1.7.0" + }, + "replace": { + "mtdowling/cron-expression": "^1.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-webmozart-assert": "^0.12.7", + "phpunit/phpunit": "^7.0|^8.0|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Tankersley", + "email": "chris@ctankersley.com", + "homepage": "https://github.com/dragonmantank" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "support": { + "issues": "https://github.com/dragonmantank/cron-expression/issues", + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.1.0" + }, + "funding": [ + { + "url": "https://github.com/dragonmantank", + "type": "github" + } + ], + "time": "2020-11-24T19:55:57+00:00" + }, + { + "name": "egulias/email-validator", + "version": "2.1.25", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/0dbf5d78455d4d6a41d186da50adc1122ec066f4", + "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "doctrine/lexer": "^1.0.1", + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.10" + }, + "require-dev": { + "dominicsayers/isemail": "^3.0.7", + "phpunit/phpunit": "^4.8.36|^7.5.15", + "satooshi/php-coveralls": "^1.0.1" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/2.1.25" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2020-12-29T14:50:06+00:00" + }, + { + "name": "encore/laravel-admin", + "version": "v1.8.16", + "source": { + "type": "git", + "url": "https://github.com/z-song/laravel-admin.git", + "reference": "0f7bafbd2acd4cdb2118c2a6105a6b2aa9dacb13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/z-song/laravel-admin/zipball/0f7bafbd2acd4cdb2118c2a6105a6b2aa9dacb13", + "reference": "0f7bafbd2acd4cdb2118c2a6105a6b2aa9dacb13", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "doctrine/dbal": "2.*|3.*", + "laravel/framework": ">=5.5", + "php": ">=7.0.0", + "symfony/dom-crawler": "~3.1|~4.0|~5.0" + }, + "require-dev": { + "fzaninotto/faker": "~1.4", + "intervention/image": "~2.3", + "laravel/browser-kit-testing": "^6.0", + "laravel/laravel": ">=5.5", + "spatie/phpunit-watcher": "^1.22.0" + }, + "suggest": { + "intervention/image": "Required to handling and manipulation upload images (~2.3).", + "spatie/eloquent-sortable": "Required to built orderable gird." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Encore\\Admin\\AdminServiceProvider" + ], + "aliases": { + "Admin": "Encore\\Admin\\Facades\\Admin" + } + } + }, + "autoload": { + "psr-4": { + "Encore\\Admin\\": "src/" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "zsong", + "email": "zosong@126.com" + } + ], + "description": "laravel admin", + "homepage": "https://github.com/z-song/laravel-admin", + "keywords": [ + "admin", + "form", + "grid", + "laravel" + ], + "support": { + "issues": "https://github.com/z-song/laravel-admin/issues", + "source": "https://github.com/z-song/laravel-admin/tree/v1.8.16" + }, + "time": "2021-11-02T07:52:31+00:00" + }, + { + "name": "fideloper/proxy", + "version": "4.4.1", + "source": { + "type": "git", + "url": "https://github.com/fideloper/TrustedProxy.git", + "reference": "c073b2bd04d1c90e04dc1b787662b558dd65ade0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/c073b2bd04d1c90e04dc1b787662b558dd65ade0", + "reference": "c073b2bd04d1c90e04dc1b787662b558dd65ade0", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "illuminate/contracts": "^5.0|^6.0|^7.0|^8.0|^9.0", + "php": ">=5.4.0" + }, + "require-dev": { + "illuminate/http": "^5.0|^6.0|^7.0|^8.0|^9.0", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Fideloper\\Proxy\\TrustedProxyServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Fideloper\\Proxy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Fidao", + "email": "fideloper@gmail.com" + } + ], + "description": "Set trusted proxies for Laravel", + "keywords": [ + "load balancing", + "proxy", + "trusted proxy" + ], + "support": { + "issues": "https://github.com/fideloper/TrustedProxy/issues", + "source": "https://github.com/fideloper/TrustedProxy/tree/4.4.1" + }, + "time": "2020-10-22T13:48:01+00:00" + }, + { + "name": "fruitcake/laravel-cors", + "version": "v2.0.4", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/laravel-cors.git", + "reference": "a8ccedc7ca95189ead0e407c43b530dc17791d6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/a8ccedc7ca95189ead0e407c43b530dc17791d6a", + "reference": "a8ccedc7ca95189ead0e407c43b530dc17791d6a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "asm89/stack-cors": "^2.0.1", + "illuminate/contracts": "^6|^7|^8|^9", + "illuminate/support": "^6|^7|^8|^9", + "php": ">=7.2", + "symfony/http-foundation": "^4|^5", + "symfony/http-kernel": "^4.3.4|^5" + }, + "require-dev": { + "laravel/framework": "^6|^7|^8", + "orchestra/testbench-dusk": "^4|^5|^6|^7", + "phpunit/phpunit": "^6|^7|^8|^9", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + }, + "laravel": { + "providers": [ + "Fruitcake\\Cors\\CorsServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Fruitcake\\Cors\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Laravel application", + "keywords": [ + "api", + "cors", + "crossdomain", + "laravel" + ], + "support": { + "issues": "https://github.com/fruitcake/laravel-cors/issues", + "source": "https://github.com/fruitcake/laravel-cors/tree/v2.0.4" + }, + "funding": [ + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2021-04-26T11:24:25+00:00" + }, + { + "name": "genealabs/laravel-model-caching", + "version": "0.11.7", + "source": { + "type": "git", + "url": "https://github.com/GeneaLabs/laravel-model-caching.git", + "reference": "ff303d782cb63a9c44bcd76a28bde73a67040c7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GeneaLabs/laravel-model-caching/zipball/ff303d782cb63a9c44bcd76a28bde73a67040c7e", + "reference": "ff303d782cb63a9c44bcd76a28bde73a67040c7e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "genealabs/laravel-pivot-events": "^8.0", + "illuminate/cache": "^8.0", + "illuminate/config": "^8.0", + "illuminate/console": "^8.0", + "illuminate/container": "^8.0", + "illuminate/database": "^8.0", + "illuminate/http": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "require-dev": { + "doctrine/dbal": "^2.10", + "fakerphp/faker": "^1.11", + "laravel/legacy-factories": "^1.0", + "laravel/nova": "^3.9", + "orchestra/testbench": "^6.0", + "orchestra/testbench-browser-kit": "^6.0", + "php-coveralls/php-coveralls": "^2.2", + "phpmd/phpmd": "^2.7", + "phpunit/phpunit": "^9.0", + "slevomat/coding-standard": "^6.4", + "squizlabs/php_codesniffer": "^3.4", + "symfony/thanks": "^1.2" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "GeneaLabs\\LaravelModelCaching\\Providers\\Service" + ] + } + }, + "autoload": { + "psr-4": { + "GeneaLabs\\LaravelModelCaching\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike Bronner", + "email": "hello@genealabs.com" + } + ], + "description": "Automatic caching for Eloquent models.", + "support": { + "issues": "https://github.com/GeneaLabs/laravel-model-caching/issues", + "source": "https://github.com/GeneaLabs/laravel-model-caching/tree/0.11.7" + }, + "time": "2022-02-03T13:21:28+00:00" + }, + { + "name": "genealabs/laravel-pivot-events", + "version": "8.0", + "source": { + "type": "git", + "url": "https://github.com/GeneaLabs/laravel-pivot-events.git", + "reference": "aafc9d7f6a0b31e0d58bd2b31e38253fbd27c2a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GeneaLabs/laravel-pivot-events/zipball/aafc9d7f6a0b31e0d58bd2b31e38253fbd27c2a9", + "reference": "aafc9d7f6a0b31e0d58bd2b31e38253fbd27c2a9", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "illuminate/database": "^8.0", + "illuminate/support": "^8.0" + }, + "require-dev": { + "orchestra/testbench": "^6.0", + "symfony/thanks": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "GeneaLabs\\LaravelPivotEvents\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike Bronner", + "email": "hello@genealabs.com", + "homepage": "https://genealabs.com", + "role": "Developer" + } + ], + "description": "This package introduces new eloquent events for sync(), attach(), detach() or updateExistingPivot() methods on BelongsToMany relation.", + "homepage": "https://github.com/fico7489/laravel-pivot", + "keywords": [ + "eloquent events", + "eloquent extra events", + "laravel BelongsToMany events", + "laravel pivot events", + "laravel sync events" + ], + "support": { + "issues": "https://github.com/fico7489/laravel-pivot/issues", + "source": "https://github.com/fico7489/laravel-pivot" + }, + "time": "2020-09-08T14:39:12+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "296c015dc30ec4322168c5ad3ee5cc11dae827ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/296c015dc30ec4322168c5ad3ee5cc11dae827ac", + "reference": "296c015dc30ec4322168c5ad3ee5cc11dae827ac", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.0 || ^8.0", + "phpoption/phpoption": "^1.8" + }, + "require-dev": { + "phpunit/phpunit": "^6.5.14 || ^7.5.20 || ^8.5.19 || ^9.5.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.0.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2021-10-17T19:48:54+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.4.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "868b3571a039f0ebc11ac8f344f4080babe2cb94" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/868b3571a039f0ebc11ac8f344f4080babe2cb94", + "reference": "868b3571a039f0ebc11ac8f344f4080babe2cb94", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.8.3 || ^2.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.4.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2021-10-18T09:52:00+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.5.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:56:57+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/089edd38f5b8abba6cb01567c2a8aaa47cec4c72", + "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.1.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2021-10-06T17:43:30+00:00" + }, + { + "name": "jasonc/api", + "version": "5.0.4", + "source": { + "type": "git", + "url": "https://github.com/cjango/laravel-api.git", + "reference": "3025a88c9ef0276426d7b56051eff697adadc48f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cjango/laravel-api/zipball/3025a88c9ef0276426d7b56051eff697adadc48f", + "reference": "3025a88c9ef0276426d7b56051eff697adadc48f", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "laravel/framework": "^8.56", + "laravel/sanctum": "^2.11", + "php": ">=7.4.0", + "yangjisen/laravel-cache-provider": "^3.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Jason\\Api\\ApiServiceProvider" + ], + "aliases": { + "Api": "Jason\\Api" + } + } + }, + "autoload": { + "psr-4": { + "Jason\\Api\\": "src/" + }, + "files": [] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "C.Jason", + "email": "chenjxlg@163.com" + } + ], + "support": { + "issues": "https://github.com/cjango/laravel-api/issues", + "source": "https://github.com/cjango/laravel-api/tree/5.0.4" + }, + "time": "2021-08-27T01:41:18+00:00" + }, + { + "name": "laravel/framework", + "version": "v8.71.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "d822426d75ecf8e8ccd3ccd404db7202d1a9875e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/d822426d75ecf8e8ccd3ccd404db7202d1a9875e", + "reference": "d822426d75ecf8e8ccd3ccd404db7202d1a9875e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "doctrine/inflector": "^1.4|^2.0", + "dragonmantank/cron-expression": "^3.0.2", + "egulias/email-validator": "^2.1.10", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "laravel/serializable-closure": "^1.0", + "league/commonmark": "^1.3|^2.0.2", + "league/flysystem": "^1.1", + "monolog/monolog": "^2.0", + "nesbot/carbon": "^2.53.1", + "opis/closure": "^3.6", + "php": "^7.3|^8.0", + "psr/container": "^1.0", + "psr/log": "^1.0 || ^2.0", + "psr/simple-cache": "^1.0", + "ramsey/uuid": "^4.2.2", + "swiftmailer/swiftmailer": "^6.3", + "symfony/console": "^5.1.4", + "symfony/error-handler": "^5.1.4", + "symfony/finder": "^5.1.4", + "symfony/http-foundation": "^5.1.4", + "symfony/http-kernel": "^5.1.4", + "symfony/mime": "^5.1.4", + "symfony/process": "^5.1.4", + "symfony/routing": "^5.1.4", + "symfony/var-dumper": "^5.1.4", + "tijsverkoyen/css-to-inline-styles": "^2.2.2", + "vlucas/phpdotenv": "^5.2", + "voku/portable-ascii": "^1.4.8" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "provide": { + "psr/container-implementation": "1.0", + "psr/simple-cache-implementation": "1.0" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/collections": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/log": "self.version", + "illuminate/macroable": "self.version", + "illuminate/mail": "self.version", + "illuminate/notifications": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/testing": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.198.1", + "doctrine/dbal": "^2.13.3|^3.1.4", + "filp/whoops": "^2.14.3", + "guzzlehttp/guzzle": "^6.5.5|^7.0.1", + "league/flysystem-cached-adapter": "^1.0", + "mockery/mockery": "^1.4.4", + "orchestra/testbench-core": "^6.27", + "pda/pheanstalk": "^4.0", + "phpunit/phpunit": "^8.5.19|^9.5.8", + "predis/predis": "^1.1.9", + "symfony/cache": "^5.1.4" + }, + "suggest": { + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.198.1).", + "brianium/paratest": "Required to run tests in parallel (^6.0).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.13.3|^3.1.4).", + "ext-bcmath": "Required to use the multiple_of validation rule.", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", + "ext-memcached": "Required to use the memcache cache driver.", + "ext-pcntl": "Required to use all features of the queue worker.", + "ext-posix": "Required to use all features of the queue worker.", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", + "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "filp/whoops": "Required for friendly error pages in development (^2.14.3).", + "guzzlehttp/guzzle": "Required to use the HTTP Client, Mailgun mail driver and the ping methods on schedules (^6.5.5|^7.0.1).", + "laravel/tinker": "Required to use the tinker console command (^2.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", + "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", + "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", + "mockery/mockery": "Required to use mocking (^1.4.4).", + "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", + "phpunit/phpunit": "Required to use assertions and run tests (^8.5.19|^9.5.8).", + "predis/predis": "Required to use the predis connector (^1.1.9).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0|^5.0|^6.0|^7.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^5.1.4).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^5.1.4).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0).", + "wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "files": [ + "src/Illuminate/Collections/helpers.php", + "src/Illuminate/Events/functions.php", + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/", + "Illuminate\\Support\\": [ + "src/Illuminate/Macroable/", + "src/Illuminate/Collections/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "https://laravel.com", + "keywords": [ + "framework", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2021-11-16T15:00:50+00:00" + }, + { + "name": "laravel/sanctum", + "version": "v2.12.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/sanctum.git", + "reference": "76b2d552c00477d520338889160f80a0cfb5fc55" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/76b2d552c00477d520338889160f80a0cfb5fc55", + "reference": "76b2d552c00477d520338889160f80a0cfb5fc55", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "illuminate/contracts": "^6.9|^7.0|^8.0", + "illuminate/database": "^6.9|^7.0|^8.0", + "illuminate/support": "^6.9|^7.0|^8.0", + "php": "^7.2|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^4.0|^5.0|^6.0", + "phpunit/phpunit": "^8.0|^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Sanctum\\SanctumServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sanctum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", + "keywords": [ + "auth", + "laravel", + "sanctum" + ], + "support": { + "issues": "https://github.com/laravel/sanctum/issues", + "source": "https://github.com/laravel/sanctum" + }, + "time": "2021-11-16T16:57:07+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "8148e72e6c2c3af7f05640ada1b26c3bca970f8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/8148e72e6c2c3af7f05640ada1b26c3bca970f8d", + "reference": "8148e72e6c2c3af7f05640ada1b26c3bca970f8d", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "pestphp/pest": "^1.18", + "phpstan/phpstan": "^0.12.98", + "symfony/var-dumper": "^5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2021-11-16T17:01:57+00:00" + }, + { + "name": "league/commonmark", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "2df87709f44b0dd733df86aef0830dce9b1f0f13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/2df87709f44b0dd733df86aef0830dce9b1f0f13", + "reference": "2df87709f44b0dd733df86aef0830dce9b1f0f13", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/polyfill-php80": "^1.15" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.30.0", + "commonmark/commonmark.js": "0.30.0", + "composer/package-versions-deprecated": "^1.8", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4", + "phpstan/phpstan": "^0.12.88", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://enjoy.gitstore.app/repositories/thephpleague/commonmark", + "type": "custom" + }, + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://www.patreon.com/colinodell", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2021-08-14T14:06:04+00:00" + }, + { + "name": "league/config", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.90", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2021-08-14T12:15:32+00:00" + }, + { + "name": "league/flysystem", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "18634df356bfd4119fe3d6156bdb990c414c14ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/18634df356bfd4119fe3d6156bdb990c414c14ea", + "reference": "18634df356bfd4119fe3d6156bdb990c414c14ea", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "league/mime-type-detection": "^1.3", + "php": "^7.2.5 || ^8.0" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/prophecy": "^1.11.1", + "phpunit/phpunit": "^8.5.8" + }, + "suggest": { + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/1.1.5" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2021-08-17T13:49:42+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "b38b25d7b372e9fddb00335400467b223349fd7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/b38b25d7b372e9fddb00335400467b223349fd7e", + "reference": "b38b25d7b372e9fddb00335400467b223349fd7e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.18", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.8.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2021-09-25T08:23:19+00:00" + }, + { + "name": "monolog/monolog", + "version": "2.3.5", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "fd4380d6fc37626e2f799f29d91195040137eba9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd4380d6fc37626e2f799f29d91195040137eba9", + "reference": "fd4380d6fc37626e2f799f29d91195040137eba9", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7", + "graylog2/gelf-php": "^1.4.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.3", + "phpspec/prophecy": "^1.6.1", + "phpstan/phpstan": "^0.12.91", + "phpunit/phpunit": "^8.5", + "predis/predis": "^1.1", + "rollbar/rollbar": "^1.3", + "ruflin/elastica": ">=0.90@dev", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.3.5" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2021-10-01T21:08:31+00:00" + }, + { + "name": "nesbot/carbon", + "version": "2.54.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "eed83939f1aed3eee517d03a33f5ec587ac529b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/eed83939f1aed3eee517d03a33f5ec587ac529b5", + "reference": "eed83939f1aed3eee517d03a33f5ec587ac529b5", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation": "^3.4 || ^4.0 || ^5.0" + }, + "require-dev": { + "doctrine/dbal": "^2.0 || ^3.0", + "doctrine/orm": "^2.7", + "friendsofphp/php-cs-fixer": "^3.0", + "kylekatarnls/multi-tester": "^2.0", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.54", + "phpunit/phpunit": "^7.5.20 || ^8.5.14", + "squizlabs/php_codesniffer": "^3.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.x-dev", + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "funding": [ + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2021-11-01T21:22:20+00:00" + }, + { + "name": "nette/schema", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": ">=7.1 <8.2" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^0.12", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.2" + }, + "time": "2021-10-15T11:40:02+00:00" + }, + { + "name": "nette/utils", + "version": "v3.2.5", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "9cd80396ca58d7969ab44fc7afcf03624dfa526e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/9cd80396ca58d7969ab44fc7afcf03624dfa526e", + "reference": "9cd80396ca58d7969ab44fc7afcf03624dfa526e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2 <8.2" + }, + "conflict": { + "nette/di": "<3.0.6" + }, + "require-dev": { + "nette/tester": "~2.0", + "phpstan/phpstan": "^0.12", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.2.5" + }, + "time": "2021-09-20T10:50:11+00:00" + }, + { + "name": "nosun/laravel-ueditor", + "version": "v2.0.3", + "source": { + "type": "git", + "url": "https://github.com/nosun/laravel-ueditor.git", + "reference": "3f6c9b9f187863f7bdf48bae94b799883d5b64a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nosun/laravel-ueditor/zipball/3f6c9b9f187863f7bdf48bae94b799883d5b64a2", + "reference": "3f6c9b9f187863f7bdf48bae94b799883d5b64a2", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "laravel/framework": "~5.5|~6.0|~7.0|~8.0" + }, + "require-dev": { + "fabpot/php-cs-fixer": "^1.10" + }, + "suggest": { + "overtrue/laravel-filesystem-qiniu": "如果你想要使用七牛云存储,也许你需要安装它哦~" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Nosun\\LaravelUEditor\\UEditorServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Nosun\\LaravelUEditor\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "nosun", + "email": "nosun2008@126.com" + } + ], + "description": "UEditor integration for Laravel.", + "support": { + "source": "https://github.com/nosun/laravel-ueditor/tree/v2.0.3" + }, + "time": "2020-11-06T09:17:38+00:00" + }, + { + "name": "nosun/ueditor", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/nosun/UEditor.git", + "reference": "02c51e93598e171d52b592391e17ca87e59dda73" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nosun/UEditor/zipball/02c51e93598e171d52b592391e17ca87e59dda73", + "reference": "02c51e93598e171d52b592391e17ca87e59dda73", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "encore/laravel-admin": "~1.7", + "nosun/laravel-ueditor": "~2.0", + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Nosun\\Ueditor\\UeditorServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Nosun\\Ueditor\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "nosun", + "email": "nosun2008@126.com" + } + ], + "description": "UEditor extension for laravel-admin", + "homepage": "https://github.com/nosun/UEditor", + "keywords": [ + "extension", + "laravel-admin", + "ueditor" + ], + "support": { + "source": "https://github.com/nosun/UEditor/tree/v3.0.2" + }, + "time": "2020-11-06T09:18:48+00:00" + }, + { + "name": "nwidart/laravel-modules", + "version": "8.2.0", + "source": { + "type": "git", + "url": "https://github.com/nWidart/laravel-modules.git", + "reference": "6ade5ec19e81a0e4807834886a2c47509d069cb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nWidart/laravel-modules/zipball/6ade5ec19e81a0e4807834886a2c47509d069cb7", + "reference": "6ade5ec19e81a0e4807834886a2c47509d069cb7", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "php": ">=7.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "laravel/framework": "^8.0", + "mockery/mockery": "~1.0", + "orchestra/testbench": "^6.2", + "phpstan/phpstan": "^0.12.14", + "phpunit/phpunit": "^8.5", + "spatie/phpunit-snapshot-assertions": "^2.1.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Nwidart\\Modules\\LaravelModulesServiceProvider" + ], + "aliases": { + "Module": "Nwidart\\Modules\\Facades\\Module" + } + }, + "branch-alias": { + "dev-master": "8.0-dev" + } + }, + "autoload": { + "psr-4": { + "Nwidart\\Modules\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Widart", + "email": "n.widart@gmail.com", + "homepage": "https://nicolaswidart.com", + "role": "Developer" + } + ], + "description": "Laravel Module management", + "keywords": [ + "laravel", + "module", + "modules", + "nwidart", + "rad" + ], + "support": { + "issues": "https://github.com/nWidart/laravel-modules/issues", + "source": "https://github.com/nWidart/laravel-modules/tree/8.2.0" + }, + "funding": [ + { + "url": "https://github.com/nwidart", + "type": "github" + } + ], + "time": "2020-11-11T09:24:22+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/06e2ebd25f2869e54a306dda991f7db58066f7f6", + "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\Closure\\": "src/" + }, + "files": [ + "functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.2" + }, + "time": "2021-04-09T13:42:10+00:00" + }, + { + "name": "paragonie/constant_time_encoding", + "version": "v2.6.3", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "58c3f47f650c94ec05a151692652a868995d2938" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", + "reference": "58c3f47f650c94ec05a151692652a868995d2938", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7|^8" + }, + "require-dev": { + "phpunit/phpunit": "^6|^7|^8|^9", + "vimeo/psalm": "^1|^2|^3|^4" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2022-06-14T06:56:20+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "5455cb38aed4523f99977c4a12ef19da4bfe2a28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/5455cb38aed4523f99977c4a12ef19da4bfe2a28", + "reference": "5455cb38aed4523f99977c4a12ef19da4bfe2a28", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "phpunit/phpunit": "^6.5.14 || ^7.0.20 || ^8.5.19 || ^9.5.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.8.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2021-08-28T21:27:29+00:00" + }, + { + "name": "pragmarx/google2fa", + "version": "v8.0.1", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa.git", + "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/80c3d801b31fe165f8fe99ea085e0a37834e1be3", + "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "paragonie/constant_time_encoding": "^1.0|^2.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.18", + "phpunit/phpunit": "^7.5.15|^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PragmaRX\\Google2FA\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" + } + ], + "description": "A One Time Password Authentication package, compatible with Google Authenticator.", + "keywords": [ + "2fa", + "Authentication", + "Two Factor Authentication", + "google2fa" + ], + "support": { + "issues": "https://github.com/antonioribeiro/google2fa/issues", + "source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.1" + }, + "time": "2022-06-13T21:57:56+00:00" + }, + { + "name": "pragmarx/google2fa-laravel", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa-laravel.git", + "reference": "d8243b8f812472f1112716c5462157e0ec128fce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa-laravel/zipball/d8243b8f812472f1112716c5462157e0ec128fce", + "reference": "d8243b8f812472f1112716c5462157e0ec128fce", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "laravel/framework": ">=5.4.36|^8.0|^9.0", + "php": ">=7.0", + "pragmarx/google2fa-qrcode": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "bacon/bacon-qr-code": "^2.0", + "orchestra/testbench": "3.4.*|3.5.*|3.6.*|3.7.*|4.*|5.*|6.*", + "phpunit/phpunit": "~5|~6|~7|~8|~9" + }, + "suggest": { + "bacon/bacon-qr-code": "Required to generate inline QR Codes.", + "pragmarx/recovery": "Generate recovery codes." + }, + "type": "library", + "extra": { + "component": "package", + "frameworks": [ + "Laravel" + ], + "branch-alias": { + "dev-master": "0.2-dev" + }, + "laravel": { + "providers": [ + "PragmaRX\\Google2FALaravel\\ServiceProvider" + ], + "aliases": { + "Google2FA": "PragmaRX\\Google2FALaravel\\Facade" + } + } + }, + "autoload": { + "psr-4": { + "PragmaRX\\Google2FALaravel\\": "src/", + "PragmaRX\\Google2FALaravel\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" + } + ], + "description": "A One Time Password Authentication package, compatible with Google Authenticator.", + "keywords": [ + "Authentication", + "Two Factor Authentication", + "google2fa", + "laravel" + ], + "support": { + "issues": "https://github.com/antonioribeiro/google2fa-laravel/issues", + "source": "https://github.com/antonioribeiro/google2fa-laravel/tree/v2.0.2" + }, + "time": "2022-03-08T18:03:04+00:00" + }, + { + "name": "pragmarx/google2fa-qrcode", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa-qrcode.git", + "reference": "ce4d8a729b6c93741c607cfb2217acfffb5bf76b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa-qrcode/zipball/ce4d8a729b6c93741c607cfb2217acfffb5bf76b", + "reference": "ce4d8a729b6c93741c607cfb2217acfffb5bf76b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1", + "pragmarx/google2fa": ">=4.0" + }, + "require-dev": { + "bacon/bacon-qr-code": "^2.0", + "chillerlan/php-qrcode": "^1.0|^2.0|^3.0|^4.0", + "khanamiryan/qrcode-detector-decoder": "^1.0", + "phpunit/phpunit": "~4|~5|~6|~7|~8|~9" + }, + "suggest": { + "bacon/bacon-qr-code": "For QR Code generation, requires imagick", + "chillerlan/php-qrcode": "For QR Code generation" + }, + "type": "library", + "extra": { + "component": "package", + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PragmaRX\\Google2FAQRCode\\": "src/", + "PragmaRX\\Google2FAQRCode\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" + } + ], + "description": "QR Code package for Google2FA", + "keywords": [ + "2fa", + "Authentication", + "Two Factor Authentication", + "google2fa", + "qr code", + "qrcode" + ], + "support": { + "issues": "https://github.com/antonioribeiro/google2fa-qrcode/issues", + "source": "https://github.com/antonioribeiro/google2fa-qrcode/tree/v3.0.0" + }, + "time": "2021-08-15T12:53:48+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/collection", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/cccc74ee5e328031b15640b51056ee8d3bb66c0a", + "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.3 || ^8", + "symfony/polyfill-php81": "^1.23" + }, + "require-dev": { + "captainhook/captainhook": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "ergebnis/composer-normalize": "^2.6", + "fakerphp/faker": "^1.5", + "hamcrest/hamcrest-php": "^2", + "jangregor/phpstan-prophecy": "^0.8", + "mockery/mockery": "^1.3", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1", + "phpstan/phpstan": "^0.12.32", + "phpstan/phpstan-mockery": "^0.12.5", + "phpstan/phpstan-phpunit": "^0.12.11", + "phpunit/phpunit": "^8.5 || ^9", + "psy/psysh": "^0.10.4", + "slevomat/coding-standard": "^6.3", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/1.2.2" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" + } + ], + "time": "2021-10-10T03:01:02+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.2.3", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", + "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "brick/math": "^0.8 || ^0.9", + "ext-json": "*", + "php": "^7.2 || ^8.0", + "ramsey/collection": "^1.0", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-php80": "^1.14" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.10", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.8", + "ergebnis/composer-normalize": "^2.15", + "mockery/mockery": "^1.3", + "moontoast/math": "^1.1", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.2", + "php-mock/php-mock-mockery": "^1.3", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-mockery": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^8.5 || ^9", + "slevomat/coding-standard": "^7.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.9" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-ctype": "Enables faster processing of character classification using ctype functions.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.x-dev" + }, + "captainhook": { + "force-install": true + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.2.3" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" + } + ], + "time": "2021-09-25T23:10:38+00:00" + }, + { + "name": "simplesoftwareio/simple-qrcode", + "version": "4.2.0", + "source": { + "type": "git", + "url": "https://github.com/SimpleSoftwareIO/simple-qrcode.git", + "reference": "916db7948ca6772d54bb617259c768c9cdc8d537" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SimpleSoftwareIO/simple-qrcode/zipball/916db7948ca6772d54bb617259c768c9cdc8d537", + "reference": "916db7948ca6772d54bb617259c768c9cdc8d537", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "bacon/bacon-qr-code": "^2.0", + "ext-gd": "*", + "php": ">=7.2|^8.0" + }, + "require-dev": { + "mockery/mockery": "~1", + "phpunit/phpunit": "~9" + }, + "suggest": { + "ext-imagick": "Allows the generation of PNG QrCodes.", + "illuminate/support": "Allows for use within Laravel." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "SimpleSoftwareIO\\QrCode\\QrCodeServiceProvider" + ], + "aliases": { + "QrCode": "SimpleSoftwareIO\\QrCode\\Facades\\QrCode" + } + } + }, + "autoload": { + "psr-4": { + "SimpleSoftwareIO\\QrCode\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Simple Software LLC", + "email": "support@simplesoftware.io" + } + ], + "description": "Simple QrCode is a QR code generator made for Laravel.", + "homepage": "https://www.simplesoftware.io/#/docs/simple-qrcode", + "keywords": [ + "Simple", + "generator", + "laravel", + "qrcode", + "wrapper" + ], + "support": { + "issues": "https://github.com/SimpleSoftwareIO/simple-qrcode/issues", + "source": "https://github.com/SimpleSoftwareIO/simple-qrcode/tree/4.2.0" + }, + "time": "2021-02-08T20:43:55+00:00" + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/8a5d5072dca8f48460fce2f4131fcc495eec654c", + "reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "egulias/email-validator": "^2.0|^3.1", + "php": ">=7.0.0", + "symfony/polyfill-iconv": "^1.0", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "symfony/phpunit-bridge": "^4.4|^5.4" + }, + "suggest": { + "ext-intl": "Needed to support internationalized email addresses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "https://swiftmailer.symfony.com", + "keywords": [ + "email", + "mail", + "mailer" + ], + "support": { + "issues": "https://github.com/swiftmailer/swiftmailer/issues", + "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.3.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/swiftmailer/swiftmailer", + "type": "tidelift" + } + ], + "time": "2021-10-18T15:26:12+00:00" + }, + { + "name": "symfony/console", + "version": "v5.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "d4e409d9fbcfbf71af0e5a940abb7b0b4bad0bd3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/d4e409d9fbcfbf71af0e5a940abb7b0b4bad0bd3", + "reference": "d4e409d9fbcfbf71af0e5a940abb7b0b4bad0bd3", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2", + "symfony/string": "^5.1" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.3.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-10-26T09:30:15+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v5.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "7fb120adc7f600a59027775b224c13a33530dd90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/7fb120adc7f600a59027775b224c13a33530dd90", + "reference": "7fb120adc7f600a59027775b224c13a33530dd90", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v5.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-07-21T12:38:00+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-03-23T23:28:01+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v5.3.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "c7eef3a60ccfdd8eafe07f81652e769ac9c7146c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c7eef3a60ccfdd8eafe07f81652e769ac9c7146c", + "reference": "c7eef3a60ccfdd8eafe07f81652e769ac9c7146c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "masterminds/html5": "<2.6" + }, + "require-dev": { + "masterminds/html5": "^2.6", + "symfony/css-selector": "^4.4|^5.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v5.3.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-08-29T19:32:13+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v5.3.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "3bc60d0fba00ae8d1eaa9eb5ab11a2bbdd1fc321" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/3bc60d0fba00ae8d1eaa9eb5ab11a2bbdd1fc321", + "reference": "3bc60d0fba00ae8d1eaa9eb5ab11a2bbdd1fc321", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^4.4|^5.0" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.1", + "symfony/http-kernel": "^4.4|^5.0", + "symfony/serializer": "^4.4|^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v5.3.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-08-28T15:07:08+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v5.3.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "ce7b20d69c66a20939d8952b617506a44d102130" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ce7b20d69c66a20939d8952b617506a44d102130", + "reference": "ce7b20d69c66a20939d8952b617506a44d102130", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/event-dispatcher-contracts": "^2", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/error-handler": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^4.4|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v5.3.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-08-04T21:20:46+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/69fee1ad2332a7cbab3aca13591953da9cdb7a11", + "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-03-23T23:28:01+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.3.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "a10000ada1e600d109a6c7632e9ac42e8bf2fb93" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/a10000ada1e600d109a6c7632e9ac42e8bf2fb93", + "reference": "a10000ada1e600d109a6c7632e9ac42e8bf2fb93", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.3.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-08-04T21:20:46+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/7e82f6084d7cae521a75ef2cb5c9457bbda785f4", + "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/http-client-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-04-11T23:07:08+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v5.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "9f34f02e8a5fdc7a56bafe011cea1ce97300e54c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9f34f02e8a5fdc7a56bafe011cea1ce97300e54c", + "reference": "9f34f02e8a5fdc7a56bafe011cea1ce97300e54c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "predis/predis": "~1.0", + "symfony/cache": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/mime": "^4.4|^5.0" + }, + "suggest": { + "symfony/mime": "To use the file extension guesser" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v5.3.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-10-11T15:41:55+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v5.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "703e4079920468e9522b72cf47fd76ce8d795e86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/703e4079920468e9522b72cf47fd76ce8d795e86", + "reference": "703e4079920468e9522b72cf47fd76ce8d795e86", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/log": "^1|^2", + "symfony/deprecation-contracts": "^2.1", + "symfony/error-handler": "^4.4|^5.0", + "symfony/event-dispatcher": "^5.0", + "symfony/http-client-contracts": "^1.1|^2", + "symfony/http-foundation": "^5.3.7", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/browser-kit": "<4.4", + "symfony/cache": "<5.0", + "symfony/config": "<5.0", + "symfony/console": "<4.4", + "symfony/dependency-injection": "<5.3", + "symfony/doctrine-bridge": "<5.0", + "symfony/form": "<5.0", + "symfony/http-client": "<5.0", + "symfony/mailer": "<5.0", + "symfony/messenger": "<5.0", + "symfony/translation": "<5.0", + "symfony/twig-bridge": "<5.0", + "symfony/validator": "<5.0", + "twig/twig": "<2.13" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^4.4|^5.0", + "symfony/config": "^5.0", + "symfony/console": "^4.4|^5.0", + "symfony/css-selector": "^4.4|^5.0", + "symfony/dependency-injection": "^5.3", + "symfony/dom-crawler": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/finder": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/routing": "^4.4|^5.0", + "symfony/stopwatch": "^4.4|^5.0", + "symfony/translation": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v5.3.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-10-29T08:36:48+00:00" + }, + { + "name": "symfony/mime", + "version": "v5.3.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "a756033d0a7e53db389618653ae991eba5a19a11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/a756033d0a7e53db389618653ae991eba5a19a11", + "reference": "a756033d0a7e53db389618653ae991eba5a19a11", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<4.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/property-access": "^4.4|^5.1", + "symfony/property-info": "^4.4|^5.1", + "symfony/serializer": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v5.3.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-09-10T12:30:38+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-iconv", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-iconv.git", + "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/63b5bb7db83e5673936d6e3b8b3e022ff6474933", + "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-iconv": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Iconv\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Iconv extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "iconv", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T09:27:20+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "16880ba9c5ebe3642d1995ab866db29270b36535" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/16880ba9c5ebe3642d1995ab866db29270b36535", + "reference": "16880ba9c5ebe3642d1995ab866db29270b36535", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T12:26:48+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "65bd267525e82759e7d8c4e8ceea44f398838e65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/65bd267525e82759e7d8c4e8ceea44f398838e65", + "reference": "65bd267525e82759e7d8c4e8ceea44f398838e65", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T09:27:20+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T12:26:48+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T09:17:38+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be", + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-07-28T13:41:28+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "e66119f3de95efc359483f810c4c3e6436279436" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/e66119f3de95efc359483f810c4c3e6436279436", + "reference": "e66119f3de95efc359483f810c4c3e6436279436", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-21T13:25:03+00:00" + }, + { + "name": "symfony/process", + "version": "v5.3.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "38f26c7d6ed535217ea393e05634cb0b244a1967" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/38f26c7d6ed535217ea393e05634cb0b244a1967", + "reference": "38f26c7d6ed535217ea393e05634cb0b244a1967", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.3.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-08-04T21:20:46+00:00" + }, + { + "name": "symfony/routing", + "version": "v5.3.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "be865017746fe869007d94220ad3f5297951811b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/be865017746fe869007d94220ad3f5297951811b", + "reference": "be865017746fe869007d94220ad3f5297951811b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "doctrine/annotations": "<1.12", + "symfony/config": "<5.3", + "symfony/dependency-injection": "<4.4", + "symfony/yaml": "<4.4" + }, + "require-dev": { + "doctrine/annotations": "^1.12", + "psr/log": "^1|^2|^3", + "symfony/config": "^5.3", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/yaml": "^4.4|^5.0" + }, + "suggest": { + "symfony/config": "For using the all-in-one router or any loader", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v5.3.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-08-04T21:42:42+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-04-01T10:43:52+00:00" + }, + { + "name": "symfony/string", + "version": "v5.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "d70c35bb20bbca71fc4ab7921e3c6bda1a82a60c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/d70c35bb20bbca71fc4ab7921e3c6bda1a82a60c", + "reference": "d70c35bb20bbca71fc4ab7921e3c6bda1a82a60c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "files": [ + "Resources/functions.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.3.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-10-27T18:21:46+00:00" + }, + { + "name": "symfony/translation", + "version": "v5.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "6ef197aea2ac8b9cd63e0da7522b3771714035aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/6ef197aea2ac8b9cd63e0da7522b3771714035aa", + "reference": "6ef197aea2ac8b9cd63e0da7522b3771714035aa", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation-contracts": "^2.3" + }, + "conflict": { + "symfony/config": "<4.4", + "symfony/dependency-injection": "<5.0", + "symfony/http-kernel": "<5.0", + "symfony/twig-bundle": "<5.0", + "symfony/yaml": "<4.4" + }, + "provide": { + "symfony/translation-implementation": "2.3" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0", + "symfony/console": "^4.4|^5.0", + "symfony/dependency-injection": "^5.0", + "symfony/finder": "^4.4|^5.0", + "symfony/http-kernel": "^5.0", + "symfony/intl": "^4.4|^5.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/service-contracts": "^1.1.2|^2", + "symfony/yaml": "^4.4|^5.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v5.3.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-10-10T06:43:24+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "95c812666f3e91db75385749fe219c5e494c7f95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/95c812666f3e91db75385749fe219c5e494c7f95", + "reference": "95c812666f3e91db75385749fe219c5e494c7f95", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-03-23T23:28:01+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v5.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "875432adb5f5570fff21036fd22aee244636b7d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/875432adb5f5570fff21036fd22aee244636b7d1", + "reference": "875432adb5f5570fff21036fd22aee244636b7d1", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v5.3.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-10-26T09:30:15+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "2.2.3", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "b43b05cf43c1b6d849478965062b6ef73e223bb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/b43b05cf43c1b6d849478965062b6ef73e223bb5", + "reference": "b43b05cf43c1b6d849478965062b6ef73e223bb5", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^5.5 || ^7.0 || ^8.0", + "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "TijsVerkoyen\\CssToInlineStyles\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Tijs Verkoyen", + "email": "css_to_inline_styles@verkoyen.eu", + "role": "Developer" + } + ], + "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", + "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "support": { + "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.3" + }, + "time": "2020-07-13T06:12:54+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.4.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "d4394d044ed69a8f244f3445bcedf8a0d7fe2403" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/d4394d044ed69a8f244f3445bcedf8a0d7fe2403", + "reference": "d4394d044ed69a8f244f3445bcedf8a0d7fe2403", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.0.2", + "php": "^7.1.3 || ^8.0", + "phpoption/phpoption": "^1.8", + "symfony/polyfill-ctype": "^1.23", + "symfony/polyfill-mbstring": "^1.23.1", + "symfony/polyfill-php80": "^1.23.1" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-filter": "*", + "phpunit/phpunit": "^7.5.20 || ^8.5.21 || ^9.5.10" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.4-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.4.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2021-11-10T01:08:39+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "1.5.6", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "80953678b19901e5165c56752d087fc11526017c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/80953678b19901e5165c56752d087fc11526017c", + "reference": "80953678b19901e5165c56752d087fc11526017c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "http://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/1.5.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2020-11-12T00:07:28+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.10.0" + }, + "time": "2021-03-09T10:59:23+00:00" + }, + { + "name": "yangjisen/laravel-cache-provider", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/yangjisen/LaravelCacheProvider.git", + "reference": "2c94bce77f642f200e073cd1bf1b6b5554f48471" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yangjisen/LaravelCacheProvider/zipball/2c94bce77f642f200e073cd1bf1b6b5554f48471", + "reference": "2c94bce77f642f200e073cd1bf1b6b5554f48471", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "laravel/framework": "^5.1||^6.0||^7.0||^8.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "YangJiSen\\CacheUserProvider\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "YangJiSen\\CacheUserProvider\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Yang Ji Sen", + "email": "381722452@qq.com" + } + ], + "keywords": [ + "auth", + "cache", + "guard", + "laravel" + ], + "support": { + "issues": "https://github.com/yangjisen/LaravelCacheProvider/issues", + "source": "https://github.com/yangjisen/LaravelCacheProvider/tree/v3.0.1" + }, + "time": "2020-12-15T13:43:38+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-11-10T18:47:58+00:00" + }, + { + "name": "facade/flare-client-php", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/facade/flare-client-php.git", + "reference": "b2adf1512755637d0cef4f7d1b54301325ac78ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facade/flare-client-php/zipball/b2adf1512755637d0cef4f7d1b54301325ac78ed", + "reference": "b2adf1512755637d0cef4f7d1b54301325ac78ed", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "facade/ignition-contracts": "~1.0", + "illuminate/pipeline": "^5.5|^6.0|^7.0|^8.0", + "php": "^7.1|^8.0", + "symfony/http-foundation": "^3.3|^4.1|^5.0", + "symfony/mime": "^3.4|^4.0|^5.1", + "symfony/var-dumper": "^3.4|^4.0|^5.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.14", + "phpunit/phpunit": "^7.5.16", + "spatie/phpunit-snapshot-assertions": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Facade\\FlareClient\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Send PHP errors to Flare", + "homepage": "https://github.com/facade/flare-client-php", + "keywords": [ + "exception", + "facade", + "flare", + "reporting" + ], + "support": { + "issues": "https://github.com/facade/flare-client-php/issues", + "source": "https://github.com/facade/flare-client-php/tree/1.9.1" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2021-09-13T12:16:46+00:00" + }, + { + "name": "facade/ignition", + "version": "2.16.0", + "source": { + "type": "git", + "url": "https://github.com/facade/ignition.git", + "reference": "23400e6cc565c9dcae2c53704b4de1c4870c0697" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facade/ignition/zipball/23400e6cc565c9dcae2c53704b4de1c4870c0697", + "reference": "23400e6cc565c9dcae2c53704b4de1c4870c0697", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "facade/flare-client-php": "^1.9.1", + "facade/ignition-contracts": "^1.0.2", + "illuminate/support": "^7.0|^8.0", + "monolog/monolog": "^2.0", + "php": "^7.2.5|^8.0", + "symfony/console": "^5.0", + "symfony/var-dumper": "^5.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.14", + "mockery/mockery": "^1.3", + "orchestra/testbench": "^5.0|^6.0", + "psalm/plugin-laravel": "^1.2" + }, + "suggest": { + "laravel/telescope": "^3.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Facade\\Ignition\\IgnitionServiceProvider" + ], + "aliases": { + "Flare": "Facade\\Ignition\\Facades\\Flare" + } + } + }, + "autoload": { + "psr-4": { + "Facade\\Ignition\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A beautiful error page for Laravel applications.", + "homepage": "https://github.com/facade/ignition", + "keywords": [ + "error", + "flare", + "laravel", + "page" + ], + "support": { + "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", + "forum": "https://twitter.com/flareappio", + "issues": "https://github.com/facade/ignition/issues", + "source": "https://github.com/facade/ignition" + }, + "time": "2021-10-28T11:47:23+00:00" + }, + { + "name": "facade/ignition-contracts", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/facade/ignition-contracts.git", + "reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facade/ignition-contracts/zipball/3c921a1cdba35b68a7f0ccffc6dffc1995b18267", + "reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.15.8", + "phpunit/phpunit": "^9.3.11", + "vimeo/psalm": "^3.17.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Facade\\IgnitionContracts\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://flareapp.io", + "role": "Developer" + } + ], + "description": "Solution contracts for Ignition", + "homepage": "https://github.com/facade/ignition-contracts", + "keywords": [ + "contracts", + "flare", + "ignition" + ], + "support": { + "issues": "https://github.com/facade/ignition-contracts/issues", + "source": "https://github.com/facade/ignition-contracts/tree/1.0.2" + }, + "time": "2020-10-16T08:27:54+00:00" + }, + { + "name": "fakerphp/faker", + "version": "v1.16.0", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "271d384d216e5e5c468a6b28feedf95d49f83b35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/271d384d216e5e5c468a6b28feedf95d49f83b35", + "reference": "271d384d216e5e5c468a6b28feedf95d49f83b35", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-intl": "*", + "symfony/phpunit-bridge": "^4.4 || ^5.2" + }, + "suggest": { + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "v1.16-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.16.0" + }, + "time": "2021-09-06T14:53:37+00:00" + }, + { + "name": "filp/whoops", + "version": "2.14.4", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "f056f1fe935d9ed86e698905a957334029899895" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/f056f1fe935d9ed86e698905a957334029899895", + "reference": "f056f1fe935d9ed86e698905a957334029899895", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.4" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2021-10-03T12:00:00+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^5.3|^7.0|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + }, + "time": "2020-07-09T08:09:16+00:00" + }, + { + "name": "laravel/sail", + "version": "v1.12.5", + "source": { + "type": "git", + "url": "https://github.com/laravel/sail.git", + "reference": "d1c31b2c3d226e70e0071e074da69e4801f0f47b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sail/zipball/d1c31b2c3d226e70e0071e074da69e4801f0f47b", + "reference": "d1c31b2c3d226e70e0071e074da69e4801f0f47b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "illuminate/console": "^8.0|^9.0", + "illuminate/contracts": "^8.0|^9.0", + "illuminate/support": "^8.0|^9.0", + "php": "^7.3|^8.0" + }, + "bin": [ + "bin/sail" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Sail\\SailServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Docker files for running a basic Laravel application.", + "keywords": [ + "docker", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/sail/issues", + "source": "https://github.com/laravel/sail" + }, + "time": "2021-11-16T16:55:18+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.4.4", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "e01123a0e847d52d186c5eb4b9bf58b0c6d00346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/e01123a0e847d52d186c5eb4b9bf58b0c6d00346", + "reference": "e01123a0e847d52d186c5eb4b9bf58b0c6d00346", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": "^7.3 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "issues": "https://github.com/mockery/mockery/issues", + "source": "https://github.com/mockery/mockery/tree/1.4.4" + }, + "time": "2021-09-13T15:28:59+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.10.2", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-11-13T09:40:50+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.13.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/63a79e8daa781cac14e5195e63ed8ae231dd10fd", + "reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.1" + }, + "time": "2021-11-03T20:52:16+00:00" + }, + { + "name": "nunomaduro/collision", + "version": "v5.10.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "3004cfa49c022183395eabc6d0e5207dfe498d00" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/3004cfa49c022183395eabc6d0e5207dfe498d00", + "reference": "3004cfa49c022183395eabc6d0e5207dfe498d00", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "facade/ignition-contracts": "^1.0", + "filp/whoops": "^2.14.3", + "php": "^7.3 || ^8.0", + "symfony/console": "^5.0" + }, + "require-dev": { + "brianium/paratest": "^6.1", + "fideloper/proxy": "^4.4.1", + "fruitcake/laravel-cors": "^2.0.3", + "laravel/framework": "8.x-dev", + "nunomaduro/larastan": "^0.6.2", + "nunomaduro/mock-final-classes": "^1.0", + "orchestra/testbench": "^6.0", + "phpstan/phpstan": "^0.12.64", + "phpunit/phpunit": "^9.5.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "NunoMaduro\\Collision\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Cli error handling for console/command-line PHP applications.", + "keywords": [ + "artisan", + "cli", + "command-line", + "console", + "error", + "handling", + "laravel", + "laravel-zero", + "php", + "symfony" + ], + "support": { + "issues": "https://github.com/nunomaduro/collision/issues", + "source": "https://github.com/nunomaduro/collision" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2021-09-20T15:06:32+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "bae7c545bef187884426f042434e561ab1ddb182" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", + "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.1.0" + }, + "time": "2021-02-23T14:00:09+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + }, + "time": "2021-10-19T17:43:47+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/a12f7e301eb7258bb68acd89d4aefa05c2906cae", + "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.1" + }, + "time": "2021-10-02T14:08:47+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.14.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", + "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.2", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0 || ^7.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/1.14.0" + }, + "time": "2021-09-10T09:02:12+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "cf04e88a2e3c56fc1a65488afd493325b4c1bc3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/cf04e88a2e3c56fc1a65488afd493325b4c1bc3e", + "reference": "cf04e88a2e3c56fc1a65488afd493325b4c1bc3e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.13.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-10-30T08:01:38+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:57:25+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.5.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c814a05837f2edb0d1471d6e3f4ab3501ca3899a", + "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpspec/prophecy": "^1.12.1", + "phpunit/php-code-coverage": "^9.2.7", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.5", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.3", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^2.3.4", + "sebastian/version": "^3.0.2" + }, + "require-dev": { + "ext-pdo": "*", + "phpspec/prophecy-phpunit": "^2.0.1" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ], + "files": [ + "src/Framework/Assert/Functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.10" + }, + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-09-25T07:38:51+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:49:45+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:10:38+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:52:38+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-11-11T14:18:36+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-11T13:31:12+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:17:30+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "2.3.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-15T12:49:02+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "spatie/laravel-package-tools", + "version": "1.9.2", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-package-tools.git", + "reference": "f710fe196c126fb9e0aee67eb5af49ad8f13f528" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/f710fe196c126fb9e0aee67eb5af49ad8f13f528", + "reference": "f710fe196c126fb9e0aee67eb5af49ad8f13f528", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "illuminate/contracts": "^7.0|^8.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.4", + "orchestra/testbench": "^5.0|^6.0", + "phpunit/phpunit": "^9.3", + "spatie/test-time": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\LaravelPackageTools\\": "src", + "Spatie\\LaravelPackageTools\\Database\\Factories\\": "database/factories" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Tools for creating Laravel packages", + "homepage": "https://github.com/spatie/laravel-package-tools", + "keywords": [ + "laravel-package-tools", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-package-tools/issues", + "source": "https://github.com/spatie/laravel-package-tools/tree/1.9.2" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2021-09-21T13:06:51+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + }, + { + "name": "wulfheart/pretty_routes", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/Wulfheart/pretty-routes.git", + "reference": "e257fac400db2c696ddaec197e634b1fc7c40d22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Wulfheart/pretty-routes/zipball/e257fac400db2c696ddaec197e634b1fc7c40d22", + "reference": "e257fac400db2c696ddaec197e634b1fc7c40d22", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "illuminate/contracts": "^8.0", + "php": "^7.4|^8.0", + "spatie/laravel-package-tools": "^1.4.3" + }, + "require-dev": { + "brianium/paratest": "^6.2", + "nunomaduro/collision": "^5.3", + "orchestra/testbench": "^6.15", + "phpunit/phpunit": "^9.3", + "spatie/laravel-ray": "^1.9", + "spatie/phpunit-snapshot-assertions": "^4.2", + "vimeo/psalm": "^4.4" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Wulfheart\\PrettyRoutes\\PrettyRoutesServiceProvider" + ], + "aliases": { + "PrettyRoutes": "Wulfheart\\PrettyRoutes\\PrettyRoutesFacade" + } + } + }, + "autoload": { + "psr-4": { + "Wulfheart\\PrettyRoutes\\": "src", + "Wulfheart\\PrettyRoutes\\Database\\Factories\\": "database/factories" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alexander Wulf", + "email": "dev@alexfwulf.de", + "role": "Developer" + } + ], + "description": "Display your Laravel routes in the console, but make it pretty. 😎", + "homepage": "https://github.com/wulfheart/pretty_routes", + "keywords": [ + "laravel", + "pretty_routes", + "wulfheart" + ], + "support": { + "issues": "https://github.com/Wulfheart/pretty-routes/issues", + "source": "https://github.com/Wulfheart/pretty-routes/tree/0.3.0" + }, + "time": "2021-05-03T09:19:08+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "^7.4|^8.0", + "ext-json": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/config/admin.php b/config/admin.php new file mode 100644 index 0000000..494f5fc --- /dev/null +++ b/config/admin.php @@ -0,0 +1,412 @@ + [ + // storage_path('app/water.png'), + // 'bottom-right', + ], + + /* + |-------------------------------------------------------------------------- + | 上传图片是否开启缩略图 + | 'small' => [100, 100], + | ... + |-------------------------------------------------------------------------- + */ + 'cover_thumb' => [], + + /* + |-------------------------------------------------------------------------- + | Laravel-admin name + |-------------------------------------------------------------------------- + | + | This value is the name of laravel-admin, This setting is displayed on the + | login page. + | + */ + 'name' => env('ADMIN_NAME', 'Jason-admin'), + + /* + |-------------------------------------------------------------------------- + | Laravel-admin html title + |-------------------------------------------------------------------------- + | + | Html title for all pages. + | + */ + 'title' => env('ADMIN_TITLE', 'Jason Admin'), + + /* + |-------------------------------------------------------------------------- + | Laravel-admin logo + |-------------------------------------------------------------------------- + | + | The logo of all admin pages. You can also set it as an image by using a + | `img` tag, eg 'Admin logo'. + | + */ + 'logo' => env('ADMIN_LOGO', 'Jason admin'), + + /* + |-------------------------------------------------------------------------- + | Laravel-admin mini logo + |-------------------------------------------------------------------------- + | + | The logo of all admin pages when the sidebar menu is collapsed. You can + | also set it as an image by using a `img` tag, eg + | 'Admin logo'. + | + */ + 'logo-mini' => env('ADMIN_LOGO_MINI', 'JA'), + + /* + |-------------------------------------------------------------------------- + | Laravel-admin bootstrap setting + |-------------------------------------------------------------------------- + | + | This value is the path of laravel-admin bootstrap file. + | + */ + 'bootstrap' => app_path('Admin/bootstrap.php'), + + /* + |-------------------------------------------------------------------------- + | Laravel-admin route settings + |-------------------------------------------------------------------------- + | + | The routing configuration of the admin page, including the path prefix, + | the controller namespace, and the default middleware. If you want to + | access through the root path, just set the prefix to empty string. + | + */ + 'route' => [ + 'prefix' => env('ADMIN_ROUTE_PREFIX', 'admin'), + 'namespace' => 'App\\Admin\\Controllers', + 'middleware' => ['web', 'admin'], + 'as' => 'admin.', + ], + + /* + |-------------------------------------------------------------------------- + | Laravel-admin install directory + |-------------------------------------------------------------------------- + | + | The installation directory of the controller and routing configuration + | files of the administration page. The default is `app/Admin`, which must + | be set before running `artisan admin::install` to take effect. + | + */ + 'directory' => app_path('Admin'), + + /* + |-------------------------------------------------------------------------- + | Access via `https` + |-------------------------------------------------------------------------- + | + | If your page is going to be accessed via https, set it to `true`. + | + */ + 'https' => env('ADMIN_HTTPS', false), + + /* + |-------------------------------------------------------------------------- + | Laravel-admin auth setting + |-------------------------------------------------------------------------- + | + | Authentication settings for all admin pages. Include an authentication + | guard and a user provider setting of authentication driver. + | + | You can specify a controller for `login` `logout` and other auth routes. + | + */ + 'auth' => [ + 'controller' => App\Admin\Controllers\AuthController::class, + 'guard' => 'admin', + 'guards' => [ + 'admin' => [ + 'driver' => 'session', + 'provider' => 'admin', + ], + ], + 'providers' => [ + 'admin' => [ + 'driver' => 'eloquent', + 'model' => Encore\Admin\Auth\Database\Administrator::class, + ], + ], + // Add "remember me" to login form + 'remember' => true, + // Redirect to the specified URI when user is not authorized. + 'redirect_to' => 'auth/login', + // The URIs that should be excluded from authorization. + 'excepts' => [ + 'auth/login', + 'auth/logout', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Laravel-admin upload setting + |-------------------------------------------------------------------------- + | + | File system configuration for form upload files and images, including + | disk and upload path. + | + */ + 'upload' => [ + // Disk in `config/filesystem.php`. + 'disk' => env('FILESYSTEM_DRIVER', 'local'), + // Image and file upload path under the disk above. + 'directory' => [ + 'image' => 'images', + 'file' => 'files', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Laravel-admin database settings + |-------------------------------------------------------------------------- + | + | Here are database settings for laravel-admin builtin model & tables. + | + */ + 'database' => [ + // Database connection for following tables. + 'connection' => '', + // User tables and model. + 'users_table' => 'admin_users', + 'users_model' => Encore\Admin\Auth\Database\Administrator::class, + // Role table and model. + 'roles_table' => 'admin_roles', + 'roles_model' => Encore\Admin\Auth\Database\Role::class, + // Permission table and model. + 'permissions_table' => 'admin_permissions', + 'permissions_model' => Encore\Admin\Auth\Database\Permission::class, + // Menu table and model. + 'menu_table' => 'admin_menu', + 'menu_model' => Encore\Admin\Auth\Database\Menu::class, + // Pivot table for table above. + 'operation_log_table' => 'admin_operation_log', + 'user_permissions_table' => 'admin_user_permissions', + 'role_users_table' => 'admin_role_users', + 'role_permissions_table' => 'admin_role_permissions', + 'role_menu_table' => 'admin_role_menu', + ], + + /* + |-------------------------------------------------------------------------- + | User operation log setting + |-------------------------------------------------------------------------- + | + | By setting this option to open or close operation log in laravel-admin. + | + */ + 'operation_log' => [ + 'enable' => true, + /* + * Only logging allowed methods in the list + */ + 'allowed_methods' => ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH'], + /* + * Routes that will not log to database. + * + * All method to path like: admin/auth/logs + * or specific method to path like: get:admin/auth/logs. + */ + 'except' => [ + 'admin/auth/logs*', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Indicates whether to check route permission. + |-------------------------------------------------------------------------- + */ + 'check_route_permission' => true, + + /* + |-------------------------------------------------------------------------- + | Indicates whether to check menu roles. + |-------------------------------------------------------------------------- + */ + 'check_menu_roles' => true, + + /* + |-------------------------------------------------------------------------- + | User default avatar + |-------------------------------------------------------------------------- + | + | Set a default avatar for newly created users. + | + */ + 'default_avatar' => '/vendor/laravel-admin/AdminLTE/dist/img/user2-160x160.jpg', + + /* + |-------------------------------------------------------------------------- + | Admin map field provider + |-------------------------------------------------------------------------- + | + | Supported: "tencent", "google", "yandex". + | + */ + 'map_provider' => 'google', + + /* + |-------------------------------------------------------------------------- + | Application Skin + |-------------------------------------------------------------------------- + | + | This value is the skin of admin pages. + | @see https://adminlte.io/docs/2.4/layout + | + | Supported: + | "skin-blue", "skin-blue-light", "skin-yellow", "skin-yellow-light", + | "skin-green", "skin-green-light", "skin-purple", "skin-purple-light", + | "skin-red", "skin-red-light", "skin-black", "skin-black-light". + | + */ + 'skin' => 'skin-blue', + + /* + |-------------------------------------------------------------------------- + | Application layout + |-------------------------------------------------------------------------- + | + | This value is the layout of admin pages. + | @see https://adminlte.io/docs/2.4/layout + | + | Supported: "fixed", "layout-boxed", "layout-top-nav", "sidebar-collapse", + | "sidebar-mini". + | + */ + 'layout' => ['fixed', 'sidebar-mini'], + + /* + |-------------------------------------------------------------------------- + | Login page background image + |-------------------------------------------------------------------------- + | + | This value is used to set the background image of login page. + | + */ + 'login_background_image' => '', + + /* + |-------------------------------------------------------------------------- + | Show version at footer + |-------------------------------------------------------------------------- + | + | Whether to display the version number of laravel-admin at the footer of + | each page + | + */ + 'show_version' => true, + + /* + |-------------------------------------------------------------------------- + | Show environment at footer + |-------------------------------------------------------------------------- + | + | Whether to display the environment at the footer of each page + | + */ + 'show_environment' => true, + + /* + |-------------------------------------------------------------------------- + | Menu bind to permission + |-------------------------------------------------------------------------- + | + | whether enable menu bind to a permission + */ + 'menu_bind_permission' => true, + + /* + |-------------------------------------------------------------------------- + | Enable default breadcrumb + |-------------------------------------------------------------------------- + | + | Whether enable default breadcrumb for every page content. + */ + 'enable_default_breadcrumb' => true, + + /* + |-------------------------------------------------------------------------- + | Enable/Disable assets minify + |-------------------------------------------------------------------------- + */ + 'minify_assets' => [ + + // Assets will not be minified. + 'excepts' => [ + + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Enable/Disable sidebar menu search + |-------------------------------------------------------------------------- + */ + 'enable_menu_search' => true, + + /* + |-------------------------------------------------------------------------- + | Alert message that will displayed on top of the page. + |-------------------------------------------------------------------------- + */ + 'top_alert' => '', + + /* + |-------------------------------------------------------------------------- + | The global Grid action display class. + |-------------------------------------------------------------------------- + */ + 'grid_action_class' => \Encore\Admin\Grid\Displayers\DropdownActions::class, + + /* + |-------------------------------------------------------------------------- + | Extension Directory + |-------------------------------------------------------------------------- + | + | When you use command `php artisan admin:extend` to generate extensions, + | the extension files will be generated in this directory. + */ + 'extension_dir' => app_path('Admin/Extensions'), + + /* + |-------------------------------------------------------------------------- + | Settings for extensions. + |-------------------------------------------------------------------------- + | + | You can find all available extensions here + | https://github.com/laravel-admin-extensions. + | + */ + 'extensions' => [ + 'ueditor' => [ + // 如果要关掉这个扩展,设置为false + 'enable' => true, + // 编辑器的前端配置 参考:http://fex.baidu.com/ueditor/#start-config + 'config' => [ + 'initialFrameHeight' => 400, // 例如初始化高度 + ], + 'field_type' => 'ueditor', + ], + ], +]; diff --git a/config/agent.php b/config/agent.php new file mode 100644 index 0000000..6c8951f --- /dev/null +++ b/config/agent.php @@ -0,0 +1,40 @@ + [ + /** + * 可配置 API 独立域名 + */ + 'domain' => env('AGENT_ROUTE_DOMAIN', ''), + /** + * 不实用独立域名,API 地址前缀 + */ + 'prefix' => env('AGENT_ROUTE_PREFIX', 'agent'), + /** + * API 控制器命名空间 + */ + 'namespace' => 'App\\Agent\\Controllers', + /** + * API 路由命名前缀 + */ + 'as' => 'agent.', + /** + * API 默认中间件 + */ + 'middleware' => ['api', 'api.accept'], + /** + * 身份认证的中间件 + */ + 'middleware_auth' => ['api', 'api.accept', 'token.auth'], + /** + * 获取token,获取不到也不报错的中间件 + */ + 'middleware_guess' => ['api', 'api.accept', 'token.guess'], + ], + + /** + * API 目录 + */ + 'directory' => app_path('Agent'), +]; diff --git a/config/api.php b/config/api.php new file mode 100644 index 0000000..b7cc95d --- /dev/null +++ b/config/api.php @@ -0,0 +1,48 @@ + env('TOKEN_AUTO_REVOKE', true), + /** + * token的名称 + */ + 'passport_token_name' => env('PASSPORT_TOKEN_NAME', ''), + + 'route' => [ + /** + * API 路由命名前缀 + */ + 'as' => 'api.', + /** + * 可配置 API 独立域名 + */ + 'domain' => env('API_ROUTE_DOMAIN', ''), + /** + * 不使用用独立域名,API 地址前缀 + */ + 'prefix' => env('API_ROUTE_PREFIX', 'api'), + /** + * API 控制器命名空间 + */ + 'namespace' => 'App\\Api\\Controllers', + /** + * 中间件 + */ + 'middleware' => ['api', 'api.accept'], + /** + * 身份认证的中间件 + */ + 'middleware_auth' => ['api', 'api.accept', 'token.auth'], + /** + * 获取token,获取不到也不报错的中间件 + */ + 'middleware_guess' => ['api', 'api.accept', 'token.guess'], + ], + + /** + * 接口目录 + */ + 'directory' => app_path('Api'), +]; diff --git a/config/app.php b/config/app.php new file mode 100644 index 0000000..e621141 --- /dev/null +++ b/config/app.php @@ -0,0 +1,235 @@ + env('APP_NAME', 'Laravel'), + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + | + | This value determines the "environment" your application is currently + | running in. This may determine how you prefer to configure various + | services the application utilizes. Set this in your ".env" file. + | + */ + + 'env' => env('APP_ENV', 'production'), + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + | + | When your application is in debug mode, detailed error messages with + | stack traces will be shown on every error that occurs within your + | application. If disabled, a simple generic error page is shown. + | + */ + + 'debug' => (bool) env('APP_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | your application so that it is used when running Artisan tasks. + | + */ + + 'url' => env('APP_URL', 'http://localhost'), + + 'asset_url' => env('ASSET_URL', null), + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. We have gone + | ahead and set this to a sensible default for you out of the box. + | + */ + + 'timezone' => 'Asia/Shanghai', + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by the translation service provider. You are free to set this value + | to any of the locales which will be supported by the application. + | + */ + + 'locale' => 'zh_CN', + + /* + |-------------------------------------------------------------------------- + | Application Fallback Locale + |-------------------------------------------------------------------------- + | + | The fallback locale determines the locale to use when the current one + | is not available. You may change the value to correspond to any of + | the language folders that are provided through your application. + | + */ + + 'fallback_locale' => 'en', + + /* + |-------------------------------------------------------------------------- + | Faker Locale + |-------------------------------------------------------------------------- + | + | This locale will be used by the Faker PHP library when generating fake + | data for your database seeds. For example, this will be used to get + | localized telephone numbers, street address information and more. + | + */ + + 'faker_locale' => 'en_US', + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is used by the Illuminate encrypter service and should be set + | to a random, 32 character string, otherwise these encrypted strings + | will not be safe. Please do this before deploying an application! + | + */ + + 'key' => env('APP_KEY'), + + 'cipher' => 'AES-256-CBC', + + /* + |-------------------------------------------------------------------------- + | Autoloaded Service Providers + |-------------------------------------------------------------------------- + | + | The service providers listed here will be automatically loaded on the + | request to your application. Feel free to add your own services to + | this array to grant expanded functionality to your applications. + | + */ + + 'providers' => [ + + /* + * Laravel Framework Service Providers... + */ + Illuminate\Auth\AuthServiceProvider::class, + Illuminate\Broadcasting\BroadcastServiceProvider::class, + Illuminate\Bus\BusServiceProvider::class, + Illuminate\Cache\CacheServiceProvider::class, + Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, + Illuminate\Cookie\CookieServiceProvider::class, + Illuminate\Database\DatabaseServiceProvider::class, + Illuminate\Encryption\EncryptionServiceProvider::class, + Illuminate\Filesystem\FilesystemServiceProvider::class, + Illuminate\Foundation\Providers\FoundationServiceProvider::class, + Illuminate\Hashing\HashServiceProvider::class, + Illuminate\Mail\MailServiceProvider::class, + Illuminate\Notifications\NotificationServiceProvider::class, + Illuminate\Pagination\PaginationServiceProvider::class, + Illuminate\Pipeline\PipelineServiceProvider::class, + Illuminate\Queue\QueueServiceProvider::class, + Illuminate\Redis\RedisServiceProvider::class, + Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, + Illuminate\Session\SessionServiceProvider::class, + Illuminate\Translation\TranslationServiceProvider::class, + Illuminate\Validation\ValidationServiceProvider::class, + Illuminate\View\ViewServiceProvider::class, + + /* + * Package Service Providers... + */ + + /* + * Application Service Providers... + */ + App\Providers\AppServiceProvider::class, + App\Providers\AuthServiceProvider::class, + // App\Providers\BroadcastServiceProvider::class, + App\Providers\EventServiceProvider::class, + App\Providers\RouteServiceProvider::class, + + ], + + /* + |-------------------------------------------------------------------------- + | Class Aliases + |-------------------------------------------------------------------------- + | + | This array of class aliases will be registered when this application + | is started. However, feel free to register as many as you wish as + | the aliases are "lazy" loaded so they don't hinder performance. + | + */ + + 'aliases' => [ + + 'App' => Illuminate\Support\Facades\App::class, + 'Arr' => Illuminate\Support\Arr::class, + 'Artisan' => Illuminate\Support\Facades\Artisan::class, + 'Auth' => Illuminate\Support\Facades\Auth::class, + 'Blade' => Illuminate\Support\Facades\Blade::class, + 'Broadcast' => Illuminate\Support\Facades\Broadcast::class, + 'Bus' => Illuminate\Support\Facades\Bus::class, + 'Cache' => Illuminate\Support\Facades\Cache::class, + 'Config' => Illuminate\Support\Facades\Config::class, + 'Cookie' => Illuminate\Support\Facades\Cookie::class, + 'Crypt' => Illuminate\Support\Facades\Crypt::class, + 'Date' => Illuminate\Support\Facades\Date::class, + 'DB' => Illuminate\Support\Facades\DB::class, + 'Eloquent' => Illuminate\Database\Eloquent\Model::class, + 'Event' => Illuminate\Support\Facades\Event::class, + 'File' => Illuminate\Support\Facades\File::class, + 'Gate' => Illuminate\Support\Facades\Gate::class, + 'Hash' => Illuminate\Support\Facades\Hash::class, + 'Http' => Illuminate\Support\Facades\Http::class, + 'Js' => Illuminate\Support\Js::class, + 'Lang' => Illuminate\Support\Facades\Lang::class, + 'Log' => Illuminate\Support\Facades\Log::class, + 'Mail' => Illuminate\Support\Facades\Mail::class, + 'Notification' => Illuminate\Support\Facades\Notification::class, + 'Password' => Illuminate\Support\Facades\Password::class, + 'Queue' => Illuminate\Support\Facades\Queue::class, + 'RateLimiter' => Illuminate\Support\Facades\RateLimiter::class, + 'Redirect' => Illuminate\Support\Facades\Redirect::class, + // 'Redis' => Illuminate\Support\Facades\Redis::class, + 'Request' => Illuminate\Support\Facades\Request::class, + 'Response' => Illuminate\Support\Facades\Response::class, + 'Route' => Illuminate\Support\Facades\Route::class, + 'Schema' => Illuminate\Support\Facades\Schema::class, + 'Session' => Illuminate\Support\Facades\Session::class, + 'Storage' => Illuminate\Support\Facades\Storage::class, + 'Str' => Illuminate\Support\Str::class, + 'URL' => Illuminate\Support\Facades\URL::class, + 'Validator' => Illuminate\Support\Facades\Validator::class, + 'View' => Illuminate\Support\Facades\View::class, + + ], + +]; diff --git a/config/auth.php b/config/auth.php new file mode 100644 index 0000000..0dc88b3 --- /dev/null +++ b/config/auth.php @@ -0,0 +1,111 @@ + [ + 'guard' => 'web', + 'passwords' => 'users', + ], + + /* + |-------------------------------------------------------------------------- + | Authentication Guards + |-------------------------------------------------------------------------- + | + | Next, you may define every authentication guard for your application. + | Of course, a great default configuration has been defined for you + | here which uses session storage and the Eloquent user provider. + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | Supported: "session" + | + */ + + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + ], + + /* + |-------------------------------------------------------------------------- + | User Providers + |-------------------------------------------------------------------------- + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | If you have multiple user tables or models you may configure multiple + | sources which represent each model / table. These sources may then + | be assigned to any extra authentication guards you have defined. + | + | Supported: "database", "eloquent" + | + */ + + 'providers' => [ + 'users' => [ + 'driver' => 'eloquent', + 'model' => App\Models\User::class, + ], + + // 'users' => [ + // 'driver' => 'database', + // 'table' => 'users', + // ], + ], + + /* + |-------------------------------------------------------------------------- + | Resetting Passwords + |-------------------------------------------------------------------------- + | + | You may specify multiple password reset configurations if you have more + | than one user table or model in the application and you want to have + | separate password reset settings based on the specific user types. + | + | The expire time is the number of minutes that the reset token should be + | considered valid. This security feature keeps tokens short-lived so + | they have less time to be guessed. You may change this as needed. + | + */ + + 'passwords' => [ + 'users' => [ + 'provider' => 'users', + 'table' => 'password_resets', + 'expire' => 60, + 'throttle' => 60, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Password Confirmation Timeout + |-------------------------------------------------------------------------- + | + | Here you may define the amount of seconds before a password confirmation + | times out and the user is prompted to re-enter their password via the + | confirmation screen. By default, the timeout lasts for three hours. + | + */ + + 'password_timeout' => 10800, + +]; diff --git a/config/broadcasting.php b/config/broadcasting.php new file mode 100644 index 0000000..59bccd5 --- /dev/null +++ b/config/broadcasting.php @@ -0,0 +1,64 @@ + env('BROADCAST_DRIVER', 'null'), + + /* + |-------------------------------------------------------------------------- + | Broadcast Connections + |-------------------------------------------------------------------------- + | + | Here you may define all of the broadcast connections that will be used + | to broadcast events to other systems or over websockets. Samples of + | each available type of connection are provided inside this array. + | + */ + + 'connections' => [ + + 'pusher' => [ + 'driver' => 'pusher', + 'key' => env('PUSHER_APP_KEY'), + 'secret' => env('PUSHER_APP_SECRET'), + 'app_id' => env('PUSHER_APP_ID'), + 'options' => [ + 'cluster' => env('PUSHER_APP_CLUSTER'), + 'useTLS' => true, + ], + ], + + 'ably' => [ + 'driver' => 'ably', + 'key' => env('ABLY_KEY'), + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => 'default', + ], + + 'log' => [ + 'driver' => 'log', + ], + + 'null' => [ + 'driver' => 'null', + ], + + ], + +]; diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 0000000..70a2674 --- /dev/null +++ b/config/cache.php @@ -0,0 +1,106 @@ + env('CACHE_DRIVER', 'file'), + + /* + |-------------------------------------------------------------------------- + | Cache Stores + |-------------------------------------------------------------------------- + | + | Here you may define all of the cache "stores" for your application as + | well as their drivers. You may even define multiple stores for the + | same cache driver to group types of items stored in your caches. + | + | Supported drivers: "apc", "array", "database", "file", + | "memcached", "redis", "dynamodb", "null" + | + */ + + 'stores' => [ + + 'apc' => [ + 'driver' => 'apc', + ], + + 'array' => [ + 'driver' => 'array', + 'serialize' => false, + ], + + 'database' => [ + 'driver' => 'database', + 'table' => 'cache', + 'connection' => null, + 'lock_connection' => null, + ], + + 'file' => [ + 'driver' => 'file', + 'path' => storage_path('framework/cache/data'), + ], + + 'memcached' => [ + 'driver' => 'memcached', + 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), + 'sasl' => [ + env('MEMCACHED_USERNAME'), + env('MEMCACHED_PASSWORD'), + ], + 'options' => [ + // Memcached::OPT_CONNECT_TIMEOUT => 2000, + ], + 'servers' => [ + [ + 'host' => env('MEMCACHED_HOST', '127.0.0.1'), + 'port' => env('MEMCACHED_PORT', 11211), + 'weight' => 100, + ], + ], + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => 'cache', + 'lock_connection' => 'default', + ], + + 'dynamodb' => [ + 'driver' => 'dynamodb', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), + 'endpoint' => env('DYNAMODB_ENDPOINT'), + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Cache Key Prefix + |-------------------------------------------------------------------------- + | + | When utilizing a RAM based store such as APC or Memcached, there might + | be other applications utilizing the same cache. So, we'll specify a + | value to get prefixed to all our keys so we can avoid collisions. + | + */ + + 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache'), + +]; diff --git a/config/cors.php b/config/cors.php new file mode 100644 index 0000000..7655fd3 --- /dev/null +++ b/config/cors.php @@ -0,0 +1,34 @@ + ['api/*'], + + 'allowed_methods' => ['*'], + + 'allowed_origins' => ['*'], + + 'allowed_origins_patterns' => [], + + 'allowed_headers' => ['*'], + + 'exposed_headers' => ['Authorization', '*'], + + 'max_age' => 0, + + 'supports_credentials' => false, + +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..b42d9b3 --- /dev/null +++ b/config/database.php @@ -0,0 +1,147 @@ + env('DB_CONNECTION', 'mysql'), + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Here are each of the database connections setup for your application. + | Of course, examples of configuring each database platform that is + | supported by Laravel is shown below to make development simple. + | + | + | All database work in Laravel is done through the PHP PDO facilities + | so make sure you have the driver for your particular database of + | choice installed on your machine before you begin development. + | + */ + + 'connections' => [ + + 'sqlite' => [ + 'driver' => 'sqlite', + 'url' => env('DATABASE_URL'), + 'database' => env('DB_DATABASE', database_path('database.sqlite')), + 'prefix' => '', + 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), + ], + + 'mysql' => [ + 'driver' => 'mysql', + 'url' => env('DATABASE_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'url' => env('DATABASE_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => '', + 'prefix_indexes' => true, + 'schema' => 'public', + 'sslmode' => 'prefer', + ], + + 'sqlsrv' => [ + 'driver' => 'sqlsrv', + 'url' => env('DATABASE_URL'), + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', '1433'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => '', + 'prefix_indexes' => true, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk haven't actually been run in the database. + | + */ + + 'migrations' => 'migrations', + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer body of commands than a typical key-value system + | such as APC or Memcached. Laravel makes it easy to dig right in. + | + */ + + 'redis' => [ + + 'client' => env('REDIS_CLIENT', 'phpredis'), + + 'options' => [ + 'cluster' => env('REDIS_CLUSTER', 'redis'), + 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), + ], + + 'default' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'password' => env('REDIS_PASSWORD', null), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_DB', '0'), + ], + + 'cache' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'password' => env('REDIS_PASSWORD', null), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_CACHE_DB', '1'), + ], + + ], + +]; diff --git a/config/filesystems.php b/config/filesystems.php new file mode 100644 index 0000000..5b2058f --- /dev/null +++ b/config/filesystems.php @@ -0,0 +1,82 @@ + env('FILESYSTEM_DRIVER', 'local'), + + /* + |-------------------------------------------------------------------------- + | Filesystem Disks + |-------------------------------------------------------------------------- + | + | Here you may configure as many filesystem "disks" as you wish, and you + | may even configure multiple disks of the same driver. Defaults have + | been setup for each driver as an example of the required options. + | + | Supported Drivers: "local", "ftp", "sftp", "s3" + | + */ + + 'disks' => [ + + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app'), + ], + + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL').'/storage', + 'visibility' => 'public', + ], + + 's3' => [ + 'driver' => 's3', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION'), + 'bucket' => env('AWS_BUCKET'), + 'url' => env('AWS_URL'), + 'endpoint' => env('AWS_ENDPOINT'), + ], + + 'oss' => [ + 'driver' => 'oss', + 'root' => '', + 'access_key' => env('OSS_ACCESS_KEY'), + 'secret_key' => env('OSS_SECRET_KEY'), + 'endpoint' => env('OSS_ENDPOINT'), + 'bucket' => env('OSS_BUCKET'), + 'isCName' => env('OSS_IS_CNAME', false), + 'cdnHost' => env('OSS_CDN_HOST'), + ], + ], + + /* + |-------------------------------------------------------------------------- + | Symbolic Links + |-------------------------------------------------------------------------- + | + | Here you may configure the symbolic links that will be created when the + | `storage:link` Artisan command is executed. The array keys should be + | the locations of the links and the values should be their targets. + | + */ + + 'links' => [ + public_path('storage') => storage_path('app/public'), + ], + +]; diff --git a/config/hashing.php b/config/hashing.php new file mode 100644 index 0000000..5b10c09 --- /dev/null +++ b/config/hashing.php @@ -0,0 +1,52 @@ + 'bcrypt', + + /* + |-------------------------------------------------------------------------- + | Bcrypt Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Bcrypt algorithm. This will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'bcrypt' => [ + 'rounds' => env('BCRYPT_ROUNDS', 10), + ], + + /* + |-------------------------------------------------------------------------- + | Argon Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Argon algorithm. These will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'argon' => [ + 'memory' => 1024, + 'threads' => 2, + 'time' => 2, + ], + +]; diff --git a/config/image.php b/config/image.php new file mode 100644 index 0000000..2b1d2c3 --- /dev/null +++ b/config/image.php @@ -0,0 +1,20 @@ + 'gd' + +]; diff --git a/config/laravel-model-caching.php b/config/laravel-model-caching.php new file mode 100644 index 0000000..cda02fb --- /dev/null +++ b/config/laravel-model-caching.php @@ -0,0 +1,11 @@ + '', + + 'enabled' => env('MODEL_CACHE_ENABLED', true), + + 'use-database-keying' => env('MODEL_CACHE_USE_DATABASE_KEYING', true), + + 'store' => env('MODEL_CACHE_STORE'), +]; diff --git a/config/logging.php b/config/logging.php new file mode 100644 index 0000000..8a5c7d7 --- /dev/null +++ b/config/logging.php @@ -0,0 +1,117 @@ + env('LOG_CHANNEL', 'stack'), + + /* + |-------------------------------------------------------------------------- + | Deprecations Log Channel + |-------------------------------------------------------------------------- + | + | This option controls the log channel that should be used to log warnings + | regarding deprecated PHP and library features. This allows you to get + | your application ready for upcoming major versions of dependencies. + | + */ + + 'deprecations' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + + /* + |-------------------------------------------------------------------------- + | Log Channels + |-------------------------------------------------------------------------- + | + | Here you may configure the log channels for your application. Out of + | the box, Laravel uses the Monolog PHP logging library. This gives + | you a variety of powerful log handlers / formatters to utilize. + | + | Available Drivers: "single", "daily", "slack", "syslog", + | "errorlog", "monolog", + | "custom", "stack" + | + */ + + 'channels' => [ + 'stack' => [ + 'driver' => 'stack', + 'channels' => ['daily'], + 'ignore_exceptions' => false, + ], + + 'single' => [ + 'driver' => 'single', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + ], + + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => 14, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => 'Laravel Log', + 'emoji' => ':boom:', + 'level' => env('LOG_LEVEL', 'critical'), + ], + + 'papertrail' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => SyslogUdpHandler::class, + 'handler_with' => [ + 'host' => env('PAPERTRAIL_URL'), + 'port' => env('PAPERTRAIL_PORT'), + ], + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'handler' => StreamHandler::class, + 'formatter' => env('LOG_STDERR_FORMATTER'), + 'with' => [ + 'stream' => 'php://stderr', + ], + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => env('LOG_LEVEL', 'debug'), + ], + + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => env('LOG_LEVEL', 'debug'), + ], + + 'null' => [ + 'driver' => 'monolog', + 'handler' => NullHandler::class, + ], + + 'emergency' => [ + 'path' => storage_path('logs/laravel.log'), + ], + ], + +]; diff --git a/config/mail.php b/config/mail.php new file mode 100644 index 0000000..e4b7258 --- /dev/null +++ b/config/mail.php @@ -0,0 +1,110 @@ + env('MAIL_MAILER', 'smtp'), + + /* + |-------------------------------------------------------------------------- + | Mailer Configurations + |-------------------------------------------------------------------------- + | + | Here you may configure all of the mailers used by your application plus + | their respective settings. Several examples have been configured for + | you and you are free to add your own as your application requires. + | + | Laravel supports a variety of mail "transport" drivers to be used while + | sending an e-mail. You will specify which one you are using for your + | mailers below. You are free to add additional mailers as required. + | + | Supported: "smtp", "sendmail", "mailgun", "ses", + | "postmark", "log", "array" + | + */ + + 'mailers' => [ + 'smtp' => [ + 'transport' => 'smtp', + 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), + 'port' => env('MAIL_PORT', 587), + 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + 'username' => env('MAIL_USERNAME'), + 'password' => env('MAIL_PASSWORD'), + 'timeout' => null, + 'auth_mode' => null, + ], + + 'ses' => [ + 'transport' => 'ses', + ], + + 'mailgun' => [ + 'transport' => 'mailgun', + ], + + 'postmark' => [ + 'transport' => 'postmark', + ], + + 'sendmail' => [ + 'transport' => 'sendmail', + 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -t -i'), + ], + + 'log' => [ + 'transport' => 'log', + 'channel' => env('MAIL_LOG_CHANNEL'), + ], + + 'array' => [ + 'transport' => 'array', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Global "From" Address + |-------------------------------------------------------------------------- + | + | You may wish for all e-mails sent by your application to be sent from + | the same address. Here, you may specify a name and address that is + | used globally for all e-mails that are sent by your application. + | + */ + + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', 'Example'), + ], + + /* + |-------------------------------------------------------------------------- + | Markdown Mail Settings + |-------------------------------------------------------------------------- + | + | If you are using Markdown based email rendering, you may configure your + | theme and component paths here, allowing you to customize the design + | of the emails. Or, you may simply stick with the Laravel defaults! + | + */ + + 'markdown' => [ + 'theme' => 'default', + + 'paths' => [ + resource_path('views/vendor/mail'), + ], + ], + +]; diff --git a/config/modules.php b/config/modules.php new file mode 100644 index 0000000..b81ed11 --- /dev/null +++ b/config/modules.php @@ -0,0 +1,272 @@ + 'Modules', + + /* + |-------------------------------------------------------------------------- + | Module Stubs + |-------------------------------------------------------------------------- + | + | Default module stubs. + | + */ + + 'stubs' => [ + 'enabled' => false, + 'path' => base_path() . '/vendor/nwidart/laravel-modules/src/Commands/stubs', + 'files' => [ + 'routes/web' => 'Routes/web.php', + 'routes/api' => 'Routes/api.php', + 'views/index' => 'Resources/views/index.blade.php', + 'views/master' => 'Resources/views/layouts/master.blade.php', + 'scaffold/config' => 'Config/config.php', + 'composer' => 'composer.json', + 'assets/js/app' => 'Resources/assets/js/app.js', + 'assets/sass/app' => 'Resources/assets/sass/app.scss', + 'webpack' => 'webpack.mix.js', + 'package' => 'package.json', + ], + 'replacements' => [ + 'routes/web' => ['LOWER_NAME', 'STUDLY_NAME'], + 'routes/api' => ['LOWER_NAME'], + 'webpack' => ['LOWER_NAME'], + 'json' => ['LOWER_NAME', 'STUDLY_NAME', 'MODULE_NAMESPACE', 'PROVIDER_NAMESPACE'], + 'views/index' => ['LOWER_NAME'], + 'views/master' => ['LOWER_NAME', 'STUDLY_NAME'], + 'scaffold/config' => ['STUDLY_NAME'], + 'composer' => [ + 'LOWER_NAME', + 'STUDLY_NAME', + 'VENDOR', + 'AUTHOR_NAME', + 'AUTHOR_EMAIL', + 'MODULE_NAMESPACE', + 'PROVIDER_NAMESPACE', + ], + ], + 'gitkeep' => false, + ], + 'paths' => [ + /* + |-------------------------------------------------------------------------- + | Modules path + |-------------------------------------------------------------------------- + | + | This path used for save the generated module. This path also will be added + | automatically to list of scanned folders. + | + */ + + 'modules' => base_path('modules'), + /* + |-------------------------------------------------------------------------- + | Modules assets path + |-------------------------------------------------------------------------- + | + | Here you may update the modules assets path. + | + */ + + 'assets' => public_path('modules'), + /* + |-------------------------------------------------------------------------- + | The migrations path + |-------------------------------------------------------------------------- + | + | Where you run 'module:publish-migration' command, where do you publish the + | the migration files? + | + */ + + 'migration' => base_path('database/migrations'), + /* + |-------------------------------------------------------------------------- + | Generator path + |-------------------------------------------------------------------------- + | Customise the paths where the folders will be generated. + | Set the generate key to false to not generate that folder + */ + 'generator' => [ + 'config' => ['path' => 'Config', 'generate' => true], + 'command' => ['path' => 'Console', 'generate' => true], + 'migration' => ['path' => 'Database/Migrations', 'generate' => true], + 'seeder' => ['path' => 'Database/Seeders', 'generate' => true], + 'factory' => ['path' => 'Database/factories', 'generate' => true], + 'model' => ['path' => 'Models', 'generate' => true], + 'routes' => ['path' => 'Routes', 'generate' => true], + 'controller' => ['path' => 'Http/Controllers', 'generate' => true], + 'filter' => ['path' => 'Http/Middleware', 'generate' => true], + 'request' => ['path' => 'Http/Requests', 'generate' => true], + 'provider' => ['path' => 'Providers', 'generate' => true], + 'assets' => ['path' => 'Resources/assets', 'generate' => false], + 'lang' => ['path' => 'Resources/lang', 'generate' => true], + 'views' => ['path' => 'Resources/views', 'generate' => false], + 'test' => ['path' => 'Tests/Unit', 'generate' => false], + 'test-feature' => ['path' => 'Tests/Feature', 'generate' => false], + 'repository' => ['path' => 'Http/Resources', 'generate' => true], + 'event' => ['path' => 'Events', 'generate' => true], + 'listener' => ['path' => 'Listeners', 'generate' => true], + 'policies' => ['path' => 'Policies', 'generate' => false], + 'rules' => ['path' => 'Rules', 'generate' => false], + 'jobs' => ['path' => 'Jobs', 'generate' => true], + 'emails' => ['path' => 'Emails', 'generate' => false], + 'notifications' => ['path' => 'Notifications', 'generate' => false], + 'resource' => ['path' => 'Transformers', 'generate' => false], + 'component-view' => ['path' => 'Resources/views/components', 'generate' => false], + 'component-class' => ['path' => 'View/Component', 'generate' => false], + ], + ], + + /* + |-------------------------------------------------------------------------- + | Package commands + |-------------------------------------------------------------------------- + | + | Here you can define which commands will be visible and used in your + | application. If for example you don't use some of the commands provided + | you can simply comment them out. + | + */ + 'commands' => [ + Commands\CommandMakeCommand::class, + Commands\ControllerMakeCommand::class, + Commands\DisableCommand::class, + Commands\DumpCommand::class, + Commands\EnableCommand::class, + Commands\EventMakeCommand::class, + Commands\JobMakeCommand::class, + Commands\ListenerMakeCommand::class, + Commands\MailMakeCommand::class, + Commands\MiddlewareMakeCommand::class, + Commands\NotificationMakeCommand::class, + Commands\ProviderMakeCommand::class, + Commands\RouteProviderMakeCommand::class, + Commands\InstallCommand::class, + Commands\ListCommand::class, + Commands\ModuleDeleteCommand::class, + Commands\ModuleMakeCommand::class, + Commands\FactoryMakeCommand::class, + Commands\PolicyMakeCommand::class, + Commands\RequestMakeCommand::class, + Commands\RuleMakeCommand::class, + Commands\MigrateCommand::class, + Commands\MigrateRefreshCommand::class, + Commands\MigrateResetCommand::class, + Commands\MigrateRollbackCommand::class, + Commands\MigrateStatusCommand::class, + Commands\MigrationMakeCommand::class, + Commands\ModelMakeCommand::class, + Commands\PublishCommand::class, + Commands\PublishConfigurationCommand::class, + Commands\PublishMigrationCommand::class, + Commands\PublishTranslationCommand::class, + Commands\SeedCommand::class, + Commands\SeedMakeCommand::class, + Commands\SetupCommand::class, + Commands\UnUseCommand::class, + Commands\UpdateCommand::class, + Commands\UseCommand::class, + Commands\ResourceMakeCommand::class, + Commands\TestMakeCommand::class, + Commands\LaravelModulesV6Migrator::class, + ], + + /* + |-------------------------------------------------------------------------- + | Scan Path + |-------------------------------------------------------------------------- + | + | Here you define which folder will be scanned. By default will scan vendor + | directory. This is useful if you host the package in packagist website. + | + */ + + 'scan' => [ + 'enabled' => false, + 'paths' => [ + base_path('vendor/*/*'), + ], + ], + /* + |-------------------------------------------------------------------------- + | Composer File Template + |-------------------------------------------------------------------------- + | + | Here is the config for composer.json file, generated by this package + | + */ + + 'composer' => [ + 'vendor' => 'jasonc', + 'author' => [ + 'name' => 'Jason.Chen', + 'email' => 'chenjxlg@163.com', + ], + ], + + 'composer-output' => true, + + /* + |-------------------------------------------------------------------------- + | Caching + |-------------------------------------------------------------------------- + | + | Here is the config for setting up caching feature. + | + */ + 'cache' => [ + 'enabled' => false, + 'key' => 'laravel-modules', + 'lifetime' => 60, + ], + /* + |-------------------------------------------------------------------------- + | Choose what laravel-modules will register as custom namespaces. + | Setting one to false will require you to register that part + | in your own Service Provider class. + |-------------------------------------------------------------------------- + */ + 'register' => [ + 'translations' => true, + /** + * load files on boot or register method + * Note: boot not compatible with asgardcms + * @example boot|register + */ + 'files' => 'register', + ], + + /* + |-------------------------------------------------------------------------- + | Activators + |-------------------------------------------------------------------------- + | + | You can define new types of activators here, file, database etc. The only + | required parameter is 'class'. + | The file activator will store the activation status in storage/installed_modules + */ + 'activators' => [ + 'file' => [ + 'class' => FileActivator::class, + 'statuses-file' => base_path('modules.json'), + 'cache-key' => 'activator.installed', + 'cache-lifetime' => 604800, + ], + ], + + 'activator' => 'file', +]; diff --git a/config/queue.php b/config/queue.php new file mode 100644 index 0000000..84f7438 --- /dev/null +++ b/config/queue.php @@ -0,0 +1,93 @@ + env('QUEUE_CONNECTION', 'sync'), + + /* + |-------------------------------------------------------------------------- + | Queue Connections + |-------------------------------------------------------------------------- + | + | Here you may configure the connection information for each server that + | is used by your application. A default configuration has been added + | for each back-end shipped with Laravel. You are free to add more. + | + | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" + | + */ + + 'connections' => [ + + 'sync' => [ + 'driver' => 'sync', + ], + + 'database' => [ + 'driver' => 'database', + 'table' => 'jobs', + 'queue' => 'default', + 'retry_after' => 90, + 'after_commit' => false, + ], + + 'beanstalkd' => [ + 'driver' => 'beanstalkd', + 'host' => 'localhost', + 'queue' => 'default', + 'retry_after' => 90, + 'block_for' => 0, + 'after_commit' => false, + ], + + 'sqs' => [ + 'driver' => 'sqs', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), + 'queue' => env('SQS_QUEUE', 'default'), + 'suffix' => env('SQS_SUFFIX'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'after_commit' => false, + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => 'default', + 'queue' => env('REDIS_QUEUE', 'default'), + 'retry_after' => 90, + 'block_for' => null, + 'after_commit' => false, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Failed Queue Jobs + |-------------------------------------------------------------------------- + | + | These options configure the behavior of failed queue job logging so you + | can control which database and table are used to store the jobs that + | have failed. You may change them to any database / table you wish. + | + */ + + 'failed' => [ + 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), + 'database' => env('DB_CONNECTION', 'mysql'), + 'table' => 'failed_jobs', + ], + +]; diff --git a/config/sanctum.php b/config/sanctum.php new file mode 100644 index 0000000..0fefc04 --- /dev/null +++ b/config/sanctum.php @@ -0,0 +1,65 @@ + explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( + '%s%s', + 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', + env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : '' + ))), + + /* + |-------------------------------------------------------------------------- + | Sanctum Guards + |-------------------------------------------------------------------------- + | + | This array contains the authentication guards that will be checked when + | Sanctum is trying to authenticate a request. If none of these guards + | are able to authenticate the request, Sanctum will use the bearer + | token that's present on an incoming request for authentication. + | + */ + + 'guard' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Expiration Minutes + |-------------------------------------------------------------------------- + | + | This value controls the number of minutes until an issued token will be + | considered expired. If this value is null, personal access tokens do + | not expire. This won't tweak the lifetime of first-party sessions. + | + */ + + 'expiration' => null, + + /* + |-------------------------------------------------------------------------- + | Sanctum Middleware + |-------------------------------------------------------------------------- + | + | When authenticating your first-party SPA with Sanctum you may need to + | customize some of the middleware Sanctum uses while processing the + | request. You may change the middleware listed below as required. + | + */ + + 'middleware' => [ + 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, + 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, + ], + +]; \ No newline at end of file diff --git a/config/services.php b/config/services.php new file mode 100644 index 0000000..3215b42 --- /dev/null +++ b/config/services.php @@ -0,0 +1,33 @@ + [ + 'domain' => env('MAILGUN_DOMAIN'), + 'secret' => env('MAILGUN_SECRET'), + 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), + ], + + 'postmark' => [ + 'token' => env('POSTMARK_TOKEN'), + ], + + 'ses' => [ + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + ], + +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..c0e798b --- /dev/null +++ b/config/session.php @@ -0,0 +1,201 @@ + env('SESSION_DRIVER', 'file'), + + /* + |-------------------------------------------------------------------------- + | Session Lifetime + |-------------------------------------------------------------------------- + | + | Here you may specify the number of minutes that you wish the session + | to be allowed to remain idle before it expires. If you want them + | to immediately expire on the browser closing, set that option. + | + */ + + 'lifetime' => env('SESSION_LIFETIME', 120), + + 'expire_on_close' => false, + + /* + |-------------------------------------------------------------------------- + | Session Encryption + |-------------------------------------------------------------------------- + | + | This option allows you to easily specify that all of your session data + | should be encrypted before it is stored. All encryption will be run + | automatically by Laravel and you can use the Session like normal. + | + */ + + 'encrypt' => false, + + /* + |-------------------------------------------------------------------------- + | Session File Location + |-------------------------------------------------------------------------- + | + | When using the native session driver, we need a location where session + | files may be stored. A default has been set for you but a different + | location may be specified. This is only needed for file sessions. + | + */ + + 'files' => storage_path('framework/sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Database Connection + |-------------------------------------------------------------------------- + | + | When using the "database" or "redis" session drivers, you may specify a + | connection that should be used to manage these sessions. This should + | correspond to a connection in your database configuration options. + | + */ + + 'connection' => env('SESSION_CONNECTION', null), + + /* + |-------------------------------------------------------------------------- + | Session Database Table + |-------------------------------------------------------------------------- + | + | When using the "database" session driver, you may specify the table we + | should use to manage the sessions. Of course, a sensible default is + | provided for you; however, you are free to change this as needed. + | + */ + + 'table' => 'sessions', + + /* + |-------------------------------------------------------------------------- + | Session Cache Store + |-------------------------------------------------------------------------- + | + | While using one of the framework's cache driven session backends you may + | list a cache store that should be used for these sessions. This value + | must match with one of the application's configured cache "stores". + | + | Affects: "apc", "dynamodb", "memcached", "redis" + | + */ + + 'store' => env('SESSION_STORE', null), + + /* + |-------------------------------------------------------------------------- + | Session Sweeping Lottery + |-------------------------------------------------------------------------- + | + | Some session drivers must manually sweep their storage location to get + | rid of old sessions from storage. Here are the chances that it will + | happen on a given request. By default, the odds are 2 out of 100. + | + */ + + 'lottery' => [2, 100], + + /* + |-------------------------------------------------------------------------- + | Session Cookie Name + |-------------------------------------------------------------------------- + | + | Here you may change the name of the cookie used to identify a session + | instance by ID. The name specified here will get used every time a + | new session cookie is created by the framework for every driver. + | + */ + + 'cookie' => env( + 'SESSION_COOKIE', + Str::slug(env('APP_NAME', 'laravel'), '_') . '_session' + ), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Path + |-------------------------------------------------------------------------- + | + | The session cookie path determines the path for which the cookie will + | be regarded as available. Typically, this will be the root path of + | your application but you are free to change this when necessary. + | + */ + + 'path' => '/', + + /* + |-------------------------------------------------------------------------- + | Session Cookie Domain + |-------------------------------------------------------------------------- + | + | Here you may change the domain of the cookie used to identify a session + | in your application. This will determine which domains the cookie is + | available to in your application. A sensible default has been set. + | + */ + + 'domain' => env('SESSION_DOMAIN', null), + + /* + |-------------------------------------------------------------------------- + | HTTPS Only Cookies + |-------------------------------------------------------------------------- + | + | By setting this option to true, session cookies will only be sent back + | to the server if the browser has a HTTPS connection. This will keep + | the cookie from being sent to you if it can not be done securely. + | + */ + + 'secure' => env('SESSION_SECURE_COOKIE'), + + /* + |-------------------------------------------------------------------------- + | HTTP Access Only + |-------------------------------------------------------------------------- + | + | Setting this value to true will prevent JavaScript from accessing the + | value of the cookie and the cookie will only be accessible through + | the HTTP protocol. You are free to modify this option if needed. + | + */ + + 'http_only' => true, + + /* + |-------------------------------------------------------------------------- + | Same-Site Cookies + |-------------------------------------------------------------------------- + | + | This option determines how your cookies behave when cross-site requests + | take place, and can be used to mitigate CSRF attacks. By default, we + | will set this value to "lax" since this is a secure default value. + | + | Supported: "lax", "strict", "none", null + | + */ + + 'same_site' => 'lax', + +]; diff --git a/config/ueditor.php b/config/ueditor.php new file mode 100644 index 0000000..acbc0d4 --- /dev/null +++ b/config/ueditor.php @@ -0,0 +1,118 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +return [ + 'hash_filename' => true, + // 存储引擎: config/filesystem.php 中 disks, public 或 qiniu + 'disk' => env('FILESYSTEM_DRIVER', 'local'), + 'route' => [ + 'name' => '/ueditor/server', + 'options' => [ + // middleware => 'auth', + ], + ], + + // 上传 配置 + 'upload' => [ + /* 前后端通信相关的配置,注释只允许使用多行方式 */ + /* 上传图片配置项 */ + 'imageActionName' => 'upload-image', /* 执行上传图片的action名称 */ + 'imageFieldName' => 'upfile', /* 提交的图片表单名称 */ + 'imageMaxSize' => 2 * 1024 * 1024, /* 上传大小限制,单位B */ + 'imageAllowFiles' => ['.png', '.jpg', '.jpeg', '.gif', '.bmp'], /* 上传图片格式显示 */ + 'imageCompressEnable' => true, /* 是否压缩图片,默认是true */ + 'imageCompressBorder' => 1600, /* 图片压缩最长边限制 */ + 'imageInsertAlign' => 'none', /* 插入的图片浮动方式 */ + 'imageUrlPrefix' => '', /* 图片访问路径前缀 */ + 'imagePathFormat' => '/uploads/images/{yyyy}/{mm}/{dd}/', /* 上传保存路径,可以自定义保存路径和文件名格式 */ + /* {filename} 会替换成原文件名,配置这项需要注意中文乱码问题 */ + /* {rand:6} 会替换成随机数,后面的数字是随机数的位数 */ + /* {time} 会替换成时间戳 */ + /* {yyyy} 会替换成四位年份 */ + /* {yy} 会替换成两位年份 */ + /* {mm} 会替换成两位月份 */ + /* {dd} 会替换成两位日期 */ + /* {hh} 会替换成两位小时 */ + /* {ii} 会替换成两位分钟 */ + /* {ss} 会替换成两位秒 */ + /* 非法字符 \ => * ? " < > | */ + /* 具请体看线上文档 => fex.baidu.com/assets/#use-format_upload_filename */ + + /* 涂鸦图片上传配置项 */ + 'scrawlActionName' => 'upload-scrawl', /* 执行上传涂鸦的action名称 */ + 'scrawlFieldName' => 'upfile', /* 提交的图片表单名称 */ + 'scrawlPathFormat' => '/uploads/images/{yyyy}/{mm}/{dd}/', /* 上传保存路径,可以自定义保存路径和文件名格式 */ + 'scrawlMaxSize' => 2048000, /* 上传大小限制,单位B */ + 'scrawlUrlPrefix' => '', /* 图片访问路径前缀 */ + 'scrawlInsertAlign' => 'none', + + /* 截图工具上传 */ + 'snapscreenActionName' => 'upload-image', /* 执行上传截图的action名称 */ + 'snapscreenPathFormat' => '/uploads/images/{yyyy}/{mm}/{dd}/', /* 上传保存路径,可以自定义保存路径和文件名格式 */ + 'snapscreenUrlPrefix' => '', /* 图片访问路径前缀 */ + 'snapscreenInsertAlign' => 'none', /* 插入的图片浮动方式 */ + + /* 抓取远程图片配置 */ + 'catcherLocalDomain' => ['127.0.0.1', 'localhost', 'img.baidu.com'], + 'catcherActionName' => 'catch-image', /* 执行抓取远程图片的action名称 */ + 'catcherFieldName' => 'source', /* 提交的图片列表表单名称 */ + 'catcherPathFormat' => '/uploads/images/{yyyy}/{mm}/{dd}/', /* 上传保存路径,可以自定义保存路径和文件名格式 */ + 'catcherUrlPrefix' => '', /* 图片访问路径前缀 */ + 'catcherMaxSize' => 2048000, /* 上传大小限制,单位B */ + 'catcherAllowFiles' => ['.png', '.jpg', '.jpeg', '.gif', '.bmp'], /* 抓取图片格式显示 */ + + /* 上传视频配置 */ + 'videoActionName' => 'upload-video', /* 执行上传视频的action名称 */ + 'videoFieldName' => 'upfile', /* 提交的视频表单名称 */ + 'videoPathFormat' => '/uploads/videos/{yyyy}/{mm}/{dd}/', /* 上传保存路径,可以自定义保存路径和文件名格式 */ + 'videoUrlPrefix' => '', /* 视频访问路径前缀 */ + 'videoMaxSize' => 102400000, /* 上传大小限制,单位B,默认100MB */ + 'videoAllowFiles' => [ + '.flv', '.swf', '.mkv', '.avi', '.rm', '.rmvb', '.mpeg', '.mpg', + '.ogg', '.ogv', '.mov', '.wmv', '.mp4', '.webm', '.mp3', '.wav', '.mid', + ], /* 上传视频格式显示 */ + + /* 上传文件配置 */ + 'fileActionName' => 'upload-file', /* controller里,执行上传视频的action名称 */ + 'fileFieldName' => 'upfile', /* 提交的文件表单名称 */ + 'filePathFormat' => '/uploads/files/{yyyy}/{mm}/{dd}/', /* 上传保存路径,可以自定义保存路径和文件名格式 */ + 'fileUrlPrefix' => '', /* 文件访问路径前缀 */ + 'fileMaxSize' => 51200000, /* 上传大小限制,单位B,默认50MB */ + 'fileAllowFiles' => [ + '.png', '.jpg', '.jpeg', '.gif', '.bmp', + '.flv', '.swf', '.mkv', '.avi', '.rm', '.rmvb', '.mpeg', '.mpg', + '.ogg', '.ogv', '.mov', '.wmv', '.mp4', '.webm', '.mp3', '.wav', '.mid', + '.rar', '.zip', '.tar', '.gz', '.7z', '.bz2', '.cab', '.iso', + '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.pdf', '.txt', '.md', '.xml', + ], /* 上传文件格式显示 */ + + /* 列出指定目录下的图片 */ + 'imageManagerActionName' => 'list-image', /* 执行图片管理的action名称 */ + 'imageManagerListPath' => '/uploads/images/', /* 指定要列出图片的目录 */ + 'imageManagerListSize' => 20, /* 每次列出文件数量 */ + 'imageManagerUrlPrefix' => '', /* 图片访问路径前缀 */ + 'imageManagerInsertAlign' => 'none', /* 插入的图片浮动方式 */ + 'imageManagerAllowFiles' => ['.png', '.jpg', '.jpeg', '.gif', '.bmp'], /* 列出的文件类型 */ + + /* 列出指定目录下的文件 */ + 'fileManagerActionName' => 'list-file', /* 执行文件管理的action名称 */ + 'fileManagerListPath' => '/uploads/files/', /* 指定要列出文件的目录 */ + 'fileManagerUrlPrefix' => '', /* 文件访问路径前缀 */ + 'fileManagerListSize' => 20, /* 每次列出文件数量 */ + 'fileManagerAllowFiles' => [ + '.png', '.jpg', '.jpeg', '.gif', '.bmp', + '.flv', '.swf', '.mkv', '.avi', '.rm', '.rmvb', '.mpeg', '.mpg', + '.ogg', '.ogv', '.mov', '.wmv', '.mp4', '.webm', '.mp3', '.wav', '.mid', + '.rar', '.zip', '.tar', '.gz', '.7z', '.bz2', '.cab', '.iso', + '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.pdf', '.txt', '.md', '.xml', + ], /* 列出的文件类型 */ + ], +]; diff --git a/config/view.php b/config/view.php new file mode 100644 index 0000000..22b8a18 --- /dev/null +++ b/config/view.php @@ -0,0 +1,36 @@ + [ + resource_path('views'), + ], + + /* + |-------------------------------------------------------------------------- + | Compiled View Path + |-------------------------------------------------------------------------- + | + | This option determines where all the compiled Blade templates will be + | stored for your application. Typically, this is within the storage + | directory. However, as usual, you are free to change this value. + | + */ + + 'compiled' => env( + 'VIEW_COMPILED_PATH', + realpath(storage_path('framework/views')) + ), + +]; diff --git a/database/.gitignore b/database/.gitignore new file mode 100644 index 0000000..97fc976 --- /dev/null +++ b/database/.gitignore @@ -0,0 +1,2 @@ +*.sqlite +*.sqlite-journal diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php new file mode 100644 index 0000000..98604f8 --- /dev/null +++ b/database/factories/UserFactory.php @@ -0,0 +1,39 @@ + $this->faker->name, + 'email' => $this->faker->unique()->safeEmail, + 'email_verified_at' => now(), + 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password + 'remember_token' => Str::random(10), + ]; + } + + /** + * Indicate that the model's email address should be unverified. + * + * @return \Illuminate\Database\Eloquent\Factories\Factory + */ + public function unverified() + { + return $this->state(function (array $attributes) { + return [ + 'email_verified_at' => null, + ]; + }); + } +} diff --git a/database/migrations/2016_01_04_173148_create_admin_tables.php b/database/migrations/2016_01_04_173148_create_admin_tables.php new file mode 100644 index 0000000..450847e --- /dev/null +++ b/database/migrations/2016_01_04_173148_create_admin_tables.php @@ -0,0 +1,119 @@ +increments('id'); + $table->string('username', 190)->unique(); + $table->string('password', 60); + $table->string('name'); + $table->string('avatar')->nullable(); + $table->string('remember_token', 100)->nullable(); + $table->timestamps(); + }); + + Schema::create(config('admin.database.roles_table'), function (Blueprint $table) { + $table->increments('id'); + $table->string('name', 50)->unique(); + $table->string('slug', 50)->unique(); + $table->timestamps(); + }); + + Schema::create(config('admin.database.permissions_table'), function (Blueprint $table) { + $table->increments('id'); + $table->string('name', 50)->unique(); + $table->string('slug', 50)->unique(); + $table->string('http_method')->nullable(); + $table->text('http_path')->nullable(); + $table->timestamps(); + }); + + Schema::create(config('admin.database.menu_table'), function (Blueprint $table) { + $table->increments('id'); + $table->integer('parent_id')->default(0); + $table->integer('order')->default(0); + $table->string('title', 50); + $table->string('icon', 50); + $table->string('uri')->nullable(); + $table->string('permission')->nullable(); + + $table->timestamps(); + }); + + Schema::create(config('admin.database.role_users_table'), function (Blueprint $table) { + $table->integer('role_id'); + $table->integer('user_id'); + $table->index(['role_id', 'user_id']); + $table->timestamps(); + }); + + Schema::create(config('admin.database.role_permissions_table'), function (Blueprint $table) { + $table->integer('role_id'); + $table->integer('permission_id'); + $table->index(['role_id', 'permission_id']); + $table->timestamps(); + }); + + Schema::create(config('admin.database.user_permissions_table'), function (Blueprint $table) { + $table->integer('user_id'); + $table->integer('permission_id'); + $table->index(['user_id', 'permission_id']); + $table->timestamps(); + }); + + Schema::create(config('admin.database.role_menu_table'), function (Blueprint $table) { + $table->integer('role_id'); + $table->integer('menu_id'); + $table->index(['role_id', 'menu_id']); + $table->timestamps(); + }); + + Schema::create(config('admin.database.operation_log_table'), function (Blueprint $table) { + $table->increments('id'); + $table->integer('user_id'); + $table->string('path'); + $table->string('method', 10); + $table->string('ip'); + $table->text('input'); + $table->index('user_id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists(config('admin.database.users_table')); + Schema::dropIfExists(config('admin.database.roles_table')); + Schema::dropIfExists(config('admin.database.permissions_table')); + Schema::dropIfExists(config('admin.database.menu_table')); + Schema::dropIfExists(config('admin.database.user_permissions_table')); + Schema::dropIfExists(config('admin.database.role_users_table')); + Schema::dropIfExists(config('admin.database.role_permissions_table')); + Schema::dropIfExists(config('admin.database.role_menu_table')); + Schema::dropIfExists(config('admin.database.operation_log_table')); + } +} diff --git a/database/migrations/2019_08_19_000000_create_failed_jobs_table.php b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php new file mode 100644 index 0000000..6aa6d74 --- /dev/null +++ b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php @@ -0,0 +1,36 @@ +id(); + $table->string('uuid')->unique(); + $table->text('connection'); + $table->text('queue'); + $table->longText('payload'); + $table->longText('exception'); + $table->timestamp('failed_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('failed_jobs'); + } +} diff --git a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php new file mode 100644 index 0000000..3ce0002 --- /dev/null +++ b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php @@ -0,0 +1,36 @@ +bigIncrements('id'); + $table->morphs('tokenable'); + $table->string('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('personal_access_tokens'); + } +} diff --git a/database/migrations/2021_11_10_102248_create_jobs_table.php b/database/migrations/2021_11_10_102248_create_jobs_table.php new file mode 100644 index 0000000..1be9e8a --- /dev/null +++ b/database/migrations/2021_11_10_102248_create_jobs_table.php @@ -0,0 +1,36 @@ +bigIncrements('id'); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('jobs'); + } +} diff --git a/database/migrations/2022_11_30_204150_create_google2fas_table.php b/database/migrations/2022_11_30_204150_create_google2fas_table.php new file mode 100644 index 0000000..642f8cf --- /dev/null +++ b/database/migrations/2022_11_30_204150_create_google2fas_table.php @@ -0,0 +1,34 @@ +id(); + $table->morphs('subscriber'); + $table->string('secret', 64)->nullable(); + $table->boolean('status')->default(0); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('google2fas'); + } +} diff --git a/database/seeders/AdminPanelSeeder.php b/database/seeders/AdminPanelSeeder.php new file mode 100644 index 0000000..e97cd97 --- /dev/null +++ b/database/seeders/AdminPanelSeeder.php @@ -0,0 +1,106 @@ + 'admin', + 'password' => bcrypt('admin'), + 'name' => 'Administrator', + ]); + + Role::create([ + 'name' => 'Administrator', + 'slug' => 'administrator', + ]); + + Permission::create([ + 'name' => 'All permission', + 'slug' => '*', + 'http_method' => '', + 'http_path' => '*', + ]); + + Menu::create([ + 'id' => 1, + 'parent_id' => 0, + 'order' => 0, + 'title' => 'Dashboard', + 'icon' => 'fa-bar-chart', + 'uri' => '/', + ]); + + $menu = Menu::create([ + 'id' => 2, + 'parent_id' => 0, + 'order' => 99, + 'title' => 'Admin', + 'icon' => 'fa-tasks', + ]); + + $menu->children()->createMany([ + [ + 'parent_id' => 2, + 'order' => 1, + 'title' => '模块管理', + 'icon' => 'fa-windows', + 'uri' => 'modules', + ], + [ + 'parent_id' => 2, + 'order' => 2, + 'title' => 'Users', + 'icon' => 'fa-users', + 'uri' => 'auth/users', + ], + [ + 'parent_id' => 2, + 'order' => 3, + 'title' => 'Roles', + 'icon' => 'fa-user', + 'uri' => 'auth/roles', + ], + [ + 'parent_id' => 2, + 'order' => 4, + 'title' => 'Permission', + 'icon' => 'fa-ban', + 'uri' => 'auth/permissions', + ], + [ + 'parent_id' => 2, + 'order' => 1, + 'title' => 'Menu', + 'icon' => 'fa-bars', + 'uri' => 'auth/menu', + ], + [ + 'parent_id' => 2, + 'order' => 1, + 'title' => 'Operation log', + 'icon' => 'fa-history', + 'uri' => 'auth/logs', + ], + ]); + + DB::insert("INSERT INTO `admin_role_menu` VALUES (1, 2, NULL, NULL);"); + DB::insert("INSERT INTO `admin_role_permissions` VALUES (1, 1, NULL, NULL);"); + DB::insert("INSERT INTO `admin_role_users` VALUES (1, 1, NULL, NULL);"); + } + +} \ No newline at end of file diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php new file mode 100644 index 0000000..57b73b5 --- /dev/null +++ b/database/seeders/DatabaseSeeder.php @@ -0,0 +1,18 @@ +create(); + } +} diff --git a/docs/READMD.md b/docs/READMD.md new file mode 100644 index 0000000..c20ed6f --- /dev/null +++ b/docs/READMD.md @@ -0,0 +1,2 @@ +# Laravel 开发文档 +

\ No newline at end of file diff --git a/docs/Trait doc.md b/docs/Trait doc.md new file mode 100644 index 0000000..173c310 --- /dev/null +++ b/docs/Trait doc.md @@ -0,0 +1,117 @@ +# Trait + +## 1. HasClicks + +> 缓存的浏览计数器 + +```php +use App\Traits\HasClicks; + +class Test extends Model +{ + use HasClicks; + protected string $clicks_filed = 'clicks'; +} + +$test = Test::first(); + +// 增加点击数量 +$test->incrementClicks(1); +// 获取点击量 +$test->clicks; +``` + +## 2. HasCovers + +> 封面图与轮播图的展示扩展,完整url + +```php +use App\Traits\HasCovers; + +class Test extends Model { + use HasCovers; + protected string $cover_field = 'cover'; + protected string $pictures_field = 'pictures'; +} + +$test = Test::first(); + +$test->cover_url; +$test->pictures_url; +``` + +## 3. HasStatus + +> 基础状态的显示与作用域查询 + +```php +use App\Traits\HasStatus; + +class Test extends Model { + use HasStatus; + protected string $status_field = 'status'; + protected array $status_map = [ + 1 => '正常', + 0 => '禁用' + ]; +} + +// 状态为 1 的 +$test = Test::shown()->first(); +// 状态为 0 的 +$test = Test::hidden()->first(); +// 查询特定状态 +$test = Test::ofStatus(3)->first(); +// 状态的文本显示 +$test->status_text; +``` + +## 4. Macroable + +> 这个主要用于,对模型的一些外部扩展使用、 +> +> 因为模型无法动态的注入trait,有些时候在模块外部,需要写入关联模型等时候,可以使用。 + +```php +use App\Tratis\Macroable; + +class Test extends Model { + use Macroable; +} +// 对模型注入一个 address 的一对多关联 +Test::macro('address', function () { + return $this->hasMany(Address::class); +}); +``` + +## 5. OrderByIdDesc + +> 直接引入模型后,查询到的数据会默认以ID 倒序排列, +> +> 暂时没有找到怎么获取主键的方法,待升级 + +## 6. OrderByOrderAsc + +> 已特定的 order 字段,按照升序排序 + +## 7. WithPosition + +> 位运算来解决的多点定位,模型中需要有 position 字段 + +```php + +use App\Traits\WithPosition; + +class Test extends Model +{ + use WithPosition; + protected array $position_map = [ + 1 => 'A', + 2 => 'B', + 4 => 'C', + 8 => 'D', + ]; +} + +$test = Test::ofPosition(3)->first(); +``` \ No newline at end of file diff --git a/init_composer.sh b/init_composer.sh new file mode 100644 index 0000000..24231ce --- /dev/null +++ b/init_composer.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +packages=$(php -f 'parseComposer.php') + +if [ "$packages" != "" ];then + composer require $packages +fi + +composer install --no-dev \ No newline at end of file diff --git a/modules.json b/modules.json new file mode 100644 index 0000000..2c63c08 --- /dev/null +++ b/modules.json @@ -0,0 +1,2 @@ +{ +} diff --git a/modules/.gitignore b/modules/.gitignore new file mode 100644 index 0000000..2157116 --- /dev/null +++ b/modules/.gitignore @@ -0,0 +1,11 @@ +* +!.gitignore +!pull-all.sh +!README.md +!Cms +!Configuration +!Mall +!Payment +!Storage +!Task +!User \ No newline at end of file diff --git a/modules/Cms/.gitignore b/modules/Cms/.gitignore new file mode 100644 index 0000000..4b6cb11 --- /dev/null +++ b/modules/Cms/.gitignore @@ -0,0 +1,4 @@ +.idea +vendor +.DS_Store +composer.lock \ No newline at end of file diff --git a/modules/Cms/Cms.php b/modules/Cms/Cms.php new file mode 100644 index 0000000..566d17b --- /dev/null +++ b/modules/Cms/Cms.php @@ -0,0 +1,88 @@ + + */ + public static function install() + { + Artisan::call('migrate', [ + '--path' => 'modules/Cms/Database/Migrations', + ]); + + self::createAdminMenu(); + } + + /** + * Notes : 卸载模块的一些操作 + * @Date : 2021/3/12 11:35 上午 + * @Author : < Jason.C > + */ + public static function uninstall() + { + $menu = config('admin.database.menu_model'); + + $mains = $menu::where('title', self::$mainTitle)->get(); + + foreach ($mains as $main) { + $main->delete(); + } + } + + protected static function createAdminMenu() + { + $menu = config('admin.database.menu_model'); + + $main = $menu::create([ + 'parent_id' => 0, + 'order' => 15, + 'title' => self::$mainTitle, + 'icon' => 'fa-wordpress', + ]); + + $main->children()->createMany([ + [ + 'order' => 1, + 'title' => '文章管理', + 'icon' => 'fa-bars', + 'uri' => 'cms/articles', + ], + [ + 'order' => 2, + 'title' => '单页管理', + 'icon' => 'fa-newspaper-o', + 'uri' => 'cms/pages', + ], + [ + 'order' => 3, + 'title' => '分类管理', + 'icon' => 'fa-indent', + 'uri' => 'cms/categories', + ], + [ + 'order' => 4, + 'title' => '标签管理', + 'icon' => 'fa-tags', + 'uri' => 'cms/tags', + ], + [ + 'order' => 5, + 'title' => '素材管理', + 'icon' => 'fa-adjust', + 'uri' => 'cms/storages', + ], + ]); + } + +} \ No newline at end of file diff --git a/modules/Cms/Config/config.php b/modules/Cms/Config/config.php new file mode 100644 index 0000000..825c0e8 --- /dev/null +++ b/modules/Cms/Config/config.php @@ -0,0 +1,10 @@ + 'Cms', +]; diff --git a/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_article_category_table.php b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_article_category_table.php new file mode 100644 index 0000000..dbb07a2 --- /dev/null +++ b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_article_category_table.php @@ -0,0 +1,33 @@ +id(); + $table->unsignedBigInteger('article_id'); + $table->unsignedBigInteger('category_id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('cms_article_category'); + } + +} diff --git a/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_articles_table.php b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_articles_table.php new file mode 100644 index 0000000..cffbcde --- /dev/null +++ b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_articles_table.php @@ -0,0 +1,42 @@ +id(); + $table->string('slug')->index()->nullable()->comment('唯一的别名'); + $table->string('title'); + $table->string('sub_title')->nullable()->comment('副标题'); + $table->string('description')->nullable(); + $table->string('cover')->nullable(); + $table->json('pictures')->nullable(); + $table->text('content'); + $table->json('attachments')->nullable(); + $table->boolean('status')->default(0); + $table->unsignedInteger('clicks')->default(0); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('cms_articles'); + } + +} diff --git a/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_categories_table.php b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_categories_table.php new file mode 100644 index 0000000..ba460cb --- /dev/null +++ b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_categories_table.php @@ -0,0 +1,41 @@ +id(); + $table->unsignedBigInteger('parent_id')->index()->default(0); + $table->string('title'); + $table->string('slug')->nullable(); + $table->string('description')->nullable(); + $table->string('cover')->nullable(); + $table->json('pictures')->nullable(); + $table->text('content')->nullable(); + $table->integer('order')->default(0); + $table->boolean('status')->default(0); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('cms_categories'); + } + +} diff --git a/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_pages_table.php b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_pages_table.php new file mode 100644 index 0000000..ecfc764 --- /dev/null +++ b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_pages_table.php @@ -0,0 +1,42 @@ +id(); + $table->string('slug')->index()->nullable()->comment('唯一的别名'); + $table->string('title'); + $table->string('sub_title')->nullable()->comment('副标题'); + $table->string('description')->nullable(); + $table->string('cover')->nullable(); + $table->json('pictures')->nullable(); + $table->text('content'); + $table->json('attachments')->nullable(); + $table->boolean('status')->default(0); + $table->unsignedInteger('clicks')->default(0); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('cms_pages'); + } + +} diff --git a/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_taggable_table.php b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_taggable_table.php new file mode 100644 index 0000000..628f455 --- /dev/null +++ b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_taggable_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('tag_id')->index(); + $table->morphs('taggable'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('cms_taggable'); + } + +} diff --git a/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_tags_table.php.php b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_tags_table.php.php new file mode 100644 index 0000000..38bc2f7 --- /dev/null +++ b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_tags_table.php.php @@ -0,0 +1,33 @@ +id(); + $table->string('name')->comment('定位名称'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('cms_tags'); + } + +} diff --git a/modules/Cms/Database/Migrations/2021_09_29_142323_create_cms_storages_table.php b/modules/Cms/Database/Migrations/2021_09_29_142323_create_cms_storages_table.php new file mode 100644 index 0000000..8320938 --- /dev/null +++ b/modules/Cms/Database/Migrations/2021_09_29_142323_create_cms_storages_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('name'); + $table->string('url')->nullable(); + $table->string('cover'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('cms_storages'); + } + +} diff --git a/modules/Cms/Database/Migrations/2022_10_20_095341_create_cms_logs_table.php b/modules/Cms/Database/Migrations/2022_10_20_095341_create_cms_logs_table.php new file mode 100644 index 0000000..5551514 --- /dev/null +++ b/modules/Cms/Database/Migrations/2022_10_20_095341_create_cms_logs_table.php @@ -0,0 +1,33 @@ +id(); + $table->unsignedBigInteger('user_id')->index(); + $table->unsignedBigInteger('article_id')->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('cms_logs'); + } +} diff --git a/modules/Cms/Http/Controllers/Admin/ArticleController.php b/modules/Cms/Http/Controllers/Admin/ArticleController.php new file mode 100644 index 0000000..0fa6a79 --- /dev/null +++ b/modules/Cms/Http/Controllers/Admin/ArticleController.php @@ -0,0 +1,136 @@ +model()->with('categories')->withCount('versions'); + + $grid->filter(function (Grid\Filter $filter) { + $filter->scope('trashed', '回收站')->onlyTrashed(); + + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('title', '文章标题'); + $filter->like('slug', 'SLUG'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('categories.title', '所属分类'); + $filter->like('tags.name', '标签'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('status', '状态')->select(Article::getStatusMap()); + }); + }); + + $grid->column('id', '#ID#'); + $grid->column('categories', '所属分类')->display(function ($categories) { + return Arr::pluck($categories, 'title'); + })->label(); + // $grid->column('tags')->pluck('name')->label('warning'); + $grid->column('title', '文章标题'); + // $grid->column('slug', 'SLUG'); + $grid->column('status', '状态')->bool(); + $grid->column('clicks', '浏览量'); + $grid->column('versions_count', '历史版本')->link(function () { + return route('admin.cms.versions', [ + 'model' => get_class($this), + 'key' => $this->id, + ]); + }, '_self'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + /** + * Notes : 内容表单 + * @Date : 2021/3/15 5:21 下午 + * @Author : < Jason.C > + * @return \Encore\Admin\Form + */ + public function form(): Form + { + $form = new Form(new Article()); + + $form->text('title', '文章标题') + ->rules([ + 'required', + 'max:255', + ], [ + 'max' => '标题最大长度不能超过 :max 个字符', + ]) + ->required(); +// $form->text('sub_title', '副标题') +// ->rules([ +// 'nullable', +// 'max:255', +// ], [ +// 'max' => '标题最大长度不能超过 :max 个字符', +// ]); +// $form->text('slug', '英文别名') +// ->rules([ +// 'nullable', +// 'alpha_dash', +// ], [ +// 'alpha_dash' => '别名中包含非法字符', +// ]) +// ->help('字母、数字、下划线组成,用来展示简短的URL'); + + $form->multipleSelect('categories', '所属分类') + ->options(Category::selectOptions(function ($model) { + return $model->where('status', 1); + }, '请选择文章分类')) + ->required(); + +// $form->multipleSelect('tags', '内容标签') +// ->options(function () { +// return Tag::pluck('name', 'id'); +// }); + + $this->cover($form); + +// $this->pictures($form); + + $form->textarea('description', '文章简介') + ->rules([ + 'nullable', + 'max:255', + ], [ + 'max' => '标题最大长度不能超过 :max 个字符', + ]); + + $form->ueditor('content', '文章内容') + ->rules([ + 'required', + ], [ + 'required' => '文章内容不能为空', + ]) + ->required(); + + $this->attachments($form); + + $form->switch('status', '状态') + ->default(1); + + return $form; + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Controllers/Admin/CategoryController.php b/modules/Cms/Http/Controllers/Admin/CategoryController.php new file mode 100644 index 0000000..4e3b667 --- /dev/null +++ b/modules/Cms/Http/Controllers/Admin/CategoryController.php @@ -0,0 +1,98 @@ +column(6, $this->treeView()); + + $row->column(6, function (Column $column) { + $form = new WidgetsForm(); + $this->formData($form, true); + $form->action(route('admin.cms.categories.store')); + $column->append((new Box('新增分类', $form))->style('success')); + }); + }; + } + + protected function treeView(): Tree + { + return Category::tree(function (Tree $tree) { + $tree->disableCreate(); + $tree->branch(function ($branch) { + if ($branch['status'] == 1) { + $payload = " "; + } else { + $payload = " "; + } + + $payload .= " [ID:{$branch['id']}] - "; + $payload .= " {$branch['title']} "; + + return $payload; + }); + }); + } + + protected function form(): Form + { + $form = new Form(new Category); + + $this->formData($form); + + return $form; + } + + private function formData($form, $hideContent = false) + { + $form->select('parent_id', '上级分类') + ->options(Category::selectOptions(function ($model) { + return $model->where('status', 1); + }, '一级分类')) + ->required(); + $form->text('title', '分类名称') + ->rules('required'); + $form->text('slug', '英文别名') + ->rules([ + 'nullable', + 'alpha_dash', + ], [ + 'alpha_dash' => '别名中包含非法字符', + ]) + ->help('字母、数字、下划线组成,用来展示简短的URL'); + $form->textarea('description', '分类简介') + ->rules('nullable'); + + $this->cover($form); + + $this->pictures($form); + + $form->number('order', '排序')->default(0); + + if (!$hideContent) { + $form->ueditor('content', '分类内容'); + } + + $form->switch('status', '状态')->default(1); + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Controllers/Admin/PageController.php b/modules/Cms/Http/Controllers/Admin/PageController.php new file mode 100644 index 0000000..3471604 --- /dev/null +++ b/modules/Cms/Http/Controllers/Admin/PageController.php @@ -0,0 +1,124 @@ +model()->withCount('versions'); + + $grid->filter(function (Grid\Filter $filter) { + $filter->scope('trashed', '回收站')->onlyTrashed(); + + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('title', '页面标题'); + $filter->like('tags.name', '标签'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('slug', 'SLUG'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('status', '状态')->select(Page::getStatusMap()); + }); + }); + + $grid->column('id', '#ID#'); + $grid->column('title', '页面标题'); + $grid->column('tags')->pluck('name')->label('warning'); + $grid->column('slug', '英文别名'); + $grid->column('status', '状态')->bool(); + $grid->column('clicks', '浏览量'); + $grid->column('versions_count', '历史版本')->link(function () { + return route('admin.cms.versions', [ + 'model' => get_class($this), + 'key' => $this->id, + ]); + }, '_self'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + /** + * Notes : 内容表单 + * @Date : 2021/3/15 5:21 下午 + * @Author : < Jason.C > + * @return \Encore\Admin\Form + */ + public function form(): Form + { + $form = new Form(new Page()); + + $form->text('title', '页面标题') + ->rules([ + 'required', + 'max:255', + ], [ + 'max' => '标题最大长度不能超过 :max 个字符', + ]) + ->required(); + $form->text('sub_title', '副标题') + ->rules([ + 'nullable', + 'max:255', + ], [ + 'max' => '标题最大长度不能超过 :max 个字符', + ]); + $form->text('slug', '英文别名') + ->rules([ + 'nullable', + 'alpha_dash', + ], [ + 'alpha_dash' => '别名中包含非法字符', + ]) + ->help('字母、数字、下划线组成,用来展示简短的URL'); + + $form->multipleSelect('tags', '页面标签') + ->options(function () { + return Tag::pluck('name', 'id'); + }); + + $this->cover($form); + + $this->pictures($form); + + $form->textarea('description', '页面简介') + ->rules([ + 'nullable', + 'max:255', + ], [ + 'max' => '标题最大长度不能超过 :max 个字符', + ]); + + $form->ueditor('content', '页面内容') + ->rules([ + 'required', + ], [ + 'required' => '页面内容不能为空', + ]) + ->required(); + + $this->attachments($form); + + $form->switch('status', '状态') + ->default(1); + + return $form; + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Controllers/Admin/StorageController.php b/modules/Cms/Http/Controllers/Admin/StorageController.php new file mode 100644 index 0000000..8f80ff3 --- /dev/null +++ b/modules/Cms/Http/Controllers/Admin/StorageController.php @@ -0,0 +1,49 @@ +column('id', '#ID#'); + $grid->column('name', '素材标题'); + $grid->column('地址')->display(function () { + return $this->cover_text; + }); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Storage()); + + $form->text('name', '素材标题') + ->rules([ + 'required', + 'max:255', + ], [ + 'max' => '标题最大长度不能超过 :max 个字符', + ]) + ->required(); + $this->cover($form); + $form->url('url', '素材地址'); + + return $form; + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Controllers/Admin/TagController.php b/modules/Cms/Http/Controllers/Admin/TagController.php new file mode 100644 index 0000000..eff5254 --- /dev/null +++ b/modules/Cms/Http/Controllers/Admin/TagController.php @@ -0,0 +1,48 @@ +model() + ->withCount(['articles', 'pages']) + ->orderByDesc('id'); + + $grid->filter(function (Grid\Filter $filter) { + $filter->scope('trashed', '回收站')->onlyTrashed(); + + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->like('name', '标签名称'); + }); + }); + + $grid->column('name', '标签名称'); + $grid->column('articles_count'); + $grid->column('pages_count'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Tag()); + + $form->text('name', '标签名称'); + + return $form; + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Controllers/Admin/VersionController.php b/modules/Cms/Http/Controllers/Admin/VersionController.php new file mode 100644 index 0000000..5c4cb87 --- /dev/null +++ b/modules/Cms/Http/Controllers/Admin/VersionController.php @@ -0,0 +1,68 @@ +header($model . ' => ' . $key) + ->description('数据操作日志') + ->body($this->grid($model, $key)); + } + + public function grid($model, $key): Grid + { + $grid = new Grid(new Version()); + + $grid->disableCreateButton(); + $grid->disableActions(); + $grid->disableTools(); + + $grid->model()->whereHasMorph( + 'versionable', + $model, + function (Builder $query) use ($key) { + $query->where('id', $key); + } + )->orderByDesc('id'); + + $grid->column('操作用户')->display(function () { + if ($this->user_id < config('mall.administrator_max_id')) { + config(['versionable.user_model' => Administrator::class]); + + return '[Admin] ' . ($this->user->name ?? '--SYSTEM--'); + } else { + return '[USER] ' . ($this->user->username ?? '--USER--'); + } + }); + $grid->column('versionable_type', '数据变动')->expand(function () { + $data = []; + foreach ($this->contents as $key => $item) { + $data[] = [ + $key, + is_array($item) ? json_encode($item, JSON_UNESCAPED_UNICODE) : $item, + ]; + } + + return new Table(['字段名称', '变更值'], $data); + }); + + $grid->column('操作时间')->display(function () { + return $this->created_at->toDateTimeString(); + }); + + return $grid; + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Controllers/Api/ArticleController.php b/modules/Cms/Http/Controllers/Api/ArticleController.php new file mode 100644 index 0000000..c868d86 --- /dev/null +++ b/modules/Cms/Http/Controllers/Api/ArticleController.php @@ -0,0 +1,113 @@ + + * @param Request $request + * @return mixed + */ + public function index(Request $request) + { + $categoryId = $request->category_id; + $title = $request->title; + $subTitle = $request->sub_title; + $slug = $request->slug; + $tagId = $request->tag_id; + + $result = Article::query() + ->when($title, function ($query) use ($title) { + $query->where('title', 'like', "%{$title}%"); + }) + ->when($subTitle, function ($query) use ($subTitle) { + $query->where('sub_title', 'like', "%{$subTitle}%"); + }) + ->when($slug, function ($query) use ($slug) { + $query->where('slug', 'like', "%{$slug}%"); + }) + ->when($categoryId, function ($query) use ($categoryId) { + $query->whereHas('categories', function ($cate) use ($categoryId) { + $cate->where('category_id', $categoryId); + }); + }) + ->when($tagId, function ($query) use ($tagId) { + $query->whereHas('tags', function ($tag) use ($tagId) { + $tag->where('tag_id', $tagId); + }); + }) + ->where('status', 1) + ->with(['categories', 'tags']) + ->paginate(10); + + return $this->success(new ArticleCollection($result)); + } + + /** + * Notes : 文章详情 + * + * @Date : 2021/4/16 10:45 上午 + * @Author : < Jason.C > + * @param Article $article + * @return mixed + */ + public function show(Article $article) + { + $article->read(Api::userId()); + + return $this->success(new ArticleResource($article)); + } + + /** + * Notes: 收藏/取消收藏 + * + * @Author: 玄尘 + * @Date: 2022/10/11 9:59 + */ + public function favorite(Article $article): JsonResponse + { + $user = Api::user(); + + $user->toggleFavorite($article); + + return $this->success([ + 'count' => $article->favorites()->count(), + 'favorite' => $user->hasFavorited($article), + ]); + } + + /** + * Notes: 点赞/取消点赞 + * + * @Author: 玄尘 + * @Date: 2022/10/11 10:18 + * @param Article $article + * @return JsonResponse + * @throws \Exception + */ + public function subscribe(Article $article): JsonResponse + { + $user = Api::user(); + + $user->toggleSubscribe($article); + + return $this->success([ + 'count' => $article->subscribers()->count(), + 'subscribed' => $user->hasSubscribed($article), + ]); + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Controllers/Api/CategoryController.php b/modules/Cms/Http/Controllers/Api/CategoryController.php new file mode 100644 index 0000000..58359a5 --- /dev/null +++ b/modules/Cms/Http/Controllers/Api/CategoryController.php @@ -0,0 +1,50 @@ + + * @param \Illuminate\Http\Request $request + * @return mixed + */ + public function index(Request $request) + { + $parentId = $request->get('parent_id', 0); + + if (!is_integer($parentId)) { + return $this->failed('The param must be integer'); + } + + $result = Category::where('parent_id', $parentId) + ->where('status', 1) + ->orderBy('order', 'asc') + ->get(); + + return $this->success(new CategoryCollection($result)); + } + + /** + * Notes : 分类的详情 + * @Date : 2021/4/19 9:29 上午 + * @Author : < Jason.C > + * @param \Modules\Cms\Models\Category $category + * @return mixed + */ + public function show(Category $category) + { + + return $this->success(new CategoryResource($category)); + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Controllers/Api/PageController.php b/modules/Cms/Http/Controllers/Api/PageController.php new file mode 100644 index 0000000..691bb8a --- /dev/null +++ b/modules/Cms/Http/Controllers/Api/PageController.php @@ -0,0 +1,90 @@ + + * @param \Illuminate\Http\Request $request + * @return mixed + */ + public function index(Request $request) + { + $title = $request->title; + $subTitle = $request->sub_title; + $slug = $request->slug; + $tagId = $request->tag_id; + + $result = Page::when($title, function ($query) use ($title) { + $query->where('title', 'like', "%{$title}%"); + })->when($subTitle, function ($query) use ($subTitle) { + $query->where('sub_title', 'like', "%{$subTitle}%"); + })->when($slug, function ($query) use ($slug) { + $query->where('slug', 'like', "%{$slug}%"); + })->when($tagId, function ($query) use ($tagId) { + $query->whereHas('tags', function ($tag) use ($tagId) { + $tag->where('tag_id', $tagId); + }); + })->where('status', 1)->with(['tags'])->paginate(); + + $result = new PageCollection($result); + + return $this->success($result); + } + + /** + * Notes : 单页详情 + * + * @Date : 2021/4/16 11:25 上午 + * @Author : < Jason.C > + * @param \Modules\Cms\Models\Page $page + * @return mixed + */ + public function show(Page $page) + { + $page->incrementClicks(); + + return $this->success(new PageResource($page)); + } + + /** + * Notes: 隐私条款 + * + * @Author: 玄尘 + * @Date: 2022/8/29 11:59 + */ + public function secret() + { + $info = Page::where('slug', 'secret')->first(); + $info->incrementClicks(); + + return $this->success(new PageResource($info)); + } + + /** + * Notes: 服务协议 + * + * @Author: 玄尘 + * @Date: 2022/8/29 12:00 + * @return \Illuminate\Http\JsonResponse + */ + public function protocol() + { + $info = Page::where('slug', 'protocol')->first(); + $info->incrementClicks(); + + return $this->success(new PageResource($info)); + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Controllers/Api/TagController.php b/modules/Cms/Http/Controllers/Api/TagController.php new file mode 100644 index 0000000..a72f52a --- /dev/null +++ b/modules/Cms/Http/Controllers/Api/TagController.php @@ -0,0 +1,37 @@ + + * @param \Illuminate\Http\Request $request + * @return mixed + */ + public function index(Request $request) + { + $name = $request->name; + + $tags = Tag::when($name, function ($query) use ($name) { + $query->where('name', 'like', "%{$name}%"); + })->orderByDesc('id')->paginate(); + + return $this->success(new TagCollection($tags)); + } + + public function show(Tag $tag) + { + return $this->success(new TagResource($tag)); + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Resources/ArticleBaseResource.php b/modules/Cms/Http/Resources/ArticleBaseResource.php new file mode 100644 index 0000000..c6da0fa --- /dev/null +++ b/modules/Cms/Http/Resources/ArticleBaseResource.php @@ -0,0 +1,29 @@ + $this->id, + 'title' => $this->title, + // 'sub_title' => $this->sub_title, + 'description' => $this->description, + // 'slug' => $this->slug, + 'categories' => CategoryBaseResource::collection($this->categories), + 'tags' => TagResource::collection($this->tags), + 'cover' => $this->cover_url, + 'pictures' => $this->pictures_url, + 'clicks' => $this->clicks, + 'favorites' => $this->favorites()->count(), + 'subscribes' => $this->subscribers()->count(), + 'created_at' => (string) $this->created_at, + ]; + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Resources/ArticleCollection.php b/modules/Cms/Http/Resources/ArticleCollection.php new file mode 100644 index 0000000..19aae07 --- /dev/null +++ b/modules/Cms/Http/Resources/ArticleCollection.php @@ -0,0 +1,31 @@ + $this->collection->map(function ($article) { + return new ArticleBaseResource($article); + }), + 'page' => $this->page(), + ]; + } + + protected function page(): array + { + return [ + 'current' => $this->currentPage(), + 'total_page' => $this->lastPage(), + 'per_page' => $this->perPage(), + 'has_more' => $this->hasMorePages(), + 'total' => $this->total(), + ]; + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Resources/ArticleResource.php b/modules/Cms/Http/Resources/ArticleResource.php new file mode 100644 index 0000000..796114e --- /dev/null +++ b/modules/Cms/Http/Resources/ArticleResource.php @@ -0,0 +1,36 @@ + $this->id, + 'title' => $this->title, + // 'sub_title' => $this->sub_title, + 'description' => $this->description, + // 'slug' => $this->slug, + 'categories' => CategoryBaseResource::collection($this->categories), + 'tags' => TagResource::collection($this->tags), + 'cover' => $this->cover_url, + 'pictures' => $this->pictures_url, + 'clicks' => $this->clicks, + 'favorites' => $this->favorites()->count(), + 'subscribes' => $this->subscribers()->count(), + 'isFavorite' => $user && $this->isFavoritedBy($user), + 'isSubscribed' => $user && $this->isSubscribedBy($user), + 'content' => $this->content, + 'attachments' => $this->attachments, + 'created_at' => (string) $this->created_at, + ]; + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Resources/CategoryBaseResource.php b/modules/Cms/Http/Resources/CategoryBaseResource.php new file mode 100644 index 0000000..eb6db15 --- /dev/null +++ b/modules/Cms/Http/Resources/CategoryBaseResource.php @@ -0,0 +1,19 @@ + $this->id, + 'title' => $this->title, + 'slug' => $this->slug, + ]; + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Resources/CategoryCollection.php b/modules/Cms/Http/Resources/CategoryCollection.php new file mode 100644 index 0000000..6a66dfc --- /dev/null +++ b/modules/Cms/Http/Resources/CategoryCollection.php @@ -0,0 +1,33 @@ + + * @param \Illuminate\Http\Request $request + * @return array + */ + public function toArray($request): array + { + return $this->collection->map(function ($category) { + return [ + 'category_id' => $category->id, + 'title' => $category->title, + 'slug' => $category->slug, + 'description' => $category->description, + 'cover' => $category->cover_url, + 'pictures' => $category->pictures_url, + 'created_at' => (string) $category->created_at, + 'children' => new self($category->children), + ]; + })->toArray(); + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Resources/CategoryResource.php b/modules/Cms/Http/Resources/CategoryResource.php new file mode 100644 index 0000000..066a874 --- /dev/null +++ b/modules/Cms/Http/Resources/CategoryResource.php @@ -0,0 +1,25 @@ + $this->id, + 'parent' => new self($this->parent), + 'title' => $this->title, + 'slug' => $this->slug, + 'description' => $this->description, + 'cover' => $this->cover_url, + 'pictures' => $this->pictures_url, + 'content' => $this->content, + 'created_at' => (string) $this->created_at, + ]; + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Resources/PageCollection.php b/modules/Cms/Http/Resources/PageCollection.php new file mode 100644 index 0000000..1cf1f84 --- /dev/null +++ b/modules/Cms/Http/Resources/PageCollection.php @@ -0,0 +1,42 @@ + $this->collection->map(function ($page) { + return [ + 'page_id' => $page->id, + 'title' => $page->title, + 'sub_title' => $page->sub_title, + 'tags' => TagResource::collection($page->tags), + 'description' => $page->description, + 'slug' => $page->slug, + 'cover' => $page->cover_url, + 'pictures' => $page->pictures_url, + 'clicks' => $page->clicks, + 'created_at' => (string) $page->created_at, + ]; + }), + 'page' => $this->page(), + ]; + } + + protected function page(): array + { + return [ + 'current' => $this->currentPage(), + 'total_page' => $this->lastPage(), + 'per_page' => $this->perPage(), + 'has_more' => $this->hasMorePages(), + 'total' => $this->total(), + ]; + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Resources/PageResource.php b/modules/Cms/Http/Resources/PageResource.php new file mode 100644 index 0000000..6dfc61e --- /dev/null +++ b/modules/Cms/Http/Resources/PageResource.php @@ -0,0 +1,28 @@ + $this->id, + 'title' => $this->title, + 'sub_title' => $this->sub_title, + 'tags' => TagResource::collection($this->tags), + 'description' => $this->description, + 'slug' => $this->slug, + 'cover' => $this->cover_url, + 'pictures' => $this->pictures_url, + 'clicks' => $this->clicks, + 'content' => $this->content, + 'attachments' => $this->attachments_url, + 'created_at' => (string) $this->created_at, + ]; + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Resources/TagCollection.php b/modules/Cms/Http/Resources/TagCollection.php new file mode 100644 index 0000000..ffd89da --- /dev/null +++ b/modules/Cms/Http/Resources/TagCollection.php @@ -0,0 +1,31 @@ + $this->collection->map(function ($tag) { + return new TagResource($tag); + }), + 'page' => $this->page(), + ]; + } + + protected function page(): array + { + return [ + 'current' => $this->currentPage(), + 'total_page' => $this->lastPage(), + 'per_page' => $this->perPage(), + 'has_more' => $this->hasMorePages(), + 'total' => $this->total(), + ]; + } + +} \ No newline at end of file diff --git a/modules/Cms/Http/Resources/TagResource.php b/modules/Cms/Http/Resources/TagResource.php new file mode 100644 index 0000000..b52185b --- /dev/null +++ b/modules/Cms/Http/Resources/TagResource.php @@ -0,0 +1,18 @@ + $this->id, + 'name' => $this->name, + ]; + } + +} \ No newline at end of file diff --git a/modules/Cms/Models/Article.php b/modules/Cms/Models/Article.php new file mode 100644 index 0000000..5d1ce45 --- /dev/null +++ b/modules/Cms/Models/Article.php @@ -0,0 +1,123 @@ + 'json', + 'attachments' => 'json', + ]; + + /** + * 不参与版本记录的字段 + * + * @var array|string[] + */ + protected array $dontVersionable = ['clicks', 'updated_at']; + + /** + * Notes: 阅读记录 + * + * @Author: 玄尘 + * @Date: 2022/10/20 9:57 + * @return HasMany + */ + public function logs(): HasMany + { + return $this->hasMany(Log::class); + } + + /** + * Notes: 阅读 + * + * @Author: 玄尘 + * @Date: 2022/10/20 9:57 + * @param int $user_id + */ + public function read($user_id = '') + { + $this->incrementClicks(); + if ($user_id) { + $this->logs()->create([ + 'user_id' => $user_id + ]); + + if (Task::query()->shown()->where('key', 'tour_article')->first()) { + # todo 阅读得水滴 + TaskFacade::do('tour_article', $user_id); + } + + if (Task::query()->shown()->where('key', + 'read_article')->first() && $this->categories()->Health()->first()) { + # todo 阅读100次得水滴 + TaskFacade::do('read_article', $user_id); + } + + + } + } + + /** + * Notes : 文章所属分类 + * + * @Date : 2021/4/15 12:44 下午 + * @Author : < Jason.C > + * @return BelongsToMany + */ + public function categories(): BelongsToMany + { + return $this->belongsToMany(Category::class, 'cms_article_category') + ->using(ArticleCategory::class) + ->withTimestamps(); + } + + /** + * Notes : 获取附件的下载地址 + * + * @Date : 2021/4/16 12:01 下午 + * @Author : < Jason.C > + * @return Collection + */ + public function getAttachmentsUrlAttribute(): Collection + { + return collect($this->attachments)->map(function ($item) { + return Storage::url($item); + }); + } + +} \ No newline at end of file diff --git a/modules/Cms/Models/ArticleCategory.php b/modules/Cms/Models/ArticleCategory.php new file mode 100644 index 0000000..63e4bc2 --- /dev/null +++ b/modules/Cms/Models/ArticleCategory.php @@ -0,0 +1,14 @@ + 'json', + ]; + + /** + * 不参与版本记录的字段 + * + * @var array|string[] + */ + protected array $dontVersionable = ['updated_at']; + + public function children(): HasMany + { + return $this->hasMany(self::class, 'parent_id'); + } + + /** + * Notes : 分类下的文章 + * + * @Date : 2021/4/15 12:47 下午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function articles(): BelongsToMany + { + return $this->belongsToMany(Article::class, (new ArticleCategory())->getTable()) + ->using(ArticleCategory::class) + ->withTimestamps(); + } + + /** + * Notes: 健康文章 + * + * @Author: 玄尘 + * @Date: 2022/10/31 10:05 + */ + public function scopeHealth(Builder $query): Builder + { + return $query->where('slug', 'HEALTH'); + } + +} \ No newline at end of file diff --git a/modules/Cms/Models/Log.php b/modules/Cms/Models/Log.php new file mode 100644 index 0000000..7bb8ef4 --- /dev/null +++ b/modules/Cms/Models/Log.php @@ -0,0 +1,15 @@ + 'json', + 'attachments' => 'json', + ]; + + /** + * 不参与版本记录的字段 + * @var array|string[] + */ + protected array $dontVersionable = ['updated_at']; + + /** + * Notes : 获取附件的下载地址 + * @Date : 2021/4/16 12:01 下午 + * @Author : < Jason.C > + * @return \Illuminate\Support\Collection + */ + public function getAttachmentsUrlAttribute(): Collection + { + return collect($this->attachments)->map(function ($item) { + return Storage::url($item); + }); + } + +} \ No newline at end of file diff --git a/modules/Cms/Models/Storage.php b/modules/Cms/Models/Storage.php new file mode 100644 index 0000000..4bfedee --- /dev/null +++ b/modules/Cms/Models/Storage.php @@ -0,0 +1,24 @@ +url) { + return $this->url . '/' . $this->cover; + } else { + return $this->cover_url; + } + } + +} diff --git a/modules/Cms/Models/Tag.php b/modules/Cms/Models/Tag.php new file mode 100644 index 0000000..3b4d1dc --- /dev/null +++ b/modules/Cms/Models/Tag.php @@ -0,0 +1,34 @@ +morphedByMany(Article::class, 'taggable', 'cms_taggable'); + } + + public function pages(): MorphToMany + { + return $this->morphedByMany(Page::class, 'taggable', 'cms_taggable'); + } + +} \ No newline at end of file diff --git a/modules/Cms/Models/Taggable.php b/modules/Cms/Models/Taggable.php new file mode 100644 index 0000000..ef814c5 --- /dev/null +++ b/modules/Cms/Models/Taggable.php @@ -0,0 +1,14 @@ +loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations')); + } + + /** + * Register the service provider. + * @return void + */ + public function register() + { + $this->registerConfig(); + + $this->app->register(RouteServiceProvider::class); + } + + /** + * Register config. + * @return void + */ + protected function registerConfig() + { + $this->publishes([ + module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower . '.php'), + ], 'config'); + $this->mergeConfigFrom( + module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower + ); + } + + /** + * Get the services provided by the provider. + * @return array + */ + public function provides(): array + { + return []; + } + +} diff --git a/modules/Cms/Providers/RouteServiceProvider.php b/modules/Cms/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..22f31fe --- /dev/null +++ b/modules/Cms/Providers/RouteServiceProvider.php @@ -0,0 +1,62 @@ +mapAdminRoutes(); + $this->mapApiRoutes(); + } + + protected function mapApiRoutes() + { + Route::as(config('api.route.as')) + ->domain(config('api.route.domain')) + ->middleware(config('api.route.middleware')) + ->namespace($this->moduleNamespace) + ->prefix(config('api.route.prefix')) + ->group(module_path($this->moduleName, 'Routes/api.php')); + } + + protected function mapAdminRoutes() + { + Route::as(config('admin.route.as')) + ->domain(config('admin.route.domain')) + ->middleware(config('admin.route.middleware')) + ->namespace($this->moduleNamespace) + ->prefix(config('admin.route.prefix')) + ->group(module_path($this->moduleName, 'Routes/admin.php')); + } + +} diff --git a/modules/Cms/README.md b/modules/Cms/README.md new file mode 100644 index 0000000..1ce5ed5 --- /dev/null +++ b/modules/Cms/README.md @@ -0,0 +1,49 @@ +# CMS + +## 安装 + +```shell +composer require overtrue/laravel-versionable +``` + +## 接口文档 + +### 1. 文章 + +#### 1. 文章列表 + +> GET: /api/cms/articles + +#### 2. 文章详情 + +> GET: /api/cms/articles/{article_id} + +### 2. 单页 + +#### 1. 单页列表 + +> GET: /api/cms/pages + +#### 2. 单页详情 + +> GET: /api/cms/pages/{page_id} + +### 3. 分类 + +#### 1. 分类列表 + +> GET: /api/cms/categories + +#### 2. 分类详情 + +> GET: /api/cms/categories/{category_id} + +### 4. 标签 + +#### 1. 标签列表 + +> GET: /api/cms/tags + +#### 2. 标签详情 + +> GET: /api/cms/tags/{tag_id} diff --git a/modules/Cms/Routes/admin.php b/modules/Cms/Routes/admin.php new file mode 100644 index 0000000..91a9013 --- /dev/null +++ b/modules/Cms/Routes/admin.php @@ -0,0 +1,17 @@ + 'cms', + 'namespace' => 'Admin', + 'as' => 'cms.', +], function (Router $router) { + $router->resource('articles', 'ArticleController'); + $router->resource('pages', 'PageController'); + $router->resource('categories', 'CategoryController'); + $router->resource('tags', 'TagController'); + $router->resource('storages', 'StorageController'); + $router->get('versions/{model}/{key}', 'VersionController@index')->name('versions'); +}); diff --git a/modules/Cms/Routes/api.php b/modules/Cms/Routes/api.php new file mode 100644 index 0000000..c2f994f --- /dev/null +++ b/modules/Cms/Routes/api.php @@ -0,0 +1,34 @@ + 'cms', + 'namespace' => 'Api', + 'middleware' => config('api.route.middleware_guess'), +], function (Router $router) { + $router->get('articles', 'ArticleController@index'); + $router->get('articles/{article}', 'ArticleController@show'); + + $router->get('pages', 'PageController@index'); + $router->get('pages/secret', 'PageController@secret'); + $router->get('pages/protocol', 'PageController@protocol'); + $router->get('pages/{page}', 'PageController@show'); + + $router->get('categories', 'CategoryController@index'); + $router->get('categories/{category}', 'CategoryController@show'); + + $router->get('tags', 'TagController@index'); + $router->get('tags/{tag}', 'TagController@show'); +}); + +Route::group([ + 'prefix' => 'cms', + 'namespace' => 'Api', + 'middleware' => config('api.route.middleware_auth'), +], function (Router $router) { + $router->get('articles/favorite/{article}', 'ArticleController@favorite');//收藏、取消收藏 + $router->get('articles/subscribe/{article}', 'ArticleController@subscribe');//点赞、取消点赞 + +}); \ No newline at end of file diff --git a/modules/Cms/Traits/HasTags.php b/modules/Cms/Traits/HasTags.php new file mode 100644 index 0000000..88a99e2 --- /dev/null +++ b/modules/Cms/Traits/HasTags.php @@ -0,0 +1,25 @@ + + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany + */ + public function tags(): MorphToMany + { + return $this->morphToMany(Tag::class, 'taggable', 'cms_taggable') + ->using(Taggable::class) + ->withTimestamps(); + } + +} \ No newline at end of file diff --git a/modules/Cms/composer.json b/modules/Cms/composer.json new file mode 100644 index 0000000..9867aa9 --- /dev/null +++ b/modules/Cms/composer.json @@ -0,0 +1,34 @@ +{ + "name": "uztech/cms-module", + "description": "内容模块", + "type": "laravel-module", + "authors": [ + { + "name": "Jason.Chen", + "email": "chenjxlg@163.com" + } + ], + "require": { + "php": "^7.3|^8.0", + "encore/laravel-admin": "^1.8", + "genealabs/laravel-model-caching": "^0.11.3", + "jasonc/api": "^5.0.0", + "joshbrw/laravel-module-installer": "^2.0", + "laravel/framework": "^8.5", + "nwidart/laravel-modules": "^8.2", + "overtrue/laravel-versionable": "^2.6" + }, + "extra": { + "module-dir": "modules", + "laravel": { + "providers": [], + "aliases": { + } + } + }, + "autoload": { + "psr-4": { + "Modules\\Cms\\": "" + } + } +} diff --git a/modules/Cms/module.json b/modules/Cms/module.json new file mode 100644 index 0000000..6dcf928 --- /dev/null +++ b/modules/Cms/module.json @@ -0,0 +1,15 @@ +{ + "name": "Cms", + "alias": "cms", + "description": "内容管理模块", + "keywords": [], + "priority": 0, + "providers": [ + "Modules\\Cms\\Providers\\CmsServiceProvider" + ], + "aliases": {}, + "files": [], + "requires": [], + "version": "1.0.0", + "author": "Jason.Chen" +} diff --git a/modules/Configuration/.gitignore b/modules/Configuration/.gitignore new file mode 100644 index 0000000..4b6cb11 --- /dev/null +++ b/modules/Configuration/.gitignore @@ -0,0 +1,4 @@ +.idea +vendor +.DS_Store +composer.lock \ No newline at end of file diff --git a/modules/Configuration/Actions/UpdateSource.php b/modules/Configuration/Actions/UpdateSource.php new file mode 100644 index 0000000..bbbf405 --- /dev/null +++ b/modules/Configuration/Actions/UpdateSource.php @@ -0,0 +1,44 @@ +source; + $array = explode("\r\n", $source); + $data = []; + foreach ($array as $value) { + $key = explode("|", $value)[0]; + $params = explode("|", $value)[1]; + $data[$key] = $params; + } + $model->source = $data; + $model->paramsValue = ''; + $model->save(); + + return $this->response()->success('更改成功')->refresh(); + } catch (Exception $e) { + return $this->response()->error($e->getMessage())->refresh(); + } + } + + public function form() + { + $this->textarea('source', '配置项')->required(); + $this->textarea('示例')->disable()->value("key|value\r\nkey|value\r\n非必要请不要随意更改"); + $this->confirm('确认改变当前配置项?(删除值)'); + } + +} diff --git a/modules/Configuration/Actions/UpdateType.php b/modules/Configuration/Actions/UpdateType.php new file mode 100644 index 0000000..f719821 --- /dev/null +++ b/modules/Configuration/Actions/UpdateType.php @@ -0,0 +1,36 @@ +type = $request->type; + $model->source = []; + $model->paramsValue = ''; + $model->save(); + + return $this->response()->success('更改成功')->refresh(); + } catch (Exception $e) { + return $this->response()->error($e->getMessage())->refresh(); + } + } + + public function form() + { + $this->select('type', '配置类型')->options(Configuration::TYPES); + $this->confirm('确认改变当前类型?(删除配置项与值)'); + } + +} diff --git a/modules/Configuration/Configuration.php b/modules/Configuration/Configuration.php new file mode 100644 index 0000000..541caa2 --- /dev/null +++ b/modules/Configuration/Configuration.php @@ -0,0 +1,73 @@ + 'modules/Configuration/Database/Migrations', + ]); + + self::createAdminMenu(); + } + + /** + * Notes : 卸载模块的一些操作 + */ + public static function uninstall() + { + $menu = config('admin.database.menu_model'); + + $main = $menu::where('title', self::$mainTitle)->first(); + + $main->delete(); + } + + /** + * 根据模块配置情况创建是否需要配置菜单 + */ + protected static function createAdminMenu() + { + $menu = config('admin.database.menu_model'); + + $main = $menu::create([ + 'parent_id' => 0, + 'order' => 50, + 'title' => self::$mainTitle, + 'icon' => 'fa-bars', + ]); + $modules = ModuleManager::toCollection(); + $data = []; + $i = 1; + foreach ($modules as $module) { + $need = $module->get('config', false); + if ($need) { + $name = $module->get('configName', ''); + if (! $name) { + $name = $module->getName(); + } + $alias = $module->getAlias(); + $data[] = [ + 'order' => $i, + 'title' => $name.'配置', + 'icon' => 'fa-bars', + 'uri' => 'configurations/'.$alias, + ]; + $i++; + } + } + if (count($data) > 0) { + $main->children()->createMany($data); + } + } +} diff --git a/modules/Configuration/Database/Migrations/2021_10_20_000000_create_configurations_table.php b/modules/Configuration/Database/Migrations/2021_10_20_000000_create_configurations_table.php new file mode 100644 index 0000000..c5b664a --- /dev/null +++ b/modules/Configuration/Database/Migrations/2021_10_20_000000_create_configurations_table.php @@ -0,0 +1,39 @@ +id(); + $table->string('module', 100)->comment('所属模块'); + $table->string('title', 100)->comment('配置项名称'); + $table->string('keyValue', 100)->comment('配置键值'); + $table->string('paramsValue', 100)->nullable()->comment('值'); + $table->string('type', 100)->comment('类型'); + $table->json('source')->nullable()->comment('其他数据源'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('configurations'); + } + +} diff --git a/modules/Configuration/Http/Controllers/IndexController.php b/modules/Configuration/Http/Controllers/IndexController.php new file mode 100644 index 0000000..9777123 --- /dev/null +++ b/modules/Configuration/Http/Controllers/IndexController.php @@ -0,0 +1,135 @@ +findByAlias($module); + $content = new Content(); + return $content + ->title($moduleName->get('configName', $moduleName->getName()).'参数配置') + ->body($this->grid($module)); + } + + public function grid(string $module): Grid + { + $grid = new Grid(new Configuration()); + $grid->model()->where('module', $module); + + $grid->actions(function (Grid\Displayers\Actions $actions) { + $actions->disableDelete(); + $actions->disableView(); + $actions->add(new UpdateType()); + if (in_array($actions->row->type, [ + Configuration::TYPE_SELECT, + Configuration::TYPE_CHECKBOX, + ])) { + $actions->add(new UpdateSource()); + } + }); + $grid->column('keyValue', '关键字'); + $grid->column('title', '配置项名称'); + $grid->column('type', '类型')->using(Configuration::TYPES); + $grid->column('值')->display(function () { + return $this->params_text; + }); + $grid->column('source', '参数')->display(function () { + $str = ''; + foreach ($this->source ?? [] as $key => $value) { + $str .= $key.' = '.$value.'
'; + } + + return $str; + }); + + return $grid; + } + + public function create(string $module): Content + { + $moduleName = app('modules')->findByAlias($module); + $content = new Content(); + return $content + ->title($moduleName->get('configName', $moduleName->getName()).'参数新增') + ->body($this->form($module)); + } + + public function store(string $module) + { + return $this->form($module)->store(); + } + + public function edit(string $module, $id): Content + { + $moduleName = app('modules')->findByAlias($module); + + $content = new Content(); + return $content + ->title($moduleName->get('configName', $moduleName->getName()).'参数修改') + ->body($this->form($module, $id)->edit($id)); + } + + public function update(string $module, $id) + { + return $this->form($module, $id)->update($id); + } + + public function form(string $module, $id = ''): Form + { + $form = new Form(new Configuration()); + $form->hidden('module')->default($module); + + $form->tools(function (Form\Tools $tools) { + $tools->disableDelete(); + $tools->disableView(); + }); + + if (! $id) { + $form->text('keyValue', '关键字')->required(); + $form->text('title', '配置项名称')->required(); + $form->select('type', '类型')->options(Configuration::TYPES)->required(); + } else { + $form->text('keyValue', '关键字')->disable(); + $form->text('title', '配置项名称')->disable(); + $config = Configuration::find($id); + switch ($config->type) { + case Configuration::TYPE_SELECT: + $form->select('paramsValue', '值') + ->options($config->source) + ->required(); + break; + case Configuration::TYPE_CHECKBOX: + $form->checkbox('paramsValue', '值') + ->options($config->source) + ->required(); + break; + case Configuration::TYPE_NUMBER: + $form->number('paramsValue', '值') + ->required(); + break; + case Configuration::TYPE_RATE: + $form->rate('paramsValue', '值') + ->required(); + break; + case Configuration::TYPE_TEXT: + default: + $form->text('paramsValue', '值') + ->required(); + break; + } + } + return $form; + } + +} diff --git a/modules/Configuration/Models/Configuration.php b/modules/Configuration/Models/Configuration.php new file mode 100644 index 0000000..f3f51b5 --- /dev/null +++ b/modules/Configuration/Models/Configuration.php @@ -0,0 +1,82 @@ + '文本', + self::TYPE_IMG => '图片', + self::TYPE_SELECT => '下拉列表', + self::TYPE_CHECKBOX => '多选框', + self::TYPE_NUMBER => '数值', + self::TYPE_RATE => '比例', + ]; + protected $casts = [ + 'source' => 'json', + ]; + + /** + * Notes : 获取当前模块下的参数配置 + * + * @Date : 2021/12/1 13:18 + * @Author : Mr.wang + * @param $module + * @return mixed + */ + public static function getModule($module) + { + return self::where('module', $module) + ->get() + ->pluck('paramsValue', 'keyValue'); + } + + /** + * Notes : 获取参数值 + * + * @Date : 2021/12/1 11:43 + * @Author : Mr.wang + * @param string $key + * @param string $default + * @return string + */ + public static function getValue(string $key = '', $default = ''): string + { + $config = self::where('keyValue', $key)->first(); + if (! $config) { + return $default; + } + + return $config->paramsValue ?? $default; + + } + + /** + * Notes : 获取参数 + * + * @Date : 2021/12/1 11:42 + * @Author : Mr.wang + * @return string + */ + public function getParamsTextAttribute(): string + { + if (in_array($this->type, [ + 'select', 'checkbox', 'radio', + ])) { + return $this->source[$this->paramsValue] ?? ''; + } else { + return $this->paramsValue ?? ''; + } + } +} diff --git a/modules/Configuration/Providers/ConfigurationServiceProvider.php b/modules/Configuration/Providers/ConfigurationServiceProvider.php new file mode 100644 index 0000000..ef17983 --- /dev/null +++ b/modules/Configuration/Providers/ConfigurationServiceProvider.php @@ -0,0 +1,60 @@ +loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations')); + + $modules = ModuleManager::toCollection(); + foreach ($modules as $module) { + if ($module->get('config', false)) { + $alias = $module->getAlias(); + $this->app->bind('Conf_'.$alias, function () use ($alias) { + return Configuration::getModule($alias); + }); + } + } + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->app->register(RouteServiceProvider::class); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides(): array + { + return []; + } +} diff --git a/modules/Configuration/Providers/RouteServiceProvider.php b/modules/Configuration/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..da54e67 --- /dev/null +++ b/modules/Configuration/Providers/RouteServiceProvider.php @@ -0,0 +1,52 @@ +mapAdminRoutes(); + } + + protected function mapAdminRoutes() + { + Route::as(config('admin.route.as')) + ->domain(config('admin.route.domain')) + ->middleware(config('admin.route.middleware')) + ->namespace($this->moduleNamespace) + ->prefix(config('admin.route.prefix')) + ->group(module_path($this->moduleName, 'Routes/admin.php')); + } +} diff --git a/modules/Configuration/README.md b/modules/Configuration/README.md new file mode 100644 index 0000000..4654ce5 --- /dev/null +++ b/modules/Configuration/README.md @@ -0,0 +1,6 @@ +# laravel-configuration-module + +# 参数配置模块 + +## 需要config的模块配置 + diff --git a/modules/Configuration/Routes/admin.php b/modules/Configuration/Routes/admin.php new file mode 100644 index 0000000..a4106c2 --- /dev/null +++ b/modules/Configuration/Routes/admin.php @@ -0,0 +1,19 @@ + 'configurations.', +], function (Router $router) { + $router->get('configurations/{module}', 'IndexController@index') + ->where(['module' => '[a-z-_]+']); + $router->post('configurations/{module}', 'IndexController@store') + ->where(['module' => '[a-z-_]+']); + $router->get('configurations/{module}/create', 'IndexController@create') + ->where(['module' => '[a-z-_]+']); + $router->get('configurations/{module}/{id}/edit', 'IndexController@edit') + ->where(['module' => '[a-z-_]+', 'id' => '[0-9]+']); + $router->put('configurations/{module}/{id}', 'IndexController@update') + ->where(['module' => '[a-z-_]+', 'id' => '[0-9]+']); +}); diff --git a/modules/Configuration/composer.json b/modules/Configuration/composer.json new file mode 100644 index 0000000..4da3e40 --- /dev/null +++ b/modules/Configuration/composer.json @@ -0,0 +1,22 @@ +{ + "name": "uztech/configuration-module", + "description": "", + "type": "laravel-module", + "authors": [ + { + "name": "Yz-Leady", + "email": "149307205@qq.com" + } + ], + "require": { + "genealabs/laravel-model-caching": "^0.11.3" + }, + "extra": { + "module-dir": "modules" + }, + "autoload": { + "psr-4": { + "Modules\\Configuration\\": "" + } + } +} diff --git a/modules/Configuration/module.json b/modules/Configuration/module.json new file mode 100644 index 0000000..c6a2d1e --- /dev/null +++ b/modules/Configuration/module.json @@ -0,0 +1,18 @@ +{ + "name": "Configuration", + "alias": "configuration", + "description": "模块参数配置管理", + "keywords": [ + "Leady", + "参数配置" + ], + "priority": 0, + "providers": [ + "Modules\\Configuration\\Providers\\ConfigurationServiceProvider" + ], + "aliases": {}, + "files": [], + "requires": [], + "version": "1.0.0", + "author": "Leady" +} diff --git a/modules/Linker/.gitignore b/modules/Linker/.gitignore new file mode 100644 index 0000000..4b6cb11 --- /dev/null +++ b/modules/Linker/.gitignore @@ -0,0 +1,4 @@ +.idea +vendor +.DS_Store +composer.lock \ No newline at end of file diff --git a/modules/Linker/Database/Migrations/0000_00_00_000000_create_linker_relations_table.php b/modules/Linker/Database/Migrations/0000_00_00_000000_create_linker_relations_table.php new file mode 100644 index 0000000..d7aaeaf --- /dev/null +++ b/modules/Linker/Database/Migrations/0000_00_00_000000_create_linker_relations_table.php @@ -0,0 +1,35 @@ +id(); + $table->unsignedBigInteger('linker_id')->index(); + $table->morphs('model'); + $table->enum('mode', ['replace', 'append', 'override'])->default('override'); + $table->json('params')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('linker_relations'); + } + +} diff --git a/modules/Linker/Database/Migrations/0000_00_00_000000_create_linkers_table.php b/modules/Linker/Database/Migrations/0000_00_00_000000_create_linkers_table.php new file mode 100644 index 0000000..a02bb1b --- /dev/null +++ b/modules/Linker/Database/Migrations/0000_00_00_000000_create_linkers_table.php @@ -0,0 +1,37 @@ +id(); + $table->string('title')->comment('链接标题'); + $table->string('type', 50)->comment('链接类型'); + $table->string('url')->comment('链接地址'); + $table->json('params')->nullable()->comment('默认参数'); + $table->boolean('status')->default(0)->comment('状态'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('linkers'); + } + +} diff --git a/modules/Linker/Http/Controllers/IndexController.php b/modules/Linker/Http/Controllers/IndexController.php new file mode 100644 index 0000000..20ecd38 --- /dev/null +++ b/modules/Linker/Http/Controllers/IndexController.php @@ -0,0 +1,51 @@ + ['value' => 1, 'text' => '启用', 'color' => 'success'], + 'off' => ['value' => 0, 'text' => '禁用', 'color' => 'danger'], + ]; + + public function grid(): Grid + { + $grid = new Grid(new Linker()); + + $grid->column('title', '标题'); + $grid->column('type', '类型')->using(Linker::TYPES); + $grid->column('url', '目标地址'); + $grid->column('params', '附加参数'); + $grid->column('status', '状态')->switch($this->status); + $grid->column('created_at', '创建时间'); + $grid->column('updated_at', '更新时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Linker()); + $form->text('title', '链接标题')->required(); + $form->select('type', '链接类型') + ->options(Linker::TYPES) + ->required(); + $form->text('url', '目标地址')->required(); + $form->keyValue('params', '附加参数'); + $form->switch('status', '状态') + ->states($this->status) + ->default(1); + + return $form; + } + +} \ No newline at end of file diff --git a/modules/Linker/Linker.php b/modules/Linker/Linker.php new file mode 100644 index 0000000..d503ad6 --- /dev/null +++ b/modules/Linker/Linker.php @@ -0,0 +1,66 @@ + + */ + public static function install() + { + Artisan::call('migrate', [ + '--path' => 'modules/Linker/Database/Migrations', + ]); + + self::createAdminMenu(); + } + + /** + * Notes : 卸载模块的一些操作 + * @Date : 2021/3/12 11:35 上午 + * @Author : < Jason.C > + */ + public static function uninstall() + { + $menu = config('admin.database.menu_model'); + + $main = $menu::where('title', self::$mainTitle)->first(); + + $main->delete(); + } + + /** + * Notes : 创建后台菜单 + * @Date : 2021/3/17 9:48 上午 + * @Author : < Jason.C > + */ + protected static function createAdminMenu() + { + $menu = config('admin.database.menu_model'); + + $main = $menu::create([ + 'parent_id' => 0, + 'order' => 45, + 'title' => self::$mainTitle, + 'icon' => 'fa-bars', + ]); + + $main->children()->createMany([ + [ + 'order' => 0, + 'title' => '链接管理', + 'icon' => 'fa-bars', + 'uri' => 'linkers', + ], + ]); + } + +} \ No newline at end of file diff --git a/modules/Linker/Models/Linker.php b/modules/Linker/Models/Linker.php new file mode 100644 index 0000000..ac99ef2 --- /dev/null +++ b/modules/Linker/Models/Linker.php @@ -0,0 +1,102 @@ + 'json', + ]; + + const TYPE_WEB = 'web'; + const TYPE_MINI = 'mini'; + const TYPE_APP_NAVIGATE_TO = 'navigateTo'; + const TYPE_APP_REDIRECT_TO = 'redirectTo'; + const TYPE_APP_RE_LAUNCH = 'reLaunch'; + const TYPE_APP_SWITCH_TAB = 'switchTab'; + const TYPE_APP_NAVIGATE_BACK = 'navigateBack'; + + const TYPES = [ + self::TYPE_WEB => '网页连接(web)', + self::TYPE_MINI => '小程序中跳转', + self::TYPE_APP_NAVIGATE_TO => 'APP内部页面转跳', + self::TYPE_APP_REDIRECT_TO => '跳转内部页面(关闭当前页)', + self::TYPE_APP_RE_LAUNCH => '跳转内部页面(关闭所有页面)', + self::TYPE_APP_SWITCH_TAB => 'TAB跳转(关闭所有非TAB页面)', + self::TYPE_APP_NAVIGATE_BACK => 'APP页面返回', + ]; + + const MODE_APPEND = 'append'; + const MODE_REPLACE = 'replace'; + const MODE_OVERRIDE = 'override'; + + const MODES = [ + self::MODE_APPEND => '追加', + self::MODE_REPLACE => '替换', + self::MODE_OVERRIDE => '覆盖', + ]; + + /** + * Notes : 转换链接到实际输出 + * @Date : 2021/6/21 4:03 下午 + * @Author : < Jason.C > + * @param \Modules\Linker\Models\LinkerRelation $relation + * @return array + */ + public function getResource(LinkerRelation $relation): array + { + $params = ''; + + if (is_array($this->params)) { + if ($relation->params) { + switch ($relation->mode) { + case self::MODE_APPEND: + $this->params = array_merge($this->params, $relation->params); + break; + case self::MODE_REPLACE: + $this->params = array_replace($this->params, $relation->params); + break; + case self::MODE_OVERRIDE; + $this->params = $relation->params; + break; + } + } + $params = http_build_query($this->params); + } + + $data = [ + 'path' => $this->url, + 'openType' => $this->type, + 'url' => $this->url, + 'params' => $params, + ]; + switch ($data['openType']) { + case self::TYPE_WEB: + $data['path'] = ''; + $data['url'] .= '?' . $data['params']; + $data['params'] = ''; + break; + case self::TYPE_APP_NAVIGATE_BACK: + $data['path'] = ''; + $data['url'] = ''; + $data['params'] = ''; + break; + default: + $data['url'] = ''; + break; + } + + return $data; + } + +} \ No newline at end of file diff --git a/modules/Linker/Models/LinkerRelation.php b/modules/Linker/Models/LinkerRelation.php new file mode 100644 index 0000000..e10ede9 --- /dev/null +++ b/modules/Linker/Models/LinkerRelation.php @@ -0,0 +1,40 @@ + 'json', + ]; + + /** + * Notes : 隶属链接 + * @Date : 2021/6/18 4:47 下午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function linker(): BelongsTo + { + return $this->belongsTo(Linker::class); + } + + /** + * Notes : 链接对应的模型 + * @Date : 2021/6/22 2:11 下午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\MorphTo + */ + public function model(): MorphTo + { + return $this->morphTo(); + } + +} diff --git a/modules/Linker/Providers/LinkerServiceProvider.php b/modules/Linker/Providers/LinkerServiceProvider.php new file mode 100644 index 0000000..75db0e8 --- /dev/null +++ b/modules/Linker/Providers/LinkerServiceProvider.php @@ -0,0 +1,47 @@ +loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations')); + } + + /** + * Register the service provider. + * @return void + */ + public function register() + { + $this->app->register(RouteServiceProvider::class); + } + + /** + * Get the services provided by the provider. + * @return array + */ + public function provides(): array + { + return []; + } + +} \ No newline at end of file diff --git a/modules/Linker/Providers/RouteServiceProvider.php b/modules/Linker/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..b8a54a5 --- /dev/null +++ b/modules/Linker/Providers/RouteServiceProvider.php @@ -0,0 +1,51 @@ +mapAdminRoutes(); + } + + protected function mapAdminRoutes() + { + Route::as(config('admin.route.as')) + ->domain(config('admin.route.domain')) + ->middleware(config('admin.route.middleware')) + ->namespace($this->moduleNamespace) + ->prefix(config('admin.route.prefix')) + ->group(module_path($this->moduleName, 'Routes/admin.php')); + } + +} \ No newline at end of file diff --git a/modules/Linker/README.md b/modules/Linker/README.md new file mode 100644 index 0000000..9b75460 --- /dev/null +++ b/modules/Linker/README.md @@ -0,0 +1,22 @@ +# 链接模块 + +## 1. 使用 + +### 1. 模型 Trait + +```php +use Modules\Linker\Traits\HasLinker; +``` + +### 2. laravel-admin + +自动增加三个字段, +1. 选择链接地址 +2. 替换链接地址里的参数 +3. 参数替换模式 + +```php +use Modules\Linker\Traits\WithLinker; + +$this->withUrl($form,'字段名称'); +``` diff --git a/modules/Linker/Routes/admin.php b/modules/Linker/Routes/admin.php new file mode 100644 index 0000000..f26e11a --- /dev/null +++ b/modules/Linker/Routes/admin.php @@ -0,0 +1,10 @@ + 'linker.', +], function (Router $router) { + $router->resource('linkers', 'IndexController'); +}); diff --git a/modules/Linker/Traits/HasLinker.php b/modules/Linker/Traits/HasLinker.php new file mode 100644 index 0000000..915149e --- /dev/null +++ b/modules/Linker/Traits/HasLinker.php @@ -0,0 +1,123 @@ +attributes['linker_id']); + unset($model->attributes['linker_params']); + unset($model->attributes['linker_mode']); + }); + self::saved(function ($model) { + $model->saveWithUrl(); + }); + // 这里要注意,如果模型使用了缓存 (use GeneaLabs\LaravelModelCaching\Traits\Cachable) + // 在表单实例化模型的时候 要使用 (new Model())->disableModelCaching() + self::retrieved(function ($model) { + if ($relation = $model->hasLinker()) { + $model->attributes['linker_id'] = $relation->linker_id; + $model->attributes['linker_params'] = $relation->params; + $model->attributes['linker_mode'] = $relation->mode; + } + }); + } + + /** + * Notes : 处理当前模型多余的三个字段 + * @Date : 2021/6/22 2:10 下午 + * @Author : < Jason.C > + * @param $value + */ + public function setLinkerIdAttribute($value) + { + $this->linker_id = $value; + } + + public function setLinkerParamsAttribute($value) + { + $this->linker_params = $value; + } + + public function setLinkerModeAttribute($value) + { + $this->linker_mode = $value; + } + + /** + * 外链入库 + * 在BOOT中触发 + */ + public function saveWithUrl(): void + { + if ($this->linker_id) { + LinkerRelation::updateOrCreate([ + 'model_type' => get_class($this), + 'model_id' => $this->getKey(), + ], [ + 'linker_id' => $this->linker_id, + 'mode' => $this->linker_mode, + 'params' => $this->linker_params, + ]); + } elseif (!$this->linker_id && $this->hasLinker()) { + $this->hasLinker()->delete(); + } + } + + /** + * 返回当前模型的链接配置 + * @return null|LinkerRelation + */ + public function hasLinker(): ?LinkerRelation + { + return LinkerRelation::whereHasMorph( + 'model', + [get_class($this)], + function (Builder $query) { + $query->where($this->getKeyName(), $this->getKey()); + } + )->first(); + } + + /** + * Notes : 返回该模型的链接数组 + * @Date : 2021/6/22 2:08 下午 + * @Author : < Jason.C > + * @return array|null + */ + public function getLinkerAttribute(): ?array + { + $relation = $this->hasLinker(); + if ($relation && $relation->linker && $relation->linker->status) { + return $relation->linker->getResource($relation); + } + + return null; + } + +} \ No newline at end of file diff --git a/modules/Linker/Traits/WithLinker.php b/modules/Linker/Traits/WithLinker.php new file mode 100644 index 0000000..f121b0b --- /dev/null +++ b/modules/Linker/Traits/WithLinker.php @@ -0,0 +1,31 @@ + '无链接']; + } + + $form->divider('链接设置'); + $form->select('linker_id', $label) + ->options($links); + $form->select('linker_mode', '参数模式') + ->options(Linker::MODES) + ->default(Linker::MODE_OVERRIDE); + $form->keyValue('linker_params', '替换参数'); + + return $form; + } + +} \ No newline at end of file diff --git a/modules/Linker/composer.json b/modules/Linker/composer.json new file mode 100644 index 0000000..bc15ec9 --- /dev/null +++ b/modules/Linker/composer.json @@ -0,0 +1,28 @@ +{ + "name": "uztech/linker-module", + "description": "", + "type": "laravel-module", + "authors": [ + { + "name": "Yz-Leady", + "email": "149307205@qq.com" + } + ], + "require": { + "php": "^7.3|^8.0", + "encore/laravel-admin": "^1.8", + "jasonc/api": "^5.0.0", + "genealabs/laravel-model-caching": "^0.11.3", + "joshbrw/laravel-module-installer": "^2.0", + "laravel/framework": "^8.5", + "nwidart/laravel-modules": "^8.2" + }, + "extra": { + "module-dir": "modules" + }, + "autoload": { + "psr-4": { + "Modules\\Linker\\": "" + } + } +} diff --git a/modules/Linker/module.json b/modules/Linker/module.json new file mode 100644 index 0000000..430ff4d --- /dev/null +++ b/modules/Linker/module.json @@ -0,0 +1,15 @@ +{ + "name": "Linker", + "alias": "linker", + "description": "链接管理模块(重构版本)", + "keywords": [], + "priority": 0, + "providers": [ + "Modules\\Linker\\Providers\\LinkerServiceProvider" + ], + "aliases": {}, + "files": [], + "requires": [], + "version": "1.0.0", + "author": "Leady" +} diff --git a/modules/Mall/.gitignore b/modules/Mall/.gitignore new file mode 100644 index 0000000..4b6cb11 --- /dev/null +++ b/modules/Mall/.gitignore @@ -0,0 +1,4 @@ +.idea +vendor +.DS_Store +composer.lock \ No newline at end of file diff --git a/modules/Mall/Config/config.php b/modules/Mall/Config/config.php new file mode 100644 index 0000000..ffa2ae1 --- /dev/null +++ b/modules/Mall/Config/config.php @@ -0,0 +1,168 @@ + 'Mall', + + /* + |-------------------------------------------------------------------------- + | 订单编号计数器位数,取值范围 6 - 16 + |-------------------------------------------------------------------------- + */ + 'order_no_counter_length' => 8, + + /* + |-------------------------------------------------------------------------- + | 退款单编号前缀 + |-------------------------------------------------------------------------- + */ + 'refund_no_counter_prefix' => 'RF', + + /* + |-------------------------------------------------------------------------- + | 退款单编号计数器位数,取值范围 6 - 16 - 退款编号前缀位数 + |-------------------------------------------------------------------------- + */ + 'refund_no_counter_length' => 6, + + /* + |-------------------------------------------------------------------------- + | 商品规格的扩展属性 + |-------------------------------------------------------------------------- + */ + 'sku_extends_attributes' => [ + 'market_fee' => '平台手续费', + ], + + 'administrator_max_id' => 10000, + + 'workflow' => [ + 'orders' => [ + 'type' => 'state_machine', + 'marking_store' => [ + 'type' => 'single_state', + 'property' => 'currentState', + ], + 'supports' => [Modules\Mall\Models\Order::class], + 'places' => [ + 'INIT', + 'CANCEL', + 'PAID', + 'DELIVERED', + 'SIGNED', + 'COMPLETED', + 'REFUND_APPLY', + 'REFUND_AGREE', + 'REFUND_REFUSE', + 'REFUND_DELIVER', + 'REFUND_DELIVERD', + 'REFUND_PROCESS', + 'REFUND_COMPLETED', + ], + 'transitions' => [ + 'cancel' => [ + 'from' => ['INIT', 'PAID'], + 'to' => 'CANCEL', + ], + 'pay' => [ + 'from' => 'INIT', + 'to' => 'PAID', + ], + 'deliver' => [ + 'from' => 'PAID', + 'to' => 'DELIVERED', + ], + 'sign' => [ + 'from' => 'DELIVERED', + 'to' => 'SIGNED', + ], + 'complete' => [ + 'from' => 'SIGNED', + 'to' => 'COMPLETED', + ], + 'refund' => [ + 'from' => ['PAID', 'SIGNED'], + 'to' => 'REFUND_APPLY', + ], + 'agree' => [ + 'from' => ['PAID', 'SIGNED', 'REFUND_APPLY'], + 'to' => 'REFUND_AGREE', + ], + 'refuse' => [ + 'from' => 'REFUND_APPLY', + 'to' => 'REFUND_REFUSE', + ], + 'user_deliver' => [ + 'from' => 'REFUND_AGREE', + 'to' => 'REFUND_DELIVER', + ], + 'user_deliverd' => [ + 'from' => 'REFUND_DELIVER', + 'to' => 'REFUND_DELIVERD', + ], + 'process' => [ + 'from' => ['REFUND_AGREE', 'REFUND_DELIVERD'], + 'to' => 'REFUND_PROCESS', + ], + 'completed' => [ + 'from' => ['PAID', 'DELIVERED', 'SIGNED', 'REFUND_PROCESS', 'REFUND_DELIVERD'], + 'to' => 'REFUND_COMPLETED', + ], + ], + ], + 'refunds' => [ + 'type' => 'state_machine', + 'marking_store' => [ + 'type' => 'single_state', + 'property' => 'currentState', + ], + 'supports' => [Modules\Mall\Models\Refund::class], + 'places' => [ + 'REFUND_APPLY', + 'REFUND_AGREE', + 'REFUND_REFUSE', + 'REFUND_PROCESS', + 'REFUND_DELIVER', + 'REFUND_DELIVERED', + 'REFUND_SIGNED', + 'REFUND_COMPLETED', + ], + 'transitions' => [ + 'agree' => [ + 'from' => 'REFUND_APPLY', + 'to' => 'REFUND_AGREE', + ], + 'refuse' => [ + 'from' => 'REFUND_APPLY', + 'to' => 'REFUND_REFUSE', + ], + 'deliver' => [ + 'from' => 'REFUND_AGREE', + 'to' => 'REFUND_DELIVER', + ], + 'delivered' => [ + 'from' => 'REFUND_DELIVER', + 'to' => 'REFUND_DELIVERED', + ], + 'sign' => [ + 'from' => 'REFUND_DELIVERED', + 'to' => 'REFUND_SIGNED', + ], + 'process' => [ + 'from' => ['REFUND_AGREE', 'REFUND_SIGNED'], + 'to' => 'REFUND_PROCESS', + ], + 'completed' => [ + 'from' => 'REFUND_PROCESS', + 'to' => 'REFUND_COMPLETED', + ], + ], + ], + ], + +]; diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_addresses_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_addresses_table.php new file mode 100644 index 0000000..be53ae1 --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_addresses_table.php @@ -0,0 +1,40 @@ +id(); + $table->unsignedInteger('user_id')->index(); + $table->string('name', 32); + $table->string('mobile', 32); + $table->unsignedInteger('province_id'); + $table->unsignedInteger('city_id'); + $table->unsignedInteger('district_id'); + $table->string('address'); + $table->boolean('is_default')->default(0); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_addresses'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_banners_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_banners_table.php new file mode 100644 index 0000000..aee80c4 --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_banners_table.php @@ -0,0 +1,37 @@ +id(); + $table->unsignedBigInteger('shop_id')->default(0)->index()->comment('所属店铺'); + $table->string('title'); + $table->string('cover'); + $table->integer('position')->unsigned(); + $table->boolean('status')->default(0); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_banners'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_brands_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_brands_table.php new file mode 100644 index 0000000..1ba6a26 --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_brands_table.php @@ -0,0 +1,37 @@ +id(); + $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺'); + $table->string('name'); + $table->string('cover')->nullable(); + $table->string('description')->nullable(); + $table->boolean('status')->default(0); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_brands'); + } + +} \ No newline at end of file diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_carts_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_carts_table.php new file mode 100644 index 0000000..b00b875 --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_carts_table.php @@ -0,0 +1,35 @@ +id(); + $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺'); + $table->unsignedBigInteger('user_id')->index(); + $table->unsignedBigInteger('sku_id')->comment('加入购物车的商品条目'); + $table->unsignedInteger('qty')->default(1)->comment('数量'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_carts'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_categories_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_categories_table.php new file mode 100644 index 0000000..0d417bf --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_categories_table.php @@ -0,0 +1,40 @@ +id(); + $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺'); + $table->unsignedBigInteger('parent_id')->default(0)->index()->comment('父级id'); + $table->string('name'); + $table->string('slug')->nullable(); + $table->string('description')->nullable(); + $table->string('cover')->nullable(); + $table->integer('order')->default(0); + $table->boolean('status')->default(0); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_categories'); + } + +} \ No newline at end of file diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_deliveries_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_deliveries_table.php new file mode 100644 index 0000000..2a40cfb --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_deliveries_table.php @@ -0,0 +1,35 @@ +id(); + $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺'); + $table->string('name')->comment('模板名称'); + $table->unsignedTinyInteger('type')->comment('计费方式:1按件数 2按重量'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_deliveries'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_delivery_rules_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_delivery_rules_table.php new file mode 100644 index 0000000..ef8fb57 --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_delivery_rules_table.php @@ -0,0 +1,40 @@ +id(); + $table->integer('delivery_id')->comment('配送模板id'); + $table->json('regions')->nullable()->comment('可配送区域(城市id集)'); + $table->decimal('first', 8, 2)->default(0)->comment('首件(个)/首重(Kg)'); + $table->decimal('first_fee', 8, 2)->default(0)->comment('运费(元)'); + + $table->decimal('additional', 8, 2)->default(0)->comment('续件/续重'); + $table->decimal('additional_fee', 8, 2)->default(0)->comment('续费(元)'); + + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_delivery_rules'); + } +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_expresses_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_expresses_table.php new file mode 100644 index 0000000..bb01a3c --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_expresses_table.php @@ -0,0 +1,43 @@ +id(); + $table->string('name'); + $table->string('slug')->nullable()->comment('简称'); + $table->string('cover')->nullable(); + $table->string('description')->nullable(); + $table->boolean('status')->default(0); + $table->timestamps(); + $table->softDeletes(); + }); + + Artisan::call('db:seed', [ + '--class' => ExpressSeeder::class, + ]); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_expresses'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_skus_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_skus_table.php new file mode 100644 index 0000000..ae661ba --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_skus_table.php @@ -0,0 +1,41 @@ +id(); + $table->unsignedBigInteger('goods_id')->index(); + $table->string('unit')->nullable()->comment('商品单元编号'); + $table->string('cover')->nullable(); + $table->decimal('price'); + $table->decimal('score')->comment('积分/原石'); + $table->decimal('assets', 8, 2)->comment('资产'); + $table->integer('stock')->default(0); + $table->decimal('weight')->default(0); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_goods_skus'); + Schema::dropIfExists('mall_goods_sku_spec'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_spec_values_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_spec_values_table.php new file mode 100644 index 0000000..efa7351 --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_spec_values_table.php @@ -0,0 +1,33 @@ +id(); + $table->unsignedBigInteger('spec_id')->index(); + $table->string('value'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_goods_spec_values'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_specs_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_specs_table.php new file mode 100644 index 0000000..0fe20bd --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_specs_table.php @@ -0,0 +1,33 @@ +id(); + $table->unsignedBigInteger('goods_id')->index(); + $table->string('name'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_goods_specs'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_table.php new file mode 100644 index 0000000..8435f46 --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_table.php @@ -0,0 +1,52 @@ +id(); + $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺'); + $table->unsignedBigInteger('category_id')->index()->comment('分类'); + $table->unsignedBigInteger('brand_id')->nullable()->index()->comment('品牌ID'); + $table->unsignedBigInteger('delivery_id')->nullable()->comment('物流模板'); + $table->unsignedTinyInteger('type')->default(1)->comment('规格类型'); + $table->unsignedTinyInteger('pay_type')->default(1)->comment('支付方式'); + $table->unsignedTinyInteger('deduct_stock_type')->default(1)->comment('库存扣减方式'); + $table->unsignedTinyInteger('channel')->default(1)->comment('商品类型'); + $table->boolean('status')->default(0); + $table->boolean('is_post_sale')->default(1)->comment('是否可以申请售后'); + $table->unsignedInteger('clicks')->default(0)->comment('浏览量'); + $table->unsignedInteger('sales')->default(0)->comment('总销量'); + $table->decimal('original_price', 10, 2)->default(0)->comment('商品显示的原价'); + $table->string('name'); + $table->string('cover')->nullable(); + $table->json('pictures')->nullable(); + $table->string('description')->nullable(); + $table->text('content')->nullable(); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_goods'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_tag_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_tag_table.php new file mode 100644 index 0000000..324d4c0 --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_tag_table.php @@ -0,0 +1,34 @@ +unsignedBigInteger('goods_id')->index(); + $table->unsignedBigInteger('tag_id')->index(); + $table->timestamps(); + + $table->primary(['goods_id', 'tag_id']); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_goods_tag'); + } + +} \ No newline at end of file diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_jobs_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_jobs_table.php new file mode 100644 index 0000000..0481262 --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_jobs_table.php @@ -0,0 +1,35 @@ +id(); + + $table->unsignedBigInteger('shop_id')->index(); + $table->string('name'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_jobs'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_order_expresses_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_order_expresses_table.php new file mode 100644 index 0000000..1afe34a --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_order_expresses_table.php @@ -0,0 +1,44 @@ +id(); + $table->unsignedBigInteger('order_id')->index(); + + $table->string('name', 32)->nullable(); + $table->string('mobile', 32)->nullable(); + $table->unsignedInteger('province_id')->index()->nullable(); + $table->unsignedInteger('city_id')->index()->nullable(); + $table->unsignedInteger('district_id')->index()->nullable(); + $table->string('address')->nullable(); + + $table->unsignedBigInteger('express_id')->nullable()->comment('物流公司'); + $table->string('express_no', 32)->nullable()->comment('物流单号'); + $table->dateTime('deliver_at')->nullable()->comment('发货时间'); + $table->dateTime('receive_at')->nullable()->comment('签收时间'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_order_expresses'); + } + +} \ No newline at end of file diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_order_items_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_order_items_table.php new file mode 100644 index 0000000..0c9cc8a --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_order_items_table.php @@ -0,0 +1,36 @@ +bigIncrements('id'); + $table->unsignedBigInteger('order_id')->index('order_id'); + $table->morphs('item'); + $table->unsignedInteger('qty')->comment('数量'); + $table->unsignedDecimal('price', 20, 2)->comment('单价'); + $table->json('source')->nullable(); + $table->timestamp('created_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_order_items'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_orders_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_orders_table.php new file mode 100644 index 0000000..17487eb --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_orders_table.php @@ -0,0 +1,49 @@ +id(); + $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺'); + $table->string('order_no', 32)->unique()->comment('订单编号'); + $table->unsignedInteger('user_id')->index()->comment('下单用户'); + + $table->unsignedDecimal('amount', 20, 2)->comment('订单金额'); + $table->unsignedDecimal('freight', 10, 2)->default(0)->comment('运费'); + + $table->timestamp('expired_at')->nullable()->comment('订单过期时间'); + $table->timestamp('paid_at')->nullable()->comment('支付时间'); + + $table->string('state', 16)->comment('状态')->nullable(); + $table->boolean('type')->comment('订单类型:1 正常 2试用'); + $table->string('remark')->nullable()->comment('用户留言'); + $table->string('description')->nullable()->comment('备注'); + + $table->json('source')->comment('附加数据')->nullable(); + + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_orders'); + } + +} \ No newline at end of file diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_reasons_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_reasons_table.php new file mode 100644 index 0000000..fb87003 --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_reasons_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('title'); + $table->boolean('status')->default(0); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('reasons'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_expresses_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_expresses_table.php new file mode 100644 index 0000000..5c45ec5 --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_expresses_table.php @@ -0,0 +1,37 @@ +bigIncrements('id'); + $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺'); + $table->unsignedBigInteger('refund_id')->index()->comment('所属退款单'); + $table->string('company')->nullable()->comment('退款物流'); + $table->string('number')->nullable()->comment('退款单号'); + $table->timestamp('deliver_at')->nullable()->comment('寄回时间'); + $table->timestamp('receive_at')->nullable()->comment('收到时间'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_refund_expresses'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_items_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_items_table.php new file mode 100644 index 0000000..db45917 --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_items_table.php @@ -0,0 +1,37 @@ +bigIncrements('id'); + $table->unsignedBigInteger('refund_id')->index()->comment('所属退款单'); + $table->unsignedBigInteger('order_id')->comment('所属订单'); + $table->unsignedBigInteger('order_item_id')->comment('详情ID'); + $table->unsignedInteger('qty')->comment('数量'); + $table->unsignedDecimal('price', 20, 2)->comment('单价'); + $table->json('source')->nullable(); + $table->timestamp('created_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_refund_items'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_logs_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_logs_table.php new file mode 100644 index 0000000..7fcd12b --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_logs_table.php @@ -0,0 +1,37 @@ +id(); + $table->unsignedBigInteger('refund_id'); + $table->morphs('userable'); + $table->string('pictures')->nullable()->comment('图片'); + $table->string('title')->nullable()->comment('原因'); + $table->string('remark')->nullable()->comment('备注'); + $table->string('state', 16)->comment('状态')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_tags'); + } + +} \ No newline at end of file diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refunds_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refunds_table.php new file mode 100644 index 0000000..e7f291c --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refunds_table.php @@ -0,0 +1,41 @@ +bigIncrements('id'); + $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺'); + $table->unsignedBigInteger('order_id')->index()->comment('所属订单'); + $table->string('refund_no', 32)->comment('退款单号'); + $table->unsignedBigInteger('user_id')->comment('下单用户'); + $table->unsignedDecimal('refund_total', 20, 2)->comment('申请退款金额'); + $table->unsignedDecimal('actual_total', 20, 2)->comment('实退金额'); + $table->string('state', 16)->comment('状态')->nullable(); + $table->string('remark')->nullable()->comment('备注'); + $table->timestamp('refunded_at')->nullable()->comment('退款时间'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_refunds'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_regions_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_regions_table.php new file mode 100644 index 0000000..95e98cb --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_regions_table.php @@ -0,0 +1,39 @@ +id(); + $table->unsignedBigInteger('parent_id')->index(); + $table->string('name'); + $table->boolean('depth'); + }); + + Artisan::call('db:seed', [ + '--class' => RegionSeeder::class, + ]); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_regions'); + } + +} \ No newline at end of file diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_express_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_express_table.php new file mode 100644 index 0000000..1544195 --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_express_table.php @@ -0,0 +1,34 @@ +unsignedBigInteger('shop_id')->index(); + $table->unsignedBigInteger('express_id')->index(); + $table->timestamps(); + + $table->primary(['shop_id', 'express_id']); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_shop_express'); + } + +} \ No newline at end of file diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_reasons_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_reasons_table.php new file mode 100644 index 0000000..a12527e --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_reasons_table.php @@ -0,0 +1,33 @@ +unsignedBigInteger('shop_id')->index(); + $table->unsignedBigInteger('reason_id')->index(); + $table->timestamps(); + $table->primary(['shop_id', 'reason_id']); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_shop_reasons'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_staffers_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_staffers_table.php new file mode 100644 index 0000000..b82caa1 --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_staffers_table.php @@ -0,0 +1,36 @@ +id(); + + $table->unsignedBigInteger('shop_id')->index(); + $table->unsignedBigInteger('user_id')->index(); + $table->unsignedBigInteger('job_id')->index(); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_shop_staffers'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shops_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shops_table.php new file mode 100644 index 0000000..a4fc1ef --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shops_table.php @@ -0,0 +1,46 @@ +id(); + $table->unsignedInteger('user_id')->index()->default(0); + $table->string('name'); + $table->boolean('is_self')->unsigned()->default(0)->comment('是否自营'); + $table->string('description')->nullable(); + $table->string('mobile')->nullable(); + $table->string('cover')->nullable(); + $table->boolean('status')->default(0); + $table->unsignedInteger('province_id')->index()->default(0); + $table->unsignedInteger('city_id')->index()->default(0); + $table->unsignedInteger('district_id')->index()->default(0); + $table->string('address')->nullable(); + $table->json('configs')->nullable(); + $table->integer('order')->default(9999)->comment('排序用的,后台控制'); + $table->string('reject_reason')->comment('驳回原因')->nullable(); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_shops'); + } + +} diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_tags_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_tags_table.php new file mode 100644 index 0000000..35192e2 --- /dev/null +++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_tags_table.php @@ -0,0 +1,33 @@ +id(); + $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺'); + $table->string('name'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_tags'); + } + +} \ No newline at end of file diff --git a/modules/Mall/Database/Migrations/2022_07_27_110430_create_mall_activities_table.php b/modules/Mall/Database/Migrations/2022_07_27_110430_create_mall_activities_table.php new file mode 100644 index 0000000..71ba5c4 --- /dev/null +++ b/modules/Mall/Database/Migrations/2022_07_27_110430_create_mall_activities_table.php @@ -0,0 +1,36 @@ +id(); + $table->string('title'); + $table->string('description')->nullable(); + $table->string('cover')->nullable(); + $table->text('content')->nullable(); + $table->boolean('status')->default(0); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_activities'); + } +} diff --git a/modules/Mall/Database/Migrations/2022_09_08_143557_add_channel_to_mall_orders_table.php b/modules/Mall/Database/Migrations/2022_09_08_143557_add_channel_to_mall_orders_table.php new file mode 100644 index 0000000..033a126 --- /dev/null +++ b/modules/Mall/Database/Migrations/2022_09_08_143557_add_channel_to_mall_orders_table.php @@ -0,0 +1,32 @@ +boolean('channel')->default(1)->comment('渠道')->after('type'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('mall_orders', function (Blueprint $table) { + + }); + } +} diff --git a/modules/Mall/Database/Migrations/2022_09_08_144917_add_type_and_person_to_mall_orders_expresses_table.php b/modules/Mall/Database/Migrations/2022_09_08_144917_add_type_and_person_to_mall_orders_expresses_table.php new file mode 100644 index 0000000..c9e7412 --- /dev/null +++ b/modules/Mall/Database/Migrations/2022_09_08_144917_add_type_and_person_to_mall_orders_expresses_table.php @@ -0,0 +1,33 @@ +boolean('type')->default(1)->comment('方式')->after('receive_at'); + $table->boolean('person')->nullable()->comment('经办人')->after('type'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('mall_orders_expresses', function (Blueprint $table) { + + }); + } +} diff --git a/modules/Mall/Database/Migrations/2022_09_30_093509_create_mall_videos_table.php b/modules/Mall/Database/Migrations/2022_09_30_093509_create_mall_videos_table.php new file mode 100644 index 0000000..44e33d6 --- /dev/null +++ b/modules/Mall/Database/Migrations/2022_09_30_093509_create_mall_videos_table.php @@ -0,0 +1,35 @@ +id(); + $table->string('name'); + $table->string('cover')->nullable(); + $table->string('path'); + $table->boolean('status')->default(1); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('mall_videos'); + } +} diff --git a/modules/Mall/Database/Seeders/ExpressSeeder.php b/modules/Mall/Database/Seeders/ExpressSeeder.php new file mode 100644 index 0000000..4cbdbf4 --- /dev/null +++ b/modules/Mall/Database/Seeders/ExpressSeeder.php @@ -0,0 +1,45 @@ +order = $order; + } + +} \ No newline at end of file diff --git a/modules/Mall/Events/OrderPaid.php b/modules/Mall/Events/OrderPaid.php new file mode 100644 index 0000000..683c538 --- /dev/null +++ b/modules/Mall/Events/OrderPaid.php @@ -0,0 +1,11 @@ +refund = $refund; + } + +} diff --git a/modules/Mall/Events/RefundApplied.php b/modules/Mall/Events/RefundApplied.php new file mode 100644 index 0000000..716e9cd --- /dev/null +++ b/modules/Mall/Events/RefundApplied.php @@ -0,0 +1,27 @@ +order = $order; + $this->refund = $refund; + } + +} diff --git a/modules/Mall/Events/RefundCompleted.php b/modules/Mall/Events/RefundCompleted.php new file mode 100644 index 0000000..0e36fb6 --- /dev/null +++ b/modules/Mall/Events/RefundCompleted.php @@ -0,0 +1,23 @@ +refund = $refund; + } + +} diff --git a/modules/Mall/Events/RefundProcessed.php b/modules/Mall/Events/RefundProcessed.php new file mode 100644 index 0000000..d03eacb --- /dev/null +++ b/modules/Mall/Events/RefundProcessed.php @@ -0,0 +1,23 @@ +refund = $refund; + } + +} diff --git a/modules/Mall/Events/RefundRefused.php b/modules/Mall/Events/RefundRefused.php new file mode 100644 index 0000000..5c75f6d --- /dev/null +++ b/modules/Mall/Events/RefundRefused.php @@ -0,0 +1,23 @@ +refund = $refund; + } + +} diff --git a/modules/Mall/Facades/Item.php b/modules/Mall/Facades/Item.php new file mode 100644 index 0000000..ca98fed --- /dev/null +++ b/modules/Mall/Facades/Item.php @@ -0,0 +1,127 @@ +model = $item; + $this->sku_id = $item->id; + $this->shop_id = $item->getShopIdAttribute(); + $this->orderby = $item->getShopTypeAttribute().'_'.$item->getShopIdAttribute(); + $this->qty = $qty; + $this->price = $item->getItemPrice(); + $this->address = $address; + } + + /** + * Notes: 获取条目总价 + * + * @Author: + * @Date : 2019/11/21 11:03 上午 + * @return float + */ + public function total(): float + { + return bcmul($this->price, $this->qty, 2); + } + + /** + * Notes: 获取总总量 + * + * @Author: 玄尘 + * @Date : 2021/5/14 13:20 + * @return float + */ + public function weight(): float + { + return bcmul($this->model->getGoodsWeight(), $this->qty, 2); + } + + /** + * Notes: 获取运费 + * + * @Author: 玄尘 + * @Date : 2021/5/14 13:34 + */ + public function freight(): int + { + return 0; + } + + /** + * Notes: 获取shop + * + * @Author: 玄尘 + * @Date : 2021/5/14 14:15 + */ + public function shop(): array + { + return [ + 'shop_id' => $this->model->getShopIdAttribute(), + 'name' => $this->model->getShopNameAttribute(), + ]; + } + + /** + * Notes: 下单存入order_item表 + * + * @Author: 玄尘 + * @Date : 2021/5/18 8:37 + */ + public function getSource(): array + { + return $this->model->getSource(); + } + + /** + * Notes: 转换成数组 + * + * @Author: + * @Date : 2020/9/23 4:00 下午 + * @return array + */ + public function toArray(): array + { + return [ + 'goods_id' => $this->model->goods_id, + 'goods_sku_id' => $this->sku_id, + 'title' => $this->model->getGoodsName(), + 'value' => $this->model->getItemValue(), + 'cover' => $this->model->cover_url, + 'price' => $this->price, + 'qty' => $this->qty, + 'score' => $this->model->score, + ]; + } + +} diff --git a/modules/Mall/Facades/Order.php b/modules/Mall/Facades/Order.php new file mode 100644 index 0000000..8557194 --- /dev/null +++ b/modules/Mall/Facades/Order.php @@ -0,0 +1,386 @@ +user_id = $user->getAuthIdentifier();; + + return $this; + } + + /** + * Notes: 设置类型 + * + * @Author: 玄尘 + * @Date : 2021/10/18 10:03 + * @param $type + * @return $this + */ + public function type($type): Order + { + $this->type = $type; + + return $this; + } + + /** + * Notes: 订单渠道 + * + * @Author: 玄尘 + * @Date: 2022/9/8 15:51 + */ + public function channel($channel): self + { + $this->channel = $channel; + return $this; + } + + /** + * Notes: 设置订单备注信息 + * + * @Author: 玄尘 + * @Date : 2021/5/19 10:40 + * @param string|null $remark + * @return $this + */ + public function remark(?string $remark = ''): Order + { + $this->remark = $remark; + + return $this; + } + + /** + * Notes: 附加数据 + * + * @Author: 玄尘 + * @Date : 2021/5/19 10:40 + * @param array $source + * @return $this + */ + public function source(array $source): Order + { + $this->source = $source; + + return $this; + } + + /** + * Notes: 设置商品数据 + * + * @Author: 玄尘 + * @Date : 2021/5/14 13:58 + * @param $items + * @return $this + */ + public function items($items): Order + { + $this->items = new Collection($items); + + return $this; + } + + /** + * Notes: 设置订单收货地址 + * + * @Author: 玄尘 + * @Date : 2021/5/19 10:41 + * @param Address $address + * @return $this + */ + public function address(Address $address): Order + { + $this->address = $address; + + return $this; + } + + /** + * Notes: 创建订单 + * + * @Author: 玄尘 + * @Date : 2021/5/19 10:42 + * @return Collection + * @throws \Exception + */ + public function create(): Collection + { + if ($this->items->isEmpty()) { + throw new Exception('无法创建无内容的订单'); + } + + if (! is_numeric($this->user_id)) { + throw new Exception('必须先设置订单用户'); + } + $splits = $this->splitOrderByShop(); + + DB::beginTransaction(); + + try { + $orders = []; + foreach ($splits as $split) { + $orders[] = $this->createOne($split['items']); + } + + DB::commit(); + $result = new Collection($orders); + + $result->total = $this->total(); + + return $result; + } catch (Exception $exception) { + DB::rollBack(); + throw new Exception($exception->getMessage()); + } + } + + /** + * Notes: 按照商户,对订单进行分组 + * + * @Author: 玄尘 + * @Date : 2021/5/19 10:43 + * @return mixed + * @throws \Exception + */ + public function splitOrderByShop() + { + if (empty($this->items)) { + throw new Exception('无法创建无内容的订单'); + } + + return $this->items->groupBy('orderby')->map(function ($items, $key) { + /** + * 计算分订单总价格 + */ + $items->amount = $items->reduce(function ($total, $item) { + return $total + $item->total(); + }); + + /** + * 计算分订单总数量 + */ + $items->qty = $items->reduce(function ($qty, $item) { + return $qty + $item->qty; + }); + + /** + * 计算分订单运费 + */ + $items->freight = $items->reduce(function ($total, $item) { + return $total + $item->freight(); + }); + + /** + * 回传店铺ID + */ + $items->shop_id = $items->first()->shop_id; + + return [ + 'shop' => $items->first()->shop(), + 'items' => $items, + ]; + }); + } + + /** + * Notes: 创建一条订单记录 + * + * @Author: 玄尘 + * @Date : 2021/5/21 10:24 + * @param Collection $split + * @return Builder|Model + * @throws \Exception + */ + protected function createOne(Collection $split) + { + /** + * 创建主订单 + */ + $order = OrderModel::query() + ->create([ + 'shop_id' => $split->shop_id, + 'user_id' => $this->user_id, + 'amount' => $split->amount, + 'type' => $this->type, + 'channel' => $this->channel, + 'freight' => $split->freight, + 'remark' => $this->remark, + 'source' => $this->source ?? null, + ]); + + /** + * 创建订单子条目 + */ + foreach ($split as $item) { + // 库存校验 + if ($item->qty > $item->model->getGoodsStock()) { + throw new Exception(sprintf('[%s]库存不足', $item->model->getGoodsName())); + } + + $order->items() + ->create([ + 'item_type' => get_class($item->model), + 'item_id' => $item->sku_id, + 'qty' => $item->qty, + 'price' => $item->price, + 'source' => $item->getSource(), + ]); + + if ($item->model->goods->deduct_stock_type == Goods::DEDUCT_STOCK_ORDER) { + // 扣减库存 + $item->model->deductStock($item->qty, $order->user_id); + } + + } + + /** + * 自动更新地址 + */ + if ($this->address instanceof Address) { + $this->setOrderAddress($order, $this->address); + } + + /** + * 订单自动审核 + */ + if (config('order.auto_audit')) { + $order->audit(); + } + + event(new OrderCreated($order)); + + return $order; + } + + /** + * Notes: 计算订单总价格 + * + * @Author: 玄尘 + * @Date : 2021/5/21 10:26 + * @return float|int|string + */ + public function total() + { + $this->total = 0; + foreach ($this->items as $item) { + $this->total = bcadd($this->total, $item->total(), 2); + } + + return $this->total; + } + + /** + * Notes: 设置订单收货地址 + * + * @Author: 玄尘 + * @Date : 2021/5/21 10:26 + * @param $order + * @param $address + */ + protected function setOrderAddress($order, $address) + { + $order->express() + ->create([ + 'name' => $address->name, + 'mobile' => $address->mobile, + 'province_id' => $address->province_id, + 'city_id' => $address->city_id, + 'district_id' => $address->district_id, + 'address' => $address->address, + ]); + } + + /** + * Notes: 魔术方法,获取一些参数 + * + * @Author: 玄尘 + * @Date : 2021/5/21 10:27 + * @param $attr + * @return float|int|string|null + */ + public function __get($attr) + { + switch ($attr) { + case 'total': + return $this->total(); + } + + return null; + } + + /** + * Notes: 获取订单详情 + * + * @Author: + * @Date : 2019/11/22 1:51 下午 + * @param string $order_no + * @return mixed + */ + public function get(string $order_no) + { + return OrderModel::where('user_id', $this->user_id) + ->with(['user', 'items', 'express', 'seller', 'logs']) + ->where('order_no', $order_no) + ->first(); + } + +} diff --git a/modules/Mall/Facades/Refund.php b/modules/Mall/Facades/Refund.php new file mode 100644 index 0000000..6dbdb51 --- /dev/null +++ b/modules/Mall/Facades/Refund.php @@ -0,0 +1,250 @@ +user = $user; + } else { + throw new Exception('非法用户'); + } + + return $this; + } + + /** + * Notes: 订单 + * + * @Author: 玄尘 + * @Date : 2021/5/17 8:46 + * @param OrderModel $order + * @return $this + */ + public function order(OrderModel $order): Refund + { + $this->order = $order; + + return $this; + } + + /** + * Notes:商品 + * + * @Author: 玄尘 + * @Date : 2021/5/17 8:46 + * @param array $items + * @return Refund + */ + public function items(array $items): Refund + { + $this->items = $items; + + return $this; + } + + /** + * Notes: 退款金额 + * + * @Author: 玄尘 + * @Date : 2021/5/17 8:48 + * @param float $total + * @return Refund + */ + public function total(float $total): Refund + { + $this->total = $total; + + return $this; + } + + /** + * Notes: 设置订单备注信息 + * + * @Author: 玄尘 + * @Date : 2021/5/21 10:31 + * @param string $remark + * @return $this + */ + public function remark(string $remark): Refund + { + $this->remark = $remark; + + return $this; + } + + /** + * Notes: 退款日志 + * + * @Author: 玄尘 + * @Date : 2021/5/17 11:37 + * @param $logs + * @return $this + */ + public function logs($logs): Refund + { + if (! isset($logs['remark']) && ! empty($this->remark)) { + $logs['remark'] = $this->remark; + } + + $this->logs = $logs; + + return $this; + } + + /** + * Notes: 设置退款商品 + * + * @Author: 玄尘 + * @Date : 2021/5/17 8:51 + */ + public function setItems(): Refund + { + $this->items = $this->order->items->map(function ($item) { + return [ + 'order_item_id' => $item->id, + 'qty' => $item->qty, + ]; + }); + + return $this; + } + + /** + * Notes: 创建退款单 + * + * @Author: 玄尘 + * @Date : 2021/5/17 8:49 + * @return mixed + * @throws \Exception + */ + public function create() + { + + if (! $this->order) { + throw new Exception('未设置订单'); + } + + if (empty($this->items)) { + $this->setItems(); + } + + if (! $this->order->can('refund')) { + throw new Exception('订单状态不可退款'); + } + + $maxAmount = 0; + $refundItems = []; + + //判断最大可退数量 + foreach ($this->items as $item) { + + $orderItem = $this->order->items()->find($item['order_item_id']); + if (! $orderItem) { + throw new Exception('未找到可退商品'); + } + + if (! $orderItem->canRefund()) { + throw new \Exception('退款/货失败,此商品不可退款/货'); + } + + if ($item['qty'] <= 0) { + throw new Exception('【'.$orderItem->source['goods_name'].'】退货数量必须大于0'); + } + + if ($item['qty'] > $orderItem->qty) { + throw new Exception('【'.$orderItem->source['goods_name'].'】超过最大可退数量'); + } + + $maxAmount += $orderItem->price * $item['qty']; + + $refundItems[] = new RefundItem($orderItem, $item['qty']); + } + + // 自动计算退款金额 + if (is_null($this->total)) { + $this->total = $maxAmount; + } elseif ($this->total > $maxAmount) { + throw new Exception('超过最大可退金额'); + } + + DB::transaction(function () use ($refundItems) { + $this->refund = $this->order->refunds() + ->create([ + 'refund_total' => $this->total, + 'actual_total' => 0, + 'user_id' => $this->user->id, + 'shop_id' => $this->order->shop_id, + 'state' => RefundModel::REFUND_APPLY, + 'remark' => $this->remark, + ]); + + foreach ($refundItems as $item) { + $this->refund->items()->create($item->toArray()); + } + + if ($this->logs) { + $this->createLog(); + } + + event(new RefundApplied($this->order, $this->refund)); + + }); + + return $this->refund; + + } + + /** + * Notes: 添加日志 + * + * @Author: 玄尘 + * @Date : 2020/12/11 11:43 + */ + public function createLog() + { + $logs = $this->logs; + + if ($this->user) { + $logs['userable_type'] = get_class($this->user); + $logs['userable_id'] = $this->user->id; + $logs['state'] = $this->refund->state; + } + + $this->refund->logs()->create($logs); + } + +} diff --git a/modules/Mall/Facades/RefundItem.php b/modules/Mall/Facades/RefundItem.php new file mode 100644 index 0000000..8f1ba8b --- /dev/null +++ b/modules/Mall/Facades/RefundItem.php @@ -0,0 +1,68 @@ +qty = $qty; + $this->price = $item->price; + $this->order_id = $item->order_id; + $this->order_item_id = $item->id; + $this->source = $item->source; + } + + /** + * Notes: 获取条目总价 + * @Author: 玄尘 + * @Date : 2020/12/10 9:43 + * @return float + */ + public function total(): float + { + return bcmul($this->price, $this->qty, 2); + } + + /** + * Notes: 返回数组 + * @Author: 玄尘 + * @Date : 2021/5/21 10:40 + * @return array + */ + public function toArray(): array + { + return [ + 'qty' => $this->qty, + 'price' => $this->price, + 'order_id' => $this->order_id, + 'order_item_id' => $this->order_item_id, + 'source' => $this->source, + ]; + } + + public function toJson($options = 0) + { + return json_encode($this->toArray(), $options); + } + +} diff --git a/modules/Mall/Facades/Workflow.php b/modules/Mall/Facades/Workflow.php new file mode 100644 index 0000000..d697402 --- /dev/null +++ b/modules/Mall/Facades/Workflow.php @@ -0,0 +1,15 @@ +pay(); + + return $this->response()->success('审核通过')->refresh(); + } catch (\Exception $exception) { + return $this->response()->error($exception->getMessage())->refresh(); + } + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Admin/Action/Order/Delivered.php b/modules/Mall/Http/Controllers/Admin/Action/Order/Delivered.php new file mode 100644 index 0000000..11d4323 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/Action/Order/Delivered.php @@ -0,0 +1,46 @@ +type == OrderExpress::TYPE_EXPRESS && (empty($request->express_id) || empty($request->express_no))) { + return $this->response()->error('缺少快递公司或快递单号'); + } + + $result = $order->deliver($request->express_id, $request->express_no, $request->type, $request->person); + if ($result === true) { + return $this->response()->success('发货成功')->refresh(); + } else { + return $this->response()->error('失败'); + } + } + + public function form() + { + $order = Order::find($this->getKey()); + $expresses = $order->shop->expresses()->pluck('name', 'id'); + + $this->select('type', '方式') + ->options(OrderExpress::TYPE_MAP) + ->required(); + + $this->select('express_id', '物流') + ->options($expresses); + $this->text('express_no', '物流单号'); + $this->text('person', '经办人'); + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/Action/Order/Pay.php b/modules/Mall/Http/Controllers/Admin/Action/Order/Pay.php new file mode 100644 index 0000000..3b9f1a7 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/Action/Order/Pay.php @@ -0,0 +1,25 @@ +pay(); + + return $this->response()->success('支付状态调整成功')->refresh(); + } catch (\Exception $exception) { + return $this->response()->error($exception->getMessage())->refresh(); + } + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Admin/Action/Order/RefundAudit.php b/modules/Mall/Http/Controllers/Admin/Action/Order/RefundAudit.php new file mode 100644 index 0000000..a75382d --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/Action/Order/RefundAudit.php @@ -0,0 +1,57 @@ +state; + $remark = $request->remark; + $res = false; + + if (!$model->can('agree')) { + return $this->response()->error('不可操作')->refresh(); + } + + if ($state == 'agree') { + $res = $model->setOperator($admin)->agree($remark); + } + + if ($state == 'refuse') { + $res = $model->setOperator($admin)->refuse($remark); + } + + if ($res === true) { + return $this->response()->success('操作成功')->refresh(); + } + + return $this->response()->error('操作失败')->refresh(); + } + + public function form(Model $model) + { + $this->select('state', '状态') + ->options([ + 'agree' => '通过', + 'refuse' => '驳回', + ]) + ->required(); + + $this->text('remark', '说明') + ->default($express->number ?? '') + ->required(); + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/Action/Order/RefundReturns.php b/modules/Mall/Http/Controllers/Admin/Action/Order/RefundReturns.php new file mode 100644 index 0000000..eb4efd5 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/Action/Order/RefundReturns.php @@ -0,0 +1,44 @@ +can('completed')) { + return $this->response()->error('不可操作')->refresh(); + } + + $res = $refund->setOperator($admin)->returns(); + + if ($res === true) { + $refund->setOperator($admin)->complete();//设置完成 + + return $this->response()->success('操作成功')->refresh(); + } + + return $this->response()->error('操作失败')->refresh(); + } catch (\Exception $exception) { + return $this->response()->error($exception->getMessage())->refresh(); + } + + } + + public function dialog() + { + $this->confirm('您确定要打款吗'); + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/Action/Order/RefundSign.php b/modules/Mall/Http/Controllers/Admin/Action/Order/RefundSign.php new file mode 100644 index 0000000..55ac747 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/Action/Order/RefundSign.php @@ -0,0 +1,42 @@ +can('sign')) { + return $this->response()->error('不可操作')->refresh(); + } + + $res = $model->setOperator($admin)->receive(); + + if ($res === true) { + return $this->response()->success('操作成功')->refresh(); + } + + return $this->response()->error('操作失败')->refresh(); + } catch (\Exception $exception) { + return $this->response()->error($exception->getMessage())->refresh(); + } + + } + + public function dialog() + { + $this->confirm('您确定已经收到货了吗'); + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/Action/Shop/Close.php b/modules/Mall/Http/Controllers/Admin/Action/Shop/Close.php new file mode 100644 index 0000000..7bf31cb --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/Action/Shop/Close.php @@ -0,0 +1,32 @@ +close()) { + return $this->response()->success('店铺关闭成功')->refresh(); + } else { + return $this->response()->error('店铺关闭失败')->refresh(); + } + } catch (\Exception $exception) { + return $this->response()->error($exception->getMessage())->refresh(); + } + } + + public function dialog() + { + $this->confirm('确定关闭店铺吗?'); + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/Action/Shop/Open.php b/modules/Mall/Http/Controllers/Admin/Action/Shop/Open.php new file mode 100644 index 0000000..0830826 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/Action/Shop/Open.php @@ -0,0 +1,32 @@ +open()) { + return $this->response()->success('店铺开启成功')->refresh(); + } else { + return $this->response()->error('店铺开启失败')->refresh(); + } + } catch (\Exception $exception) { + return $this->response()->error($exception->getMessage())->refresh(); + } + } + + public function dialog() + { + $this->confirm('确定开启店铺吗?'); + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/Action/Shop/Pass.php b/modules/Mall/Http/Controllers/Admin/Action/Shop/Pass.php new file mode 100644 index 0000000..6a201ca --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/Action/Shop/Pass.php @@ -0,0 +1,32 @@ +pass()) { + return $this->response()->success('审核通过成功')->refresh(); + } else { + return $this->response()->error('审核通过失败')->refresh(); + } + } catch (\Exception $exception) { + return $this->response()->error($exception->getMessage())->refresh(); + } + } + + public function dialog() + { + $this->confirm('确定审核通过?'); + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/Action/Shop/Reject.php b/modules/Mall/Http/Controllers/Admin/Action/Shop/Reject.php new file mode 100644 index 0000000..efec0e4 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/Action/Shop/Reject.php @@ -0,0 +1,33 @@ +reject($request->reject_reason)) { + return $this->response()->success('驳回申请成功')->refresh(); + } else { + return $this->response()->error('驳回申请失败')->refresh(); + } + } catch (\Exception $exception) { + return $this->response()->error($exception->getMessage())->refresh(); + } + } + + public function form() + { + $this->textarea('reject_reason', '驳回原因')->rules('required'); + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/ActivityController.php b/modules/Mall/Http/Controllers/Admin/ActivityController.php new file mode 100644 index 0000000..65897a5 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/ActivityController.php @@ -0,0 +1,48 @@ +disableFilter(); + + + $grid->column('id', '#ID#'); + $grid->column('title', '活动名称'); + $grid->column('description', '简介'); + $grid->column('status', '状态')->switch(); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + protected function form(): Form + { + $form = new Form(new Activity()); + + $form->text('title', '活动名称'); + $this->cover($form); + $form->textarea('description', '简介'); + $form->ueditor('content', '详情')->required(); + + $form->switch('status', '状态')->default(1); + + return $form; + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/AddressController.php b/modules/Mall/Http/Controllers/Admin/AddressController.php new file mode 100644 index 0000000..98ff05f --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/AddressController.php @@ -0,0 +1,93 @@ +filter(function (Grid\Filter $filter) { + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('user_id', '所属用户')->select()->ajax(route('admin.user.users.ajax')); + }); + }); + + $grid->model()->with(['user']); + + $grid->column('id', '#ID#'); + $grid->column('user.username', '所属用户'); + $grid->column('name', '姓名'); + $grid->column('mobile', '手机号'); + $grid->column('province.name', '省'); + $grid->column('city.name', '市'); + $grid->column('district.name', '区'); + $grid->column('address', '详细地址'); + $grid->column('is_default', '默认?')->bool(); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + protected function form(): Form + { + $form = new Form(new Address); + + $form->select('user_id', '所属用户') + ->options(function ($userId) { + $user = User::find($userId); + if ($user) { + return [$user->id => $user->username.' ['.$user->info->nickname.']']; + } + }) + ->value(request()->user_id ?? '') + ->ajax(route('admin.user.users.ajax')) + ->required(); + $form->text('name', '姓名'); + $form->text('mobile', '手机号'); + $form->select('province_id', '省份') + ->options(Region::where('parent_id', 1)->pluck('name', 'id')) + ->load('city_id', route('admin.mall.regions.region')) + ->required(); + $form->select('city_id', '城市') + ->options(function ($option) { + $parent = Region::where('id', $option)->value('parent_id'); + + return Region::where(['parent_id' => $parent])->pluck('name', 'id'); + }) + ->load('district_id', route('admin.mall.regions.region')) + ->required(); + $form->select('district_id', '区/县') + ->options(function ($option) { + $parent = Region::where('id', $option)->value('parent_id'); + + return Region::where(['parent_id' => $parent])->pluck('name', 'id'); + }) + ->required(); + $form->text('address', '详细地址') + ->required(); + + $form->switch('is_default', '默认?')->default(1); + + return $form; + } +} diff --git a/modules/Mall/Http/Controllers/Admin/AjaxController.php b/modules/Mall/Http/Controllers/Admin/AjaxController.php new file mode 100644 index 0000000..c8b6ee6 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/AjaxController.php @@ -0,0 +1,70 @@ +get('q'); + + return Address::ByUserId($user_id) + ->oldest('is_default') + ->get() + ->map(function ($address) { + return [ + 'id' => $address->id, + 'text' => $address->getFullAddress(), + ]; + }); + + } + + /** + * Notes: 获取商品 + * + * @Author: 玄尘 + * @Date: 2022/7/29 13:46 + * @param Request $request + */ + public function goods(Request $request) + { + $address_id = $request->q; + if (!$address_id){ + return []; + } + $address = Address::find($address_id); + + return GoodsSku::query() + ->whereHas('goods', function ($q) { + $q->where('channel', Goods::CHANNEL_FREE)->where('status', Goods::STATUS_UP); + }) + ->get() + ->map(function ($sku) use ($address) { + $stockData = $address->user->getStockData(); + return [ + 'id' => $sku->id, + 'text' => $sku->goods->name."(库存:{$stockData['residue']})", + ]; + }); + } +} diff --git a/modules/Mall/Http/Controllers/Admin/BannerController.php b/modules/Mall/Http/Controllers/Admin/BannerController.php new file mode 100644 index 0000000..e9fd62f --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/BannerController.php @@ -0,0 +1,74 @@ +filter(function (Grid\Filter $filter) { + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('title', '轮播标题'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('status', '状态')->radio([ + 0 => '禁用', + 1 => '正常', + ]); + }); + }); + + $grid->model()->with(['shop']); + + $grid->column('id', '#ID#'); + $grid->column('shop.name', '所属店铺'); + $grid->column('cover', '封面图片')->image('', 100, 100); + $grid->column('展示位置')->display(function () { + $data = []; + foreach ($this->position as $position) { + $data[] = $this->position_map[$position]; + } + + return $data; + })->label(); + $grid->column('title', '轮播标题'); + $grid->column('status', '状态')->bool(); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + protected function form(): Form + { + $form = new Form((new Banner)->disableModelCaching()); + + $this->shop($form)->help('不选择,默认为平台轮播'); + $form->text('title', '标题') + ->required(); + $form->multipleSelect('position', '展示位置') + ->options($form->model()->position_map) + ->required(); + $this->cover($form); + $this->withUrl($form); + $form->switch('status', '显示')->default(1); + + return $form; + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/BrandController.php b/modules/Mall/Http/Controllers/Admin/BrandController.php new file mode 100644 index 0000000..fd163f0 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/BrandController.php @@ -0,0 +1,56 @@ +model()->with(['shop']); + + $grid->column('cover', 'LOGO')->image('', 80); + $grid->column('shop.name', '所属店铺'); + $grid->column('name', '品牌名称'); + $grid->column('status', '状态')->bool(); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Brand()); + + $this->shop($form)->required(); + $form->text('name', '品牌名称') + ->required(); + $this->cover($form); + $form->textarea('description', '品牌简介'); + $form->switch('status', '状态')->default(1); + + return $form; + } + + public function ajax(Request $request) + { + $key = $request->q; + + return Brand::where('name', 'like', "%$key%")->paginate(null, ['id', 'name as text']); + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Admin/CategoryController.php b/modules/Mall/Http/Controllers/Admin/CategoryController.php new file mode 100644 index 0000000..5dadb37 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/CategoryController.php @@ -0,0 +1,101 @@ +get('parent_id'); + $grid->model() + ->with(['shop']) + ->withCount('goods') + ->when($parentId, function ($query) use ($parentId) { + $query->where('parent_id', $parentId); + }); + + $grid->filter(function (Grid\Filter $filter) { + $filter->scope('trashed', '回收站')->onlyTrashed(); + + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('name', '分类名称'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('slug', '分类简称'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('status', '状态')->select([ + 0 => '禁用', + 1 => '正常', + ]); + }); + }); + + $grid->column('shop.name', '所属店铺'); + $grid->column('parent.name', '上级分类') + ->link(function () { + return route('admin.mall.categories.index', ['parent_id' => $this->parent_id]); + }, '_self');; + $grid->column('name', '分类名称') + ->link(function () { + return route('admin.mall.categories.index', ['parent_id' => $this->id]); + }, '_self'); + $grid->column('slug', '分类简称'); + $grid->column('goods_count', '分类商品'); + $grid->column('order', '排序'); + $grid->column('status', '状态')->bool(); + + return $grid; + } + + /** + * Notes : 编辑表单 + * @Date : 2021/4/25 1:41 下午 + * @Author : < Jason.C > + * @return \Encore\Admin\Form + */ + protected function form(): Form + { + $form = new Form(new Category); + + $this->shop($form)->required(); + $form->select('parent_id', '上级分类') + ->options(Category::selectOptions(function ($model) { + return $model->where('status', 1); + }, '一级分类')) + ->required(); + $form->text('name', '分类名称') + ->required() + ->rules('required'); + $form->text('slug', '分类简称'); + $form->textarea('description', '分类简介'); + $this->cover($form, 'cover', '分类图片'); + $form->number('order', '排序')->default(0); + $form->switch('status', '显示')->default(1); + + return $form; + } + + public function ajax(Request $request) + { + $key = $request->q; + + return Category::where('name', 'like', "%$key%")->paginate(null, ['id', 'name as text']); + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/DashboardController.php b/modules/Mall/Http/Controllers/Admin/DashboardController.php new file mode 100644 index 0000000..e6ed33a --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/DashboardController.php @@ -0,0 +1,67 @@ +header('商城看板') + ->description('商城数据概览') + ->row(function (Row $row) { + $row->column(3, function (Column $column) { + $column->append(new InfoBox('店铺数量', + 'anchor', + 'blue', + route('admin.mall.shops.index'), + Shop::count() + ) + ); + }); + + $row->column(3, function (Column $column) { + $column->append(new InfoBox('商品数量', + 'anchor', + 'blue', + route('admin.mall.goods.index'), + Goods::count() + ) + ); + }); + + $row->column(3, function (Column $column) { + $column->append(new InfoBox('订单总数', + 'anchor', + 'blue', + route('admin.mall.orders.index'), + Order::count() + ) + ); + }); + + $row->column(3, function (Column $column) { + $column->append(new InfoBox('退款订单', + 'anchor', + 'blue', + route('admin.mall.refunds.index'), + Refund::count() + ) + ); + }); + }); + + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Admin/DeliveryController.php b/modules/Mall/Http/Controllers/Admin/DeliveryController.php new file mode 100644 index 0000000..24b2c27 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/DeliveryController.php @@ -0,0 +1,82 @@ +model() + ->with(['shop']) + ->withCount('rules'); + + $grid->filter(function (Grid\Filter $filter) { + $filter->scope('trashed', '回收站')->onlyTrashed(); + + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('shop.name', '所属店铺'); + }); + + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('name', '模板名称'); + }); + + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('name', '计费方式')->select(Delivery::TYPE_MAP); + }); + }); + + $grid->column('shop.name', '所属店铺'); + $grid->column('name', '模板名称'); + $grid->column('type', '计费方式')->using(Delivery::TYPE_MAP); + + $grid->column('rules_count', '模板规则')->link(function () { + return route('admin.mall.deliveries.rules.index', $this); + }, '_self'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Delivery()); + + $this->shop($form)->required(); + $form->text('name', '模板名称')->required(); + $form->radioButton('type', '计费方式') + ->options(Delivery::TYPE_MAP) + ->default(Delivery::TYPE_BY_COUNT) + ->required(); + + return $form; + } + + /** + * Notes : 选择运费模板 + * @Date : 2021/5/11 11:36 上午 + * @Author : < Jason.C > + * @param \Illuminate\Http\Request $request + * @return mixed + */ + public function ajax(Request $request) + { + $key = $request->get('q'); + + return Delivery::where('name', 'like', "%$key%")->paginate(null, ['id', 'name as text']); + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Admin/DeliveryRuleController.php b/modules/Mall/Http/Controllers/Admin/DeliveryRuleController.php new file mode 100644 index 0000000..8a79dae --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/DeliveryRuleController.php @@ -0,0 +1,106 @@ +delivery); + + return $content + ->header($delivery->name) + ->description('模板规则') + ->body($this->grid($delivery)); + } + + public function grid($delivery): Grid + { + $grid = new Grid(new DeliveryRule()); + + $grid->model()->where('delivery_id', $delivery->id); + + if ($delivery->type === Delivery::TYPE_BY_COUNT) { + $firstTitle = '首件(个)'; + $additionalTitle = '续件'; + } else { + $firstTitle = '首重(Kg)'; + $additionalTitle = '续重'; + } + + $grid->column('regions', '配送区域') + ->display(function () { + return Region::find($this->regions); + }) + ->pluck('name') + ->label(); + $grid->column('first', $firstTitle); + $grid->column('first_fee', '运费(元)'); + $grid->column('additional', $additionalTitle); + $grid->column('additional_fee', '续费(元)'); + + return $grid; + } + + public function edit($id, Content $content): Content + { + return $content + ->title($this->title()) + ->description($this->description['edit'] ?? trans('admin.edit')) + ->body($this->form()->edit(request()->rule)); + } + + public function update($id) + { + return $this->form()->update(request()->rule); + } + + public function form(): Form + { + $delivery = Delivery::find(request()->delivery); + + if ($delivery->type === Delivery::TYPE_BY_COUNT) { + $firstTitle = '首件(个)'; + $additionalTitle = '续件'; + } else { + $firstTitle = '首重(Kg)'; + $additionalTitle = '续重'; + } + + $form = new Form(new DeliveryRule()); + + $form->multipleSelect('regions', '配送区域') + ->options(function ($regions) { + if ($regions) { + return Region::find($regions)->pluck('name', 'id'); + } + }) + ->ajax(route('admin.mall.regions.ajax')); + $form->text('first', $firstTitle) + ->required(); + $form->currency('first_fee') + ->symbol('¥') + ->required(); + $form->text('additional', $additionalTitle) + ->required(); + $form->currency('additional_fee') + ->required() + ->symbol('¥'); + + $form->hidden('delivery_id')->value($delivery->id); + + return $form; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Admin/ExpressController.php b/modules/Mall/Http/Controllers/Admin/ExpressController.php new file mode 100644 index 0000000..02f3249 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/ExpressController.php @@ -0,0 +1,70 @@ +model()->withCount('shops'); + + $grid->filter(function (Grid\Filter $filter) { + $filter->scope('trashed', '回收站')->onlyTrashed(); + + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('name', '物流名称'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('slug', '物流简称'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('status', '状态')->select([ + 0 => '禁用', + 1 => '正常', + ]); + }); + }); + + $grid->column('id'); + $grid->column('cover', 'LOGO')->image('', 60); + $grid->column('name', '物流名称'); + $grid->column('slug', '物流简称'); + $grid->column('description', '物流简介'); + $grid->column('status', '状态')->bool(); + $grid->column('shops_count', '使用商户'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Express()); + + $form->text('name', '物流名称') + ->required(); + $form->text('slug', '物流简称') + ->required(); + $form->textarea('description', '物流简介'); + + $this->cover($form); + + $form->switch('status', '状态')->default(1); + + return $form; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Admin/GoodsController.php b/modules/Mall/Http/Controllers/Admin/GoodsController.php new file mode 100644 index 0000000..e5e6918 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/GoodsController.php @@ -0,0 +1,228 @@ +quickSearch('id')->placeholder('商品编号快速搜索'); + + $grid->filter(function (Grid\Filter $filter) { + $filter->scope('trashed', '回收站')->onlyTrashed(); + + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('name', '商品名称'); + $filter->equal('channel', '类型')->select(Goods::Channels); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('shop_id', '所属店铺')->select(function ($shopId) { + if ($shopId) { + return Shop::where('id', $shopId)->pluck('name', 'id'); + } + })->ajax(route('admin.mall.shops.ajax')); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('category_id', '分类')->select(Category::shown()->pluck('name', 'id')); + }); + }); + + $grid->model() + ->with(['category', 'shop', 'delivery']) + ->withCount(['specs', 'skus']) + ->orderByDesc('updated_at'); + + $grid->column('id', '#ID#'); + $grid->column('brand.name', '品牌名称'); + $grid->column('category.name', '商品分类'); +// $grid->column('tags', '标签') +// ->pluck('name') +// ->label(); + $grid->column('shop.name', '所属店铺'); + $grid->column('name', '商品名称'); + $grid->column('type', '规格类型')->using(Goods::TYPE_MAP); + $grid->column('sales', '销量'); + $grid->column('original_price', '原价'); + $grid->column('channel', '类型')->using(Goods::Channels)->label(); + + $grid->column('规格属性') + ->display(function () { + if ($this->type === Goods::TYPE_MULTIPLE) { + return $this->specs_count; + } + }) + ->link(function () { + return route('admin.mall.goods.specs.index', $this); + }, '_self'); + $grid->column('价格条目') + ->display(function () { + return $this->skus_count; + }) + ->link(function () { + return route('admin.mall.goods.skus.index', $this); + }, '_self'); + $grid->column('delivery.name', '运费模板'); + $grid->column('status', '状态')->switch([ + 'on' => ['value' => 1, 'text' => '上架', 'color' => 'primary'], + 'off' => ['value' => 3, 'text' => '下架', 'color' => 'default'], + ]); + // $grid->column('status', '状态')->display(function () { + // return $this->status_text; + // }); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Goods()); + + $form->text('name', '商品名称')->required(); + $form->textarea('description', '商品简介')->required(); + + $this->shop($form)->required(); + + $form->select('category_id', '商品分类') + ->options(function ($categoryId) { + $category = Category::find($categoryId); + if ($category) { + return [$category->id => $category->name]; + } + }) + ->ajax(route('admin.mall.categories.ajax')) + ->required(); + $form->select('brand_id', '所属品牌') + ->options(function ($brandId) { + $brand = Brand::find($brandId); + if ($brand) { + return [$brand->id => $brand->name]; + } + }) + ->ajax(route('admin.mall.brands.ajax')) + ->required(); +// $form->multipleSelect('tags', '商品标签') +// ->options(function ($tagIds) { +// if ($tagIds) { +// return Tag::find($tagIds)->pluck('name', 'id'); +// } +// }) +// ->ajax(route('admin.mall.tags.ajax')); + $this->cover($form); + $this->pictures($form); + $states = [ + 'on' => ['value' => 1, 'text' => '是', 'color' => 'success'], + 'off' => ['value' => 0, 'text' => '否', 'color' => 'danger'], + ]; + $form->switch('is_post_sale', '是否允许售后')->states($states)->default(1); + $form->radioButton('deduct_stock_type', '库存扣减') + ->default(1) + ->options(Goods::DEDUCT_STOCK_MAP) + ->required(); + $form->radioButton('pay_type', '支付方式') + ->default(1) + ->options(Goods::PAY_TYPE_MAP) + ->required(); + $form->select('delivery_id', '运费模板') + ->options(function ($deliveryId) { + $delivery = Delivery::find($deliveryId); + if ($delivery) { + return [$delivery->id => $delivery->name]; + } + }) + ->ajax(route('admin.mall.deliveries.ajax')) + ->required(); + $form->currency('original_price', '展示原价') + ->default(0) + ->help('仅作展示使用'); + $form->radioButton('channel', '商品类型') + ->options(Goods::Channels) + ->required(); + $form->radioButton('type', '规格类型') + ->options(Goods::TYPE_MAP) + ->default(Goods::CHANNEL_NORMAL) + ->required() + ->when(Goods::TYPE_SINGLE, function (Form $form) { + $form->hasMany('skus', '单规格', function (Form\NestedForm $form) { + $form->currency('price', '销售价格') + ->default(0) + ->required(); + $form->hidden('score', '积分') + ->default(0) + ->required(); + $form->hidden('assets', '资产') + ->default(0) + ->required(); + $form->number('stock', '商品库存') + ->default(0) + ->required(); + $form->text('weight', '商品重量') + ->default(0) + ->setWidth(2) + ->required(); + }); + }) + ->when(Goods::TYPE_MULTIPLE, function ($form) { + + }) + ->default(function () use ($form) { + return $form->isCreating() ? Goods::TYPE_SINGLE : ''; + }); + $form->ueditor('content', '商品详情')->required(); + + $form->switch('status', '状态') + ->states([ + 'on' => ['value' => 1, 'text' => '上架', 'color' => 'success'], + 'off' => ['value' => 3, 'text' => '下架', 'color' => 'danger'], + ]) + ->default(1); + + $form->ignore(['single']); + + $form->saving(function (Form $form) { + if ($form->type === Goods::TYPE_SINGLE) { + $form->skus = [Arr::first($form->skus)]; + } + + if ($form->channel == Goods::CHANNEL_FREE && $form->isCreating()) { + $hasOne = Goods::where('channel', Goods::CHANNEL_FREE)->exists(); + if ($hasOne) { + $error = new MessageBag([ + 'title' => '错误', + 'message' => '免费产品只有一个', + ]); + + return back()->withInput()->with(compact('error')); + } + } + + $form->content = Str::of(request()->get('content'))->replace('style=""', ''); + }); + + + return $form; + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/JobController.php b/modules/Mall/Http/Controllers/Admin/JobController.php new file mode 100644 index 0000000..685a0d4 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/JobController.php @@ -0,0 +1,47 @@ +filter(function (Grid\Filter $filter) { + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->like('shop.name', '所属店铺'); + }); + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->like('name', '职位名称'); + }); + }); + + $grid->column('shop.name', '所属店铺'); + $grid->column('name', '职位名称'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Job()); + + $this->shop($form)->required(); + $form->text('name', '职位名称')->required(); + + return $form; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Admin/OrderController.php b/modules/Mall/Http/Controllers/Admin/OrderController.php new file mode 100644 index 0000000..6a0bb9d --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/OrderController.php @@ -0,0 +1,222 @@ +model()->whereIn('type', [Order::TYPE_NORMAL, Order::TYPE_SCORE])->with('shop')->withCount('versions'); + + $grid->disableExport(false); + $grid->exporter(new OrderExporter); + $grid->disableCreateButton(); + $grid->quickSearch('order_no')->placeholder('搜索订单编号'); + + $grid->filter(function (Grid\Filter $filter) { + $filter->scope('unPay', '未付款')->unPay(); + $filter->scope('paid', '待发货')->paid(); + $filter->scope('delivered', '已发货')->delivered(); + $filter->scope('signed', '已签收')->signed(); + $filter->scope('completed', '已完成')->completed(); + + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->like('order_no', '订单编号'); + $filter->equal('shop_id', '所属店铺')->select(Shop::pluck('name', 'id')); + $filter->equal('user_id', '下单用户')->select()->ajax(route('admin.user.users.ajax')); + $filter->equal('type', '订单类型')->select(Order::TYPE_MAP); + }); + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->between('created_at', '下单时间')->datetime(); + $filter->between('paid_at', '付款时间')->datetime(); + $filter->between('expired_at', '过期时间')->datetime(); + }); + }); + + $grid->actions(function (Grid\Displayers\Actions $actions) { + $actions->disableEdit(); + $actions->disableDelete(); + $actions->disableView(); + if ($actions->row->can('pay') && $this->row->type == Order::TYPE_NORMAL) { + $actions->add(new Pay()); + } + if ($actions->row->can('deliver')) { + $actions->add(new Delivered); + } + }); + + $grid->model()->with(['shop', 'user.info']); + + $grid->column('order_no', '订单编号')->display(function () { + return sprintf('%s', route('admin.mall.orders.show', $this), $this->order_no); + }); + $grid->column('shop.name', '所属店铺'); + $grid->column('user.username', '下单用户'); + $grid->column('amount', '商品金额'); + $grid->column('freight', '运费'); + $grid->column('qty', '发货数量') + ->display(function () { + return $this->items()->sum('qty'); + }); + $grid->column('订单金额')->display(function () { + return $this->total; + }); + $grid->column('state', '订单状态') + ->display(function () { + return $this->state_text; + }) + ->label(); + $grid->column('type', '订单类型')->using(Order::TYPE_MAP)->label(); + $grid->column('paid_at', '支付时间')->sortable(); + $grid->column('expired_at', '过期时间')->sortable(); + $grid->column('versions_count', '操作日志')->link(function () { + return route('admin.mall.versions', [ + 'model' => get_class($this), + 'key' => $this->id, + ]); + }, '_self'); + $grid->column('created_at', '下单时间')->sortable(); + + return $grid; + } + + /** + * Notes: 添加订单 + * + * @Author: 玄尘 + * @Date: 2022/7/29 13:21 + * @return Form + */ + public function form(): Form + { + $form = new Form(new Order()); + + $form->select('user_id', '用户') + ->options(function ($userId) { + $user = User::find($userId); + + if ($user) { + return [$user->id => $user->username.' ['.$user->info->nickname.']']; + } + }) + ->ajax(route('admin.user.users.ajax')) + ->load('address_id', route('admin.mall.ajax.address')) + ->required(); + + $form->select('address_id', '收货地址') + ->load('goods_sku_id', route('admin.mall.ajax.goods')) + ->required(); + $form->select('goods_sku_id', '商品') + ->required(); + $form->number('qty', '数量')->default(1); + + $form->select('express_id', '物流') + ->options(function () { + return Express::pluck('name', 'id'); + }) + ->rules('required', ['required' => '物流公司必须选择']); + $form->text('express_no', '物流单号')->rules('required'); + + return $form; + } + + /** + * Notes: 获取提交数据 + * + * @Author: 玄尘 + * @Date: 2022/7/29 14:16 + * @return mixed|void + * @throws \Exception + */ + public function store() + { + $user_id = request('user_id', 0); + $remark = request('remark', '后台录入'); + $address_id = request('address_id', 0); + $goods_sku_id = request('goods_sku_id', 0); + $qty = request('qty', 0); + $express_id = request('express_id'); + $express_no = request('express_no'); + + $user = User::find($user_id); + $address = Address::find($address_id); + $goods_sku = GoodsSku::find($goods_sku_id); + + if (! $goods_sku) { + $error = new MessageBag([ + 'title' => '错误', + 'message' => '未找到商品', + ]); + + return back()->withInput()->with(compact('error')); + } + + $stockData = $user->getStockData(); + + if ($qty > $stockData['residue']) { + $error = new MessageBag([ + 'title' => '错误', + 'message' => '用户库存不足', + ]); + + return back()->withInput()->with(compact('error')); + } else { + $detail = collect(); + $item = new Item($goods_sku, $address, $qty); + $detail->push($item); + + $orders = (new OrderFacade)->user($user) + ->remark($remark) + ->type(Order::TYPE_SAMPLE) + ->items($detail) + ->address($address) + ->create(); + + foreach ($orders as $order) { + $order->pay();//设置已支付 + $order->deliver($express_id, $express_no);//发货 + } + + admin_toastr('操作完成'); + return redirect()->to('/admin/mall/orders?user_id='.$user->id); + } + } + + + /** + * Notes : 订单详情 + * + * @Date : 2021/3/16 1:46 下午 + * @Author : < Jason.C > + * @param string $orderNo 订单的 order_no + */ + protected function detail(string $orderNo) + { + $order = Order::query()->firstWhere(['order_no' => $orderNo]); + + return view('mall::admin.order.detail', compact('order')); + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/ReasonController.php b/modules/Mall/Http/Controllers/Admin/ReasonController.php new file mode 100644 index 0000000..fb893c7 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/ReasonController.php @@ -0,0 +1,48 @@ +model()->withCount('shops'); + + $grid->filter(function (Grid\Filter $filter) { + $filter->scope('trashed', '回收站')->onlyTrashed(); + }); + + $grid->column('id'); + $grid->column('title', '名称'); + $grid->column('status', '状态')->bool(); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Reason()); + + $form->text('title', '名称')->required(); + $form->switch('status', '状态')->default(1); + + return $form; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Admin/RefundController.php b/modules/Mall/Http/Controllers/Admin/RefundController.php new file mode 100644 index 0000000..deaf506 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/RefundController.php @@ -0,0 +1,106 @@ +disableCreateButton(); + + $grid->actions(function ($actions) { + $actions->disableDelete(); + $actions->disableEdit(); + $actions->disableView(); + + if ($actions->row->can('agree')) { + $actions->add(new RefundAudit()); + } + + if ($actions->row->can('sign')) { + $actions->add(new RefundSign()); + } + + if ($actions->row->can('completed')) { + $actions->add(new RefundReturns()); + } + + }); + + $grid->filter(function (Grid\Filter $filter) { + $filter->disableIdFilter(); + + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->where(function ($query) { + $query->whereHas('order', function ($query) { + $query->where('order_no', 'like', "%{$this->input}%"); + }); + }, '订单编号'); + + $filter->equal('state', '状态')->select(Refund::STATUS_MAP); + + }); + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->where(function ($query) { + $query->whereHas('user', function ($query) { + $query->whereHas('info', function ($query) { + $query->where('nickname', 'like', "%{$this->input}%"); + }); + }); + }, '下单用户'); + $filter->where(function ($query) { + $query->whereHas('user', function ($query) { + $query->where('username', $this->input); + }); + }, '手机号'); + }); + }); + + $grid->model() + ->with(['shop', 'order', 'user.info']) + ->withCount('versions'); + + $grid->column('id'); + $grid->column('shop.name', '所属店铺'); + $grid->column('order.order_no', '订单编号')->display(function () { + return sprintf('%s', route('admin.mall.orders.show', $this->order), + $this->order->order_no); + }); + + $grid->column('user.username', '下单用户'); + $grid->column('refund_total', '退款金额'); + $grid->column('actual_total', '实退金额'); + + $grid->column('状态') + ->display(function () { + return $this->status_text; + }) + ->label(); + $grid->column('remark', '备注'); + + $grid->column('versions_count', '操作日志')->link(function () { + return route('admin.mall.versions', [ + 'model' => get_class($this), + 'key' => $this->id, + ]); + }, '_self'); + + $grid->column('created_at', '申请时间'); + + return $grid; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Admin/RegionController.php b/modules/Mall/Http/Controllers/Admin/RegionController.php new file mode 100644 index 0000000..a1257a3 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/RegionController.php @@ -0,0 +1,27 @@ +q; + + return Region::where('name', 'like', "%$q%")->paginate(null, ['id', 'name as text']); + } + + public function region(Request $request) + { + $regionId = $request->get('q'); + + return Region::where('parent_id', $regionId)->get(['id', DB::raw('name as text')]); + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/Selectable/Expresses.php b/modules/Mall/Http/Controllers/Admin/Selectable/Expresses.php new file mode 100644 index 0000000..29b8c98 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/Selectable/Expresses.php @@ -0,0 +1,34 @@ +column('id', '#ID#'); + $this->column('name', '物流名称'); + $this->column('status', '状态')->bool(); + $this->column('created_at', '时间'); + + $this->filter(function (Filter $filter) { + $filter->like('name', '物流名称'); + }); + } + + public static function display() + { + return function ($value) { + if (is_array($value)) { + return implode(';', array_column($value, 'name')); + } + return optional($this->expresses)->name; + }; + } +} diff --git a/modules/Mall/Http/Controllers/Admin/Selectable/Reasons.php b/modules/Mall/Http/Controllers/Admin/Selectable/Reasons.php new file mode 100644 index 0000000..fe5ee64 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/Selectable/Reasons.php @@ -0,0 +1,32 @@ +column('id', '#ID#'); + $this->column('title', '名称'); + $this->column('status', '状态')->bool(); + $this->column('created_at', '时间'); + } + + public static function display() + { + return function ($value) { + if (is_array($value)) { + return implode(';', array_column($value, 'title')); + } + + return optional($this->reaons)->name; + }; + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/ShopController.php b/modules/Mall/Http/Controllers/Admin/ShopController.php new file mode 100644 index 0000000..5a71e20 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/ShopController.php @@ -0,0 +1,180 @@ +actions(function (Grid\Displayers\Actions $actions) { + $actions->disableView(); + // $actions->disableEdit(); + $actions->disableDelete(); + if ($actions->row->status == Shop::STATUS_APPLYING) { + $actions->add(new Pass); + $actions->add(new Reject); + } + if ($actions->row->status == Shop::STATUS_NORMAL) { + $actions->add(new Close); + } + + if ($actions->row->status == Shop::STATUS_CLOSE) { + $actions->add(new Open); + } + }); + + $grid->filter(function (Grid\Filter $filter) { + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('name', '店铺名称'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('user.info.nickname', '用户昵称'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('mobile', '联系电话'); + }); + }); + + $grid->model()->withCount(['versions', 'staffers'])->with(['user.info']); + + $grid->column('name', '店铺名称'); + $grid->column('所属用户')->display(fn() => $this->user->username . '[' . $this->user->info->nickname . ']'); + $grid->column('is_self', '类型') + ->using([ + 0 => '合作', + 1 => '自营', + ])->label([ + 0 => 'info', + 1 => 'danger', + ]); + $grid->column('mobile', '联系电话'); + $grid->column('address', '地址')->display(fn() => $this->getFullAddress()); + $grid->column('status', '状态') + ->using(Shop::STATUS_MAP) + ->label(Shop::LABEL_MAP); + $grid->column('order', '排序')->sortable()->editable(); + $grid->column('versions_count', '操作记录')->link(fn() => route('admin.mall.versions', [ + 'model' => get_class($this), + 'key' => $this->id, + ]), '_self'); + $grid->column('staffers_count', '员工数量')->link(fn() => route('admin.mall.shops.staffers.index', $this), '_self'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Shop()); + $form->select('user_id', '所属用户') + ->options(function ($userId) { + $user = User::find($userId); + + if ($user) { + return [$user->id => $user->username . ' [' . $user->info->nickname . ']']; + } + }) + ->ajax(route('admin.user.users.ajax')) + ->creationRules([ + 'required', + "unique:mall_shops", + ], [ + 'unique' => '用户已存在店铺', + ]) + ->updateRules([ + 'required', + "unique:mall_shops,user_id,{{id}}", + ], [ + 'unique' => '用户已存在店铺', + ]) + ->required(); + $form->text('name', '店铺名称') + ->creationRules(['required', "unique:mall_shops"], ['unique' => '店铺名称已存在']) + ->updateRules(['required', "unique:mall_shops,name,{{id}}"], ['unique' => '店铺名称已存在']) + ->required(); + $form->switch('is_self', '自营店铺'); + $form->textarea('description', '店铺简介') + ->required(); + $form->text('mobile', '联系电话') + ->rules([ + 'size:11', + 'phone:CN', + ], [ + 'size' => '手机号格式不正确', + 'phone' => '手机号格式不正确', + ]) + ->creationRules(['required', "unique:mall_shops"], ['unique' => '店铺联系电话已存在']) + ->updateRules(['required', "unique:mall_shops,name,{{id}}"], ['unique' => '店铺联系电话已存在']) + ->required(); + $form->divider('地址信息'); + $form->select('province_id', '省份') + ->options(Region::where('parent_id', 1)->pluck('name', 'id')) + ->load('city_id', route('admin.mall.regions.region')) + ->required(); + $form->select('city_id', '城市') + ->options(function ($option) { + $parent = Region::where('id', $option)->value('parent_id'); + + return Region::where(['parent_id' => $parent])->pluck('name', 'id'); + }) + ->load('district_id', route('admin.mall.regions.region')) + ->required(); + $form->select('district_id', '区/县') + ->options(function ($option) { + $parent = Region::where('id', $option)->value('parent_id'); + + return Region::where(['parent_id' => $parent])->pluck('name', 'id'); + }) + ->required(); + $form->text('address', '详细地址') + ->required(); + $form->divider('其他信息'); + $this->cover($form, 'cover', '店铺LOGO'); + $form->radio('status', '店铺状态') + ->options(Shop::STATUS_MAP); + $form->number('order', '排序') + ->default('9999') + ->help('仅后台可见,用于店铺推荐序列'); + $form->belongsToMany('expresses', Expresses::class, '发货物流'); + $form->belongsToMany('reasons', Reasons::class, '退款原因'); + + return $form; + } + + /** + * Notes : 获取店铺AJAX + * @Date : 2021/5/7 4:39 下午 + * @Author : < Jason.C > + * @param \Illuminate\Http\Request $request + * @return mixed + */ + public function ajax(Request $request) + { + $key = $request->get('q'); + + return Shop::where('name', 'like', "%$key%")->paginate(null, ['id', 'name as text']); + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/SkuController.php b/modules/Mall/Http/Controllers/Admin/SkuController.php new file mode 100644 index 0000000..7695b6d --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/SkuController.php @@ -0,0 +1,77 @@ +header($good->name) + ->description('价格表') + ->body($this->grid($good)); + } + + public function grid($goods): Grid + { + $grid = new Grid(new GoodsSku()); + + $grid->model()->where('goods_id', $goods->id); + + $grid->actions(function (Grid\Displayers\Actions $actions) { + $actions->disableView(); + $actions->disableDelete(); + }); + $grid->disableCreateButton(); + + $grid->column('cover', '封面图')->image('', 80); + $grid->column('unit', '产品单元'); + $grid->column('price', '售价'); + $grid->column('score', '积分/原石'); + $grid->column('assets', '资产'); + $grid->column('stock', '库存'); + $grid->column('weight', '重量'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function edit(Content $content, Goods $good, GoodsSku $sku) + { + return $content + ->header($good->name) + ->description('编辑价格') + ->body($this->form($good)->edit($sku->id)); + } + + public function form($good): Form + { + $form = new Form(new GoodsSku()); + + $this->cover($form); + $form->currency('price', '销售价格'); + $form->currency('score', '积分/原石'); + $form->currency('assets', '资产'); + $form->number('stock', '库存'); + $form->text('weight', '重量')->setWidth(2); + + return $form; + } + + public function update(Goods $good, GoodsSku $sku) + { + return $this->form($good)->update($sku->id); + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Admin/SpecController.php b/modules/Mall/Http/Controllers/Admin/SpecController.php new file mode 100644 index 0000000..3b8699b --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/SpecController.php @@ -0,0 +1,81 @@ +header($good->name) + ->description('属性列表') + ->body($this->grid($good)); + } + + public function grid($good): Grid + { + $grid = new Grid(new GoodsSpec()); + + $grid->model() + ->where('goods_id', $good->id) + ->withCount('values'); + + $grid->column('name', '属性名称'); + $grid->column('values_count', '属性值数量') + ->link(function () use ($good) { + return route('admin.mall.goods.specs.values.index', [$good, $this]); + }, '_self'); + + return $grid; + } + + public function create(Content $content, Goods $good): Content + { + return $content + ->header($good->name) + ->description('属性列表') + ->body($this->form($good)); + } + + public function form($good): Form + { + $form = new Form(new GoodsSpec()); + + $form->text('name', '属性名称'); + $form->hidden('goods_id')->value($good->id); + + $form->hasMany('values', '属性值列表', function (NestedForm $form) { + $form->text('value', '属性值'); + }); + + return $form; + } + + public function store(Goods $good) + { + return $this->form($good)->store(); + } + + public function edit(Content $content, Goods $good, GoodsSpec $spec): Content + { + return $content + ->header($good->name) + ->description('属性列表') + ->body($this->form($good)->edit($spec->id)); + } + + public function update(Goods $good, GoodsSpec $spec) + { + return $this->form($good)->update($spec->id); + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Admin/SpecValueController.php b/modules/Mall/Http/Controllers/Admin/SpecValueController.php new file mode 100644 index 0000000..5017a37 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/SpecValueController.php @@ -0,0 +1,70 @@ +header($spec->name) + ->description('属性值列表') + ->body($this->grid($spec)); + } + + public function grid($spec): Grid + { + $grid = new Grid(new GoodsSpecValue()); + + $grid->model()->where('spec_id', $spec->id); + $grid->column('value', '属性值'); + + return $grid; + } + + public function create(Content $content, Goods $good, GoodsSpec $spec): Content + { + return $content + ->header($spec->name) + ->description('属性列表') + ->body($this->form($spec)); + } + + public function form($spec): Form + { + $form = new Form(new GoodsSpecValue()); + + $form->text('value', '属性值'); + $form->hidden('spec_id')->value($spec->id); + + return $form; + } + + public function store(Goods $good, GoodsSpec $spec) + { + return $this->form($spec)->store(); + } + + public function edit(Content $content, Goods $good, GoodsSpec $spec, GoodsSpecValue $value): Content + { + return $content + ->header($spec->name) + ->description('属性列表') + ->body($this->form($spec)->edit($value->id)); + } + + public function update(Goods $good, GoodsSpec $spec, GoodsSpecValue $value) + { + return $this->form($spec)->update($value->id); + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Admin/StafferController.php b/modules/Mall/Http/Controllers/Admin/StafferController.php new file mode 100644 index 0000000..4c48e45 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/StafferController.php @@ -0,0 +1,98 @@ +header($shop->name) + ->description('员工管理') + ->body($this->grid($shop)); + } + + public function grid($shop): Grid + { + $grid = new Grid(new ShopStaffer()); + + $grid->model()->byShop($shop); + + $grid->filter(function (Grid\Filter $filter) { + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('user.username', '用户名'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('user.info.nickname', '用户昵称'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('job.name', '职位名称'); + }); + }); + + $grid->column('所属用户')->display(fn() => $this->user->username . '[' . $this->user->info->nickname . ']'); + $grid->column('job.name', '职位名称'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function create(Content $content, Shop $shop): Content + { + return $content + ->header($shop->name) + ->description('新增员工') + ->body($this->form($shop)); + } + + public function store(Shop $shop) + { + return $this->form($shop)->store(); + } + + public function form(Shop $shop): Form + { + $form = new Form(new ShopStaffer()); + + $form->hidden('shop_id')->value($shop->getKey()); + + $form->select('user_id', '所属用户') + ->options(function ($userId) { + $user = User::find($userId); + + if ($user) { + return [$user->id => $user->username . ' [' . $user->info->nickname . ']']; + } + }) + ->ajax(route('admin.user.users.ajax')) + ->required(); + + $form->select('job_id', '职位')->options(fn() => Job::byShop($shop)->pluck('name', 'id')); + + return $form; + } + + public function edit(Content $content, Shop $shop, ShopStaffer $staffer): Content + { + return $content + ->header($shop->name) + ->description('编辑员工') + ->body($this->form($shop)->edit($staffer->getKey())); + } + + public function update(Shop $shop, ShopStaffer $staffer) + { + return $this->form($shop)->update($staffer->getKey()); + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Admin/StockOrderBySystemController.php b/modules/Mall/Http/Controllers/Admin/StockOrderBySystemController.php new file mode 100644 index 0000000..d1e5252 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/StockOrderBySystemController.php @@ -0,0 +1,254 @@ +model() + ->where('type', Order::TYPE_SAMPLE) + ->where('channel', Order::CHANNEL_SYSTEM) + ->with(['shop', 'user', 'items']) + ->withCount('versions'); + + + $grid->quickSearch('order_no')->placeholder('搜索订单编号'); + + $grid->filter(function (Grid\Filter $filter) { + $filter->scope('unPay', '未付款')->unPay(); + $filter->scope('paid', '待发货')->paid(); + $filter->scope('delivered', '已发货')->delivered(); + $filter->scope('signed', '已签收')->signed(); + $filter->scope('completed', '已完成')->completed(); + + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->like('order_no', '订单编号'); +// $filter->equal('shop_id', '所属店铺')->select(Shop::pluck('name', 'id')); + $filter->equal('user_id', '提货用户')->select()->ajax(route('admin.user.users.ajax')); +// $filter->equal('type', '订单类型')->select(Order::TYPE_MAP); + }); + $filter->column(1 / 2, function (Grid\Filter $filter) { +// $filter->between('created_at', '下单时间')->datetime(); +// $filter->between('paid_at', '付款时间')->datetime(); +// $filter->between('expired_at', '过期时间')->datetime(); + }); + }); + + $grid->actions(function (Grid\Displayers\Actions $actions) { + $actions->disableEdit(); + $actions->disableDelete(); + $actions->disableView(); + if ($actions->row->can('pay') && $this->row->type == Order::TYPE_NORMAL) { + $actions->add(new Pay()); + } + if ($actions->row->can('deliver')) { + $actions->add(new Delivered); + } + }); + + $grid->model()->with(['shop', 'user.info']); + + $grid->column('order_no', '订单编号')->display(function () { + return sprintf('%s', route('admin.mall.stock_orders.show', $this), $this->order_no); + }); +// $grid->column('shop.name', '所属店铺'); + $grid->column('user.username', '提货用户'); +// $grid->column('amount', '商品金额'); +// $grid->column('freight', '运费'); + $grid->column('qty', '提货数量') + ->display(function () { + return $this->items()->sum('qty'); + }); +// $grid->column('订单金额')->display(function () { +// return $this->total; +// }); + $grid->column('state', '订单状态') + ->display(function () { + return $this->state_text; + }) + ->label(); + $grid->column('type', '订单类型')->using(Order::TYPE_MAP)->label(); +// $grid->column('paid_at', '支付时间')->sortable(); +// $grid->column('expired_at', '过期时间')->sortable(); + $grid->column('versions_count', '操作日志')->link(function () { + return route('admin.mall.versions', [ + 'model' => get_class($this), + 'key' => $this->id, + ]); + }, '_self'); + $grid->column('created_at', '下单时间')->sortable(); + + $grid->disableExport(false); + $grid->export(function ($export) { + $export->column('order_no', function ($value, $original) { + return strip_tags($value)."\n"; + }); + $export->column('type', function ($value, $original) { + return strip_tags($value); + }); + $export->column('state', function ($value, $original) { + return strip_tags($value); + }); + + $export->except(['versions_count', 'paid_at', 'expired_at']); + + $export->filename($this->title.date("YmdHis")); + }); + return $grid; + } + + /** + * Notes: 添加订单 + * + * @Author: 玄尘 + * @Date: 2022/7/29 13:21 + * @return Form + */ + public function form(): Form + { + $form = new Form(new Order()); + + $form->select('user_id', '会员') + ->options(function ($userId) { + $user = User::find($userId); + + if ($user) { + return [$user->id => $user->username.' ['.$user->info->nickname.']']; + } + }) + ->ajax(route('admin.user.users.ajax')) + ->load('address_id', route('admin.mall.ajax.address')) + ->required(); + + $form->select('address_id', '收货地址') + ->load('goods_sku_id', route('admin.mall.ajax.goods')) + ->required(); + $form->html("", + ''); + $form->select('goods_sku_id', '剩余箱数')->required(); + + $form->number('qty', '发货箱数')->default(1); + $form->radioButton('type', '方式') + ->options(OrderExpress::TYPE_MAP) + ->required() + ->when(OrderExpress::TYPE_EXPRESS, function ($form) { + $form->select('express_id', '物流') + ->options(function () { + return Express::pluck('name', 'id'); + }) + ->rules('required', ['required' => '物流公司必须选择']); + $form->text('express_no', '物流单号')->rules('required'); + + }) + ->when(OrderExpress::TYPE_LOGISTICS, function ($form) { + $form->text('person', '经办人'); + }); + + return $form; + } + + /** + * Notes: 获取提交数据 + * + * @Author: 玄尘 + * @Date: 2022/7/29 14:16 + * @return mixed|void + * @throws \Exception + */ + public function store() + { + $user_id = request('user_id', 0); + $remark = request('remark', '后台录入'); + $address_id = request('address_id', 0); + $goods_sku_id = request('goods_sku_id', 0); + $qty = request('qty', 0); + $express_id = request('express_id'); + $express_no = request('express_no'); + $type = request('type'); + $person = request('person'); + + $user = User::find($user_id); + $address = Address::find($address_id); + $goods_sku = GoodsSku::find($goods_sku_id); + + if (! $goods_sku) { + $error = new MessageBag([ + 'title' => '错误', + 'message' => '未找到商品', + ]); + + return back()->withInput()->with(compact('error')); + } + + $stockData = $user->getStockData(); + + if ($qty > $stockData['residue']) { + $error = new MessageBag([ + 'title' => '错误', + 'message' => '用户库存不足', + ]); + + return back()->withInput()->with(compact('error')); + } else { + $detail = collect(); + $item = new Item($goods_sku, $address, $qty); + $detail->push($item); + + $orders = (new OrderFacade)->user($user) + ->remark($remark) + ->channel(Order::CHANNEL_SYSTEM) + ->type(Order::TYPE_SAMPLE) + ->items($detail) + ->address($address) + ->create(); + + foreach ($orders as $order) { + $order->pay();//设置已支付 + $order->deliver($express_id, $express_no, $type, $person);//发货 + } + + admin_toastr('操作完成'); + return redirect()->to('/admin/mall/stock_orders_by_system?user_id='.$user->id); + } + } + + + /** + * Notes : 订单详情 + * + * @Date : 2021/3/16 1:46 下午 + * @Author : < Jason.C > + * @param string $orderNo 订单的 order_no + */ + protected function detail(string $orderNo) + { + $order = Order::query()->firstWhere(['order_no' => $orderNo]); + + return view('mall::admin.stock_order.detail', compact('order')); + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/StockOrderController.php b/modules/Mall/Http/Controllers/Admin/StockOrderController.php new file mode 100644 index 0000000..0a30afe --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/StockOrderController.php @@ -0,0 +1,142 @@ +model() + ->where('type', Order::TYPE_SAMPLE) + ->where('channel', Order::CHANNEL_USER) + ->with(['shop', 'user', 'items']) + ->withCount('versions'); + + $grid->disableCreateButton(); + $grid->quickSearch('order_no')->placeholder('搜索订单编号'); + + $grid->filter(function (Grid\Filter $filter) { + $filter->scope('unPay', '未付款')->unPay(); + $filter->scope('paid', '待发货')->paid(); + $filter->scope('delivered', '已发货')->delivered(); + $filter->scope('signed', '已签收')->signed(); + $filter->scope('completed', '已完成')->completed(); + + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->like('order_no', '订单编号'); +// $filter->equal('shop_id', '所属店铺')->select(Shop::pluck('name', 'id')); + $filter->equal('user_id', '提货用户')->select()->ajax(route('admin.user.users.ajax')); + }); + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->between('created_at', '下单时间')->datetime(); + $filter->equal('state', '订单状态')->select([ + Order::STATUS_CANCEL => '已取消', + Order::STATUS_PAID => '待发货', + Order::STATUS_DELIVERED => '已发货', + Order::STATUS_SIGNED => '已签收', + ]); + +// $filter->between('paid_at', '付款时间')->datetime(); +// $filter->between('expired_at', '过期时间')->datetime(); + }); + }); + + $grid->actions(function (Grid\Displayers\Actions $actions) { + $actions->disableEdit(); + $actions->disableDelete(); + $actions->disableView(); + if ($actions->row->can('pay') && $this->row->type == Order::TYPE_NORMAL) { + $actions->add(new Pay()); + } + if ($actions->row->can('deliver')) { + $actions->add(new Delivered); + } + }); + + $grid->model()->with(['shop', 'user.info']); + + $grid->column('order_no', '订单编号')->display(function () { + return sprintf('%s', route('admin.mall.stock_orders.show', $this), $this->order_no); + }); +// $grid->column('shop.name', '所属店铺'); + $grid->column('user.username', '提货用户'); +// $grid->column('amount', '商品金额'); +// $grid->column('freight', '运费'); + $grid->column('qty', '提货数量') + ->display(function () { + return $this->items()->sum('qty'); + }); +// $grid->column('订单金额')->display(function () { +// return $this->total; +// }); + $grid->column('state', '订单状态') + ->display(function () { + return $this->state_text; + }) + ->label(); +// $grid->column('type', '订单类型')->using(Order::TYPE_MAP)->label(); +// $grid->column('paid_at', '支付时间')->sortable(); +// $grid->column('expired_at', '过期时间')->sortable(); + $grid->column('versions_count', '操作日志')->link(function () { + return route('admin.mall.versions', [ + 'model' => get_class($this), + 'key' => $this->id, + ]); + }, '_self'); + $grid->column('created_at', '下单时间')->sortable(); + + $grid->disableExport(false); + $grid->export(function ($export) { + $export->column('order_no', function ($value, $original) { + return strip_tags($value)."\n"; + }); + $export->column('type', function ($value, $original) { + return strip_tags($value); + }); + $export->column('state', function ($value, $original) { + return strip_tags($value); + }); + + $export->except(['versions_count', 'paid_at', 'expired_at']); + + $export->filename($this->title.date("YmdHis")); + }); + return $grid; + } + + /** + * Notes : 订单详情 + * + * @Date : 2021/3/16 1:46 下午 + * @Author : < Jason.C > + * @param string $orderNo 订单的 order_no + */ + protected function detail(string $orderNo) + { + $order = Order::query()->firstWhere(['order_no' => $orderNo]); + + return view('mall::admin.stock_order.detail', compact('order')); + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/TagController.php b/modules/Mall/Http/Controllers/Admin/TagController.php new file mode 100644 index 0000000..4a61488 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/TagController.php @@ -0,0 +1,48 @@ +model()->with(['shop']); + + $grid->column('shop.name', '所属店铺'); + $grid->column('name', '标签名称'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Tag()); + + $this->shop($form)->required(); + $form->text('name', '标签名称') + ->required(); + + return $form; + } + + public function ajax(Request $request) + { + $key = $request->q; + + return Tag::where('name', 'like', "%$key%")->paginate(null, ['id', 'name as text']); + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Admin/VersionController.php b/modules/Mall/Http/Controllers/Admin/VersionController.php new file mode 100644 index 0000000..3647436 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/VersionController.php @@ -0,0 +1,68 @@ +header($model . ' => ' . $key) + ->description('数据操作日志') + ->body($this->grid($model, $key)); + } + + public function grid($model, $key): Grid + { + $grid = new Grid(new Version()); + + $grid->disableCreateButton(); + $grid->disableActions(); + $grid->disableTools(); + + $grid->model()->whereHasMorph( + 'versionable', + $model, + function (Builder $query) use ($key) { + $query->where('id', $key); + } + )->orderByDesc('id'); + + $grid->column('操作用户')->display(function () { + if ($this->user_id < config('mall.administrator_max_id')) { + config(['versionable.user_model' => Administrator::class]); + + return '[Admin] ' . ($this->user->name ?? '--SYSTEM--'); + } else { + return '[USER] ' . ($this->user->username ?? '--USER--'); + } + }); + $grid->column('versionable_type', '数据变动')->expand(function () { + $data = []; + foreach ($this->contents as $key => $item) { + $data[] = [ + $key, + is_array($item) ? json_encode($item, JSON_UNESCAPED_UNICODE) : $item, + ]; + } + + return new Table(['字段名称', '变更值'], $data); + }); + + $grid->column('操作时间')->display(function () { + return $this->created_at->toDateTimeString(); + }); + + return $grid; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Admin/VideoController.php b/modules/Mall/Http/Controllers/Admin/VideoController.php new file mode 100644 index 0000000..76ba035 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/VideoController.php @@ -0,0 +1,64 @@ +filter(function (Grid\Filter $filter) { + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('name', '视频名称'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('status', '状态')->radio([ + 0 => '禁用', + 1 => '正常', + ]); + }); + }); + + + $grid->column('id', '#ID#'); + $grid->column('name', '视频名称'); + $grid->column('cover', '封面地址')->image('', 60, 60); + $grid->column('path', '视频地址') + ->display(function () { + return $this->path_url; + }); + $grid->column('status', '状态')->switch(); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + protected function form(): Form + { + $form = new Form((new Video())->disableModelCaching()); + + $form->text('name', '视频标题')->required(); + $this->cover($form); + $this->video($form); + $form->switch('status', '显示')->default(1); + + return $form; + } + +} diff --git a/modules/Mall/Http/Controllers/Admin/WithShop.php b/modules/Mall/Http/Controllers/Admin/WithShop.php new file mode 100644 index 0000000..23d5823 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/WithShop.php @@ -0,0 +1,22 @@ +select('shop_id', '所属店铺') + ->options(function ($shopId) { + $shop = Shop::find($shopId); + if ($shop) { + return [$shop->id => $shop->name]; + } + }) + ->ajax(route('admin.mall.shops.ajax')); + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Agent/IndexController.php b/modules/Mall/Http/Controllers/Agent/IndexController.php new file mode 100644 index 0000000..de8fa90 --- /dev/null +++ b/modules/Mall/Http/Controllers/Agent/IndexController.php @@ -0,0 +1,16 @@ +success($request->shop); + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Agent/ShopController.php b/modules/Mall/Http/Controllers/Agent/ShopController.php new file mode 100644 index 0000000..0686d91 --- /dev/null +++ b/modules/Mall/Http/Controllers/Agent/ShopController.php @@ -0,0 +1,19 @@ +success(); + } + +} diff --git a/modules/Mall/Http/Controllers/Api/ActivityController.php b/modules/Mall/Http/Controllers/Api/ActivityController.php new file mode 100644 index 0000000..22a57b9 --- /dev/null +++ b/modules/Mall/Http/Controllers/Api/ActivityController.php @@ -0,0 +1,34 @@ +get(); + + return $this->success(ActivityBaseResource::collection($activities)); + } + + public function show(Activity $activity) + { + return $this->success(new ActivityResource($activity)); + } + + +} diff --git a/modules/Mall/Http/Controllers/Api/AddressController.php b/modules/Mall/Http/Controllers/Api/AddressController.php new file mode 100644 index 0000000..277be11 --- /dev/null +++ b/modules/Mall/Http/Controllers/Api/AddressController.php @@ -0,0 +1,160 @@ + + * @Date : 2020/11/5 5:09 下午 + * @return mixed + */ + public function index() + { + $addresses = Address::byUser(Api::user())->orderBy('is_default', 'desc')->get(); + + return $this->success(AddressResource::collection($addresses)); + } + + /** + * Notes: 创建地址,展示地区联动数据 + * + * @Author: + * @Date : 2020/11/5 5:10 下午 + */ + public function create(Request $request) + { + $parentId = $request->parent_id ?? 1; + + $list = Region::where('parent_id', $parentId)->get(); + + return $this->success($list); + } + + /** + * Notes: 保存地址 + * + * @Author: + * @Date : 2020/11/5 5:13 下午 + * @param \Modules\Mall\Http\Requests\Address\AddressRequest $request + * @return mixed + */ + public function store(AddressRequest $request) + { + $result = Address::create([ + 'user_id' => Api::userId(), + 'name' => $request->name, + 'mobile' => $request->mobile, + 'province_id' => $request->province_id, + 'city_id' => $request->city_id, + 'district_id' => $request->district_id, + 'address' => $request->address, + 'is_default' => $request->is_default, + ]); + if ($result) { + return $this->success('操作成功'); + } else { + return $this->failed('失败'); + } + } + + /** + * Notes: 地址详情,如果不是自己的地址,不显示 + * + * @Author: + * @Date : 2020/11/5 5:31 下午 + * @param Address $address + * @return mixed + */ + public function show(Address $address) + { + if ($address->user()->isNot(Api::user())) { + return $this->failed('', 404); + } + + return $this->success(new AddressResource($address)); + } + + /** + * Notes: 设置默认地址 + * + * @Author: + * @Date : 2020/11/5 5:34 下午 + * @param Address $address + * @return mixed + */ + public function setDefault(Address $address) + { + if ($address->user()->isNot(Api::user())) { + return $this->failed('', 404); + } + try { + $address->setDefault(); + + return $this->success('操作成功'); + } catch (Exception $exception) { + return $this->failed('失败'); + } + } + + /** + * Notes: 更新地址 + * + * @Author: + * @Date : 2020/11/5 5:40 下午 + */ + public function update(AddressRequest $request, Address $address) + { + if ($address->user()->isNot(Api::user())) { + return $this->failed('', 404); + } + + $address->update([ + 'name' => $request->name, + 'mobile' => $request->mobile, + 'province_id' => $request->province_id, + 'city_id' => $request->city_id, + 'district_id' => $request->district_id, + 'address' => $request->address, + 'is_default' => $request->is_default, + ]); + + return $this->success('操作成功'); + } + + /** + * Notes: 删除地址 + * + * @Author: + * @Date : 2020/11/5 5:52 下午 + * @param Address $address + * @return mixed + */ + public function destroy(Address $address) + { + if ($address->user()->isNot(Api::user())) { + return $this->failed('', 404); + } + + try { + $address->delete(); + + return $this->success('操作成功'); + } catch (Exception$exception) { + return $this->failed('失败'); + } + } + +} diff --git a/modules/Mall/Http/Controllers/Api/BannerController.php b/modules/Mall/Http/Controllers/Api/BannerController.php new file mode 100644 index 0000000..ae45195 --- /dev/null +++ b/modules/Mall/Http/Controllers/Api/BannerController.php @@ -0,0 +1,37 @@ + + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function index(Request $request): JsonResponse + { + $position = $request->position; + $shopId = $request->shop_id; + + // 店铺ID为null的是平台轮播 + $banners = Banner::when($shopId, function (Builder $query) use ($shopId) { + $query->where('shop_id', $shopId); + })->when(is_numeric($position), function (Builder $query) use ($position) { + $query->ofPosition($position); + })->get(); + + return $this->success(BannerResource::collection($banners)); + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Api/CartController.php b/modules/Mall/Http/Controllers/Api/CartController.php new file mode 100644 index 0000000..7739df4 --- /dev/null +++ b/modules/Mall/Http/Controllers/Api/CartController.php @@ -0,0 +1,159 @@ + + * @return \Illuminate\Http\JsonResponse + */ + public function index(): JsonResponse + { + $list = Cart::byUser(Api::user())->with('sku')->orderByDesc('updated_at')->get(); + + $collection = $list->groupBy('shop_id'); + + $items = []; + + foreach ($collection as $shopId => $item) { + $items[] = [ + 'shop' => new ShopBaseInfoResource(Shop::find($shopId)), + 'items' => CartResource::collection($item), + ]; + } + + return $this->success($items); + } + + /** + * Notes : 加入购物车 + * @Date : 2021/5/14 2:17 下午 + * @Author : + * @param \Illuminate\Http\Request $request + * @return mixed + */ + public function store(Request $request) + { + $skuId = $request->sku_id; + $qty = $request->qty ?? 1; + + $sku = GoodsSku::findOrFail($skuId); + + if (!$sku->canBuy($qty)) { + return $this->failed('超过最大可购买数量'); + } + + $cart = Cart::where('user_id', Api::userId())->where('sku_id', $skuId)->first(); + + if ($cart) { + $cart->qty = $cart->qty + $qty; + $cart->save(); + } else { + $cart = Cart::create([ + 'user_id' => Api::userId(), + 'shop_id' => $sku->goods->shop_id, + 'sku_id' => $skuId, + 'qty' => $qty, + ]); + } + + return $this->success($cart->qty); + } + + /** + * Notes : 获取购物车商品数量 + * @Date : 2021/5/14 1:47 下午 + * @Author : + */ + public function count(): JsonResponse + { + if (Api::userId()) { + $count = Cart::where('user_id', Api::userId())->count(); + } else { + $count = 0; + } + + return $this->success($count); + } + + /** + * Notes : 更新购物车商品数量 + * @Date : 2021/5/13 2:52 下午 + * @Author : + * @param \Modules\Mall\Http\Requests\Cart\CartRequest $request + * @param \Modules\Mall\Models\Cart $cart + * @return mixed + */ + public function update(CartRequest $request, Cart $cart) + { + $skuId = $request->sku_id; + $qty = $request->qty; + + if ($cart->user()->isNot(Api::user())) { + return $this->failed('', 404); + } + + if ($skuId && ($skuId != $cart->sku_id)) { + $sku = GoodsSku::find($skuId); + + if (!$cart->sku->goods->is($sku->goods)) { + return $this->failed('错误的商品'); + } + + $cart->sku_id = $skuId; + } else { + $sku = $cart->sku; + } + + if ($qty < 1 || $qty > $sku->stock) { + return $this->failed('库存不足'); + } + + $cart->qty = $qty; + $cart->save(); + + return $this->success($qty); + } + + /** + * Notes : 删除购物车商品 + * @Date : 2021/5/17 4:00 下午 + * @Author : + * @param string $cart + * @return mixed|string + */ + public function destroy(string $cart) + { + if (Str::contains($cart, ',')) { + $count = Cart::query()->where('user_id', Api::userId())->whereIn('id', explode(',', $cart))->delete(); + if (!$count) { + return $this->failed('empty'); + } + } else { + $cart = Cart::find($cart); + if ($cart->user()->isNot(Api::user())) { + return $this->failed('', 404); + } + $cart->delete(); + } + + return $this->success('删除成功'); + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Api/CategoryController.php b/modules/Mall/Http/Controllers/Api/CategoryController.php new file mode 100644 index 0000000..43e045f --- /dev/null +++ b/modules/Mall/Http/Controllers/Api/CategoryController.php @@ -0,0 +1,37 @@ + + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function index(Request $request): JsonResponse + { + $shopId = $request->shop_id; + + $list = Category::query() + ->where('id', '<>', 2) + ->when($shopId, function (Builder $query) use ($shopId) { + $query->where('shop_id', $shopId); + })->where('parent_id', 0)->get(); + + return $this->success(CategoryResource::collection($list)); + + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Api/GoodsController.php b/modules/Mall/Http/Controllers/Api/GoodsController.php new file mode 100644 index 0000000..cf499dc --- /dev/null +++ b/modules/Mall/Http/Controllers/Api/GoodsController.php @@ -0,0 +1,87 @@ +get('name'); + $shopId = $request->get('shop_id'); + $categoryId = $request->get('category_id'); + $brandId = $request->get('brand_id'); + $payType = $request->get('pay_type'); + $tagId = $request->get('tag_id'); + $isSelf = $request->get('is_self'); + $perPage = $request->get('per_page'); + $orderBy = $request->get('order_by'); + + $goods = Goods::shown() + ->select(Goods::LIST_SELECT_FIELDS) + ->with(['tags', 'shop']) + ->when($name, function (Builder $query) use ($name) { + $query->where('name', 'like', "%$name%"); + }) + ->when($isSelf, function (Builder $query) { + $query->whereHas('shop', function (Builder $query) { + $query->where('is_self', 1); + }); + }) + ->when($shopId, function (Builder $query) use ($shopId) { + $query->where('shop_id', $shopId); + }) + ->when($categoryId, function (Builder $query) use ($categoryId) { + $query->where('category_id', $categoryId); + }) + ->when($brandId, function (Builder $query) use ($brandId) { + $query->where('brand_id', $brandId); + }) + ->when($payType, function (Builder $query) use ($payType) { + $query->where('pay_type', $payType); + }) + ->when($tagId, function (Builder $query) use ($tagId) { + $query->whereHas('tags', function (Builder $query) use ($tagId) { + $query->whereIn('id', explode(',', $tagId)); + }); + }) + ->when($orderBy, function (Builder $query) use ($orderBy) { + switch ($orderBy) { + case 'sales': + $query->orderByDesc('sales'); + break; + case 'price': + $query->orderByPrice(); + break; + } + }) + ->paginate($perPage); + + return $this->success(new GoodsCollection($goods)); + } + + public function show(Goods $goods): JsonResponse + { + $goods->incrementClicks(); + + return $this->success(new GoodsResource($goods)); + } + +} diff --git a/modules/Mall/Http/Controllers/Api/IndexController.php b/modules/Mall/Http/Controllers/Api/IndexController.php new file mode 100644 index 0000000..e9b796f --- /dev/null +++ b/modules/Mall/Http/Controllers/Api/IndexController.php @@ -0,0 +1,99 @@ + + * @return JsonResponse + */ + public function index(): JsonResponse + { + $banners = Banner::query()->ofPosition(Banner::POSITION_INDEX)->get(); + $centers = Banner::query()->ofPosition(Banner::POSITION_INDEX_CENTER)->get(); + + + $show_goods_id = app('Conf_mall')['show_goods_id'] ?? 0; + $show_goods = ''; + if ($show_goods_id > 0) { + $show_goods = Goods::find($show_goods_id); + } + + $goods = Goods::query() + ->where('id', '<>', $show_goods_id) + ->shown() + ->where('channel', Goods::CHANNEL_SCORE) + ->get(); + + $videos = Video::query() + ->shown() + ->get(); + + $memories = Article::query() + ->whereHas('categories', function ($q) { + $q->where('slug', 'MEMORY'); + }) + ->inRandomOrder() + ->take(10) + ->get(); + + $memoriesData = []; + $all = $memories->count() * 30; + foreach ($memories as $key => $memory) { + $id = ++$key; + $memoriesData[] = [ + 'id' => $memory->id, + 'title' => $memory->title, + 'description' => $memory->description, + 'zIndex' => $id, + 'left' => (int) bcsub($all, bcmul($key, 30)), + 'image' => $memory->cover_url, + 'animation' => null, + ]; + } + + $healths = Article::query() + ->whereHas('categories', function ($q) { + $q->where('slug', 'HEALTH'); + }) + ->inRandomOrder() + ->take(3) + ->get(); + + $data = [ + 'show_goods' => $show_goods ? new GoodsBaseResource($show_goods) : '', + 'videos' => VideoResource::collection($videos), + 'goods' => GoodsBaseResource::collection($goods), + 'banners' => BannerResource::collection($banners), + 'center' => BannerResource::collection($centers), + 'memories' => $memoriesData, + 'healths' => ArticleBaseResource::collection($healths), + 'water_mobile' => app('Conf_mall')['water_mobile'] ?? '', + 'categories' => [ + 'memory' => Category::query()->where('slug', 'MEMORY')->value('id') ?? 0, + 'health' => Category::query()->where('slug', 'HEALTH')->value('id') ?? 0, + ], + ]; + + return $this->success($data); + } + +} diff --git a/modules/Mall/Http/Controllers/Api/OrderBuyController.php b/modules/Mall/Http/Controllers/Api/OrderBuyController.php new file mode 100644 index 0000000..4526d97 --- /dev/null +++ b/modules/Mall/Http/Controllers/Api/OrderBuyController.php @@ -0,0 +1,298 @@ +all(), [ + 'goods_sku_id' => 'required|integer', + 'qty' => 'nullable|integer', + ], [ + 'goods_sku_id.required' => '缺少商品规格id', + 'goods_sku_id.integer' => '商品规格id必须是数字', + 'qty.integer' => '数量必须是数字', + ]); + + if ($validator->fails()) { + return $this->failed($validator->errors()->first()); + } + + $goods_sku_id = $request->goods_sku_id; + $qty = $request->qty; + $address_id = $request->address_id; + + $detail = collect(); + $user = Api::user(); + + if ($address_id) { + $address = Address::find($address_id); + } else { + $address = Address::where('user_id', $user->id)->orderBy('is_default', 'desc')->first(); + } + + $goods_sku = GoodsSku::find($goods_sku_id); + + if (! $goods_sku) { + return $this->failed('未找到商品'); + } + + if (! $goods_sku->canBuy($qty)) { + return $this->failed($goods_sku->getGoodsName().'商品不可购买'); + } + + $item = new Item($goods_sku, $address, $qty); + $detail->push($item); + + $freight = $item->freight(); + $amount = $item->total(); + $weight = $item->weight(); + + $detail = (new Order)->items($detail)->splitOrderByShop(); + $items = []; + foreach ($detail as $item) { + $items[] = $item; + } + + $data = [ + 'address' => $address ? new AddressResource($address) : '', + 'detail' => $items, + 'amount' => floatval(bcadd($amount, $freight, 2)), + 'freight' => $freight, + 'weight' => $weight, + ]; + + return $this->success($data); + + } + + /** + * Notes: 商品下单 + * + * @Author: 玄尘 + * @Date : 2021/5/14 13:53 + * @param \Modules\Mall\Http\Requests\OrderBuy\OrderBuyRequest $request + * @return mixed + * @throws \Exception + */ + public function goodsBuy(OrderBuyRequest $request) + { + $validator = Validator::make($request->all(), [ + 'goods_sku_id' => 'required|integer', + 'qty' => 'nullable|integer', + 'address_id' => 'required|integer', + ], [ + 'goods_sku_id.required' => '缺少商品规格id', + 'goods_sku_id.integer' => '商品规格id必须是数字', + 'qty.integer' => '数量必须是数字', + 'address_id.required' => '缺少收货地址', + 'address_id.integer' => '收货地址必须是数字', + ]); + + if ($validator->fails()) { + return $this->failed($validator->errors()->first()); + } + + $user = Api::user(); + $remark = $request->remark ?? ''; + $address_id = $request->address_id ?? ''; + $goods_sku_id = $request->goods_sku_id; + $qty = $request->qty; + $share_user_id = $request->share_user_id ?? 0; + + $address = Address::where('user_id', $user->id)->where('id', $address_id)->first(); + if (! $address) { + return $this->failed('未找到收货地址'); + } + + $detail = collect(); + + $goods_sku = GoodsSku::find($goods_sku_id); + + if (! $goods_sku) { + return $this->failed('未找到商品'); + } + + if (! $goods_sku->canBuy($qty)) { + return $this->failed($goods_sku->getGoodsName().' 商品不可购买'); + } + + $item = new Item($goods_sku, $address, $qty); + $detail->push($item); + + $type = OrderModel::TYPE_NORMAL; + if ($goods_sku->goods->channel == Goods::CHANNEL_FREE) { + $type = OrderModel::TYPE_SAMPLE; + } + + if ($goods_sku->goods->channel == Goods::CHANNEL_SCORE) { + $type = OrderModel::TYPE_SCORE; + } + + $orders = (new Order)->user($user) + ->remark($remark) + ->items($detail) + ->type($type) + ->address($address) + ->source(['share_user_id' => $share_user_id,]) + ->create(); + + return $this->success([ + 'order_no' => $orders->first()->order_no, + 'total' => $orders->first()->total, + 'score' => $user->account->score, + ]); + } + + /** + * Notes: 购物车确认下单 + * + * @Author: 玄尘 + * @Date : 2021/5/17 10:14 + */ + public function cartsCreate(Request $request) + { + $user = Api::user(); + + $cart_ids = $request->cart_ids;//1,2,3 + $cart_ids = explode(',', $cart_ids); + $carts = Cart::where('user_id', $user->id)->whereIn('id', $cart_ids)->get(); + if ($carts->isEmpty()) { + return $this->failed('未找到购物车'); + } + + $detail = collect(); + + $all_address = Address::where('user_id', $user->id)->orderBy('is_default', 'desc')->get(); + $address = Address::where('user_id', $user->id)->orderBy('is_default', 'desc')->first(); + + $amount = $weight = $freight = 0; + foreach ($carts as $cart) { + $goods_sku = $cart->sku; + + if (! $goods_sku) { + return $this->failed('未找到商品'); + } + + if (! $goods_sku->canBuy($cart->qty)) { + return $this->failed($goods_sku->getGoodsName().' 不可购买'); + } + + $item = new Item($goods_sku, $address, $cart->qty); + $detail->push($item); + + $freight += $item->freight(); + $amount += $item->total(); + $weight += $item->weight(); + } + + try { + $data = [ + 'address' => $address ? new AddressResource($address) : '', + 'all_address' => AddressResource::collection($all_address), + 'detail' => (new Order)->items($detail)->splitOrderByShop(), + 'amount' => bcadd($amount, $freight, 2), + 'freight' => $freight, + 'weight' => $weight, + ]; + + return $this->success($data); + + } catch (Exception $e) { + return $this->failed($e->getMessage()); + } + + } + + /** + * Notes: 购物车购买 + * + * @Author: 玄尘 + * @Date : 2021/5/17 10:14 + */ + public function cartsBuy(Request $request) + { + $user = Api::user(); + + $cart_ids = $request->cart_ids; //1,2,3 + $cart_ids = explode(',', $cart_ids); + $address_id = $request->address_id ?? 0; + $remark = $request->remark ?? ''; + $share_user_id = $request->share_user_id ?? 0; + + $carts = Cart::where('user_id', $user->id)->whereIn('id', $cart_ids)->get(); + if ($carts->isEmpty()) { + return $this->failed('未找到购物车'); + } + + $address = Address::where('user_id', $user->id)->where('id', $address_id)->first(); + if (! $address) { + return $this->failed('未找到收货地址'); + } + + $detail = collect(); + foreach ($carts as $cart) { + $goods_sku = $cart->sku; + + if (! $goods_sku) { + return $this->failed('未找到商品'); + } + + if (! $goods_sku->canBuy($cart->qty)) { + return $this->failed($goods_sku->getGoodsName().' 不可购买'); + } + + $item = new Item($goods_sku, $address, $cart->qty); + $detail->push($item); + } + + try { + (new Order)->user($user) + ->remark($remark) + ->items($detail) + ->address($address) + ->source(['share_user_id' => $share_user_id]) + ->create(); + //删除购物车 + Cart::whereIn('id', $cart_ids)->delete(); + + return $this->success('下单成功'); + } catch (Exception $e) { + return $this->failed($e->getMessage()); + } + + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Api/OrderController.php b/modules/Mall/Http/Controllers/Api/OrderController.php new file mode 100644 index 0000000..33cd43a --- /dev/null +++ b/modules/Mall/Http/Controllers/Api/OrderController.php @@ -0,0 +1,287 @@ + + * @param Request $request + * @return JsonResponse + */ + public function index(Request $request): JsonResponse + { + $state = $request->state ?? ''; + $type = $request->type ?? '';// score sample + + #todo 类别筛选 + #todo 商品名称搜索 + #todo 订单编号搜索 + + $list = Order::byUser(Api::user()) + ->when($state, function ($query) use ($state) { + switch ($state) { + case 'unpay': + $query->unPay(); + break; + case 'paid': + $query->paid(); + break; + case 'delivered': + $query->Delivered(); + break; + case 'signed': + $query->signed(); + break; + case 'completed': + $query->completed(); + break; + } + }, function ($query) { + $query->common(); + }) +// ->when($type, function ($q) use ($type) { +// if ($type == 'score') { +// $q->TypeScore(); +// } +// +// if ($type == 'sample') { +// $q->TypeSample(); +// } +// }) + ->paginate(); + + return $this->success(new OrderCollection($list)); + } + + /** + * Notes : 订单详情 + * + * @Date : 2021/5/13 5:17 下午 + * @Author : + * @param Order $order + * @return mixed + */ + public function show(Order $order) + { + if ($order->user()->isNot(Api::user())) { + return $this->failed('', 404); + } + + return $this->success(new OrderResource($order)); + } + + /** + * Notes : 查看物流 + * + * @Date : 2021/5/14 16:52 + * @Author : Mr.wang + * @param Order $order + * @return mixed + * @throws HttpException + */ + public function logistic(Order $order) + { + if ($order->user()->isNot(Api::user())) { + return $this->failed('', 404); + } + if (! $order->is_logistic_show) { + return $this->failed('', 404); + } + + $express = new Synquery(); + $synQuery = $express->pollQuery($order->express->express->slug, $order->express->express_no); + if (isset($synQuery->result) && $synQuery->result === false) { + return $this->failed($synQuery->message); + } + $logistics = $synQuery->data; + + return $this->success([ + 'orderExpress' => new OrderLogisticResource($order->express), + 'logistics' => LogisticResource::collection($logistics), + ]); + } + + /** + * Notes : 签收成功 + * + * @Date : 2021/5/17 16:52 + * @Author : Mr.wang + * @param Order $order + * @return mixed + * @throws \Exception + */ + public function sign(Order $order) + { + if ($order->user()->isNot(Api::user())) { + return $this->failed('', 404); + } + if ($order->sign() === true) { + return $this->success('操作成功'); + } else { + return $this->failed(); + } + } + + /** + * Notes : 取消订单 + * + * @Date : 2021/5/19 11:35 + * @Author : Mr.wang + * @param Order $order + * @return mixed + */ + public function cancel(Order $order) + { + if ($order->user()->isNot(Api::user())) { + return $this->failed('', 404); + } + try { + $order->cancel(); + + return $this->success('操作成功'); + } catch (Exception $e) { + return $this->failed($e->getMessage()); + } + } + + /** + * Notes : 删除订单(对订单进行软删除操作) + * + * @Date : 2021/5/19 11:35 + * @Author : Mr.wang + * @param Order $order + * @return mixed + */ + public function destroy(Order $order) + { + if ($order->user()->isNot(Api::user())) { + return $this->failed('', 404); + } + if ($order->canDelete() === false) { + return $this->failed('订单不允许删除'); + } + try { + $order->delete(); + + return $this->success('操作成功'); + } catch (Exception $e) { + return $this->failed($e->getMessage()); + } + } + + /** + * Notes: 订单商品 + * + * @Author: 玄尘 + * @Date : 2021/5/18 8:47 + * @param Order $order + * @return mixed + */ + public function goods(Order $order) + { + $user = Api::user(); + + if ($order->user()->isNot($user)) { + return $this->failed('', 404); + } + $data = [ + 'order' => new OrderResource($order), + 'title' => Reason::query()->pluck('title'), + ]; + + return $this->success($data); + } + + /** + * Notes: 退款/退货 + * + * @Author: 玄尘 + * @Date : 2021/5/19 9:11 + * @param Order $order + * @param Request $request + * @return mixed + */ + public function refund(Order $order, Request $request) + { + $user = Api::user(); + + if (! $order->type == Order::TYPE_SAMPLE) { + return $this->failed('操作失败,提货单不可退货'); + } + + if (! $order->can('refund')) { + return $this->failed('操作失败,此订单不可退货'); + } + + if ($order->user()->isNot($user)) { + return $this->failed('您没有权限进行此操作'); + } + + $remark = $request->remark ?? ''; + $title = $request->title;//退款原因 + $pictures = $request->pictures;//图片 many + + if (! $order->isPay()) { + return $this->failed('退款失败,未找到支付信息'); + } + + try { + (new Refund)->user($user) + ->remark($remark) + ->order($order) + ->logs([ + 'remark' => $remark, + 'title' => $title, + 'pictures' => $pictures, + ]) + ->create(); + + return $this->success('提交成功,请等待审核。'); + } catch (Exception $e) { + return $this->failed($e->getMessage()); + } + + } + + /** + * Notes : 个人中心对订单的总数 + * + * @Date : 2021/6/3 13:36 + * @Author : Mr.wang + * @return mixed + */ + public function counts() + { + $orderCount = Order::byUser(Api::user())->common()->count(); + $refundCount = RefundModel::byUser(Api::user())->count(); + + return $this->success([ + 'orderCount' => $orderCount, + 'refundCount' => $refundCount, + ]); + } + +} diff --git a/modules/Mall/Http/Controllers/Api/PayController.php b/modules/Mall/Http/Controllers/Api/PayController.php new file mode 100644 index 0000000..c3514f0 --- /dev/null +++ b/modules/Mall/Http/Controllers/Api/PayController.php @@ -0,0 +1,113 @@ +can('pay')) { + return $this->failed('支付失败,订单不可支付'); + } + + if ($order->user()->isNot($user)) { + return $this->failed('支付失败,您没有权限支付'); + } + + $payment = $order->createWechatPayment($user, $order->total, 'app'); + + $notify = $payment->getPaymentParams('商品下单', [ + 'notify_url' => route('api.payment.notify.wechat'), + ]); + + return $this->success($notify->getContent()); + } + + /** + * Notes: 支付宝支付 + * + * @Author: 玄尘 + * @Date : 2021/5/20 9:13 + * @param \Modules\Mall\Models\Order $order + * @return mixed + * @throws \Exception + */ + public function alipay(Order $order) + { + $user = Api::user(); + + if (! $order->can('pay')) { + return $this->failed('支付失败,订单不可支付'); + } + + if ($order->user()->isNot($user)) { + return $this->failed('支付失败,您没有权限支付'); + } + + $payment = $order->createAlipayPayment($user, $order->total, 'app'); + + $notify = $payment->getPaymentParams('商品下单', [ + 'notify_url' => route('api.payment.notify.alipay'), + ]); + + return $this->success($notify->getContent()); + + } + + /** + * Notes: 水滴支付 + * + * @Author: 玄尘 + * @Date: 2022/9/5 15:51 + * @param Order $order + * @return JsonResponse|mixed + * @throws \Exception + */ + public function score(Order $order, Request $request) + { + $gateway = $request->gateway ?? 'web'; + $user = Api::user(); + + if (! $order->can('pay')) { + return $this->failed('支付失败,订单不可支付'); + } + + if ($order->user()->isNot($user)) { + return $this->failed('支付失败,您没有权限支付'); + } + + if ($user->account->score < $order->total) { + return $this->failed('支付失败,您水滴积分不足'); + } + + $payment = $order->createScorePayment($user, $order->total, $gateway); + + if ($payment) { + $payment->paid(); + $payment->order->pay(); + return $this->success('支付成功!'); + } else { + return $this->failed('未知错误'); + } + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Api/RefundController.php b/modules/Mall/Http/Controllers/Api/RefundController.php new file mode 100644 index 0000000..f21ba68 --- /dev/null +++ b/modules/Mall/Http/Controllers/Api/RefundController.php @@ -0,0 +1,124 @@ +state ?? 'refunding';//refunding refunded + + $list = Refund::byUser(Api::user()) + ->when($state, function ($query) use ($state) { + switch ($state) { + case 'refunding': + $query->whereIn('state', [ + Refund::REFUND_APPLY, + Refund::REFUND_AGREE, + Refund::REFUND_PROCESS, + Refund::REFUND_DELIVER, + Refund::REFUND_DELIVERED, + Refund::REFUND_SIGNED, + ]); + break; + case 'refunded': + $query->whereIn('state', [ + Refund::REFUND_REFUSE, + Refund::REFUND_COMPLETED, + ]); + break; + } + }) + ->paginate(); + + return $this->success(new RefundCollection($list)); + } + + /** + * Notes: 退单详情 + * @Author: 玄尘 + * @Date : 2021/5/18 11:35 + * @param \Modules\Mall\Models\Refund $refund + * @return mixed + */ + public function show(Refund $refund) + { + if ($refund->user()->isNot(Api::user())) { + return $this->failed('您没有权限查看', 404); + } + + return $this->success(new RefundResource($refund)); + } + + /** + * Notes: 退货 + * @Author: 玄尘 + * @Date : 2021/5/17 15:02 + * @param \Modules\Mall\Models\Refund $refund + * @param \Illuminate\Http\Request $request + * @return mixed + * @throws \Exception + */ + public function deliver(Refund $refund, Request $request) + { + if ($refund->user()->isNot(Api::user())) { + return $this->failed('您没有权限操作'); + } + + $validator = Validator::make($request->all(), [ + 'company' => 'required', + 'number' => 'required', + ], [ + 'company.required' => '缺少物流公司', + 'number.required' => '缺少物流单号', + ]); + + if ($validator->fails()) { + return $this->failed($validator->errors()->first()); + } + // $express = Express::find($request->company); + // if (!$express) { + // return $this->failed('未找到物流公司'); + // } + + $res = $refund->deliver($request->company, $request->number); + if ($res === true) { + return $this->success('退货成功,请等待退款'); + } + + return $this->failed('退货失败'); + } + + /** + * Notes: 退单日志 + * @Author: 玄尘 + * @Date : 2021/5/18 11:36 + * @param \Modules\Mall\Models\Refund $refund + * @return \Illuminate\Http\JsonResponse + */ + public function logs(Refund $refund): JsonResponse + { + $logs = $refund->logs()->latest()->get(); + + return $this->success(RefundLogResource::collection($logs)); + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Api/ShopController.php b/modules/Mall/Http/Controllers/Api/ShopController.php new file mode 100644 index 0000000..a54d5a4 --- /dev/null +++ b/modules/Mall/Http/Controllers/Api/ShopController.php @@ -0,0 +1,210 @@ + + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function index(Request $request): JsonResponse + { + $name = $request->name ?? ''; + + $list = Shop::where('status', Shop::STATUS_NORMAL) + ->when($name, function (Builder $query) use ($name) { + $query->where('name', 'like', "%{$name}%"); + }) + ->paginate(); + + return $this->success(new ShopCollection($list)); + } + + /** + * Notes : 店铺详情 + * @Date : 2021/5/6 3:28 下午 + * @Author : + */ + public function show(Shop $shop): JsonResponse + { + if ($shop->status !== Shop::STATUS_NORMAL) { + return $this->failed('店铺不存在或已关闭'); + } + + return $this->success(new ShopResource($shop)); + } + + /** + * Notes : 申请的前置判断和基础数据 + * @Date : 2021/5/6 3:29 下午 + * @Author : + */ + public function create() + { + $user = Api::user(); + $checkShop = Shop::firstWhere('user_id', $user->id); + if ($checkShop) { + return $this->failed('用户已有店铺,不能重复申请'); + } + $regions = Region::where('depth', 1)->get(); + + return $this->success(RegionResource::collection($regions)); + } + + /** + * Notes : 申请一个店铺 + * @Date : 2021/5/6 3:29 下午 + * @Author : + */ + public function store(ShopRequest $request) + { + $user = Api::user(); + + if (Shop::firstWhere('user_id', $user->id)) { + return $this->failed('用户已有店铺,不能重复申请'); + } + $result = Shop::create([ + 'name' => $request->name, + 'user_id' => $user->id, + 'mobile' => $request->mobile, + 'description' => $request->description, + 'province_id' => $request->province_id, + 'city_id' => $request->city_id, + 'district_id' => $request->district_id, + 'cover' => $request->cover, + 'address' => $request->address, + 'status' => Shop::STATUS_APPLYING, + ]); + if ($result) { + return $this->success('操作成功'); + } else { + return $this->failed('失败'); + } + } + + /** + * Notes : 编辑店铺需要的数据 + * @Date : 2021/5/7 10:26 上午 + * @Author : + */ + public function edit() + { + $user = Api::user(); + $shop = Shop::firstWhere('user_id', $user->id); + if (!$shop) { + return $this->failed('用户没有店铺,不能编辑'); + } + if ($shop->status == Shop::STATUS_NORMAL) { + return $this->failed('用户已通过审核,不能编辑'); + } + + return $this->success(new ShopEditResource($shop)); + } + + /** + * Notes : 更新店铺数据 + * @Date : 2021/5/7 10:27 上午 + * @Author : + */ + public function update(Request $request) + { + $user = Api::user(); + $shop = Shop::firstWhere('user_id', $user->id); + if (!$shop) { + return $this->failed('用户没有店铺,不能编辑'); + } + if ($shop->status == Shop::STATUS_NORMAL) { + return $this->failed('用户已通过审核,不能编辑'); + } + $validator = Validator::make( + $request->all(), + [ + 'name' => ['required', Rule::unique('mall_shops', 'name')->ignore($shop->id)], + 'mobile' => [ + 'required', 'phone:CN', Rule::unique('mall_shops', 'mobile')->ignore($shop->id), + ], + 'description' => 'required', + 'province_id' => ['required', 'integer', new ProvinceRule()], + 'city_id' => ['required', 'integer', new CityRule()], + 'district_id' => ['required', 'integer', new DistrictRule()], + 'address' => 'required', + 'cover' => 'required', + ], [ + 'name.required' => '店铺名称必须填写', + 'name.unique' => '店铺名称不能重复', + 'mobile.required' => '联系方式必须填写', + 'mobile.phone' => '联系方式格式不正确', + 'mobile.unique' => '联系方式不能重复', + 'description.required' => '店铺简介必须填写', + 'province_id.required' => '店铺所在省份必须选择', + 'province_id.integer' => '店铺所在省份格式不正确', + 'city_id.required' => '店铺所在城市必须选择', + 'city_id.integer' => '店铺所在城市格式不正确', + 'district_id.required' => '店铺所在地区必须选择', + 'district_id.integer' => '店铺所在地区格式不正确', + 'address.required' => '店铺详细地址必须填写', + 'cover.required' => '店铺封面图必须上传', + ] + ); + if ($validator->fails()) { + return $this->failed($validator->errors()->first(), 422); + } + $result = $shop->update([ + 'name' => $request->name, + 'mobile' => $request->mobile, + 'description' => $request->description, + 'province_id' => $request->province_id, + 'city_id' => $request->city_id, + 'district_id' => $request->district_id, + 'cover' => $request->cover, + 'address' => $request->address, + 'status' => Shop::STATUS_APPLYING, + ]); + if ($result) { + return $this->success('操作成功'); + } else { + return $this->failed('失败'); + } + } + + /** + * Notes : 关闭店铺 + * @Date : 2021/5/6 3:29 下午 + * @Author : + */ + public function destroy(Shop $shop) + { + if ($shop->user()->isNot(Api::user())) { + return $this->failed('', '404'); + } + if ($shop->close()) { + return $this->success('店铺关闭成功'); + } else { + return $this->failed('失败'); + } + } + +} diff --git a/modules/Mall/Http/Controllers/Api/ShopExtendController.php b/modules/Mall/Http/Controllers/Api/ShopExtendController.php new file mode 100644 index 0000000..b59f116 --- /dev/null +++ b/modules/Mall/Http/Controllers/Api/ShopExtendController.php @@ -0,0 +1,87 @@ + + * @param \Modules\Mall\Models\Shop $shop + * @return \Illuminate\Http\JsonResponse + */ + public function categories(Shop $shop): JsonResponse + { + $list = $shop->categories()->where('parent_id', 0)->get(); + + return $this->success(CategoryResource::collection($list)); + } + + /** + * Notes : 店铺标签 + * @Date : 2021/5/13 11:35 上午 + * @Author : + * @param \Modules\Mall\Models\Shop $shop + * @return \Illuminate\Http\JsonResponse + */ + public function tags(Shop $shop): JsonResponse + { + $list = $shop->tags()->get(); + + return $this->success(TagResource::collection($list)); + } + + /** + * Notes : 店铺品牌 + * @Date : 2021/5/13 11:35 上午 + * @Author : + * @param \Modules\Mall\Models\Shop $shop + * @return \Illuminate\Http\JsonResponse + */ + public function brands(Shop $shop): JsonResponse + { + $list = $shop->brands()->get(); + + return $this->success(BrandResource::collection($list)); + } + + /** + * Notes : 支持的物流 + * @Date : 2021/5/13 11:36 上午 + * @Author : + * @param \Modules\Mall\Models\Shop $shop + * @return \Illuminate\Http\JsonResponse + */ + public function expresses(Shop $shop): JsonResponse + { + $list = $shop->expresses()->get(); + + return $this->success(ExpressResource::collection($list)); + } + + /** + * Notes : 轮播图 + * @Date : 2021/5/13 11:36 上午 + * @Author : + * @param \Modules\Mall\Models\Shop $shop + * @return \Illuminate\Http\JsonResponse + */ + public function banners(Shop $shop): JsonResponse + { + $list = $shop->banners()->get(); + + return $this->success(BannerResource::collection($list)); + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Controllers/Api/TagController.php b/modules/Mall/Http/Controllers/Api/TagController.php new file mode 100644 index 0000000..46584a0 --- /dev/null +++ b/modules/Mall/Http/Controllers/Api/TagController.php @@ -0,0 +1,36 @@ + + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function index(Request $request): JsonResponse + { + $shopId = $request->shop_id; + $limit = $request->limit; + + $list = Tag::when($shopId, function (Builder $query) use ($shopId) { + $query->where('shop_id', $shopId); + })->when($limit, function (Builder $query) use ($limit) { + $query->take($limit); + })->get(); + + return $this->success(TagResource::collection($list)); + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Exporter/OrderExporter.php b/modules/Mall/Http/Exporter/OrderExporter.php new file mode 100644 index 0000000..9f0f571 --- /dev/null +++ b/modules/Mall/Http/Exporter/OrderExporter.php @@ -0,0 +1,49 @@ +headings; + + $data = $this->getData(); + if (count($data)) { + foreach ($data as $v) { + $rows[] = [ + $v['order_no'], + $v['shop']['name'], + $v['user']['username'], + $v['amount'], + $v['freight'], + $v['items'][0]['qty'], + $v['total'], + $v['state'], + $v['type'], + $v['paid_at'], + $v['created_at'], + ]; + } + } + + // 数据格式化 + $export = new OrderFromArray($rows); + + // 导出 + return Excel::download($export, $this->fileName)->send(); + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Exporter/OrderFromArray.php b/modules/Mall/Http/Exporter/OrderFromArray.php new file mode 100644 index 0000000..c86c6b9 --- /dev/null +++ b/modules/Mall/Http/Exporter/OrderFromArray.php @@ -0,0 +1,27 @@ +order = $order; + } + + public function array(): array + { + return $this->order; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Middleware/Authenticate.php b/modules/Mall/Http/Middleware/Authenticate.php new file mode 100644 index 0000000..e9c8b24 --- /dev/null +++ b/modules/Mall/Http/Middleware/Authenticate.php @@ -0,0 +1,35 @@ + + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + * @throws \Exception + */ + public function handle(Request $request, Closure $next) + { + $shop = Shop::byUser(Api::user())->first(); + + if ($shop) { + $request->merge(['shop' => $shop]); + } else { + throw new \Exception('Do not have a shop'); + } + + return $next($request); + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Middleware/ShopOwner.php b/modules/Mall/Http/Middleware/ShopOwner.php new file mode 100644 index 0000000..1ea828f --- /dev/null +++ b/modules/Mall/Http/Middleware/ShopOwner.php @@ -0,0 +1,24 @@ + + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle(Request $request, Closure $next) + { + return $next($request); + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Requests/Address/AddressRequest.php b/modules/Mall/Http/Requests/Address/AddressRequest.php new file mode 100644 index 0000000..5189b75 --- /dev/null +++ b/modules/Mall/Http/Requests/Address/AddressRequest.php @@ -0,0 +1,58 @@ + + * @Date : 2020/11/5 5:55 下午 + * @return array + */ + public function rules(): array + { + return [ + 'name' => 'required|min:2|max:16', + 'mobile' => ['required', 'numeric', 'regex:/^1[3-9]\d{9}$/'], + 'province_id' => ['required', 'numeric', new ProvinceRule()], + 'city_id' => ['required', 'numeric', new CityRule()], + 'district_id' => ['required', 'numeric', new DistrictRule()], + 'address' => 'required|min:2|max:255', + ]; + } + + /** + * Notes: 验证错误提示信息 + * @Author: + * @Date : 2020/11/6 9:33 上午 + * @return array + */ + public function messages(): array + { + return [ + 'name.required' => '收件人必须填写', + 'name.min' => '收件人姓名至少:min位字符', + 'name.max' => '收件人姓名最多:max位字符', + 'mobile.required' => '手机号码必须填写', + 'mobile.numeric' => '手机号码必须是数字', + 'mobile.regex' => '手机号码格式不正确', + 'province_id.required' => '所在省份必须填写', + 'province_id.numeric' => '所在省份格式不正确', + 'city_id.required' => '所在城市必须填写', + 'city_id.numeric' => '所在城市格式不正确', + 'district_id.required' => '所在区县必须填写', + 'district_id.numeric' => '所在区县格式不正确', + 'address.required' => '详细地址必须填写', + 'address.min' => '详细地址至少:min位字符', + 'address.max' => '详细地址最多:max位字符', + ]; + } + +} diff --git a/modules/Mall/Http/Requests/Cart/CartRequest.php b/modules/Mall/Http/Requests/Cart/CartRequest.php new file mode 100644 index 0000000..15553c5 --- /dev/null +++ b/modules/Mall/Http/Requests/Cart/CartRequest.php @@ -0,0 +1,24 @@ + 'nullable|integer', + ]; + } + + public function messages(): array + { + return [ + 'qty.integer' => '商品数量必须是整数', + ]; + } + +} diff --git a/modules/Mall/Http/Requests/OrderBuy/OrderBuyRequest.php b/modules/Mall/Http/Requests/OrderBuy/OrderBuyRequest.php new file mode 100644 index 0000000..2f31343 --- /dev/null +++ b/modules/Mall/Http/Requests/OrderBuy/OrderBuyRequest.php @@ -0,0 +1,30 @@ + 'required|integer', + 'qty' => 'nullable|integer', + 'address_id' => ['nullable', 'required', 'integer'], + ]; + } + + public function messages(): array + { + return [ + 'goods_sku_id.required' => '缺少商品规格id', + 'goods_sku_id.integer' => '商品规格id必须是数字', + 'qty.integer' => '数量必须是数字', + 'address_id.required' => '缺少收货地址', + 'address_id.integer' => '收货地址必须是数字', + ]; + } + +} diff --git a/modules/Mall/Http/Requests/Shop/ShopRequest.php b/modules/Mall/Http/Requests/Shop/ShopRequest.php new file mode 100644 index 0000000..4271444 --- /dev/null +++ b/modules/Mall/Http/Requests/Shop/ShopRequest.php @@ -0,0 +1,47 @@ + 'required|unique:mall_shops', + 'mobile' => 'required|phone:CN|unique:mall_shops', + 'description' => 'required', + 'province_id' => ['required', 'integer', new ProvinceRule()], + 'city_id' => ['required', 'integer', new CityRule()], + 'district_id' => ['required', 'integer', new DistrictRule()], + 'address' => 'required', + 'cover' => 'required', + ]; + } + + public function messages(): array + { + return [ + 'name.required' => '店铺名称必须填写', + 'name.unique' => '店铺名称不能重复', + 'mobile.required' => '联系方式必须填写', + 'mobile.phone' => '联系方式格式不正确', + 'mobile.unique' => '联系方式不能重复', + 'description.required' => '店铺简介必须填写', + 'province_id.required' => '店铺所在省份必须选择', + 'province_id.integer' => '店铺所在省份格式不正确', + 'city_id.required' => '店铺所在城市必须选择', + 'city_id.integer' => '店铺所在城市格式不正确', + 'district_id.required' => '店铺所在地区必须选择', + 'district_id.integer' => '店铺所在地区格式不正确', + 'address.required' => '店铺详细地址必须填写', + 'cover.required' => '店铺封面图必须上传', + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Activity/ActivityBaseResource.php b/modules/Mall/Http/Resources/Api/Activity/ActivityBaseResource.php new file mode 100644 index 0000000..c2ac3a3 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Activity/ActivityBaseResource.php @@ -0,0 +1,24 @@ + $this->id, + 'cover' => $this->cover_url, + 'title' => $this->title, + 'description' => $this->description, + 'created_at' => (string)$this->created_at, + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Activity/ActivityCollection.php b/modules/Mall/Http/Resources/Api/Activity/ActivityCollection.php new file mode 100644 index 0000000..0b355cc --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Activity/ActivityCollection.php @@ -0,0 +1,20 @@ + $this->collection->map(function ($activity) { + return new ActivityBaseResource($activity); + }), + 'page' => $this->page(), + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Activity/ActivityResource.php b/modules/Mall/Http/Resources/Api/Activity/ActivityResource.php new file mode 100644 index 0000000..8bb3daf --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Activity/ActivityResource.php @@ -0,0 +1,25 @@ + $this->id, + 'cover' => $this->cover_url, + 'title' => $this->title, + 'description' => $this->description, + 'content' => $this->content, + 'created_at' => $this->created_at->format('Y-m-d'), + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Address/AddressResource.php b/modules/Mall/Http/Resources/Api/Address/AddressResource.php new file mode 100644 index 0000000..c3131cd --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Address/AddressResource.php @@ -0,0 +1,28 @@ + $this->id, + 'name' => $this->name, + 'mobile' => $this->mobile, + 'province' => new RegionResource($this->province), + 'city' => new RegionResource($this->city), + 'district' => new RegionResource($this->district), + 'address' => $this->address, + 'full_address' => $this->getFullAddress(), + 'default' => $this->is_default, + 'created_at' => (string) $this->created_at, + 'updated_at' => (string) $this->updated_at, + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Banner/BannerResource.php b/modules/Mall/Http/Resources/Api/Banner/BannerResource.php new file mode 100644 index 0000000..07e4277 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Banner/BannerResource.php @@ -0,0 +1,19 @@ + $this->title, + 'cover' => $this->cover_url, + 'url' => $this->linker, + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Category/CategoryBaseResource.php b/modules/Mall/Http/Resources/Api/Category/CategoryBaseResource.php new file mode 100644 index 0000000..448a64c --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Category/CategoryBaseResource.php @@ -0,0 +1,19 @@ + $this->id, + 'name' => $this->name, + 'cover' => $this->cover_url, + ]; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Resources/Api/Category/CategoryResource.php b/modules/Mall/Http/Resources/Api/Category/CategoryResource.php new file mode 100644 index 0000000..3291479 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Category/CategoryResource.php @@ -0,0 +1,22 @@ + $this->id, + 'name' => $this->name, + 'cover' => $this->cover_url, + 'children' => $this->when(!$this->children()->shown()->get()->isEmpty(), function () { + return self::collection($this->children()->shown()->get()); + }), + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Goods/BrandResource.php b/modules/Mall/Http/Resources/Api/Goods/BrandResource.php new file mode 100644 index 0000000..b02a01f --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Goods/BrandResource.php @@ -0,0 +1,18 @@ + $this->id, + 'name' => $this->name, + ]; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Resources/Api/Goods/CartResource.php b/modules/Mall/Http/Resources/Api/Goods/CartResource.php new file mode 100644 index 0000000..a0bfdb3 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Goods/CartResource.php @@ -0,0 +1,24 @@ + $this->id, + 'sku_id' => $this->sku_id, + 'goods_id' => $this->sku->goods_id, + 'qty' => $this->qty, + 'max_qty' => $this->sku->stock, + 'price' => (float) $this->sku->price, + 'cover' => $this->sku->cover_url, + 'name' => $this->sku->goods->name, + ]; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Resources/Api/Goods/DetailResource.php b/modules/Mall/Http/Resources/Api/Goods/DetailResource.php new file mode 100644 index 0000000..c503730 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Goods/DetailResource.php @@ -0,0 +1,24 @@ + $this->id, + 'sku_id' => $this->sku_id, + 'qty' => $this->qty, + 'max_qty' => $this->sku->stock, + 'price' => (float) $this->sku->price, + 'cover' => ($this->sku->goods->type === Goods::TYPE_SINGLE) ? $this->sku->goods->cover_url : $this->sku->cover_url, + 'name' => $this->sku->goods->name, + ]; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Resources/Api/Goods/GoodsBaseResource.php b/modules/Mall/Http/Resources/Api/Goods/GoodsBaseResource.php new file mode 100644 index 0000000..081ae16 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Goods/GoodsBaseResource.php @@ -0,0 +1,33 @@ + $this->id, + 'shop' => new ShopBaseInfoResource($this->shop), + 'is_self' => (bool) $this->shop->is_self, + 'name' => $this->name, + 'description' => $this->description ?? '', + 'cover' => $this->cover_url, + 'tags' => TagResource::collection($this->tags), + 'original_price' => (float) $this->original_price, + 'price' => [ + 'show' => $this->price, + 'score' => $this->score, + 'price_min' => $this->price_min, + 'price_max' => $this->price_max, + ], + 'clicks' => $this->clicks, + 'sales' => $this->sales, + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Goods/GoodsCollection.php b/modules/Mall/Http/Resources/Api/Goods/GoodsCollection.php new file mode 100644 index 0000000..3559c1e --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Goods/GoodsCollection.php @@ -0,0 +1,20 @@ + $this->collection->map(function ($goods) { + return new GoodsBaseResource($goods); + }), + 'page' => $this->page(), + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Goods/GoodsResource.php b/modules/Mall/Http/Resources/Api/Goods/GoodsResource.php new file mode 100644 index 0000000..92b8ffd --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Goods/GoodsResource.php @@ -0,0 +1,50 @@ + $this->id, + 'shop' => new ShopBaseInfoResource($this->shop), + 'brand' => new BrandResource($this->brand), + 'category' => new CategoryBaseResource($this->category), + 'tags' => TagResource::collection($this->tags), + + 'type' => $this->type, + 'pay_type' => $this->pay_type, + 'clicks' => $this->clicks, + 'sales' => $this->sales, + + 'name' => $this->name, + 'cover' => $this->cover_url, + 'pictures' => $this->pictures_url, + 'description' => $this->description ?? '', + 'content' => $this->content, + 'created_at' => (string) $this->created_at, + 'original_price' => (float) $this->original_price, + 'price' => [ + 'show' => $this->price, + 'score' => $this->score, + 'price_min' => $this->price_min, + 'price_max' => $this->price_max, + ], + 'specs' => SpecResource::collection($this->specs), + 'skus' => SkuResource::collection($this->skus), + 'canPick' => $user && $user->canPick(), + 'hasCase' => $user && (bool) $user->case, + 'hasLogin' => (bool) $user, + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Goods/SkuBaseResource.php b/modules/Mall/Http/Resources/Api/Goods/SkuBaseResource.php new file mode 100644 index 0000000..c9c228e --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Goods/SkuBaseResource.php @@ -0,0 +1,19 @@ + $this->id, + 'goods_name' => $this->goods->name, + 'cover' => $this->cover_url, + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Goods/SkuResource.php b/modules/Mall/Http/Resources/Api/Goods/SkuResource.php new file mode 100644 index 0000000..add095f --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Goods/SkuResource.php @@ -0,0 +1,26 @@ + $this->id, + 'goods_id' => $this->goods_id, + 'goods_name' => $this->goods->name, + 'cover' => $this->cover_url, + 'price' => (float) $this->price, + 'score' => (float) $this->score, + 'assets' => (float) $this->assets, + 'unit' => $this->unit ?? '', + 'stock' => $this->stock, + 'weight' => $this->weight, + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Goods/SpecResource.php b/modules/Mall/Http/Resources/Api/Goods/SpecResource.php new file mode 100644 index 0000000..b815d6c --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Goods/SpecResource.php @@ -0,0 +1,19 @@ + $this->id, + 'name' => $this->name, + 'values' => SpecValueResource::collection($this->values), + ]; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Resources/Api/Goods/SpecValueResource.php b/modules/Mall/Http/Resources/Api/Goods/SpecValueResource.php new file mode 100644 index 0000000..947615d --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Goods/SpecValueResource.php @@ -0,0 +1,18 @@ + $this->id, + 'value' => $this->value, + ]; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Resources/Api/Goods/TagResource.php b/modules/Mall/Http/Resources/Api/Goods/TagResource.php new file mode 100644 index 0000000..3fb6fd5 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Goods/TagResource.php @@ -0,0 +1,18 @@ + $this->id, + 'name' => $this->name, + ]; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Resources/Api/Order/LogisticResource.php b/modules/Mall/Http/Resources/Api/Order/LogisticResource.php new file mode 100644 index 0000000..3b76110 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Order/LogisticResource.php @@ -0,0 +1,21 @@ + $this->time, + 'context' => $this->context, + 'ftime' => $this->ftime, + 'areaName' => $this->areaName ?? "", + 'status' => $this->status, + ]; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Resources/Api/Order/OrderCollection.php b/modules/Mall/Http/Resources/Api/Order/OrderCollection.php new file mode 100644 index 0000000..e7f6dd9 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Order/OrderCollection.php @@ -0,0 +1,34 @@ + $this->collection->map(function ($order) { + return [ + 'order_no' => $order->order_no, + 'shop' => new ShopBaseInfoResource($order->shop), + 'amount' => $order->amount, + 'freight' => $order->freight, + 'total' => $order->total, + 'type' => $order->type_text, + 'state' => $order->state_text, + 'can' => $order->getCanAction(), + 'is_logistic_show' => $order->is_logistic_show, + 'created_at' => (string) $order->created_at, + 'items' => OrderItemResource::collection($order->items), + ]; + }), + 'page' => $this->page(), + ]; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Resources/Api/Order/OrderExpressResource.php b/modules/Mall/Http/Resources/Api/Order/OrderExpressResource.php new file mode 100644 index 0000000..e5e7e4b --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Order/OrderExpressResource.php @@ -0,0 +1,22 @@ + $this->id, + 'name' => $this->name, + 'mobile' => $this->mobile, + 'express_name' => $this->express_id ? $this->express->name : '', + 'express_no' => $this->express_no, + 'full_address' => $this->getFullAddress(), + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Order/OrderItemResource.php b/modules/Mall/Http/Resources/Api/Order/OrderItemResource.php new file mode 100644 index 0000000..3cedf7b --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Order/OrderItemResource.php @@ -0,0 +1,26 @@ + $this->id, + 'qty' => $this->qty, + 'price' => $this->price, + 'total' => $this->total, + 'can' => [ + 'refund' => $this->canRefund(), + ], + 'sku' => $this->source, + 'refund_status_text' => $this->refund_status_text, + ]; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Resources/Api/Order/OrderLogisticResource.php b/modules/Mall/Http/Resources/Api/Order/OrderLogisticResource.php new file mode 100644 index 0000000..abc96c4 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Order/OrderLogisticResource.php @@ -0,0 +1,22 @@ + $this->express_id, + 'express_no' => $this->express_no, + 'order_state' => $this->order->state_text, + 'logistic_name' => $this->express->name, + 'logistic_cover' => $this->express->cover_url, + 'address' => $this->getFullAddress(), + ]; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Resources/Api/Order/OrderResource.php b/modules/Mall/Http/Resources/Api/Order/OrderResource.php new file mode 100644 index 0000000..bd296e7 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Order/OrderResource.php @@ -0,0 +1,32 @@ + $this->order_no, + 'shop' => new ShopBaseInfoResource($this->shop), + 'amount' => $this->amount, + 'freight' => $this->freight, + 'total' => $this->total, + 'type' => $this->type_text, + 'state' => $this->state_text, + 'can' => $this->getCanAction(), + 'is_logistic_show' => $this->is_logistic_show, + 'items' => OrderItemResource::collection($this->items), + 'express' => new OrderExpressResource($this->express), + 'remark' => (string) $this->remark, + 'expired_at' => (string) $this->expired_at, + 'paid_at' => (string) $this->paid_at, + 'created_at' => (string) $this->created_at, + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Order/RefundCollection.php b/modules/Mall/Http/Resources/Api/Order/RefundCollection.php new file mode 100644 index 0000000..05439a2 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Order/RefundCollection.php @@ -0,0 +1,37 @@ + $this->collection->map(function ($refund) { + return [ + 'refund_no' => $refund->refund_no, + 'order_no' => $refund->order->order_no, + 'shop' => new ShopBaseInfoResource($refund->shop), + 'refund_total' => $refund->refund_total, + 'actual_total' => $refund->actual_total, + 'state' => [ + 'state' => $refund->state, + 'text' => $refund->status_text, + 'remark' => $refund->state_text, + ], + 'can' => collect($refund->transitions())->map(function ($item) { + return $item->getName(); + }), + 'created_at' => (string) $refund->created_at, + 'items' => RefundItemsResource::collection($refund->items), + ]; + }), + 'page' => $this->page(), + ]; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Resources/Api/Order/RefundItemsResource.php b/modules/Mall/Http/Resources/Api/Order/RefundItemsResource.php new file mode 100644 index 0000000..40c8188 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Order/RefundItemsResource.php @@ -0,0 +1,22 @@ + $this->id, + 'price' => $this->price, + 'goods_id' => $this->source ? $this->source['goods_id'] : '', + 'cover' => $this->source ? $this->source['cover'] : '', + 'goods_name' => $this->source ? $this->source['goods_name'] : '', + 'qty' => $this->qty, + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Order/RefundLogResource.php b/modules/Mall/Http/Resources/Api/Order/RefundLogResource.php new file mode 100644 index 0000000..f3d6198 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Order/RefundLogResource.php @@ -0,0 +1,22 @@ + $this->id, + 'title' => $this->title, + 'remark' => $this->remark, + 'state_text' => $this->state_text, + 'isMy' => $this->refund->user->is($this->userable), + 'created_at' => (string) $this->created_at, + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Order/RefundResource.php b/modules/Mall/Http/Resources/Api/Order/RefundResource.php new file mode 100644 index 0000000..18a0749 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Order/RefundResource.php @@ -0,0 +1,40 @@ + $this->id, + 'refund_no' => $this->refund_no, + 'order_no' => $this->order->order_no, + 'state' => [ + 'state' => $this->state, + 'text' => $this->status_text, + 'remark' => $this->state_text, + ], + 'refund_total' => $this->refund_total, + 'actual_total' => $this->actual_total, + 'order' => [ + 'order_id' => $this->order->id, + 'order_no' => $this->order->order_no, + 'paid_at' => $this->order->paid_at ? $this->order->paid_at->toDateTimeString() : '', + 'deliver_at' => $this->order->express->deliver_at ? $this->order->express->deliver_at->toDateTimeString() : '', + 'receive_at' => $this->order->express->receive_at ? $this->order->express->receive_at->toDateTimeString() : '', + ], + 'items' => RefundItemsResource::collection($this->items), + 'can' => collect($this->transitions())->map(function ($item) { + return $item->getName(); + }), + 'log' => new RefundLogResource($this->logs()->oldest()->first()), + 'created_at' => $this->created_at->toDateTimeString(), + 'updated_at' => $this->updated_at->toDateTimeString(), + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Region/RegionResource.php b/modules/Mall/Http/Resources/Api/Region/RegionResource.php new file mode 100644 index 0000000..0d14eef --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Region/RegionResource.php @@ -0,0 +1,18 @@ + $this->id, + 'name' => $this->name, + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Shop/ExpressResource.php b/modules/Mall/Http/Resources/Api/Shop/ExpressResource.php new file mode 100644 index 0000000..79c6023 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Shop/ExpressResource.php @@ -0,0 +1,19 @@ + $this->id, + 'name' => $this->name, + 'cover' => $this->cover_url, + ]; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Resources/Api/Shop/ShopBaseInfoResource.php b/modules/Mall/Http/Resources/Api/Shop/ShopBaseInfoResource.php new file mode 100644 index 0000000..0e53054 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Shop/ShopBaseInfoResource.php @@ -0,0 +1,20 @@ + $this->id, + 'name' => $this->name, + 'cover' => $this->cover_url, + 'is_self' => (bool) $this->is_self, + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Shop/ShopCollection.php b/modules/Mall/Http/Resources/Api/Shop/ShopCollection.php new file mode 100644 index 0000000..b39e023 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Shop/ShopCollection.php @@ -0,0 +1,27 @@ + $this->collection->map(function ($shop) { + return [ + 'shop_id' => $shop->id, + 'name' => $shop->name, + 'is_self' => (bool) $shop->is_self, + 'mobile' => $shop->mobile, + 'description' => $shop->description, + 'cover' => $shop->cover_url, + ]; + }), + 'page' => $this->page(), + ]; + } + +} \ No newline at end of file diff --git a/modules/Mall/Http/Resources/Api/Shop/ShopEditResource.php b/modules/Mall/Http/Resources/Api/Shop/ShopEditResource.php new file mode 100644 index 0000000..9e6acbf --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Shop/ShopEditResource.php @@ -0,0 +1,35 @@ + $this->id, + 'name' => $this->name, + 'mobile' => $this->mobile, + 'cover' => $this->cover_url, + 'description' => $this->description, + 'province' => [ + 'id' => $this->province_id, + 'name' => $this->province->name, + ], + 'city' => [ + 'id' => $this->city_id, + 'name' => $this->city->name, + ], + 'district' => [ + 'id' => $this->district_id, + 'name' => $this->district->name, + ], + 'address' => $this->address ?? '', + 'created_at' => (string) $this->created_at, + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Shop/ShopResource.php b/modules/Mall/Http/Resources/Api/Shop/ShopResource.php new file mode 100644 index 0000000..a749765 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Shop/ShopResource.php @@ -0,0 +1,35 @@ + $this->id, + 'name' => $this->name, + 'is_self' => (bool) $this->is_self, + 'description' => $this->description, + 'user' => [ + 'user_id' => $this->user->id, + 'nickname' => $this->user->info->nickname, + 'avatar' => $this->user->info->avatar ?? '', + ], + 'mobile' => $this->mobile, + 'cover' => $this->cover_url, + 'goodsCount' => $this->goods()->count(), + 'region' => [ + 'province' => $this->province->name, + 'city' => $this->city->name, + 'district' => $this->district->name, + 'address' => $this->address, + ], + 'created_at' => (string) $this->created_at, + ]; + } + +} diff --git a/modules/Mall/Http/Resources/Api/Video/VideoResource.php b/modules/Mall/Http/Resources/Api/Video/VideoResource.php new file mode 100644 index 0000000..6026180 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Video/VideoResource.php @@ -0,0 +1,20 @@ + $this->id, + 'name' => $this->name, + 'cover' => $this->cover_url, + 'path' => $this->path_url, + ]; + } + +} diff --git a/modules/Mall/Jobs/CloseOrder.php b/modules/Mall/Jobs/CloseOrder.php new file mode 100644 index 0000000..1c67226 --- /dev/null +++ b/modules/Mall/Jobs/CloseOrder.php @@ -0,0 +1,67 @@ +order = $order; + } + + public function handle() + { + $order = $this->order->fresh(); + if ($order->canCancel()) { + $order->cancel(); + foreach ($order->items as $item) { + if ($item->source['deduct_stock_type'] == Goods::DEDUCT_STOCK_ORDER) { + $item->item->addStock($item->qty); //加库存 + } + } + } + } + +} diff --git a/modules/Mall/Listeners/OrderCreatedListener.php b/modules/Mall/Listeners/OrderCreatedListener.php new file mode 100644 index 0000000..df62a84 --- /dev/null +++ b/modules/Mall/Listeners/OrderCreatedListener.php @@ -0,0 +1,24 @@ +order); + } + +} diff --git a/modules/Mall/Listeners/OrderPaidListener.php b/modules/Mall/Listeners/OrderPaidListener.php new file mode 100644 index 0000000..57515a3 --- /dev/null +++ b/modules/Mall/Listeners/OrderPaidListener.php @@ -0,0 +1,42 @@ +order->refresh(); + //增加销量 + foreach ($order->items as $item) { + if ($item->source['deduct_stock_type'] == Goods::DEDUCT_STOCK_PAID) { + $item->item->deductStock($item->qty, $order->user_id); + } + $item->item->addSold($item->qty); //加销量 + } + + //扣除积分 + if ($order->type == Order::TYPE_SCORE) { + $order->user->account->rule('score_buy', -$order->total, false, [ + 'order_no' => $order->order_no, + ]); + } + + } + +} diff --git a/modules/Mall/Listeners/RefundAgreedListener.php b/modules/Mall/Listeners/RefundAgreedListener.php new file mode 100644 index 0000000..8e284db --- /dev/null +++ b/modules/Mall/Listeners/RefundAgreedListener.php @@ -0,0 +1,39 @@ +refund->refresh(); + + foreach ($refund->order->items as $item) { + $item->item->addStock($item->qty); //加库存 + $item->item->deductSold($item->qty); //减销量 + } + } catch (Exception $exception) { + Log::error($exception->getMessage()); + } + } + +} diff --git a/modules/Mall/Listeners/RefundCompletedListener.php b/modules/Mall/Listeners/RefundCompletedListener.php new file mode 100644 index 0000000..e17de0b --- /dev/null +++ b/modules/Mall/Listeners/RefundCompletedListener.php @@ -0,0 +1,29 @@ +refund->refresh(); + $order = $refund->order->refresh(); + + if ($order->refund_items_count == $order->items()->count()) { + $order->completed(); + } + } + +} diff --git a/modules/Mall/Mall.php b/modules/Mall/Mall.php new file mode 100644 index 0000000..ee4065c --- /dev/null +++ b/modules/Mall/Mall.php @@ -0,0 +1,151 @@ + + */ + public static function install() + { + Artisan::call('migrate', [ + '--path' => 'modules/Mall/Database/Migrations', + ]); + + self::createAdminMenu(); + } + + /** + * Notes : 卸载模块的一些操作 + * + * @Date : 2021/3/12 11:35 上午 + * @Author : < Jason.C > + */ + public static function uninstall() + { + $menu = config('admin.database.menu_model'); + + $mains = $menu::where('title', self::$mainTitle)->get(); + + foreach ($mains as $main) { + $main->delete(); + } + } + + protected static function createAdminMenu() + { + $menu = config('admin.database.menu_model'); + + $main = $menu::create([ + 'parent_id' => 0, + 'order' => 20, + 'title' => self::$mainTitle, + 'icon' => 'fa-institution', + ]); + + $main->children()->createMany([ + [ + 'order' => 0, + 'title' => '商城看板', + 'icon' => 'fa-bar-chart', + 'uri' => 'mall/dashboard', + ], [ + 'order' => 0, + 'title' => '活动管理', + 'icon' => 'fa-bar-chart', + 'uri' => 'mall/activities', + ], + // [ + // 'order' => 1, + // 'title' => '店铺管理', + // 'icon' => 'fa-puzzle-piece', + // 'uri' => 'mall/shops', + // ], + // [ + // 'order' => 1, + // 'title' => '职位管理', + // 'icon' => 'fa-joomla', + // 'uri' => 'mall/jobs', + // ], + [ + 'order' => 2, + 'title' => '商品列表', + 'icon' => 'fa-archive', + 'uri' => 'mall/goods', + ], [ + 'order' => 3, + 'title' => '商品分类', + 'icon' => 'fa-bars', + 'uri' => 'mall/categories', + ], + // [ + // 'order' => 4, + // 'title' => '商品标签', + // 'icon' => 'fa-tags', + // 'uri' => 'mall/tags', + // ], + [ + 'order' => 5, + 'title' => '品牌管理', + 'icon' => 'fa-copyright', + 'uri' => 'mall/brands', + ], [ + 'order' => 6, + 'title' => '订单管理', + 'icon' => 'fa-bars', + 'uri' => 'mall/orders', + ], [ + 'order' => 6, + 'title' => '提货订单', + 'icon' => 'fa-bars', + 'uri' => 'mall/stock_orders', + ], [ + 'order' => 7, + 'title' => '退款订单', + 'icon' => 'fa-bars', + 'uri' => 'mall/refunds', + ], [ + 'order' => 8, + 'title' => '物流公司', + 'icon' => 'fa-cab', + 'uri' => 'mall/expresses', + ], [ + 'order' => 9, + 'title' => '运费模板', + 'icon' => 'fa-send', + 'uri' => 'mall/deliveries', + ], [ + 'order' => 10, + 'title' => '轮播图管理', + 'icon' => 'fa-th-large', + 'uri' => 'mall/banners', + ], [ + 'order' => 11, + 'title' => '退货理由', + 'icon' => 'fa-th-large', + 'uri' => 'mall/reasons', + ], [ + 'order' => 12, + 'title' => '收货地址', + 'icon' => 'fa-th-large', + 'uri' => 'mall/addresses', + ], [ + 'order' => 13, + 'title' => '视频管理', + 'icon' => 'fa-th-large', + 'uri' => 'mall/videos', + ], + ] + ); + } + +} diff --git a/modules/Mall/Models/Activity.php b/modules/Mall/Models/Activity.php new file mode 100644 index 0000000..71506eb --- /dev/null +++ b/modules/Mall/Models/Activity.php @@ -0,0 +1,20 @@ + '关闭', + 1 => '正常', + ]; + protected $table = 'mall_activities'; + +} diff --git a/modules/Mall/Models/Address.php b/modules/Mall/Models/Address.php new file mode 100644 index 0000000..3c835b6 --- /dev/null +++ b/modules/Mall/Models/Address.php @@ -0,0 +1,86 @@ + 'boolean', + ]; + + /** + * 不参与版本记录的字段 + * @var array|string[] + */ + protected array $dontVersionable = ['updated_at']; + + /** + * @var array + */ + protected $guarded = [ + 'created_at', + 'updated_at', + ]; + + public static function boot() + { + parent::boot(); + /** + * 如果当前地址为默认地址,取消其他地址的默认地址设置 + */ + self::saved(function ($model) { + if ($model->is_default && $model->id) { + Address::where('id', '<>', $model->id) + ->where('user_id', $model->user_id) + ->where('is_default', 1) + ->update(['is_default' => 0]); + } +// else { +// Address::where('user_id', $model->user_id) +// ->where('is_default', 1) +// ->update(['is_default' => 0]); +// } + }); + } + + /** + * Notes: 保存默认地址时转换格式,非0的都转换成0 + * @Author: + * @Date : 2020/11/5 5:23 下午 + * @param $value + */ + protected function setIsDefaultAttribute($value): void + { + if (strtolower($value) === 'false' || $value === '0') { + $this->attributes['is_default'] = 0; + } else { + $this->attributes['is_default'] = !!$value; + } + } + + /** + * Notes : 将地址设置为默认地址 + * @Date : 2021/4/23 1:34 下午 + * @Author : < Jason.C > + */ + public function setDefault(): void + { + $this->is_default = 1; + $this->save(); + } + +} diff --git a/modules/Mall/Models/Banner.php b/modules/Mall/Models/Banner.php new file mode 100644 index 0000000..89974d5 --- /dev/null +++ b/modules/Mall/Models/Banner.php @@ -0,0 +1,33 @@ + '首页顶部', + self::POSITION_INDEX_CENTER => '体检官介绍', + ]; + + protected $table = 'mall_banners'; + +} diff --git a/modules/Mall/Models/Brand.php b/modules/Mall/Models/Brand.php new file mode 100644 index 0000000..1d420ec --- /dev/null +++ b/modules/Mall/Models/Brand.php @@ -0,0 +1,39 @@ + + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function goods(): HasMany + { + return $this->hasMany(Goods::class); + } + +} \ No newline at end of file diff --git a/modules/Mall/Models/Cart.php b/modules/Mall/Models/Cart.php new file mode 100644 index 0000000..5edc646 --- /dev/null +++ b/modules/Mall/Models/Cart.php @@ -0,0 +1,29 @@ + + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function sku(): BelongsTo + { + return $this->belongsTo(GoodsSku::class, 'sku_id'); + } + +} \ No newline at end of file diff --git a/modules/Mall/Models/Category.php b/modules/Mall/Models/Category.php new file mode 100644 index 0000000..523b4b0 --- /dev/null +++ b/modules/Mall/Models/Category.php @@ -0,0 +1,56 @@ +setTitleColumn('name'); + } + + /** + * Notes : 分类商品 + * @Date : 2021/5/12 9:49 上午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function goods(): HasMany + { + return $this->hasMany(Goods::class); + } + +} diff --git a/modules/Mall/Models/Delivery.php b/modules/Mall/Models/Delivery.php new file mode 100644 index 0000000..3834632 --- /dev/null +++ b/modules/Mall/Models/Delivery.php @@ -0,0 +1,39 @@ + '按数量', + self::TYPE_BY_WEIGHT => '按重量', + ]; + + /** + * Notes : 模板对应的规则 + * @Date : 2021/3/15 1:11 下午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function rules(): HasMany + { + return $this->hasMany(DeliveryRule::class); + } + +} diff --git a/modules/Mall/Models/DeliveryRule.php b/modules/Mall/Models/DeliveryRule.php new file mode 100644 index 0000000..cc85000 --- /dev/null +++ b/modules/Mall/Models/DeliveryRule.php @@ -0,0 +1,31 @@ + 'json', + ]; + + /** + * Notes : 隶属的模板 + * @Date : 2021/3/15 1:12 下午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function delivery(): BelongsTo + { + return $this->belongsTo(Delivery::class); + } + +} \ No newline at end of file diff --git a/modules/Mall/Models/Express.php b/modules/Mall/Models/Express.php new file mode 100644 index 0000000..c264667 --- /dev/null +++ b/modules/Mall/Models/Express.php @@ -0,0 +1,32 @@ + + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function shops(): BelongsToMany + { + return $this->belongsToMany(Shop::class, 'mall_shop_express') + ->withTimestamps(); + } + +} diff --git a/modules/Mall/Models/Goods.php b/modules/Mall/Models/Goods.php new file mode 100644 index 0000000..8b22036 --- /dev/null +++ b/modules/Mall/Models/Goods.php @@ -0,0 +1,267 @@ + '单规格', + // self::TYPE_MULTIPLE => '多规格', + ]; + /** + * 状态 + */ + const STATUS_AUDIT = 0; + const STATUS_UP = 1; + const STATUS_REJECT = 2; + const STATUS_DOWN = 3; + const STATUS_MAP = [ + self::STATUS_AUDIT => '审核中', + self::STATUS_UP => '上架', + self::STATUS_REJECT => '驳回', + self::STATUS_DOWN => '下架', + ]; + /** + * 库存的扣减方式 + */ + const DEDUCT_STOCK_ORDER = 1; + const DEDUCT_STOCK_PAID = 2; + const DEDUCT_STOCK_MAP = [ + self::DEDUCT_STOCK_ORDER => '下单减库存', + self::DEDUCT_STOCK_PAID => '付款减库存', + ]; + /** + * 支付方式 + */ + const PAY_TYPE_ONLINE = 1; + const PAY_TYPE_OFFLINE = 2; + const PAY_TYPE_ALL = 3; + const PAY_TYPE_MAP = [ + self::PAY_TYPE_ONLINE => '在线支付', + self::PAY_TYPE_OFFLINE => '货到付款', + self::PAY_TYPE_ALL => '在线支付/货到付款', + ]; + + /** + * 商品类型 + */ + const CHANNEL_NORMAL = 1; + const CHANNEL_SCORE = 2; + const CHANNEL_FREE = 3; + + const Channels = [ + self::CHANNEL_NORMAL => '正常', + self::CHANNEL_SCORE => '积分兑换', + self::CHANNEL_FREE => '赠送', + ]; + + /** + * 展示列表,从数据库查询的字段 + */ + const LIST_SELECT_FIELDS = [ + 'id', + 'name', + 'description', + 'cover', + 'shop_id', + 'original_price', + 'sales', + 'clicks', + ]; + + protected $table = 'mall_goods'; + + protected $casts = [ + 'pictures' => 'json', + ]; + + protected $with = [ + 'specs', + 'skus', + ]; + + /** + * 不参与版本记录的字段 + * + * @var array|string[] + */ + protected array $dontVersionable = ['clicks', 'updated_at']; + + /** + * Notes : 商品分类 + * + * @Date : 2021/5/12 9:48 上午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function category(): BelongsTo + { + return $this->belongsTo(Category::class); + } + + /** + * Notes : 标签 + * + * @Date : 2021/5/12 10:14 上午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function tags(): BelongsToMany + { + return $this->belongsToMany(Tag::class, 'mall_goods_tag'); + } + + /** + * Notes : 所属品牌 + * + * @Date : 2021/5/11 11:00 上午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function brand(): BelongsTo + { + return $this->belongsTo(Brand::class); + } + + /** + * Notes : 运费模板 + * + * @Date : 2021/5/11 11:04 上午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function delivery(): BelongsTo + { + return $this->belongsTo(Delivery::class); + } + + /** + * Notes : 商品属性 + * + * @Date : 2021/5/12 11:31 上午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function specs(): HasMany + { + return $this->hasMany(GoodsSpec::class); + } + + /** + * Notes : 商品SKU + * + * @Date : 2021/5/11 1:47 下午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function skus(): HasMany + { + return $this->hasMany(GoodsSku::class); + } + + /** + * Notes : 获取规格的价格区间 + * + * @Date : 2021/5/13 11:49 上午 + * @Author : < Jason.C > + * @return string + */ + public function getPriceAttribute(): string + { + if ($this->type === self::TYPE_SINGLE) { + return $this->price_min; + } else { + return $this->price_min.'|'.$this->price_max; + } + } + + /** + * Notes : 返回最低积分 + * + * @Date : 2021/5/20 11:26 上午 + * @Author : < Jason.C > + * @return float + */ + public function getScoreAttribute(): float + { + return $this->skus->min('score') ?? 0; + } + + /** + * Notes : 最低价格 + * + * @Date : 2021/5/13 11:50 上午 + * @Author : < Jason.C > + * @return float + */ + public function getPriceMinAttribute(): float + { + return $this->skus->min('price') ?? 0; + } + + /** + * Notes : 最高价格 + * + * @Date : 2021/5/13 11:51 上午 + * @Author : < Jason.C > + * @return float + */ + public function getPriceMaxAttribute(): float + { + return $this->skus->max('price') ?? 0; + } + + /** + * Notes : 按价格排序 + * + * @Date : 2021/5/17 14:18 + * @Author : Mr.wang + * @param Builder $query + * @param string $direction + * @return Builder + */ + public function scopeOrderByPrice(Builder $query, string $direction = 'asc'): Builder + { + if ($direction) { + $price = DB::raw('MIN(price) as min_price'); + } else { + $price = DB::raw('MAX(price) as min_price'); + } + + $minPrice = DB::table('mall_goods_skus') + ->select('goods_id', $price) + ->whereNull('deleted_at') + ->groupBy('goods_id'); + + return $query->joinSub($minPrice, 'min_price', function ($join) { + $join->on('mall_goods.id', '=', 'min_price.goods_id'); + })->orderBy('min_price', $direction); + } + +} diff --git a/modules/Mall/Models/GoodsSku.php b/modules/Mall/Models/GoodsSku.php new file mode 100644 index 0000000..2732a27 --- /dev/null +++ b/modules/Mall/Models/GoodsSku.php @@ -0,0 +1,62 @@ + + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function goods(): BelongsTo + { + return $this->belongsTo(Goods::class); + } + + /** + * Notes : 当前规格是否可购买 + * + * @Date : 2021/5/14 10:08 + * @Author : Mr.wang + * @param int $qty + * @return bool + */ + public function canBuy(int $qty = 1): bool + { + if ($this->goods->channel == Goods::CHANNEL_FREE) { + return true; + } + + if ($this->goods->status == Goods::STATUS_UP && $this->stock >= $qty) { + return true; + } + + return false; + } + +} diff --git a/modules/Mall/Models/GoodsSpec.php b/modules/Mall/Models/GoodsSpec.php new file mode 100644 index 0000000..7ceba09 --- /dev/null +++ b/modules/Mall/Models/GoodsSpec.php @@ -0,0 +1,36 @@ + + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function goods(): BelongsTo + { + return $this->belongsTo(Goods::class); + } + + /** + * Notes : 属性值 + * @Date : 2021/5/11 4:52 下午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function values(): HasMany + { + return $this->hasMany(GoodsSpecValue::class, 'spec_id'); + } + +} \ No newline at end of file diff --git a/modules/Mall/Models/GoodsSpecValue.php b/modules/Mall/Models/GoodsSpecValue.php new file mode 100644 index 0000000..dddb6c1 --- /dev/null +++ b/modules/Mall/Models/GoodsSpecValue.php @@ -0,0 +1,24 @@ + + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function spec(): BelongsTo + { + return $this->belongsTo(GoodsSpec::class); + } + +} \ No newline at end of file diff --git a/modules/Mall/Models/Job.php b/modules/Mall/Models/Job.php new file mode 100644 index 0000000..e6f34dd --- /dev/null +++ b/modules/Mall/Models/Job.php @@ -0,0 +1,19 @@ + 'json', + ]; + protected $appends = ['total']; + /** + * 不参与版本记录的字段 + * + * @var array|string[] + */ + protected array $dontVersionable = ['updated_at']; + + const STATUS_INIT = 'INIT'; // 订单初始化:用户已下单,未付款 + const STATUS_CANCEL = 'CANCEL'; // 订单取消:用户未支付并取消订单,超时未支付后自动取消订单 + const STATUS_PAID = 'PAID'; // 已支付:用户付款完成,等待发货 + const STATUS_DELIVERED = 'DELIVERED'; // 已发货:卖家已发货 + const STATUS_SIGNED = 'SIGNED'; // 已签收:用户已签收 + const STATUS_COMPLETED = 'COMPLETED'; // 已完成:用户签收N天后,完成订单,不再做任何操作, + + const REFUND_APPLY = 'REFUND_APPLY'; // 申请退款 + const REFUND_AGREE = 'REFUND_AGREE'; // 同意退款 + const REFUND_DELIVER = 'REFUND_DELIVER'; // 等待客户退货 + const REFUND_DELIVERD = 'REFUND_DELIVERD'; // 客户发货 + const REFUND_REFUSE = 'REFUND_REFUSE'; // 拒绝退款 + const REFUND_PROCESS = 'REFUND_PROCESS'; // 退款中 + const REFUND_COMPLETED = 'REFUND_COMPLETED'; // 退款完成 + + const STATUS_MAP = [ + self::STATUS_INIT => '未付款', + self::STATUS_CANCEL => '已取消', + self::STATUS_PAID => '待发货', + self::STATUS_DELIVERED => '已发货', + self::STATUS_SIGNED => '已签收', + self::STATUS_COMPLETED => '完成', + self::REFUND_APPLY => '申请退款', + self::REFUND_AGREE => '同意退款', + self::REFUND_REFUSE => '拒绝退款', + self::REFUND_DELIVER => '等待退货', + self::REFUND_DELIVERD => '客户退货', + self::REFUND_PROCESS => '退款中', + self::REFUND_COMPLETED => '退款完成', + ]; + + const CANCEL_BY_USER = 2; // 买家取消 + const CANCEL_BY_SELLER = 3; // 卖家取消 + const CANCEL_BY_SYSTEM = 4; // 系统取消 + + const TYPE_NORMAL = 1; // 正常 + const TYPE_SAMPLE = 2; // 提货 + const TYPE_SCORE = 3; // 积分兑换 + + const TYPE_MAP = [ + self::TYPE_NORMAL => '正常', + self::TYPE_SAMPLE => '提货', + self::TYPE_SCORE => '积分兑换', + ]; + + const CHANNEL_USER = 1; + const CHANNEL_SYSTEM = 2; + + const CHANNEL_MAP = [ + self::CHANNEL_USER => '会员', + self::CHANNEL_SYSTEM => '公司', + ]; + + public static function boot() + { + parent::boot(); + + self::creating(function ($model) { + $time = explode(' ', microtime()); + + $counter = $model->whereDate('created_at', Carbon::today())->count() + 1; + + $len = config('mall.order_no_counter_length'); + + $len = $len < 6 ? 6 : $len; + $len = $len > 16 ? 16 : $len; + + $model->order_no = date('YmdHis'). + sprintf('%06d', $time[0] * 1e6). + sprintf('%0'.$len.'d', $counter); + + $model->state = self::STATUS_INIT; + $model->expired_at = Carbon::now()->addMinutes(10); + }); + } + + /** + * 获取该模型的路由的自定义键名 + * + * @return string + */ + public function getRouteKeyName(): string + { + return 'order_no'; + } + + /** + * Notes : 获取订单总额 + * + * @Date : 2021/3/16 1:48 下午 + * @Author : < Jason.C > + * @return float + */ + public function getTotalAttribute(): float + { + if ($this->original) { + return sprintf("%.2f", $this->original['amount'] + $this->original['freight']); + } + + return sprintf("%.2f", $this->amount + $this->freight); + } + + /** + * Notes : 订单状态文本 + * + * @Date : 2021/5/10 3:24 下午 + * @Author : < Jason.C > + * @return string + */ + public function getStateTextAttribute(): string + { + return self::STATUS_MAP[$this->state]; + } + + /** + * Notes: 订单类型 + * + * @Author: 玄尘 + * @Date: 2022/10/13 16:53 + * @return string + */ + public function getTypeTextAttribute(): string + { + return self::TYPE_MAP[$this->type]; + + } + + /** + * Notes : 订单发货记录 + * + * @Date : 2021/5/11 9:47 上午 + * @Author : < Jason.C > + * @return HasOne + */ + public function express(): HasOne + { + return $this->hasOne(OrderExpress::class); + } + + /** + * Notes : 订单条目 + * + * @Date : 2021/5/11 9:47 上午 + * @Author : < Jason.C > + * @return HasMany + */ + public function items(): HasMany + { + return $this->hasMany(OrderItem::class); + } + + /** + * Notes: 获取商品 + * + * @Author: 玄尘 + * @Date : 2021/10/27 15:01 + * @return \Illuminate\Database\Eloquent\HigherOrderBuilderProxy|mixed + */ + public function getGoodsAttribute() + { + return $this->items()->first()->source; + } + + /** + * Notes : 退款单 + * + * @Date : 2021/5/11 9:48 上午 + * @Author : < Jason.C > + * @return HasMany + */ + public function refunds(): HasMany + { + return $this->hasMany(Refund::class); + } + + /** + * Notes: 获取退款商品数 + * + * @Author: 玄尘 + * @Date : 2021/5/19 9:50 + * @return int + */ + public function getRefundItemsCountAttribute(): int + { + $order_id = $this->id; + + return RefundItem::query() + ->whereHas('order_item', function ($query) use ($order_id) { + $query->where('order_id', $order_id); + }) + ->whereHas('refund', function ($query) { + $query->whereNotIN('state', [Refund::REFUND_APPLY]); + }) + ->count(); + } + + /** + * Notes : 工作流获取状态 + * + * @Date : 2021/5/8 3:52 下午 + * @Author : < Jason.C > + * @return string + */ + public function getCurrentState(): string + { + return $this->state; + } + + /** + * Notes : 工作流设置状态 + * + * @Date : 2021/5/8 4:01 下午 + * @Author : < Jason.C > + * @param $state + */ + public function setCurrentState($state) + { + $this->state = $state; + $this->save(); + } + + /** + * Notes : 当前订单能不能查看物流 + * + * @Date : 2021/5/19 11:55 + * @Author : Mr.wang + * @return bool + */ + public function getIsLogisticShowAttribute(): bool + { + return in_array($this->state, [ + self::STATUS_DELIVERED, + self::STATUS_SIGNED, + self::REFUND_COMPLETED, + ]); + } + + public function getCanAction(): array + { + return [ + 'pay' => $this->canPay(), + 'cancel' => $this->canCancel(), + 'sign' => $this->canSign(), + 'refund' => $this->canRefund(), + ]; + } + +} diff --git a/modules/Mall/Models/OrderExpress.php b/modules/Mall/Models/OrderExpress.php new file mode 100644 index 0000000..a64ba10 --- /dev/null +++ b/modules/Mall/Models/OrderExpress.php @@ -0,0 +1,48 @@ + '快递', + self::TYPE_LOGISTICS => '物流', + ]; + + /** + * Notes : 订单物流信息 + * + * @Date : 2021/5/25 12:04 下午 + * @Author : < Jason.C > + * @return BelongsTo + */ + public function express(): BelongsTo + { + return $this->belongsTo(Express::class); + } + + public function getTypeTextAttribute(): string + { + return self::TYPE_MAP[$this->type]; + } + +} diff --git a/modules/Mall/Models/OrderItem.php b/modules/Mall/Models/OrderItem.php new file mode 100644 index 0000000..02fb015 --- /dev/null +++ b/modules/Mall/Models/OrderItem.php @@ -0,0 +1,121 @@ + 'json', + ]; + + /** + * Notes: 关联商品 + * @Author: 玄尘 + * @Date : 2021/8/11 9:26 + * @return \Illuminate\Database\Eloquent\Relations\MorphTo + */ + public function item(): morphTo + { + return $this->morphTo(); + } + + /** + * Notes: 关联退款单 + * @Author: 玄尘 + * @Date : 2021/5/19 9:30 + */ + public function refund_items(): HasMany + { + return $this->hasMany(RefundItem::class, 'order_item_id'); + } + + /** + * Notes: 商品状态 + * @Author: 玄尘 + * @Date : 2021/6/17 9:07 + */ + public function getRefundStatusTextAttribute() + { + if ($this->isRefund()) { + $refund_item = $this->refund_items()->latest()->first(); + if ($refund_item) { + return $refund_item->refund->status_test; + } else { + return '未知状态'; + } + } else { + return '正常'; + } + + } + + /** + * Notes: 是否申请退款 + * @Author: 玄尘 + * @Date : 2021/5/21 11:15 + */ + public function isRefund(): int + { + return $this->refund_items()->count() > 0; + } + + /** + * Notes: 是否可以退款/货 + * @Author: 玄尘 + * @Date : 2021/5/19 9:32 + */ + public function canRefund(): bool + { + $can = true; + + //是否已申请退款/货 + if ($this->isRefund()) { + //最后一个申请 + $refundItem = $this->refund_items()->latest()->first(); + if ($refundItem && $refundItem->refund->state != Refund::REFUND_REFUSE) { + $can = false; + } + } + + $is_post_sale = $this->source['is_post_sale'] ?? 0; + + return $is_post_sale && $can && $this->order->can('refund'); + } + + /** + * Notes: 获取总额 + * @Author: 玄尘 + * @Date : 2021/5/20 13:13 + * @return float + */ + public function getTotalAttribute(): float + { + return sprintf("%.2f", $this->price * $this->qty); + } + + /** + * Notes: 获取水晶总数 + * @Author: 玄尘 + * @Date : 2021/5/21 11:26 + */ + public function getTotalScoreAttribute(): string + { + $score = $this->source['score'] ?? 0; + + return sprintf("%.2f", $score * $this->qty); + + } + +} diff --git a/modules/Mall/Models/Reason.php b/modules/Mall/Models/Reason.php new file mode 100644 index 0000000..7c20280 --- /dev/null +++ b/modules/Mall/Models/Reason.php @@ -0,0 +1,30 @@ +belongsToMany(Shop::class, 'mall_shop_reasons') + ->withTimestamps(); + } + +} diff --git a/modules/Mall/Models/Refund.php b/modules/Mall/Models/Refund.php new file mode 100644 index 0000000..716cb15 --- /dev/null +++ b/modules/Mall/Models/Refund.php @@ -0,0 +1,216 @@ + '申请退款', + self::REFUND_AGREE => '同意退款', + self::REFUND_REFUSE => '拒绝退款', + self::REFUND_DELIVER => '等待客户退货', + self::REFUND_DELIVERED => '客户退货,等待签收', + self::REFUND_SIGNED => '已签收', + self::REFUND_PROCESS => '退款中', + self::REFUND_COMPLETED => '退款完成', + ]; + + /** + * @var array + */ + protected $dates = [ + 'refunded_at', + ]; + + public static function boot() + { + parent::boot(); + + self::creating(function ($model) { + $model->state = self::REFUND_APPLY; + + $time = explode(' ', microtime()); + + $counter = $model->whereDate('created_at', Carbon::today())->count() + 1; + + $len = config('mall.refund_no_counter_length'); + $prefix = config('refund_no_counter_prefix'); + + $len = $len < 6 ? 6 : $len; + $len = $len > 16 ? 16 : $len; + + $model->refund_no = $prefix . date('YmdHis') . + sprintf('%06d', $time[0] * 1e6) . + sprintf('%0' . $len . 'd', $counter); + }); + } + + /** + * 获取该模型的路由的自定义键名 + * @return string + */ + public function getRouteKeyName(): string + { + return 'refund_no'; + } + + /** + * Notes: 退款单详情 + * @Author: + * @Date : 2019/11/22 4:25 下午 + * @return HasMany + */ + public function items(): HasMany + { + return $this->hasMany(RefundItem::class); + } + + /** + * Notes: 退款单物流 + * @Author: + * @Date : 2019/11/22 4:25 下午 + * @return HasOne + */ + public function express(): HasOne + { + return $this->hasOne(RefundExpress::class); + } + + /*** + * Notes: 获取状态 + * @Author: 玄尘 + * @Date : 2021/5/18 11:50 + */ + public function getStatusTextAttribute(): string + { + return self::STATUS_MAP[$this->state] ?? '---'; + } + + /** + * Notes: 获取退款状态 + * @Author: + * @Date : 2019/11/22 4:25 下午 + * @return string + */ + protected function getStateTextAttribute(): string + { + switch ($this->state) { + case self::REFUND_APPLY: + $state = '退款申请中'; + break; + case self::REFUND_AGREE: + $state = '同意退款'; + break; + case self::REFUND_PROCESS: + $state = '退款中,请等待商家打款'; + break; + case self::REFUND_COMPLETED: + $state = '退款完毕'; + break; + case self::REFUND_REFUSE: + $state = '拒绝退款'; + break; + case self::REFUND_DELIVER: + $state = '您的申请已经通过,请将商品退回给商家'; + break; + case self::REFUND_DELIVERED: + $state = '客户退货,等待签收'; + break; + case self::REFUND_SIGNED: + $state = '店家已签收,等待打款'; + break; + default: + $state = '未知状态'; + break; + } + + return $state; + } + + /** + * Notes : 关联店铺 + * @Date : 2021/5/7 1:46 下午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function shop(): BelongsTo + { + return $this->belongsTo(Shop::class); + } + + /** + * Notes: 关联日志 + * @Author: 玄尘 + * @Date : 2021/5/17 11:38 + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function logs(): HasMany + { + return $this->hasMany(RefundLog::class); + } + + /** + * Notes : 工作流获取状态 + * @Date : 2021/5/8 3:52 下午 + * @Author : < Jason.C > + * @return string + */ + public function getCurrentState(): string + { + return $this->state; + } + + /** + * Notes : 工作流设置状态 + * @Date : 2021/5/8 4:01 下午 + * @Author : < Jason.C > + * @param $state + */ + public function setCurrentState($state) + { + $this->state = $state; + $this->save(); + } + +} diff --git a/modules/Mall/Models/RefundExpress.php b/modules/Mall/Models/RefundExpress.php new file mode 100644 index 0000000..24c4901 --- /dev/null +++ b/modules/Mall/Models/RefundExpress.php @@ -0,0 +1,22 @@ + 'json', + ]; + + /** + * Notes: 关联order_item + * @Author: 玄尘 + * @Date : 2021/5/19 9:50 + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function order_item(): BelongsTo + { + return $this->belongsTo(OrderItem::class); + } + +} diff --git a/modules/Mall/Models/RefundLog.php b/modules/Mall/Models/RefundLog.php new file mode 100644 index 0000000..7966280 --- /dev/null +++ b/modules/Mall/Models/RefundLog.php @@ -0,0 +1,40 @@ +morphTo(); + } + + /** + * Notes: 状态 + * @Author: 玄尘 + * @Date : 2021/5/18 11:39 + * @return string + */ + public function getStateTextAttribute(): string + { + return Refund::STATUS_MAP[$this->state] ?? '---'; + } + +} diff --git a/modules/Mall/Models/Region.php b/modules/Mall/Models/Region.php new file mode 100644 index 0000000..ad4bc1d --- /dev/null +++ b/modules/Mall/Models/Region.php @@ -0,0 +1,63 @@ + + * @Date : 2019/11/19 3:01 下午 + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function parent(): BelongsTo + { + return $this->belongsTo(__CLASS__, 'parent_id'); + } + + /** + * Notes: 下级地区 + * @Author: + * @Date : 2019/11/19 3:01 下午 + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function children(): HasMany + { + return $this->hasMany(__CLASS__, 'parent_id'); + } + + /** + * Notes : 获取区划层级名称 + * @Date : 2021/5/6 1:47 下午 + * @Author : < Jason.C > + * @return string + */ + public function getDepthNameAttribute(): string + { + switch ($this->depth) { + case 0: + return '国家级'; + case 1: + return '省级'; + case 2: + return '市级'; + case 3: + return '区县'; + default: + return ''; + } + } + +} \ No newline at end of file diff --git a/modules/Mall/Models/Shop.php b/modules/Mall/Models/Shop.php new file mode 100644 index 0000000..af528da --- /dev/null +++ b/modules/Mall/Models/Shop.php @@ -0,0 +1,187 @@ + '待审核', + self::STATUS_NORMAL => '正常', + self::STATUS_REJECT => '驳回申请', + self::STATUS_CLOSE => '关闭', + ]; + + const LABEL_MAP = [ + self::STATUS_APPLYING => 'default', + self::STATUS_NORMAL => 'success', + self::STATUS_REJECT => 'warning', + self::STATUS_CLOSE => 'danger', + ]; + + protected $table = 'mall_shops'; + + protected $casts = [ + 'pictures' => 'json', + 'configs' => 'json', + ]; + + /** + * 不参与版本记录的字段 + * @var array|string[] + */ + protected array $dontVersionable = ['updated_at']; + + /** + * Notes : 店铺支持的物流 + * @Date : 2021/5/6 5:20 下午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function expresses(): BelongsToMany + { + return $this->belongsToMany(Express::class, 'mall_shop_express') + ->withTimestamps(); + } + + /** + * Notes : 店铺的商品 + * @Date : 2021/5/6 5:32 下午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function goods(): HasMany + { + return $this->hasMany(Goods::class); + } + + /** + * Notes : 分类 + * @Date : 2021/5/6 5:32 下午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function categories(): HasMany + { + return $this->hasMany(Category::class); + } + + /** + * Notes : 店铺设置的标签 + * @Date : 2021/5/13 10:34 上午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function tags(): HasMany + { + return $this->hasMany(Tag::class); + } + + /** + * Notes : 店铺的品牌 + * @Date : 2021/5/13 10:35 上午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function brands(): HasMany + { + return $this->hasMany(Brand::class); + } + + /** + * Notes : 轮播图 + * @Date : 2021/5/13 11:34 上午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function banners(): HasMany + { + return $this->hasMany(Banner::class); + } + + /** + * Notes : 订单 + * @Date : 2021/5/6 5:33 下午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function orders(): HasMany + { + return $this->hasMany(Order::class); + } + + /** + * Notes : 退款订单 + * @Date : 2021/5/7 9:16 上午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function refunds(): HasMany + { + return $this->hasMany(Refund::class); + } + + /** + * Notes : 获取状态文本 + * @Date : 2021/5/7 10:57 上午 + * @Author : < Jason.C > + * @return string + */ + public function getStatusTextAttribute(): string + { + return self::STATUS_MAP[$this->status] ?? '未知状态'; + } + + /** + * Notes: 退货理由 + * @Author: 玄尘 + * @Date : 2021/5/18 15:31 + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function reasons(): BelongsToMany + { + return $this->belongsToMany(Reason::class, 'mall_shop_reasons') + ->withTimestamps(); + } + + /** + * Notes : 店铺的员工 + * @Date : 2021/7/1 4:25 下午 + * @Author : < Jason.C > + */ + public function staffers(): BelongsToMany + { + return $this->belongsToMany(User::class, 'mall_shop_staffers') + ->using(ShopStaffer::class) + ->withPivot('job_id') + ->withTimestamps(); + } + +} diff --git a/modules/Mall/Models/ShopStaffer.php b/modules/Mall/Models/ShopStaffer.php new file mode 100644 index 0000000..0fcb9b0 --- /dev/null +++ b/modules/Mall/Models/ShopStaffer.php @@ -0,0 +1,31 @@ + + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function job(): BelongsTo + { + return $this->belongsTo(Job::class); + } + +} diff --git a/modules/Mall/Models/Tag.php b/modules/Mall/Models/Tag.php new file mode 100644 index 0000000..81a95f2 --- /dev/null +++ b/modules/Mall/Models/Tag.php @@ -0,0 +1,29 @@ + + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function goods(): BelongsToMany + { + return $this->belongsToMany(Goods::class, 'mall_goods_tag')->withTimestamps(); + } + +} \ No newline at end of file diff --git a/modules/Mall/Models/Traits/BelongsToOrder.php b/modules/Mall/Models/Traits/BelongsToOrder.php new file mode 100644 index 0000000..744d7ff --- /dev/null +++ b/modules/Mall/Models/Traits/BelongsToOrder.php @@ -0,0 +1,22 @@ + + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function order(): BelongsTo + { + return $this->belongsTo(Order::class); + } + +} \ No newline at end of file diff --git a/modules/Mall/Models/Traits/BelongsToRefund.php b/modules/Mall/Models/Traits/BelongsToRefund.php new file mode 100644 index 0000000..0e3c7ca --- /dev/null +++ b/modules/Mall/Models/Traits/BelongsToRefund.php @@ -0,0 +1,22 @@ +belongsTo(Refund::class); + } + +} \ No newline at end of file diff --git a/modules/Mall/Models/Traits/BelongsToShop.php b/modules/Mall/Models/Traits/BelongsToShop.php new file mode 100644 index 0000000..cd26f86 --- /dev/null +++ b/modules/Mall/Models/Traits/BelongsToShop.php @@ -0,0 +1,49 @@ + + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function shop(): BelongsTo + { + return $this->belongsTo(Shop::class); + } + + /** + * Notes : 作用域查询 + * @Date : 2021/6/21 8:57 上午 + * @Author : < Jason.C > + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Modules\Mall\Models\Shop $shop + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeByShop(Builder $query, Shop $shop): Builder + { + return $query->where('shop_id', $shop->getKey()); + } + + /** + * Notes : 店铺ID 作用域查询 + * @Date : 2021/6/21 9:04 上午 + * @Author : < Jason.C > + * @param \Illuminate\Database\Eloquent\Builder $query + * @param int $shopId + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeByShopId(Builder $query, int $shopId): Builder + { + return $query->where('shop_id', $shopId); + } + +} \ No newline at end of file diff --git a/modules/Mall/Models/Traits/GoodsAttribute.php b/modules/Mall/Models/Traits/GoodsAttribute.php new file mode 100644 index 0000000..886287c --- /dev/null +++ b/modules/Mall/Models/Traits/GoodsAttribute.php @@ -0,0 +1,211 @@ +goods->type == Goods::TYPE_SINGLE) { + + } + return $this->stock ?? 0; + } + + /** + * Notes: 扣除库存方法 + * + * @Author: 玄尘 + * @Date : 2021/5/14 11:12 + * @param $stock + * @param $user_id + * @return bool + */ + public function deductStock($stock, $user_id): bool + { + if ($this->goods->channel == Goods::CHANNEL_FREE) { + $user = User::find($user_id); + $user->addHold($stock); + } else { + $this->decrement('stock', $stock); + } + return true; + } + + /** + * Notes: 增加库存方法 + * + * @Author: 玄尘 + * @Date : 2021/5/14 11:12 + * @param $stock + */ + public function addStock($stock) + { + $this->increment('stock', $stock); + } + + /** + * Notes: 增加销量 + * + * @Author: 玄尘 + * @Date : 2021/5/14 11:12 + * @param $sold + */ + public function addSold($sold) + { + $this->goods()->increment('sales', $sold); + } + + /** + * Notes: 减少销量 + * + * @Author: 玄尘 + * @Date : 2021/5/14 11:12 + * @param $sold + */ + public function deductSold($sold) + { + $this->goods()->decrement('sales', $sold); + } + + /** + * Notes: 获取卖家ID + * + * @Author: 玄尘 + * @Date : 2021/5/14 11:14 + * @return int + */ + public function getShopIdAttribute(): int + { + return $this->goods->shop_id ?? 0; + } + + /** + * Notes: 获取卖家type + * + * @Author: 玄尘 + * @Date : 2021/5/14 11:14 + * @return string|null + */ + public function getShopTypeAttribute(): ?string + { + if ($this->goods && $this->goods->shop) { + return get_class($this->goods->shop); + } + + return null; + } + + /** + * Notes: 获取卖家名称 + * + * @Author: 玄尘 + * @Date : 2021/5/14 11:14 + * @return false|string|null + */ + public function getShopNameAttribute() + { + if ($this->goods && $this->goods->shop) { + return $this->goods->shop->name; + } + + return null; + } + + /** + * Notes: 重量 + * + * @Author: 玄尘 + * @Date : 2021/5/14 11:15 + * @return int + */ + public function getGoodsWeight(): int + { + return $this->weight ?? 0; + } + + /** + * Notes: 返回source + * + * @Author: 玄尘 + * @Date : 2021/5/14 11:15 + * @return array + */ + public function getSource(): array + { + return [ + 'goods_sku_id' => $this->id, + 'goods_id' => $this->goods_id, + 'deduct_stock_type' => $this->goods->deduct_stock_type, + 'goods_name' => $this->getGoodsName(), + // 'unit' => $this->getItemValue(), + 'cover' => $this->goods->cover_url, + 'price' => $this->getItemPrice(), + 'stock' => $this->getGoodsStock(), + 'weight' => $this->weight, + 'is_post_sale' => $this->goods->is_post_sale ?? 0, + 'score' => $this->score, // 积分/原石 + 'assets' => $this->assets, // 资产 + ]; + } + + /** + * Notes: 获取商品名称 + * + * @Author: 玄尘 + * @Date : 2021/5/14 11:19 + * @return string + */ + public function getGoodsName(): string + { + return $this->goods->name ?? '--'; + } + + /** + * Notes: 获取商品规格名称 + * + * @Author: 玄尘 + * @Date : 2021/5/14 11:16 + * @return string + */ + public function getItemValue(): string + { + switch ($this->goods->type) { + case Goods::TYPE_SINGLE: + return ''; + case Goods::TYPE_MULTIPLE: + return $this->unit ?? ''; + } + } + + /** + * Notes: 获取商品单价 + * + * @Author: 玄尘 + * @Date : 2021/5/14 11:16 + * @return mixed + */ + public function getItemPrice() + { + $tags = $this->goods->tags()->pluck('tag_id')->toArray(); + //1 为试用产品 + if (in_array(1, $tags)) { + return 0; + } + + return $this->price; + } + +} diff --git a/modules/Mall/Models/Traits/HasRegion.php b/modules/Mall/Models/Traits/HasRegion.php new file mode 100644 index 0000000..f520465 --- /dev/null +++ b/modules/Mall/Models/Traits/HasRegion.php @@ -0,0 +1,60 @@ + + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function province(): BelongsTo + { + return $this->belongsTo(Region::class, 'province_id'); + } + + /** + * Notes : 市 + * @Date : 2021/5/11 9:50 上午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function city(): BelongsTo + { + return $this->belongsTo(Region::class, 'city_id'); + } + + /** + * Notes : 区 + * @Date : 2021/5/11 9:50 上午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function district(): BelongsTo + { + return $this->belongsTo(Region::class, 'district_id'); + } + + /** + * Notes : 获取完整地址 + * @Date : 2021/5/11 9:49 上午 + * @Author : < Jason.C > + * @param string $separators 地址的分隔符 + * @return string + */ + public function getFullAddress(string $separators = ' '): string + { + return + $this->province->name . $separators . + $this->city->name . $separators . + $this->district->name . $separators . + $this->address; + } + +} diff --git a/modules/Mall/Models/Traits/OrderActions.php b/modules/Mall/Models/Traits/OrderActions.php new file mode 100644 index 0000000..2d42d17 --- /dev/null +++ b/modules/Mall/Models/Traits/OrderActions.php @@ -0,0 +1,188 @@ + + * @throws \Exception + */ + public function cancel(): bool + { + if ($this->canCancel()) { + // 修改状态机 + $this->apply('cancel'); + + event(new OrderCanceled($this)); + + return true; + } else { + throw new \Exception('订单状态不可取消'); + } + } + + /** + * 订单支付 + * + * @throws \Exception + */ + public function pay(): bool + { + if ($this->can('pay')) { + // 修改状态机 + $this->paid_at = Carbon::now(); + $this->apply('pay'); + + event(new OrderPaid($this)); + + //增加记录 + $this->addTimeline(); + + return true; + } else { + throw new \Exception('订单状态不可支付'); + } + } + + /** + * Notes : 订单发货 + * + * @Date : 2021/5/14 16:45 + * @Author : Mr.wang + * @param int $expressId 物流公司ID + * @param string $expressNo 物流单号 + * @return bool + * @throws \Exception + */ + public function deliver($expressId, $expressNo, $type = 1, $person = ''): bool + { + if ($this->can('deliver')) { + if ($this->refund_items_count == $this->items()->count()) { + throw new \Exception('商品已全部退款/货不能发货'); + } + + DB::transaction(function () use ($expressId, $expressNo, $type, $person) { + $this->express()->update([ + 'express_id' => $expressId ?? null, + 'express_no' => $expressNo ?? null, + 'type' => $type, + 'person' => $person, + 'deliver_at' => now(), + ]); + // 修改状态机 + $this->apply('deliver'); + + event(new OrderDelivered($this)); + + if ($this->type == Order::TYPE_SAMPLE) { + $this->user->notify(new SystemOrderDelivered('发货提醒', $this)); + } + + }); + + return true; + } else { + throw new \Exception('订单状态不可发货'); + } + } + + /** + * Notes : 订单签收 + * + * @Date : 2021/5/14 17:09 + * @Author : Mr.wang + * @return bool + * @throws \Exception + */ + public function sign(): bool + { + if ($this->can('sign')) { + DB::transaction(function () { + $this->express()->update([ + 'receive_at' => now(), + ]); + // 修改状态机 + $this->apply('sign'); + }); + event(new OrderSigned($this)); + + return true; + } else { + throw new \Exception('订单状态不可签收'); + } + } + + /** + * Notes : 订单完成 + * + * @Date : 2021/5/21 2:57 下午 + * @Author : < Jason.C > + * @return bool + * @throws \Exception + */ + public function complete(): bool + { + if ($this->can('complete')) { + // 修改状态机 + $this->apply('complete'); + + event(new OrderCompleted($this)); + + return true; + } else { + throw new \Exception('订单状态不可完成'); + } + } + + /** + * Notes: 设置退款 + * + * @Author: 玄尘 + * @Date : 2021/6/9 9:53 + */ + public function refund() + { + if ($this->can('refund')) { + // 修改状态机 + $this->apply('refund'); + + return true; + } else { + throw new \Exception('订单状态不可设置退款'); + } + } + + /** + * Notes: 退款完成 + * + * @Author: 玄尘 + * @Date : 2021/6/9 12:00 + */ + public function completed() + { + if ($this->can('completed')) { + // 修改状态机 + $this->apply('completed'); + + return true; + } else { + throw new \Exception('订单状态不可设置退款完成'); + } + } + +} diff --git a/modules/Mall/Models/Traits/OrderCando.php b/modules/Mall/Models/Traits/OrderCando.php new file mode 100644 index 0000000..9ba04e3 --- /dev/null +++ b/modules/Mall/Models/Traits/OrderCando.php @@ -0,0 +1,82 @@ + + * @Date : 2019/11/20 3:39 下午 + * @return bool + */ + public function canPay(): bool + { + return $this->can('pay'); + } + + /** + * Notes: 是否可取消 + * + * @Author: + * @Date : 2019/11/20 3:39 下午 + * @return bool + */ + public function canCancel(): bool + { + return in_array($this->state, [Order::STATUS_INIT,]); + } + + /** + * 可发货 + * + * @Author: + * @Date :2018-10-22T17:12:13+0800 + * @return boolean + */ + public function canDeliver(): bool + { + return $this->can('deliver'); + } + + /** + * 可签收 + * + * @Author: + * @Date :2018-10-22T17:12:43+0800 + * @return boolean + */ + public function canSign(): bool + { + return $this->can('sign'); + } + + /** + * 可完成订单 + * + * @Author: + * @Date :2018-10-25T17:35:12+0800 + * @return boolean + */ + public function canComplete(): bool + { + return $this->can('complete'); + } + + /** + * 可申请退款 + * + * @Author: + * @Date :2018-10-22T17:11:45+0800 + * @return boolean + */ + public function canRefund(): bool + { + return $this->can('refund'); + } + +} diff --git a/modules/Mall/Models/Traits/OrderScopes.php b/modules/Mall/Models/Traits/OrderScopes.php new file mode 100644 index 0000000..e677261 --- /dev/null +++ b/modules/Mall/Models/Traits/OrderScopes.php @@ -0,0 +1,138 @@ + + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeUnPay(Builder $query): Builder + { + return $query->where('state', self::STATUS_INIT); + } + + /** + * Notes : 已支付,待发货 + * + * @Date : 2021/5/17 11:00 上午 + * @Author : < Jason.C > + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopePaid(Builder $query): Builder + { + return $query->where('state', self::STATUS_PAID); + } + + /** + * Notes : 已发货,代签收 + * + * @Date : 2021/5/17 11:03 上午 + * @Author : < Jason.C > + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeDelivered(Builder $query): Builder + { + return $query->where('state', self::STATUS_DELIVERED); + } + + /** + * Notes : 已签收 + * + * @Date : 2021/5/17 11:04 上午 + * @Author : < Jason.C > + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeSigned(Builder $query): Builder + { + return $query->where('state', self::STATUS_SIGNED); + } + + /** + * Notes : 已完成的订单 + * + * @Date : 2021/5/17 11:04 上午 + * @Author : < Jason.C > + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeCompleted(Builder $query): Builder + { + return $query->where('state', self::STATUS_COMPLETED); + } + + /** + * Notes : 订单列表排除退款部分 + * + * @Date : 2021/5/18 9:50 + * @Author : Mr.wang + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeCommon(Builder $query): Builder + { + return $query->whereIn('state', [ + self::STATUS_INIT, + self::STATUS_CANCEL, + self::STATUS_PAID, + self::STATUS_DELIVERED, + self::STATUS_SIGNED, + self::STATUS_COMPLETED, + ]); + } + + /** + * Notes: 签收和发货 + * + * @Author: 玄尘 + * @Date: 2022/8/30 8:33 + * @param Builder $query + * @return Builder + */ + public function scopeDeliveredAndSigned(Builder $query): Builder + { + return $query->whereIn('state', [ + self::STATUS_DELIVERED, + self::STATUS_SIGNED, + ]); + } + + /** + * Notes: 积分兑换订单 + * + * @Author: 玄尘 + * @Date: 2022/9/19 14:40 + * @param Builder $query + * @return Builder + */ + public function scopeTypeScore(Builder $query): Builder + { + return $query->where('type', Order::TYPE_SCORE); + } + + /** + * Notes: 提货订单 + * + * @Author: 玄尘 + * @Date: 2022/9/19 14:40 + * @param Builder $query + * @return Builder + */ + public function scopeTypeSample(Builder $query): Builder + { + return $query->where('type', Order::TYPE_SAMPLE); + } + +} diff --git a/modules/Mall/Models/Traits/RefundActions.php b/modules/Mall/Models/Traits/RefundActions.php new file mode 100644 index 0000000..a243db7 --- /dev/null +++ b/modules/Mall/Models/Traits/RefundActions.php @@ -0,0 +1,318 @@ +operator = $operator; + + return $this; + } + + /** + * Notes: 设置备注 + * + * @Author: 玄尘 + * @Date : 2021/5/17 16:12 + * @param $remark + * @return $this + */ + public function setRemark($remark) + { + $this->remark = $remark; + + return $this; + } + + /** + * Notes: 同意退款 + * + * @Author: 玄尘 + * @Date : 2021/5/17 11:59 + * @param $remark + * @return bool + * @throws \Exception + */ + public function agree($remark = null): bool + { + if (! $this->can('agree')) { + throw new Exception("退款单状态不可以同意退款"); + } + + DB::transaction(function () use ($remark) { + $this->actual_total = $this->refund_total; + $this->save(); + + $this->apply('agree'); + $this->setOrderApply($this->order, 'agree'); + + if ($this->order->express) { + $this->express()->create(); + } + + $this->setLogs($remark); + + //订单已经进入发货流程,需要客户退货 + if ($this->order->express && $this->order->express->deliver_at) { + $this->apply('deliver'); + $this->setOrderApply($this->order, 'user_deliver'); + } else { + //未发货设置为退款中 + $this->apply('process'); + $this->setOrderApply($this->order, 'process'); + } + + event(new RefundAgreed($this)); + }); + + return true; + } + + /** + * Notes: 拒绝退款 + * + * @Author: 玄尘 + * @Date : 2021/5/17 12:01 + * @param string|null $remark + * @return bool + * @throws \Exception + */ + public function refuse(string $remark = null): bool + { + if (! $this->can('refuse')) { + throw new Exception("退款单状态不可以拒绝退款"); + } + + DB::transaction(function () use ($remark) { + + $this->apply('refuse'); + $this->setOrderApply($this->order, 'refuse'); + + $this->setLogs($remark); + + event(new RefundRefused($this)); + }); + + return true; + } + + /** + * Notes: 退货退款中 + * + * @Author: 玄尘 + * @Date : 2021/5/17 14:29 + * @param $company + * @param string $number + * @return bool + * @throws \Exception + */ + public function deliver($company, string $number): bool + { + if ($this->can('delivered')) { + DB::transaction(function () use ($company, $number) { + $this->express()->update([ + 'company' => $company, + 'number' => $number, + 'deliver_at' => now(), + ]); + + // 修改状态机 + $this->apply('delivered'); + $this->setOrderApply($this->order, 'user_deliverd'); + + $this->setLogs('客户退货'); + + }); + + return true; + } else { + throw new Exception('订单状态不可发货'); + } + + } + + /** + * Notes: 确认收货 + * + * @Author: 玄尘 + * @Date : 2021/5/17 14:30 + * @return bool + * @throws \Exception + */ + public function receive(): bool + { + if (! $this->can('sign')) { + throw new Exception('退款单状态不可以确认收货'); + } + + $this->express->receive_at = now(); + $this->express->save(); + + $this->apply('sign'); + //设置为退款中 + $this->apply('process'); + $this->setOrderApply($this->order, 'process'); + + $this->setLogs('店家签收'); + + return true; + } + + /** + * Notes: 标记退款完成 + * + * @Author: 玄尘 + * @Date : 2021/5/17 14:32 + * @return bool + * @throws \Exception + */ + public function complete(): bool + { + if (! $this->can('completed')) { + throw new Exception("订单状态不对,不可设置完成"); + } + + DB::transaction(function () { + $this->apply('completed'); + //设置时间 + $this->refunded_at = now(); + $this->save(); + + $this->setLogs('订单标记完成'); + + event(new RefundCompleted($this)); + }); + + return true; + } + + /** + * Notes: 退款 + * + * @Author: 玄尘 + * @Date : 2021/5/17 14:52 + * @throws \Exception + */ + public function returns(): bool + { + try { + $payment = $this->order->payment; + if (! $payment) { + throw new Exception("退款失败,未找到支付信息"); + } + //微信支付 + if ($payment->driver == Payment::DRIVER_WECHAT) { + $order = [ + 'out_trade_no' => $payment->trade_id, + 'out_refund_no' => $this->refund_no, + 'total_fee' => $payment->total * 100, + 'refund_fee' => $this->actual_total * 100, + ]; + $result = app('pay.wechat')->refund($order); + + if ($result->result_code != 'SUCCESS') { + throw new Exception("退款失败,".$result->return_msg); + } + + $this->setLogs('退款完成'); + + return true; + } elseif ($payment->driver == Payment::DRIVER_ALIPAY) {//支付宝支付 + + $order = [ + 'out_trade_no' => $this->order->order_no, + 'refund_amount' => $this->actual_total, + ]; + + $result = app('pay.alipay')->refund($order); + + if ($result->code != '10000') { + throw new Exception("退款失败,".$result->msg); + } + + return true; + } elseif ($payment->driver == Payment::DRIVER_SCORE) {//水滴支付 + + $this->user->account->rule('score_buy_refund', $this->actual_total, false, [ + 'order_no' => $this->order->order_no, + 'refund_no' => $this->refund_no, + ]); + + return true; + } else { + throw new Exception("退款失败,未找到支付路径"); + } + } catch (Exception $exception) { + throw new Exception($exception->getMessage()); + } + + } + + /** + * Notes: 记录日志 + * + * @Author: 玄尘 + * @Date : 2021/5/17 16:03 + * @param null $remark + * @param null $title + * @param null $pictures + */ + public function setLogs($remark = null, $title = null, $pictures = null) + { + if (empty($this->operator)) { + $this->operator = $this->user; + } + + if (empty($this->operator)) { + $this->operator = Admin::user(); + } + + $refund = $this->refresh(); + $logs = [ + 'userable_type' => get_class($this->operator), + 'userable_id' => $this->operator->id, + 'remark' => $remark, + 'title' => $title, + 'pictures' => $pictures, + 'state' => $refund->state, + ]; + + $refund->logs()->create($logs); + } + + /** + * Notes: 修改订单状态机 + * + * @Author: 玄尘 + * @Date : 2021/6/17 9:22 + */ + public function setOrderApply($order, $status) + { + //如果都已经退款/货 + if ($order->refund_items_count == $order->items()->count()) { + $order->apply($status); + } + } + +} diff --git a/modules/Mall/Models/Traits/ShopActions.php b/modules/Mall/Models/Traits/ShopActions.php new file mode 100644 index 0000000..421073f --- /dev/null +++ b/modules/Mall/Models/Traits/ShopActions.php @@ -0,0 +1,94 @@ + + * @return bool + * @throws \Exception + */ + public function close(): bool + { + if ($this->status != Shop::STATUS_NORMAL) { + throw new Exception('当前状态不可关闭'); + } + + $this->status = Shop::STATUS_CLOSE; + + return $this->save(); + } + + /** + * Notes : 开启店铺 + * @Date : 2021/5/7 5:02 下午 + * @Author : < Jason.C > + * @return bool + * @throws \Exception + */ + public function open(): bool + { + if ($this->status != Shop::STATUS_CLOSE) { + throw new Exception('当前状态不可开启'); + } + + $this->status = Shop::STATUS_NORMAL; + + return $this->save(); + } + + /** + * Notes : 通过申请 + * @Date : 2021/5/7 5:03 下午 + * @Author : < Jason.C > + * @return bool + * @throws \Exception + */ + public function pass(): bool + { + if ($this->status != Shop::STATUS_APPLYING) { + throw new Exception('当前状态不可审核' . $this->status); + } + + // 店铺审核通过后,增加物流的关联 + + $expressIds = Express::where('status', 1)->pluck('id'); + $this->expresses()->attach($expressIds); + $reasonIds = Reason::where('status', 1)->pluck('id'); + $this->reasons()->attach($reasonIds); + + $this->status = Shop::STATUS_CLOSE; + + return $this->save(); + } + + /** + * Notes : 驳回申请 + * @Date : 2021/5/7 5:03 下午 + * @Author : < Jason.C > + * @param string $reason + * @return bool + * @throws \Exception + */ + public function reject(string $reason): bool + { + if ($this->status != Shop::STATUS_APPLYING) { + throw new Exception('当前状态不可驳回'); + } + + $this->status = Shop::STATUS_REJECT; + $this->reject_reason = $reason; + + return $this->save(); + } + +} \ No newline at end of file diff --git a/modules/Mall/Models/Video.php b/modules/Mall/Models/Video.php new file mode 100644 index 0000000..3a3fa95 --- /dev/null +++ b/modules/Mall/Models/Video.php @@ -0,0 +1,22 @@ +parseImageUrl($this->path); + } + +} diff --git a/modules/Mall/Providers/EventServiceProvider.php b/modules/Mall/Providers/EventServiceProvider.php new file mode 100644 index 0000000..76abb95 --- /dev/null +++ b/modules/Mall/Providers/EventServiceProvider.php @@ -0,0 +1,38 @@ + [ + OrderCreatedListener::class, + ], + OrderPaid::class => [ + OrderPaidListener::class, + ], + RefundAgreed::class => [ + RefundAgreedListener::class, + ], + RefundCompleted::class => [ + RefundCompletedListener::class, + ], + ]; + +} diff --git a/modules/Mall/Providers/MallServiceProvider.php b/modules/Mall/Providers/MallServiceProvider.php new file mode 100644 index 0000000..5c251e4 --- /dev/null +++ b/modules/Mall/Providers/MallServiceProvider.php @@ -0,0 +1,100 @@ + Authenticate::class, + 'agent.owner' => ShopOwner::class, + ]; + + /** + * Boot the application events. + * @return void + */ + public function boot(): void + { + $this->loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations')); + + if ($this->app->runningInConsole()) { + $this->publishes([module_path($this->moduleName, 'Database/Seeders') => database_path('seeders')]); + } + } + + /** + * Register the service provider. + * @return void + */ + public function register(): void + { + $this->registerConfig(); + + $this->app->register(RouteServiceProvider::class); + + $this->app->singleton('workflow', function ($app) { + return new WorkflowRegistry($app['config']->get('mall.workflow')); + }); + + $viewPath = resource_path('views/modules/' . $this->moduleNameLower); + + $sourcePath = module_path($this->moduleName, 'Resources/views'); + + $this->publishes([ + $sourcePath => $viewPath, + ], 'views'); + + $this->loadViewsFrom(array_merge(array_map(function ($path) { + return $path . '/modules/' . $this->moduleNameLower; + }, Config::get('view.paths')), [$sourcePath]), $this->moduleNameLower); + + foreach ($this->routeMiddleware as $key => $middleware) { + Route::aliasMiddleware($key, $middleware); + } + } + + /** + * Register config. + * @return void + */ + protected function registerConfig(): void + { + $this->publishes([ + module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower . '.php'), + ], 'config'); + $this->mergeConfigFrom( + module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower + ); + } + + /** + * Get the services provided by the provider. + * @return array + */ + public function provides(): array + { + return ['workflow']; + } + +} diff --git a/modules/Mall/Providers/RouteServiceProvider.php b/modules/Mall/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..c9d8f6f --- /dev/null +++ b/modules/Mall/Providers/RouteServiceProvider.php @@ -0,0 +1,80 @@ +mapApiRoutes(); + + $this->mapAdminRoutes(); + + $this->mapAgentRoutes(); + } + + protected function mapApiRoutes() + { + Route::as(config('api.route.as')) + ->domain(config('api.route.domain')) + ->middleware(config('api.route.middleware')) + ->namespace($this->moduleNamespace) + ->prefix(config('api.route.prefix')) + ->group(module_path($this->moduleName, 'Routes/api.php')); + } + + protected function mapAdminRoutes() + { + Route::as(config('admin.route.as')) + ->domain(config('admin.route.domain')) + ->middleware(config('admin.route.middleware')) + ->namespace($this->moduleNamespace) + ->prefix(config('admin.route.prefix')) + ->group(module_path($this->moduleName, 'Routes/admin.php')); + } + + /** + * Notes : 中台路由 + * @Date : 2021/5/7 9:25 上午 + * @Author : < Jason.C > + */ + protected function mapAgentRoutes() + { + Route::as(config('agent.route.as')) + ->domain(config('agent.route.domain')) + ->middleware(array_merge(config('agent.route.middleware_auth'), ['agent.auth'])) + ->namespace($this->moduleNamespace) + ->prefix(config('agent.route.prefix')) + ->group(module_path($this->moduleName, 'Routes/agent.php')); + } + +} diff --git a/modules/Mall/Providers/WorkflowRegistry.php b/modules/Mall/Providers/WorkflowRegistry.php new file mode 100644 index 0000000..aed1ed3 --- /dev/null +++ b/modules/Mall/Providers/WorkflowRegistry.php @@ -0,0 +1,135 @@ +registry = new Registry(); + $this->config = $config; + $this->dispatcher = new EventDispatcher(); + + foreach ($this->config as $name => $workflowData) { + $this->addFromArray($name, $workflowData); + } + } + + /** + * Notes : 添加一个工作流 + * @Date : 2021/5/8 3:48 下午 + * @Author : < Jason.C > + * @param Workflow $workflow + * @param string $supportStrategy + */ + public function add(Workflow $workflow, string $supportStrategy) + { + $this->registry->addWorkflow($workflow, new InstanceOfSupportStrategy($supportStrategy)); + } + + /** + * Notes : 获取工作流 + * @Date : 2021/5/8 3:49 下午 + * @Author : < Jason.C > + * @param object $subject + * @param string|null $workflowName + * @return Workflow + */ + public function get(object $subject, string $workflowName = null): Workflow + { + return $this->registry->get($subject, $workflowName); + } + + /** + * 批量添加工作流到对象 + * @param string $name + * @param array $workflowData + */ + public function addFromArray(string $name, array $workflowData) + { + $builder = new DefinitionBuilder($workflowData['places']); + + foreach ($workflowData['transitions'] as $transitionName => $transition) { + foreach ((array) $transition['from'] as $form) { + $builder->addTransition(new Transition($transitionName, $form, $transition['to'])); + } + } + + $definition = $builder->build(); + $markingStore = $this->getMarkingStoreInstance($workflowData); + $workflow = $this->getWorkflowInstance($name, $workflowData, $definition, $markingStore); + + foreach ($workflowData['supports'] as $supportedClass) { + $this->add($workflow, $supportedClass); + } + } + + /** + * Notes : 根据配置获取工作流或状态机 + * @Date : 2021/5/8 3:50 下午 + * @Author : < Jason.C > + * @param string $name + * @param array $workflowData + * @param Definition $definition + * @param MethodMarkingStore $markingStore + * @return Workflow + */ + protected function getWorkflowInstance( + string $name, + array $workflowData, + Definition $definition, + MethodMarkingStore $markingStore + ): Workflow { + if (isset($workflowData['type']) && $workflowData['type'] === 'state_machine') { + $className = StateMachine::class; + } else { + $className = Workflow::class; + } + + return new $className($definition, $markingStore, $this->dispatcher, $name); + } + + /** + * Notes : 获取对象存储的实例 + * @Date : 2021/5/8 3:49 下午 + * @Author : < Jason.C > + * @param array $workflowData + * @return MethodMarkingStore + */ + protected function getMarkingStoreInstance(array $workflowData): MethodMarkingStore + { + $markingStoreData = $workflowData['marking_store'] ?? []; + $property = $markingStoreData['property'] ?? ''; + + $singleState = $markingStoreData['type'] === 'single_state'; + + return new MethodMarkingStore($singleState, $property); + } + +} \ No newline at end of file diff --git a/modules/Mall/README.md b/modules/Mall/README.md new file mode 100644 index 0000000..b1c03f1 --- /dev/null +++ b/modules/Mall/README.md @@ -0,0 +1,37 @@ +# UzTech Mall Module 多用户商城模块 + +## 1. 使用 + +> php artisan module:publish-config Mall + +## 2. traits 说明 + +### 1. UserHasAddress 用户地址列表 + +## 3. 功能说明 + +### 1. 物流管理,主后台添加一个主表,各商户,通过多对多的关系来确定使用哪些物流 + +### 2. + +## 4. 工作流说明 + +> 参考文档 +> +> https://github.com/brexis/laravel-workflow +> +> https://symfony.com/doc/current/components/workflow.html + +```php + $order = Order::find(2); + // 方法1 + $app = app('workflow'); + $workflow = $app->get($order); + if ($workflow->can($order, 'pay')) { + $workflow->apply($order, 'pay') + } + // 方法2,引入 WithWorkflow + $order->can('pay'); + // 方法3 + $workflow = Modules\Mall\Facades\Workflow::get($order)->can('pay'); +``` \ No newline at end of file diff --git a/modules/Mall/Resources/views/admin/order/detail.blade.php b/modules/Mall/Resources/views/admin/order/detail.blade.php new file mode 100644 index 0000000..e51adbf --- /dev/null +++ b/modules/Mall/Resources/views/admin/order/detail.blade.php @@ -0,0 +1,271 @@ +
+
+

订单编号:{{ $order->order_no }}

+ +
+ + + +
+
    + @foreach(\Modules\Mall\Models\Order::STATUS_MAP as $key=>$status) +
  • state==$key) class="act" @endif>{{ $status }}
  • + @endforeach +
+
+ +
+
+
+
+

订单信息

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
订单信息购买人收货信息费用信息订单状态支付渠道支付时间
{{ $order->order_no }}{{ $order->user->info->nickname }} + @if ($order->express) +

收件人:{{$order->express->name}}

+

电话:{{$order->express->mobile}}

+

地址:{{$order->express->getFullAddress()}}

+ + @endif +
+

商品总额:¥ {{$order->amount}}元

+

订单总额:¥ {{$order->total}}元

+

说明: {{$order->remark}}

+ +

运费:¥ {{$order->freight}}元

+

+

+ 支付总额: ¥ {{$order->total}}元 +

+ +
{{ $order->state_text }}{{ $order->payment->driver_text ?? '---' }}{{ $order->paid_at }}
+
+
+
+
+ +
+
+
+
+

商品信息

+
+
+ + + + + + {{-- --}} + + + {{-- --}} + {{-- --}} + + + + @foreach ($order->items as $item) + + + + {{-- --}} + + + {{-- --}} + {{-- --}} + + @endforeach + + +
id商品名称属性数量单价水晶退款/退货
{{ $item->id }}{{ $item->source['goods_name'] }}{{ $item->source['unit']??'---' }}{{ $item->qty }}{{ $item->price }}{{ $item->source['score'] }}{{ $item->isRefund()?'是':'否' }}
+
+
+
+
+ @if ($order->express) +
+
+
+
+

物流信息

+
+
+ + + + + + + + + @if($order->express->express_no) + + + @endif + + + + + + + + + + + @if($order->express->express_no) + + + @endif + + + +
Id类型收件人电话地址物流公司物流单号经办人
{{ $order->express->id }}{{ $order->express->type_text }}{{ $order->express->name }}{{ $order->express->mobile }}{{ $order->express->getFullAddress() }}物流公司:{{$order->express->express->name}}物流单号:{{$order->express->express_no }}{{ $order->express->person }}
+
+
+
+
+ @endif + + @if ($order->payments->isNotEmpty()) +
+
+
+
+

支付信息

+
+
+ + + + + + + + + + + + + + @foreach ($order->payments as $payment) + + + + + + + + + + @endforeach + +
Id支付方式支付金额(元)流水号支付单号状态支付时间
{{ $payment->id }}{{ $payment->driver_text }}{{ $payment->total }}{{ $payment->trade_id }}{{ $payment->transaction_id }}{{ $payment->state_text }}{{ $payment->created_at }}
+
+
+
+
+ @endif + + @if ($order->refunds->isNotEmpty()) +
+
+
+
+

退款/货信息

+
+
+ + + + + + + + + + + + + @foreach ($order->refunds as $refund) + + + + + + + + + @endforeach + +
退款/货单号申请退款金额实退金额状态申请时间退款时间
{{ $refund->refund_no }}{{ $refund->refund_total }}{{ $refund->actual_total }}{{ $refund->state_text }}{{ $refund->created_at }}{{ $refund->refunded_at }}
+
+
+
+
+ @endif + +
diff --git a/modules/Mall/Resources/views/admin/stock_order/detail.blade.php b/modules/Mall/Resources/views/admin/stock_order/detail.blade.php new file mode 100644 index 0000000..e51adbf --- /dev/null +++ b/modules/Mall/Resources/views/admin/stock_order/detail.blade.php @@ -0,0 +1,271 @@ +
+
+

订单编号:{{ $order->order_no }}

+ +
+ + + +
+
    + @foreach(\Modules\Mall\Models\Order::STATUS_MAP as $key=>$status) +
  • state==$key) class="act" @endif>{{ $status }}
  • + @endforeach +
+
+ +
+
+
+
+

订单信息

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
订单信息购买人收货信息费用信息订单状态支付渠道支付时间
{{ $order->order_no }}{{ $order->user->info->nickname }} + @if ($order->express) +

收件人:{{$order->express->name}}

+

电话:{{$order->express->mobile}}

+

地址:{{$order->express->getFullAddress()}}

+ + @endif +
+

商品总额:¥ {{$order->amount}}元

+

订单总额:¥ {{$order->total}}元

+

说明: {{$order->remark}}

+ +

运费:¥ {{$order->freight}}元

+

+

+ 支付总额: ¥ {{$order->total}}元 +

+ +
{{ $order->state_text }}{{ $order->payment->driver_text ?? '---' }}{{ $order->paid_at }}
+
+
+
+
+ +
+
+
+
+

商品信息

+
+
+ + + + + + {{-- --}} + + + {{-- --}} + {{-- --}} + + + + @foreach ($order->items as $item) + + + + {{-- --}} + + + {{-- --}} + {{-- --}} + + @endforeach + + +
id商品名称属性数量单价水晶退款/退货
{{ $item->id }}{{ $item->source['goods_name'] }}{{ $item->source['unit']??'---' }}{{ $item->qty }}{{ $item->price }}{{ $item->source['score'] }}{{ $item->isRefund()?'是':'否' }}
+
+
+
+
+ @if ($order->express) +
+
+
+
+

物流信息

+
+
+ + + + + + + + + @if($order->express->express_no) + + + @endif + + + + + + + + + + + @if($order->express->express_no) + + + @endif + + + +
Id类型收件人电话地址物流公司物流单号经办人
{{ $order->express->id }}{{ $order->express->type_text }}{{ $order->express->name }}{{ $order->express->mobile }}{{ $order->express->getFullAddress() }}物流公司:{{$order->express->express->name}}物流单号:{{$order->express->express_no }}{{ $order->express->person }}
+
+
+
+
+ @endif + + @if ($order->payments->isNotEmpty()) +
+
+
+
+

支付信息

+
+
+ + + + + + + + + + + + + + @foreach ($order->payments as $payment) + + + + + + + + + + @endforeach + +
Id支付方式支付金额(元)流水号支付单号状态支付时间
{{ $payment->id }}{{ $payment->driver_text }}{{ $payment->total }}{{ $payment->trade_id }}{{ $payment->transaction_id }}{{ $payment->state_text }}{{ $payment->created_at }}
+
+
+
+
+ @endif + + @if ($order->refunds->isNotEmpty()) +
+
+
+
+

退款/货信息

+
+
+ + + + + + + + + + + + + @foreach ($order->refunds as $refund) + + + + + + + + + @endforeach + +
退款/货单号申请退款金额实退金额状态申请时间退款时间
{{ $refund->refund_no }}{{ $refund->refund_total }}{{ $refund->actual_total }}{{ $refund->state_text }}{{ $refund->created_at }}{{ $refund->refunded_at }}
+
+
+
+
+ @endif + +
diff --git a/modules/Mall/Routes/admin.php b/modules/Mall/Routes/admin.php new file mode 100644 index 0000000..1c395d4 --- /dev/null +++ b/modules/Mall/Routes/admin.php @@ -0,0 +1,85 @@ + 'mall', + 'namespace' => 'Admin', + 'as' => 'mall.', +], function (Router $router) { + $router->get('dashboard', 'DashboardController@index'); + /** + * 版本记录 + */ + $router->get('versions/{model}/{key}', 'VersionController@index')->name('versions'); + /** + * 店铺管理 + */ + $router->get('shops/ajax', 'ShopController@ajax')->name('shops.ajax'); + $router->resource('shops', 'ShopController'); + $router->resource('shops.staffers', 'StafferController'); + $router->resource('jobs', 'JobController'); + /** + * 订单管理 + */ + $router->resource('orders', 'OrderController')->only(['index', 'show', 'create', 'store']); + $router->resource('stock_orders', 'StockOrderController')->only(['index', 'show']); + $router->resource('stock_orders_by_system', 'StockOrderBySystemController')->only([ + 'index', 'show', 'create', 'store' + ]); + $router->resource('refunds', 'RefundController')->only(['index', 'show']); + /** + * 商品分类,品牌 + */ + $router->get('categories/ajax', 'CategoryController@ajax')->name('categories.ajax'); + $router->resource('categories', 'CategoryController'); + $router->get('brands/ajax', 'BrandController@ajax')->name('brands.ajax'); + $router->resource('brands', 'BrandController'); + $router->get('tags/ajax', 'TagController@ajax')->name('tags.ajax'); + $router->resource('tags', 'TagController'); + /** + * 商品管理 + */ + $router->resource('goods', 'GoodsController'); + $router->resource('goods.skus', 'SkuController'); + $router->resource('goods.specs', 'SpecController'); + $router->resource('goods.specs.values', 'SpecValueController'); + /** + * 物流管理 + */ + $router->resource('expresses', 'ExpressController'); + /** + * 运费模板 + */ + $router->get('deliveries/ajax', 'DeliveryController@ajax')->name('deliveries.ajax'); + $router->resource('deliveries', 'DeliveryController'); + $router->resource('deliveries.rules', 'DeliveryRuleController'); + /** + * 地域组件 + */ + $router->get('regions/ajax', 'RegionController@ajax')->name('regions.ajax'); + $router->get('regions/getRegion', 'RegionController@region')->name('regions.region'); + /** + * 商城轮播图 + */ + $router->resource('banners', 'BannerController'); + + /** + * 退款/货原因 + */ + $router->resource('reasons', 'ReasonController'); + + /** + * 收货地址 + */ + $router->get('ajax/address', 'AjaxController@address')->name('ajax.address'); + $router->get('ajax/goods', 'AjaxController@goods')->name('ajax.goods'); + $router->resource('addresses', 'AddressController'); + $router->resource('activities', 'ActivityController'); + /** + * 视频管理 + */ + $router->resource('videos', 'VideoController'); + +}); diff --git a/modules/Mall/Routes/agent.php b/modules/Mall/Routes/agent.php new file mode 100644 index 0000000..842fafe --- /dev/null +++ b/modules/Mall/Routes/agent.php @@ -0,0 +1,14 @@ + 'mall', + 'namespace' => 'Agent', + 'as' => 'mall.', +], function (Router $router) { + $router->get('', 'IndexController@index'); + // 店铺管理 + $router->get('shop', 'ShopController@index'); +}); diff --git a/modules/Mall/Routes/api.php b/modules/Mall/Routes/api.php new file mode 100644 index 0000000..5c0c49d --- /dev/null +++ b/modules/Mall/Routes/api.php @@ -0,0 +1,124 @@ + 'mall', + 'namespace' => 'Api', +], function (Router $router) { + + /** + * 轮播图 + */ + $router->get('banners', 'BannerController@index'); + /** + * 商品分类 + */ + $router->get('categories', 'CategoryController@index'); + /** + * 商品分类 + */ + $router->get('tags', 'TagController@index'); + /** + * 店铺列表 + */ + $router->get('shops', 'ShopController@index'); + $router->get('shops/{shop}', 'ShopController@show')->where('shop', '[0-9]+'); + $router->get('shops/{shop}/categories', 'ShopExtendController@categories'); + $router->get('shops/{shop}/tags', 'ShopExtendController@tags'); + $router->get('shops/{shop}/brands', 'ShopExtendController@brands'); + $router->get('shops/{shop}/expresses', 'ShopExtendController@expresses'); + $router->get('shops/{shop}/banners', 'ShopExtendController@banners'); + /** + * 商品管理 + */ + $router->get('goods', 'GoodsController@index'); +}); + +Route::group([ + 'prefix' => 'mall', + 'namespace' => 'Api', + 'middleware' => config('api.route.middleware_guess'), +], function (Router $router) { + /** + * 商城的首页 + */ + $router->get('', 'IndexController@index'); + $router->get('carts/count', 'CartController@count'); + $router->get('goods/{goods}', 'GoodsController@show'); + /** + * 活动接口 + */ + $router->get('activities', 'ActivityController@index'); + $router->get('activities/{activity}', 'ActivityController@show'); + +}); + +Route::group([ + 'prefix' => 'mall', + 'namespace' => 'Api', + 'middleware' => config('api.route.middleware_auth'), +], function (Router $router) { + /** + * 店铺操作 + */ + $router->get('shops/create', 'ShopController@create'); + $router->post('shops', 'ShopController@store'); + $router->get('shops/edit', 'ShopController@edit'); + $router->put('shops', 'ShopController@update'); + /** + * 购物车 + */ + $router->get('carts', 'CartController@index'); + $router->post('carts', 'CartController@store'); + $router->put('carts/{cart}', 'CartController@update'); + $router->delete('carts/{cart}', 'CartController@destroy'); + /** + * 订单管理 + */ + $router->get('orders/counts', 'OrderController@counts'); + $router->get('orders', 'OrderController@index'); + // 订单详情 可以通过 order_no 来查询 + $router->get('orders/{order}', 'OrderController@show')->where(['order' => '[0-9]+']); + // 创建订单 + $router->get('buy/goods', 'OrderBuyController@goodsCreate');//商品确认订单 + $router->post('buy/goods', 'OrderBuyController@goodsBuy'); //商品购买 + $router->get('buy/carts', 'OrderBuyController@cartsCreate');//购物车确认订单 + $router->post('buy/carts', 'OrderBuyController@cartsBuy'); //购物车购买 + + $router->get('buy/samples', 'OrderBuyController@sampleCreate');//试用商品 + $router->post('buy/samples', 'OrderBuyController@sampleBuy'); //试用商品购买 + + // 更新订单,地址或其他信息 + $router->put('orders/{order}', 'OrderController@update')->where(['order' => '[0-9]+']); + // 查看物流 + $router->get('orders/{order}/logistic', 'OrderController@logistic')->where(['order' => '[0-9]+']); + // 签收订单 + $router->put('orders/{order}/sign', 'OrderController@sign')->where(['order' => '[0-9]+']); + // 取消订单 + $router->put('orders/{order}/cancel', 'OrderController@cancel')->where(['order' => '[0-9]+']); + // 删除订单 + $router->delete('orders/{order}', 'OrderController@destroy')->where(['order' => '[0-9]+']); + //退款 + $router->get('orders/{order}/refund', 'OrderController@goods')->where(['order' => '[0-9]+']); + $router->post('orders/{order}/refund', 'OrderController@refund')->where(['order' => '[0-9]+']); + //订单支付 + $router->get('pay/{order}/wechat', 'PayController@wechat')->where(['order' => '[0-9]+']); + $router->get('pay/{order}/alipay', 'PayController@alipay')->where(['order' => '[0-9]+']); + $router->post('pay/{order}/score', 'PayController@score')->where(['order' => '[0-9]+']); + //退货 + $router->post('refunds/{refund}/deliver', 'RefundController@deliver')->where(['refund' => '[0-9]+']); + + //退款售后 + $router->get('refunds', 'RefundController@index'); + $router->get('refunds/{refund}', 'RefundController@show')->where(['refund' => '[0-9]+']); + $router->get('refunds/{refund}/logs', 'RefundController@logs')->where(['refund' => '[0-9]+']); + + /** + * 收货地址 + */ + $router->post('addresses/{address}/default', 'AddressController@setDefault'); + $router->resource('addresses', 'AddressController'); + +}); diff --git a/modules/Mall/Rules/CityRule.php b/modules/Mall/Rules/CityRule.php new file mode 100644 index 0000000..b79646a --- /dev/null +++ b/modules/Mall/Rules/CityRule.php @@ -0,0 +1,38 @@ + + * @Date : 2020/11/6 9:56 上午 + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function passes($attribute, $value): bool + { + $city = Region::find($value); + if ($city->depth != 2) { + return false; + } + + return true; + } + + /** + * 获取校验错误信息 + * @return string + */ + public function message(): string + { + return '市区信息选择有误'; + } + +} diff --git a/modules/Mall/Rules/DistrictRule.php b/modules/Mall/Rules/DistrictRule.php new file mode 100644 index 0000000..8d1990f --- /dev/null +++ b/modules/Mall/Rules/DistrictRule.php @@ -0,0 +1,38 @@ + + * @Date : 2020/11/6 9:56 上午 + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function passes($attribute, $value): bool + { + $district = Region::find($value); + if ($district->depth != 3) { + return false; + } + + return true; + } + + /** + * 获取校验错误信息 + * @return string + */ + public function message(): string + { + return '区县信息选择有误'; + } + +} diff --git a/modules/Mall/Rules/ProvinceRule.php b/modules/Mall/Rules/ProvinceRule.php new file mode 100644 index 0000000..6972c34 --- /dev/null +++ b/modules/Mall/Rules/ProvinceRule.php @@ -0,0 +1,38 @@ + + * @Date : 2020/11/6 9:56 上午 + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function passes($attribute, $value) + { + $province = Region::find($value); + if ($province->depth != 1) { + return false; + } + + return true; + } + + /** + * 获取校验错误信息 + * @return string + */ + public function message() + { + return '省份信息选择有误'; + } + +} diff --git a/modules/Mall/Traits/HasAddresses.php b/modules/Mall/Traits/HasAddresses.php new file mode 100644 index 0000000..68be014 --- /dev/null +++ b/modules/Mall/Traits/HasAddresses.php @@ -0,0 +1,22 @@ + + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function addresses(): HasMany + { + return $this->hasMany(Address::class); + } + +} \ No newline at end of file diff --git a/modules/Mall/Traits/HasCart.php b/modules/Mall/Traits/HasCart.php new file mode 100644 index 0000000..0a03e48 --- /dev/null +++ b/modules/Mall/Traits/HasCart.php @@ -0,0 +1,22 @@ + + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function carts(): HasMany + { + return $this->hasMany(Cart::class); + } + +} \ No newline at end of file diff --git a/modules/Mall/Traits/HasOrders.php b/modules/Mall/Traits/HasOrders.php new file mode 100644 index 0000000..9346f5b --- /dev/null +++ b/modules/Mall/Traits/HasOrders.php @@ -0,0 +1,53 @@ + + * @return HasMany + */ + public function orders(): HasMany + { + return $this->hasMany(Order::class); + } + + /** + * Notes: 是否可以申请提货 + * + * @Author: 玄尘 + * @Date : 2021/10/22 14:13 + * @return bool + */ + public function canPick(): bool + { + return $this->case && $this->case->status == GoutCase::STATUS_PASS && + $this->identities()->where('order', '>', 1)->count() > 0 && + $this->userStock->stock > $this->userStock->hold; + } + + /** + * Notes: 提货 + * + * @Author: 玄尘 + * @Date: 2022/7/27 16:18 + */ + public function pickGoods() + { + if (! $this->canPick()) { + return '不可提货'; + } + } + +} \ No newline at end of file diff --git a/modules/Mall/Traits/HasShop.php b/modules/Mall/Traits/HasShop.php new file mode 100644 index 0000000..13e59f0 --- /dev/null +++ b/modules/Mall/Traits/HasShop.php @@ -0,0 +1,22 @@ + + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function shop(): HasOne + { + return $this->hasOne(Shop::class); + } + +} \ No newline at end of file diff --git a/modules/Mall/Traits/WithWorkflow.php b/modules/Mall/Traits/WithWorkflow.php new file mode 100644 index 0000000..b82793d --- /dev/null +++ b/modules/Mall/Traits/WithWorkflow.php @@ -0,0 +1,48 @@ + + * @param $transition + * @param null $workflow + * @return mixed + */ + public function apply($transition, $workflow = null) + { + return Workflow::get($this, $workflow)->apply($this, $transition); + } + + /** + * Notes : 是否可以执行 + * @Date : 2021/5/8 5:36 下午 + * @Author : < Jason.C > + * @param $transition + * @param null $workflow + * @return mixed + */ + public function can($transition, $workflow = null) + { + return Workflow::get($this, $workflow)->can($this, $transition); + } + + /** + * Notes : 可执行的操作 + * @Date : 2021/5/8 5:37 下午 + * @Author : < Jason.C > + * @param null $workflow + * @return mixed + */ + public function transitions($workflow = null) + { + return Workflow::get($this, $workflow)->getEnabledTransitions($this); + } + +} \ No newline at end of file diff --git a/modules/Mall/composer.json b/modules/Mall/composer.json new file mode 100644 index 0000000..0012e20 --- /dev/null +++ b/modules/Mall/composer.json @@ -0,0 +1,38 @@ +{ + "name": "uztech/mall-module", + "description": "多用户商城模块", + "type": "laravel-module", + "authors": [ + { + "name": "Jason.Chen", + "email": "chenjxlg@163.com" + } + ], + "require": { + "php": "^7.3|^8.0", + "encore/laravel-admin": "^1.8", + "genealabs/laravel-model-caching": "^0.11.3", + "jasonc/api": "^5.0.0", + "joshbrw/laravel-module-installer": "^2.0", + "laravel/framework": "^8.5", + "maatwebsite/excel": "^3.1", + "nwidart/laravel-modules": "^8.2", + "propaganistas/laravel-phone": "^4.3", + "overtrue/laravel-versionable": "^2.6", + "symfony/workflow": "^5.2", + "wang-tech-commits/kuaidi100-api": "^1.0" + }, + "extra": { + "module-dir": "modules", + "laravel": { + "providers": [], + "aliases": { + } + } + }, + "autoload": { + "psr-4": { + "Modules\\Shop\\": "" + } + } +} diff --git a/modules/Mall/docs/README.md b/modules/Mall/docs/README.md new file mode 100644 index 0000000..69957f1 --- /dev/null +++ b/modules/Mall/docs/README.md @@ -0,0 +1,25 @@ +# 商城模块文档 + +## 1. 安装 + +```shell +composer require maatwebsite/excel +composer require propaganistas/laravel-phone +composer require overtrue/laravel-versionable +composer require symfony/workflow +composer require wang-tech-commits/kuaidi100-api +``` + +## 2. Traits 说明 + +### 1. HasAddresses + +对象拥有收货地址,可对收货地址管理 + +### 2. HasCart + +### 3. HasOrders + +### 4. HasShop + +### 5. WithWorkflow \ No newline at end of file diff --git a/modules/Mall/docs/商品接口.md b/modules/Mall/docs/商品接口.md new file mode 100644 index 0000000..e69de29 diff --git a/modules/Mall/module.json b/modules/Mall/module.json new file mode 100644 index 0000000..646ff8e --- /dev/null +++ b/modules/Mall/module.json @@ -0,0 +1,20 @@ +{ + "name": "Mall", + "alias": "mall", + "description": "多用户商城模块", + "keywords": [], + "priority": 0, + "providers": [ + "Modules\\Mall\\Providers\\MallServiceProvider", + "Modules\\Mall\\Providers\\EventServiceProvider" + ], + "aliases": {}, + "files": [], + "requires": [ + "User" + ], + "config": true, + "configName": "用户模块", + "version": "1.0.0", + "author": "Jason.Chen" +} diff --git a/modules/Notification/Channels/AppChannel.php b/modules/Notification/Channels/AppChannel.php new file mode 100644 index 0000000..865bc53 --- /dev/null +++ b/modules/Notification/Channels/AppChannel.php @@ -0,0 +1,45 @@ +bindAlias(); + // $api = new GTClient("https://restapi.getui.com", "APPKEY", "APPID", "MASTERSECRET"); + // 设置推送参数 + // $push = new \GTPushRequest(); + // $push->setRequestId(uniqid()); + // $message = new GTPushMessage(); + // $notify = new GTNotification(); + // $notify->setTitle("设置通知标题"); + // $notify->setBody("设置通知内容"); + // // 点击通知后续动作,目前支持以下后续动作: + // // 1、intent:打开应用内特定页面url:打开网页地址。 + // // 2、payload:自定义消息内容启动应用。 + // // 3、payload_custom:自定义消息内容不启动应用。 + // // 4、startapp:打开应用首页。 + // // 5、none:纯通知,无后续动作 + // $notify->setClickType("none"); + // $message->setNotification($notify); + // $push->setPushMessage($message); + // $push->setCid("CID"); + // //处理返回结果 + // $result = $api->pushApi()->pushToSingleByCid($push); + + $notification->toApp($notifiable); + } + +} diff --git a/modules/Notification/Config/config.php b/modules/Notification/Config/config.php new file mode 100644 index 0000000..bc1883c --- /dev/null +++ b/modules/Notification/Config/config.php @@ -0,0 +1,34 @@ + 'Notification', + + /* + |-------------------------------------------------------------------------- + | 消息分类 + |-------------------------------------------------------------------------- + */ + 'types' => [ + 'App\\Notifications\\SystemOpenVip' => [ + 'name' => '开通会员', + 'icon' => 'http://api.siyuankunlun.com/storage/materials/2022/08/09/newsIcon_04.jpg', + ], + 'App\\Notifications\\SystemOrderDelivered' => [ + 'name' => '发货', + 'icon' => 'http://api.siyuankunlun.com/storage/materials/2022/08/09/newsIcon_02.png', + ], + 'App\\Notifications\\SystemRemindUserSign' => [ + 'name' => '打卡', + 'icon' => 'http://api.siyuankunlun.com/storage/materials/2022/08/09/newsIcon_01.png', + ], + 'App\\Notifications\\SystemUpdateCase' => [ + 'name' => '上传报告', + 'icon' => 'http://api.siyuankunlun.com/storage/materials/2022/08/09/newsIcon_05.png', + ], + ], +]; \ No newline at end of file diff --git a/modules/Notification/Database/Migrations/0000_00_00_000000_create_notification_templates_table.php b/modules/Notification/Database/Migrations/0000_00_00_000000_create_notification_templates_table.php new file mode 100644 index 0000000..0fca391 --- /dev/null +++ b/modules/Notification/Database/Migrations/0000_00_00_000000_create_notification_templates_table.php @@ -0,0 +1,35 @@ +id(); + $table->string('title')->comment('模板标题'); + $table->string('slug')->unique()->comment('唯一标识'); + $table->text('content')->comment('模板内容'); + $table->boolean('status')->unsigned()->default(0); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('notification_templates'); + } + +} diff --git a/modules/Notification/Http/Controllers/Admin/IndexController.php b/modules/Notification/Http/Controllers/Admin/IndexController.php new file mode 100644 index 0000000..2411401 --- /dev/null +++ b/modules/Notification/Http/Controllers/Admin/IndexController.php @@ -0,0 +1,47 @@ +column('title', '模板标题'); + $grid->column('slug', '调用标识'); + $grid->column('status', '状态')->bool(); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form((new Template())->disableModelCaching()); + + $form->text('title', '模板标题') + ->required(); + $form->text('slug', '调用标识') + ->required(); + $form->textarea('content', '模板内容') + ->required(); + $form->switch('status', '状态'); + + $this->withUrl($form); + + return $form; + } + +} \ No newline at end of file diff --git a/modules/Notification/Http/Controllers/Admin/SettingController.php b/modules/Notification/Http/Controllers/Admin/SettingController.php new file mode 100644 index 0000000..2735fa2 --- /dev/null +++ b/modules/Notification/Http/Controllers/Admin/SettingController.php @@ -0,0 +1,17 @@ + + * @return JsonResponse + */ + public function index(): JsonResponse + { + $user = Api::user(); + + $result = []; + foreach (config('notification.types') as $type => $name) { + $result[] = [ + 'type' => Str::remove('App\\Notifications\\', $type), + 'icon' => $name['icon'], + 'name' => $name['name'], + 'count' => $user->unreadNotifications()->where('type', $type)->count(), + 'title' => data_get($user->unreadNotifications()->where('type', $type)->latest()->value('data'), + 'title') ?? '', + ]; + } + + return $this->success($result); + } + + /** + * Notes : 未读消息数量 + * + * @Date : 2021/5/20 12:10 下午 + * @Author : + */ + public function counts(): JsonResponse + { + $user = Api::user(); + + if ($user) { + $count = $user->unreadNotifications()->count(); + } else { + $count = 0; + } + + return $this->success($count); + } + + /** + * Notes : 分类读取消息 + * + * @Date : 2021/5/18 3:12 下午 + * @Author : + * @param string $type + * @return JsonResponse + */ + public function type(string $type): JsonResponse + { + $list = DatabaseNotification::query() + ->whereHasMorph( + 'notifiable', + User::class, + function (Builder $query) { + $query->where('id', Api::userId()); + } + ) + ->where('type', 'like', "%$type") + ->orderBy('read_at') + ->orderByDesc('created_at')->paginate(); + + return $this->success(new NotificationCollection($list)); + } + + /** + * Notes : 消息详情,自动标记已读 + * + * @Date : 2021/5/18 3:17 下午 + * @Author : + * @param string $id + * @return JsonResponse + */ + public function show(string $id): JsonResponse + { + $notification = DatabaseNotification::findOrFail($id); + + $notification->markAsRead(); + + return $this->success(new NotificationResource($notification)); + } + + /** + * Notes : 全部标记已读 + * + * @Date : 2021/5/18 3:22 下午 + * @Author : + */ + public function markAsRead(string $type = null): JsonResponse + { + $user = Api::user(); + + $user->unreadNotifications()->when($type, function ($query) use ($type) { + $query->where('type', 'like', "%$type"); + })->update(['read_at' => now()]); + + return $this->success(''); + } + + /** + * Notes : 清空消息 + * + * @Date : 2021/5/18 5:49 下午 + * @Author : + * @param string|null $type + * @return JsonResponse + */ + public function clean(string $type = null): JsonResponse + { + $user = Api::user(); + + $user->notifications()->when($type, function ($query) use ($type) { + $query->where('type', 'like', "%$type"); + })->delete(); + + return $this->success(''); + } + +} diff --git a/modules/Notification/Http/Resources/NotificationCollection.php b/modules/Notification/Http/Resources/NotificationCollection.php new file mode 100644 index 0000000..f96bd69 --- /dev/null +++ b/modules/Notification/Http/Resources/NotificationCollection.php @@ -0,0 +1,20 @@ + $this->collection->map(function ($notification) { + return new NotificationResource($notification); + }), + 'page' => $this->page(), + ]; + } + +} \ No newline at end of file diff --git a/modules/Notification/Http/Resources/NotificationResource.php b/modules/Notification/Http/Resources/NotificationResource.php new file mode 100644 index 0000000..7056345 --- /dev/null +++ b/modules/Notification/Http/Resources/NotificationResource.php @@ -0,0 +1,23 @@ + $this->id, + 'type' => Str::remove('App\\Notifications\\', $this->type), + 'title' => $this->data['title'] ?? '', + 'content' => $this->data['content'] ?? '', + 'read_at' => (string) $this->read_at, + 'created_at' => (string) $this->created_at, + ]; + } + +} \ No newline at end of file diff --git a/modules/Notification/Models/Template.php b/modules/Notification/Models/Template.php new file mode 100644 index 0000000..803a336 --- /dev/null +++ b/modules/Notification/Models/Template.php @@ -0,0 +1,17 @@ + + */ + public static function install() + { + Artisan::call('migrate', [ + '--path' => 'modules/Notification/Database/Migrations', + ]); + + self::createAdminMenu(); + } + + protected static function createAdminMenu() + { + $menu = config('admin.database.menu_model'); + + $main = $menu::create([ + 'parent_id' => 0, + 'order' => 40, + 'title' => self::$mainTitle, + 'icon' => 'fa-commenting-o', + ]); + + $main->children()->createMany([ + [ + 'order' => 1, + 'title' => '消息模板', + 'icon' => 'fa-dashboard', + 'uri' => 'notifications/templates', + ], + [ + 'order' => 2, + 'title' => '消息设置', + 'icon' => 'fa-cogs', + 'uri' => 'notifications/settings', + ], + ]); + } + + /** + * Notes : 卸载模块的一些操作 + * + * @Date : 2021/3/12 11:35 上午 + * @Author : + */ + public static function uninstall() + { + $menu = config('admin.database.menu_model'); + + $mains = $menu::where('title', self::$mainTitle)->get(); + + foreach ($mains as $main) { + $main->delete(); + } + } + +} diff --git a/modules/Notification/Providers/NotificationServiceProvider.php b/modules/Notification/Providers/NotificationServiceProvider.php new file mode 100644 index 0000000..86ff431 --- /dev/null +++ b/modules/Notification/Providers/NotificationServiceProvider.php @@ -0,0 +1,67 @@ +loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations')); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->registerConfig(); + + $this->app->register(RouteServiceProvider::class); + } + + /** + * Register config. + * + * @return void + */ + protected function registerConfig() + { + $this->publishes([ + module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower.'.php'), + ], 'config'); + $this->mergeConfigFrom( + module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower + ); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides(): array + { + return []; + } + +} diff --git a/modules/Notification/Providers/RouteServiceProvider.php b/modules/Notification/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..83dc6c8 --- /dev/null +++ b/modules/Notification/Providers/RouteServiceProvider.php @@ -0,0 +1,65 @@ +mapAdminRoutes(); + $this->mapApiRoutes(); + } + + protected function mapApiRoutes() + { + Route::as(config('api.route.as')) + ->domain(config('api.route.domain')) + ->middleware(config('api.route.middleware')) + ->namespace($this->moduleNamespace) + ->prefix(config('api.route.prefix')) + ->group(module_path($this->moduleName, 'Routes/api.php')); + } + + protected function mapAdminRoutes() + { + Route::as(config('admin.route.as')) + ->domain(config('admin.route.domain')) + ->middleware(config('admin.route.middleware')) + ->namespace($this->moduleNamespace) + ->prefix(config('admin.route.prefix')) + ->group(module_path($this->moduleName, 'Routes/admin.php')); + } + +} diff --git a/modules/Notification/README.md b/modules/Notification/README.md new file mode 100644 index 0000000..aa32b2c --- /dev/null +++ b/modules/Notification/README.md @@ -0,0 +1,154 @@ +# Notification + +通知模块 + +## 1. 安装 + +```shell +composer require getuilaboratory/getui-pushapi-php-client-v2 +``` + +## 2. 接口文档 + +### 1. 获取未读消息数量(不登陆返回0) + +> GET: /api/notifications/counts + +```json +{ + "status": "SUCCESS", + "status_code": 200, + "data": 16 +} +``` + +### 2. 消息主页 + +> GET: /api/notifications + +```json +{ + "status": "SUCCESS", + "status_code": 200, + "data": [ + { + "type": "SystemNotification", + "icon": "", + "name": "系统通知", + "count": 16, + "title": "登录成功" + }, + { + "type": "OrderNotification", + "icon": "", + "name": "订单通知", + "count": 0, + "title": "" + }, + { + "type": "ActivityNotification", + "icon": "", + "name": "活动通知", + "count": 0, + "title": "" + } + ] +} +``` + +### 3. 分类消息的列表 + +> GET: /api/notifications/{type}/list + +```json +{ + "status": "SUCCESS", + "status_code": 200, + "data": { + "data": [ + { + "notification_id": "91e31d1e-95e1-40ff-88d8-8049df974860", + "type": "SystemNotification", + "title": "登录成功", + "content": "您于北京时间:2021-07-02 15:31:03,通过【账号密码】渠道,在IP:1.190.203.218成功登录系统。请注意保管好您的账号密码。", + "read_at": "", + "created_at": "2021-07-02 15:31:03" + } + ], + "page": { + "current": 1, + "total_page": 2, + "per_page": 15, + "has_more": true, + "total": 16 + } + } +} +``` + +### 4. 消息详情 + +> GET: /api/notifications/{notification_id} + +```json +{ + "status": "SUCCESS", + "status_code": 200, + "data": { + "notification_id": "91e31d1e-95e1-40ff-88d8-8049df974860", + "type": "SystemNotification", + "title": "登录成功", + "content": "您于北京时间:2021-07-02 15:31:03,通过【账号密码】渠道,在IP:1.190.203.218成功登录系统。请注意保管好您的账号密码。", + "read_at": "2021-07-02 16:27:07", + "created_at": "2021-07-02 15:31:03" + } +} +``` + +### 5. 全部标记已读 + +> PUT: /api/notifications + +```json +{ + "status": "SUCCESS", + "status_code": 200, + "data": "" +} +``` + +### 6. 全部标记已读(分类) + +> PUT: /api/notifications/{type} + +```json +{ + "status": "SUCCESS", + "status_code": 200, + "data": "" +} +``` + +### 7. 清空全部消息 + +> DELETE: /api/notifications + +```json +{ + "status": "SUCCESS", + "status_code": 200, + "data": "" +} +``` + +### 8. 清空分类消息 + +> DELETE: /api/notifications/{type} + +```json +{ + "status": "SUCCESS", + "status_code": 200, + "data": "" +} +``` \ No newline at end of file diff --git a/modules/Notification/Routes/admin.php b/modules/Notification/Routes/admin.php new file mode 100644 index 0000000..ce83f58 --- /dev/null +++ b/modules/Notification/Routes/admin.php @@ -0,0 +1,13 @@ + 'notifications', + 'namespace' => 'Admin', + 'as' => 'notification.', +], function (Router $router) { + $router->resource('templates', 'IndexController'); + $router->get('settings', 'SettingController@index'); +}); diff --git a/modules/Notification/Routes/api.php b/modules/Notification/Routes/api.php new file mode 100644 index 0000000..1369fa8 --- /dev/null +++ b/modules/Notification/Routes/api.php @@ -0,0 +1,26 @@ + 'notifications', + 'namespace' => 'Api', + 'middleware' => config('api.route.middleware_guess'), +], function (Router $router) { + // 用户未读消息数量,不登陆返回0 + $router->get('counts', 'IndexController@counts'); +}); + +Route::group([ + 'prefix' => 'notifications', + 'namespace' => 'Api', + 'middleware' => config('api.route.middleware_auth'), +], function (Router $router) { + $router->get('', 'IndexController@index'); + $router->get('{type}/list', 'IndexController@type'); + $router->put('{type?}', 'IndexController@markAsRead'); + $router->delete('{type?}', 'IndexController@clean'); + $router->get('{id}', 'IndexController@show'); +}); + diff --git a/modules/Notification/composer.json b/modules/Notification/composer.json new file mode 100644 index 0000000..f47780e --- /dev/null +++ b/modules/Notification/composer.json @@ -0,0 +1,23 @@ +{ + "name": "uztech/notification-module", + "description": "", + "type": "laravel-module", + "authors": [ + { + "name": "Jason.Chen", + "email": "chenjxlg@163.com" + } + ], + "require": { + "genealabs/laravel-model-caching": "^0.11.3", + "getuilaboratory/getui-pushapi-php-client-v2": "dev-master" + }, + "extra": { + "module-dir": "modules" + }, + "autoload": { + "psr-4": { + "Modules\\Notification\\": "" + } + } +} diff --git a/modules/Notification/module.json b/modules/Notification/module.json new file mode 100644 index 0000000..d657f67 --- /dev/null +++ b/modules/Notification/module.json @@ -0,0 +1,17 @@ +{ + "name": "Notification", + "alias": "notification", + "description": "消息通知管理模块", + "keywords": [], + "priority": 0, + "providers": [ + "Modules\\Notification\\Providers\\NotificationServiceProvider" + ], + "aliases": {}, + "files": [], + "requires": [ + "Linker" + ], + "version": "1.0.0", + "author": "Jason.Chen" +} diff --git a/modules/Payment/.gitignore b/modules/Payment/.gitignore new file mode 100644 index 0000000..4b6cb11 --- /dev/null +++ b/modules/Payment/.gitignore @@ -0,0 +1,4 @@ +.idea +vendor +.DS_Store +composer.lock \ No newline at end of file diff --git a/modules/Payment/Config/config.php b/modules/Payment/Config/config.php new file mode 100644 index 0000000..b2105e5 --- /dev/null +++ b/modules/Payment/Config/config.php @@ -0,0 +1,18 @@ + 3, + 'logger' => true,//是否开启日志,yansongda/pay v3版本有效 + /* + |-------------------------------------------------------------------------- + | 退款单编号前缀 + |-------------------------------------------------------------------------- + */ + 'refund_no_counter_prefix' => 'RF', + /* + |-------------------------------------------------------------------------- + | 退款单编号计数器位数,取值范围 6 - 16 - 退款编号前缀位数 + |-------------------------------------------------------------------------- + */ + 'refund_no_counter_length' => 6, +]; diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_alipays_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_alipays_table.php new file mode 100644 index 0000000..4f5365d --- /dev/null +++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_alipays_table.php @@ -0,0 +1,42 @@ +id(); + $table->string('name'); + $table->string('app_id'); + $table->string('notify_url')->nullable(); + $table->string('return_url')->nullable(); + $table->text('ali_public_key')->nullable(); + $table->text('private_key')->nullable(); + $table->string('app_cert_path')->nullable()->comment('应用公钥证书'); + $table->string('alipay_cert_path')->nullable()->comment('支付宝公钥证书'); + $table->string('alipay_root_cert_path')->nullable()->comment('支付宝根证书文件'); + $table->json('log'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('payment_alipays'); + } + +} diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_bills_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_bills_table.php new file mode 100644 index 0000000..9b4e94f --- /dev/null +++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_bills_table.php @@ -0,0 +1,34 @@ +id(); + $table->date('date')->comment('账单日期'); + $table->decimal('total', 20)->comment('累计收款'); + $table->decimal('refund', 20)->comment('累计退款'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('payment_bills'); + } + +} diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_notifies_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_notifies_table.php new file mode 100644 index 0000000..4136297 --- /dev/null +++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_notifies_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('payment_id'); + $table->json('payload'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('payment_notifies'); + } + +} diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_redpacks_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_redpacks_table.php new file mode 100644 index 0000000..fd4b8ee --- /dev/null +++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_redpacks_table.php @@ -0,0 +1,39 @@ +id(); + $table->boolean('type')->default(0)->index()->comment('0:普通红包,1:裂变红包'); + $table->uuid('redpack_no')->comment('商户订单号')->index(); + $table->decimal('amount')->comment('发放金额'); + $table->unsignedTinyInteger('number')->default(1)->comment('总发放个数'); + $table->enum('driver', ['alipay', 'wechat'])->index()->comment('付款渠道'); + $table->string('to')->index()->comment('用户OPENID'); + $table->string('status', 16)->nullable()->comment('发放状态'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('payment_redpacks'); + } + +} diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_refunds_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_refunds_table.php new file mode 100644 index 0000000..4b97049 --- /dev/null +++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_refunds_table.php @@ -0,0 +1,35 @@ +id(); + $table->unsignedBigInteger('payment_id')->index(); + $table->uuid('refund_no')->comment('退款单号'); + $table->decimal('total', 20)->comment('退款金额'); + $table->timestamp('refund_at')->nullable()->comment('退款时间'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('payment_refunds'); + } + +} diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_settings_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_settings_table.php new file mode 100644 index 0000000..1ba2926 --- /dev/null +++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_settings_table.php @@ -0,0 +1,37 @@ +id(); + $table->string('name')->comment('配置名称'); + $table->tinyInteger('trade_id_counter_length')->default(6); + $table->boolean('in_use')->default(0)->comment('是否是使用中的配置'); + $table->unsignedBigInteger('alipay_id')->nullable(); + $table->unsignedBigInteger('wechat_id')->nullable(); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('payment_settings'); + } + +} diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_transfers_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_transfers_table.php new file mode 100644 index 0000000..2a88814 --- /dev/null +++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_transfers_table.php @@ -0,0 +1,40 @@ +id(); + $table->uuid('transfer_no')->index()->comment('商户订单号'); + $table->decimal('amount')->comment('转账金额'); + $table->enum('driver', ['alipay', 'wechat'])->index()->comment('付款渠道'); + $table->string('to')->index()->comment('微信openid,支付宝identity'); + $table->boolean('check')->default(0)->comment('是否校验真实姓名'); + $table->string('user_name')->nullable()->comment('收款人姓名'); + $table->string('desc')->nullable()->comment('付款说明'); + $table->string('status', 16)->nullable()->comment('转账状态'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('payment_transfers'); + } + +} diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_wechats_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_wechats_table.php new file mode 100644 index 0000000..f4ca3b3 --- /dev/null +++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_wechats_table.php @@ -0,0 +1,42 @@ +id(); + $table->string('name'); + $table->string('appid')->nullable(); + $table->string('app_id')->nullable(); + $table->string('miniapp_id')->nullable(); + $table->string('mch_id'); + $table->string('key'); + $table->string('notify_url')->nullable(); + $table->string('cert_client')->nullable(); + $table->string('cert_key')->nullable(); + $table->json('log'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('payment_wechats'); + } + +} diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payments_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payments_table.php new file mode 100644 index 0000000..5a5d8b5 --- /dev/null +++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payments_table.php @@ -0,0 +1,44 @@ +id(); + $table->morphs('order'); + $table->unsignedBigInteger('user_id')->index(); + $table->uuid('trade_id')->unique(); + $table->decimal('total', 20, 2)->unsigned()->comment('支付金额'); + $table->enum('driver', ['alipay', 'wechat', 'score'])->index()->comment('支付方式'); + $table->string('gateway', 16)->comment('支付渠道'); + $table->string('state', 16)->index()->comment('订单状态'); + $table->uuid('transaction_id')->nullable()->index()->comment('三方交易号'); + $table->timestamp('paid_at')->nullable()->comment('订单支付时间'); + $table->timestamps(); + + $table->index('created_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('payments'); + } + +} diff --git a/modules/Payment/Events/Paid.php b/modules/Payment/Events/Paid.php new file mode 100644 index 0000000..4393c34 --- /dev/null +++ b/modules/Payment/Events/Paid.php @@ -0,0 +1,39 @@ +payment = $payment; + } + +} \ No newline at end of file diff --git a/modules/Payment/Facades/Pay.php b/modules/Payment/Facades/Pay.php new file mode 100644 index 0000000..4050a40 --- /dev/null +++ b/modules/Payment/Facades/Pay.php @@ -0,0 +1,31 @@ +disableFilter(); + + $grid->column('id', '#ID#'); + $grid->column('name', '支付名称'); + $grid->column('app_id', '应用APPID'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Alipay()); + + $form->text('name', '支付名称')->required(); + + $form->text('app_id', '应用APPID')->required(); + $form->url('notify_url', '通知地址'); + $form->url('return_url', '返回地址'); + + $form->textarea('ali_public_key', '支付公钥'); + $form->textarea('private_key', '私钥'); + + $form->text('app_cert_path', '应用公钥证书')->help('相对/storage/app下的路径如:certs/XXX.crt'); + $form->text('alipay_cert_path', '支付宝公钥证书')->help('相对/storage/app下的路径如:certs/XXX.crt'); + $form->text('alipay_root_cert_path', '支付宝根证书文件')->help('相对/storage/app下的路径如:certs/XXX.crt'); + + $form->embeds('log', '日志配置', function (Form\EmbeddedForm $form) { + $form->text('file', '日志文件名'); + $form->select('level')->options([ + 'info' => 'info', + 'debug' => 'debug', + ])->default('info'); + $form->select('type', '记录方式')->options([ + 'daily' => '按日期', + 'single' => '单文件', + ])->default('daily'); + $form->number('max_file') + ->default(30) + ->help('当 【记录方式】 为 【按日期】 时有效'); + }); + + return $form; + } + + public function ajax(Request $request) + { + $q = $request->q; + + return Alipay::where('name', 'like', "%$q%") + ->orWhere('app_id', 'like', "%$q%") + ->paginate(null, ['id', 'name as text']); + } + +} \ No newline at end of file diff --git a/modules/Payment/Http/Controllers/Admin/BillController.php b/modules/Payment/Http/Controllers/Admin/BillController.php new file mode 100644 index 0000000..d99746c --- /dev/null +++ b/modules/Payment/Http/Controllers/Admin/BillController.php @@ -0,0 +1,26 @@ +column('date', '账单日期'); + $grid->column('total', '累计收款'); + $grid->column('refund', '累计退款'); + $grid->column('created_at', '统计时间'); + + return $grid; + } + +} \ No newline at end of file diff --git a/modules/Payment/Http/Controllers/Admin/IndexController.php b/modules/Payment/Http/Controllers/Admin/IndexController.php new file mode 100644 index 0000000..e4472cd --- /dev/null +++ b/modules/Payment/Http/Controllers/Admin/IndexController.php @@ -0,0 +1,60 @@ +disableCreateButton(); + $grid->disableActions(); + + $grid->quickSearch('trade_id')->placeholder('交易单号'); + + $grid->filter(function (Grid\Filter $filter) { + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->like('trade_id', '交易单号'); + $filter->like('transaction_id', '交易单号'); + $filter->like('user.username', '下单用户'); + $filter->between('created_at', '下单时间')->datetime(); + + }); + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->equal('driver', '支付通道')->select(Payment::DRIVER_MAP); + $filter->equal('state', '支付状态')->select(Payment::STATUS_MAP); + $filter->between('paid_at', '支付时间')->datetime(); + }); + }); + + $grid->column('trade_id', '交易单号'); + $grid->column('transaction_id', '支付单号'); + $grid->column('user.username', '下单用户'); + $grid->column('driver', '支付通道') + ->using(Payment::DRIVER_MAP) + ->label(Payment::DRIVER_LABEL_MAP); + $grid->column('gateway', '支付渠道'); + $grid->column('state', '支付状态') + ->using(Payment::STATUS_MAP) + ->label(Payment::STATUS_LABEL_MAP); + $grid->column('total', '应付金额'); + $grid->column('paid_at', '支付时间'); + $grid->column('退款状态')->display(function () { + return $this->refund_status_text; + })->link(function () { + return route('admin.payment.refunds.index', ['payment[trade_id]' => $this->trade_id]); + }, '_self'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + +} \ No newline at end of file diff --git a/modules/Payment/Http/Controllers/Admin/RedpackController.php b/modules/Payment/Http/Controllers/Admin/RedpackController.php new file mode 100644 index 0000000..3703c10 --- /dev/null +++ b/modules/Payment/Http/Controllers/Admin/RedpackController.php @@ -0,0 +1,50 @@ +disableActions(); + $grid->disableCreateButton(); + + $grid->quickSearch('redpack_no')->placeholder('红包编号'); + + $grid->filter(function (Grid\Filter $filter) { + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('redpack_no', '红包编号'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('driver', '发送渠道')->select(Payment::DRIVER_MAP); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('type', '红包类型')->select(Redpack::TYPE_MAP); + }); + }); + + $grid->column('redpack_no', '红包编号'); + $grid->column('driver', '发送渠道') + ->using(Payment::DRIVER_MAP) + ->label(Payment::DRIVER_LABEL_MAP); + $grid->column('type', '红包类型') + ->using(Redpack::TYPE_MAP); + $grid->column('amount', '红包金额'); + $grid->column('to', '发送对象'); + $grid->column('status', '发送状态'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + +} \ No newline at end of file diff --git a/modules/Payment/Http/Controllers/Admin/RefundController.php b/modules/Payment/Http/Controllers/Admin/RefundController.php new file mode 100644 index 0000000..4b29364 --- /dev/null +++ b/modules/Payment/Http/Controllers/Admin/RefundController.php @@ -0,0 +1,47 @@ +disableCreateButton(); + $grid->disableActions(); + + $grid->quickSearch('refund_no')->placeholder('退款单号'); + $grid->filter(function (Grid\Filter $filter) { + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->like('payment.trade_id', '订单编号'); + }); + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->like('refund_no', '退款单号'); + }); + }); + + $grid->column('payment.trade_id', '订单编号') + ->link(function () { + return route('admin.payment.index', ['trade_id' => $this->payment->trade_id]); + }, '_self'); + $grid->column('payment.total', '订单金额'); + $grid->column('refund_no', '退款单号'); + $grid->column('total', '退款金额'); + $grid->column('退款类型')->display(function () { + return $this->total === $this->payment->total ? '全额退款' : '部分退款'; + }); + $grid->column('refund_at', '退款时间'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + +} \ No newline at end of file diff --git a/modules/Payment/Http/Controllers/Admin/SettingController.php b/modules/Payment/Http/Controllers/Admin/SettingController.php new file mode 100644 index 0000000..a110e86 --- /dev/null +++ b/modules/Payment/Http/Controllers/Admin/SettingController.php @@ -0,0 +1,73 @@ +disableFilter(); + + $grid->model()->orderByDesc('in_use'); + + $grid->column('name', '配置名称'); + $grid->column('trade_id_counter_length', '计数器位数'); + $grid->column('in_use', '使用中')->bool(); + $grid->column('wechat.name', '微信支付'); + $grid->column('alipay.name', '支付宝'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Setting()); + + $form->text('name', '配置名称') + ->required(); + $form->number('trade_id_counter_length', '计数器位数') + ->required() + ->default(4) + ->rules('integer|max:16', [ + 'max' => '计数器最大不能超过16', + ]); + $form->switch('in_use', '当前配置'); + + $form->select('wechat_id', '微信支付') + ->options(function ($wechatId) { + if ($wechatId) { + $wechat = Wechat::find($wechatId); + if ($wechat) { + return [$wechat->id => $wechat->name]; + } + } + }) + ->ajax(route('admin.payment.wechats.ajax')); + $form->select('alipay_id', '支付宝') + ->options(function ($alipayId) { + if ($alipayId) { + $alipay = Alipay::find($alipayId); + if ($alipay) { + return [$alipay->id => $alipay->name]; + } + } + }) + ->ajax(route('admin.payment.alipays.ajax')); + + return $form; + } + +} + diff --git a/modules/Payment/Http/Controllers/Admin/TransferController.php b/modules/Payment/Http/Controllers/Admin/TransferController.php new file mode 100644 index 0000000..1f8e4a1 --- /dev/null +++ b/modules/Payment/Http/Controllers/Admin/TransferController.php @@ -0,0 +1,50 @@ +disableActions(); + $grid->disableCreateButton(); + + $grid->quickSearch('transfer_no')->placeholder('转账单号'); + + $grid->filter(function (Grid\Filter $filter) { + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('transfer_no', '转账单号'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('driver', '转账渠道')->select(Payment::DRIVER_MAP); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('user_name', '收款人姓名'); + }); + }); + + $grid->column('transfer_no', '转账单号'); + $grid->column('driver', '转账渠道') + ->using(Payment::DRIVER_MAP) + ->label(Payment::DRIVER_LABEL_MAP);; + $grid->column('to', '收款用户'); + $grid->column('check', '实名校验')->bool(); + $grid->column('user_name', '收款人姓名'); + $grid->column('desc', '转账附言'); + $grid->column('status', '转账状态'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + +} \ No newline at end of file diff --git a/modules/Payment/Http/Controllers/Admin/WechatController.php b/modules/Payment/Http/Controllers/Admin/WechatController.php new file mode 100644 index 0000000..4f4a004 --- /dev/null +++ b/modules/Payment/Http/Controllers/Admin/WechatController.php @@ -0,0 +1,96 @@ +disableFilter(); + $grid->column('id', '#ID#'); + $grid->column('name', '支付名称'); + $grid->column('appid', 'APP APPID'); + $grid->column('app_id', '公众号 APPID'); + $grid->column('miniapp_id', '小程序 APPID'); + $grid->column('mch_id', '商户号'); + + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Wechat()); + + $form->text('name', '支付名称') + ->required(); + + $form->text('appid', 'APP APPID'); + $form->text('app_id', '公众号 APPID'); + $form->text('miniapp_id', '小程序 APPID'); + $form->text('mch_id', '商户号') + ->required() + ->rules('required|size:10', [ + 'required' => '商户号必须填写', + 'size' => '商户号长度应为:size位', + ]); + $form->text('key', '支付密钥') + ->required()->rules('required|size:32', [ + 'required' => '支付密钥必须填写', + 'size' => '支付密钥长度应为:size位', + ]); + $form->url('notify_url', '通知地址'); + + // $form->file('cert_client', '公钥证书') + // ->disk('local') + // ->move('certs') + // ->removable(); + // + // $form->file('cert_key', '私钥证书') + // ->disk('local') + // ->move('certs') + // ->removable(); + + $form->text('cert_client', '公钥证书')->help('相对/storage/app下的路径如:certs/apiclient_key.pem'); + $form->text('cert_key', '私钥证书')->help('相对/storage/app下的路径如:certs/apiclient_cert.pem'); + + $form->embeds('log', '日志配置', function (Form\EmbeddedForm $form) { + $form->text('file', '日志文件名')->default('wechat'); + $form->select('level')->options([ + 'info' => 'info', + 'debug' => 'debug', + ])->default('info'); + $form->select('type', '记录方式')->options([ + 'daily' => '按日期', + 'single' => '单文件', + ])->default('daily'); + $form->number('max_file') + ->default(30) + ->help('当 【记录方式】 为 【按日期】 时有效'); + }); + + return $form; + } + + public function ajax(Request $request) + { + $q = $request->q; + + return Wechat::where('name', 'like', "%$q%") + ->orWhere('mch_id', 'like', "%$q%") + ->paginate(null, ['id', 'name as text']); + } + +} \ No newline at end of file diff --git a/modules/Payment/Http/Controllers/Api/GatewayController.php b/modules/Payment/Http/Controllers/Api/GatewayController.php new file mode 100644 index 0000000..2f66d34 --- /dev/null +++ b/modules/Payment/Http/Controllers/Api/GatewayController.php @@ -0,0 +1,29 @@ + + * @return \Illuminate\Http\JsonResponse + */ + public function index(): JsonResponse + { + $setting = Setting::orderByDesc('in_use')->first(); + + return $this->success([ + 'alipay' => (bool) $setting->alipay_id, + 'wechat' => (bool) $setting->wechat_id, + ]); + } + +} \ No newline at end of file diff --git a/modules/Payment/Http/Controllers/Api/NotifyController.php b/modules/Payment/Http/Controllers/Api/NotifyController.php new file mode 100644 index 0000000..92d0858 --- /dev/null +++ b/modules/Payment/Http/Controllers/Api/NotifyController.php @@ -0,0 +1,63 @@ +verify(); + } else { + $data = $alipay->callback(); + } + + $this->getPaymentByTradeNo($data->out_trade_no); + + //设置支付完成 + if ($this->payment) { + $this->payment->paid(); + $this->payment->order->pay(); + } + + return $alipay->success(); + + } + + public function wechat() + { + $wechat = Pay::wechat(); + + if (config('payment.version', 2) == 2) { + $data = $wechat->verify(); + $this->getPaymentByTradeNo($data->out_trade_no); + } else { + $data = $wechat->callback(); + $this->getPaymentByTradeNo($data->resource['ciphertext']['out_trade_no']); + } + + //设置支付完成 + if ($this->payment) { + $this->payment->paid(); + $this->payment->order->pay(); + } + + return $wechat->success(); + } + + public function getPaymentByTradeNo($tradeNo) + { + $this->payment = Payment::where('trade_id', $tradeNo)->first(); + } + +} \ No newline at end of file diff --git a/modules/Payment/Models/Alipay.php b/modules/Payment/Models/Alipay.php new file mode 100644 index 0000000..8f4775f --- /dev/null +++ b/modules/Payment/Models/Alipay.php @@ -0,0 +1,56 @@ + 'json', + ]; + + /** + * Notes : 生成配置文件 + * + * @Date : 2021/5/26 2:21 下午 + * @Author : < Jason.C > + * @return int[] + */ + public function toConfig(): array + { + $ali_public_key = $this->ali_public_key ?? ''; + if ($this->alipay_cert_path) { + $ali_public_key = storage_path('app/'.$this->alipay_cert_path); + } + + return [ + 'app_id' => $this->app_id ?? '', + 'notify_url' => $this->notify_url ?? '', + 'return_url' => $this->return_url ?? '', + 'ali_public_key' => $ali_public_key, + 'app_secret_cert' => $this->private_key ?? '', + //应用公钥证书 + 'app_public_cert_path' => $this->app_cert_path ? storage_path('app/'.$this->app_cert_path) : '', + //支付宝公钥证书 + 'alipay_public_cert_path' => $this->alipay_cert_path ? storage_path('app/'.$this->alipay_cert_path) : '', + 'alipay_root_cert_path' => $this->alipay_root_cert_path ? storage_path('app/'.$this->alipay_root_cert_path) : '', + 'logger' => [ + 'enable' => true, + 'file' => storage_path('logs/alipay/'.$this->log['file'].'.log'), + 'level' => $this->log['level'], + 'type' => $this->log['type'], + 'max_file' => (int) $this->log['max_file'], + ], + ]; + } + +} diff --git a/modules/Payment/Models/Bill.php b/modules/Payment/Models/Bill.php new file mode 100644 index 0000000..42759bc --- /dev/null +++ b/modules/Payment/Models/Bill.php @@ -0,0 +1,12 @@ + '未支付', + self::STATUS_SUCCESS => '支付成功', + self::STATUS_FAIL => '支付失败', + self::STATUS_REFUND => '转入退款', + self::STATUS_CLOSED => '支付关闭', + self::STATUS_REVOKED => '支付撤销', + self::STATUS_ERROR => '支付错误', + self::STATUS_FINISHED => '支付完结', + ]; + + const STATUS_LABEL_MAP = [ + self::STATUS_NOPAY => 'default', + self::STATUS_SUCCESS => 'success', + self::STATUS_FAIL => 'warning', + self::STATUS_REFUND => 'info', + self::STATUS_CLOSED => 'primary', + self::STATUS_REVOKED => 'primary', + self::STATUS_ERROR => 'danger', + self::STATUS_FINISHED => 'success', + ]; + + /** + * 支付方式 + */ + const DRIVER_ALIPAY = 'alipay'; + const DRIVER_WECHAT = 'wechat'; + const DRIVER_SCORE = 'score'; + + const DRIVER_MAP = [ + self::DRIVER_ALIPAY => '支付宝', + self::DRIVER_WECHAT => '微信支付', + self::DRIVER_SCORE => '水滴支付', + ]; + + const DRIVER_LABEL_MAP = [ + self::DRIVER_ALIPAY => 'info', + self::DRIVER_WECHAT => 'success', + ]; + + protected static function boot() + { + parent::boot(); + + self::creating(function ($model) { + $time = explode(' ', microtime()); + + $counter = $model->whereDate('created_at', Carbon::today())->count() + 1; + + $len = Setting::orderByDesc('in_use')->value('trade_id_counter_length'); + + $len = $len < 6 ? 6 : $len; + $len = $len > 16 ? 16 : $len; + + $model->trade_id = date('YmdHis'). + sprintf('%06d', $time[0] * 1e6). + sprintf('%0'.$len.'d', $counter); + + $model->state = self::STATUS_NOPAY; + }); + } + + /** + * Notes: 支付渠道 + * + * @Author: 玄尘 + * @Date : 2021/5/18 11:14 + * @return string + */ + public function getDriverTextAttribute(): string + { + return self::DRIVER_MAP[$this->driver]; + } + + /** + * Notes: 支付状态 + * + * @Author: 玄尘 + * @Date : 2021/5/18 11:17 + * @return string + */ + public function getStateTextAttribute(): string + { + return self::STATUS_MAP[$this->state]; + } + + /** + * Notes : 要支付的模型 + * + * @Date : 2021/4/21 1:59 下午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\MorphTo + */ + public function order(): MorphTo + { + return $this->morphTo(); + } + + /** + * Notes : 结果通知 + * + * @Date : 2021/4/23 11:49 上午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function notifies(): HasMany + { + return $this->hasMany(PaymentNotify::class); + } + + /** + * Notes : 退款单 + * + * @Date : 2021/6/1 11:13 上午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function refunds(): HasMany + { + return $this->hasMany(Refund::class); + } + + /** + * Notes : 退款状态 + * + * @Date : 2021/6/1 11:40 上午 + * @Author : < Jason.C > + * @return string + */ + public function getRefundStatusTextAttribute(): string + { + if ($this->refunds->count()) { + return ($this->refunds->sum('total') == $this->total) ? '全额退款' : '部分退款'; + } else { + return ''; + } + } + + /** + * Notes : 直接通过 order 来设置关联的支付订单 + * + * @Date : 2021/4/21 12:43 下午 + * @Author : < Jason.C > + * @param \App\Models\Model $order + */ + public function setOrderAttribute(Model $order) + { + $this->attributes['order_type'] = get_class($order); + $this->attributes['order_id'] = $order->getKey(); + } + + /** + * Notes : 通过模型来设置所属用户 + * + * @Date : 2021/4/21 1:48 下午 + * @Author : < Jason.C > + * @param \Modules\User\Models\User $user + */ + public function setUserAttribute(User $user) + { + $this->attributes['user_id'] = $user->getKey(); + } + + /** + * Notes : 支付成功,调用的方法 + * + * @Date : 2021/4/20 5:42 下午 + * @Author : < Jason.C > + */ + public function paid(): void + { + $this->state = self::STATUS_SUCCESS; + $this->paid_at = now(); + $this->save(); + + event(new Paid($this)); + } + + /** + * Notes : 根据支付订单,获取支付渠道需要的接口数据 + * + * @Date : 2021/4/21 4:24 下午 + * @Author : < Jason.C > + * @param string $title 订单标题 + * @param array $extends 扩展数据, 微信公众号 ['openid' => OPENID] + * @return mixed + * @throws \Exception + */ + public function getPaymentParams(string $title, array $extends = []) + { + $order['out_trade_no'] = $this->trade_id; + + $driver = $this->driver; + $gateway = $this->gateway; + + if ($driver === self::DRIVER_WECHAT) { + $order['out_trade_no'] = $this->trade_id; + if (config('payment.version', 2) == 2) { + $order['body'] = $title; + $order['total_fee'] = $this->total * 100; + } else { + $order['description'] = $title; + $order['amount']['total'] = $this->total * 100; + } + } elseif ($driver === self::DRIVER_ALIPAY) { + $order['total_amount'] = $this->total; + $order['out_trade_no'] = $this->trade_id; + $order['subject'] = $title; + } else { + throw new Exception('unsupported driver'); + } + + if (! empty($extends)) { + $order = array_merge($order, $extends); + } + + return Pay::$driver()->$gateway($order); + } + +} diff --git a/modules/Payment/Models/PaymentNotify.php b/modules/Payment/Models/PaymentNotify.php new file mode 100644 index 0000000..06bf157 --- /dev/null +++ b/modules/Payment/Models/PaymentNotify.php @@ -0,0 +1,27 @@ + 'json', + ]; + + /** + * Notes : 所属支付单 + * + * @Date : 2021/5/26 9:55 上午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function payment(): BelongsTo + { + return $this->belongsTo(Payment::class); + } + +} diff --git a/modules/Payment/Models/Redpack.php b/modules/Payment/Models/Redpack.php new file mode 100644 index 0000000..49f6b82 --- /dev/null +++ b/modules/Payment/Models/Redpack.php @@ -0,0 +1,25 @@ + '普通红包', + self::GROUP_REDPACK => '裂变红包', + ]; + +} \ No newline at end of file diff --git a/modules/Payment/Models/Refund.php b/modules/Payment/Models/Refund.php new file mode 100644 index 0000000..ee4173c --- /dev/null +++ b/modules/Payment/Models/Refund.php @@ -0,0 +1,49 @@ +whereDate('created_at', Carbon::today())->count() + 1; + $len = config('payment.refund_no_counter_length'); + $prefix = config('payment.refund_no_counter_prefix'); + + $len = $len < 6 ? 6 : $len; + $len = $len > 16 ? 16 : $len; + + $model->refund_no = $prefix.date('YmdHis'). + sprintf('%06d', $time[0] * 1e6). + sprintf('%0'.$len.'d', $counter); + }); + } + + /** + * Notes : 支付订单 + * + * @Date : 2021/6/1 11:13 上午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function payment(): BelongsTo + { + return $this->belongsTo(Payment::class); + } + +} \ No newline at end of file diff --git a/modules/Payment/Models/Setting.php b/modules/Payment/Models/Setting.php new file mode 100644 index 0000000..dd65435 --- /dev/null +++ b/modules/Payment/Models/Setting.php @@ -0,0 +1,83 @@ + 'boolean', + ]; + + public static function boot() + { + parent::boot(); + + self::saved(function ($model) { + if ($model->in_use && $model->id) { + self::where('id', '<>', $model->id) + ->where('in_use', 1) + ->update(['in_use' => 0]); + } + +// Cache::tags('payment_config')->flush(); + }); + } + + /** + * Notes : 挂载的微信支付 + * + * @Date : 2021/5/26 2:01 下午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function wechat(): BelongsTo + { + return $this->belongsTo(Wechat::class); + } + + /** + * Notes : 挂载的支付宝 + * + * @Date : 2021/5/26 2:18 下午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function alipay(): BelongsTo + { + return $this->belongsTo(Alipay::class); + } + + /** + * Notes : 获取微信/支付宝的配置 + * + * @Date : 2021/5/26 2:19 下午 + * @Author : < Jason.C > + * @param string $type + * @return array + * @throws \Exception + */ + public static function getDefaultConfig(string $type): array + { + if (! in_array($type, ['wechat', 'alipay'])) { + throw new Exception('不支持的支付渠道'); + } + + $setting = self::orderByDesc('in_use')->first(); + + return $setting->$type->toConfig(); + } + +} \ No newline at end of file diff --git a/modules/Payment/Models/Traits/WithConfig.php b/modules/Payment/Models/Traits/WithConfig.php new file mode 100644 index 0000000..b63c43d --- /dev/null +++ b/modules/Payment/Models/Traits/WithConfig.php @@ -0,0 +1,37 @@ + [ + 'default' => $defaule_config, + ], + 'logger' => array_merge($defaule_config['logger'], [ + 'enable' => config('payment.logger', true), + ]), + ]; + } + + return $config; + + } + +} \ No newline at end of file diff --git a/modules/Payment/Models/Transfer.php b/modules/Payment/Models/Transfer.php new file mode 100644 index 0000000..2f66784 --- /dev/null +++ b/modules/Payment/Models/Transfer.php @@ -0,0 +1,17 @@ + 'json', + ]; + + /** + * Notes : 生成配置文件 + * + * @Date : 2021/5/26 2:21 下午 + * @Author : < Jason.C > + * @return int[] + */ + public function toConfig(): array + { + return [ + 'app_id' => $this->appid,//app + 'mp_app_id' => $this->app_id,//mp + 'mini_app_id' => $this->miniapp_id,//mini + 'mch_id' => $this->mch_id, + // 必填-商户秘钥 + 'mch_secret_key' => $this->key, + // 必填-商户私钥 字符串或路径 + 'mch_secret_cert' => $this->cert_key ? storage_path('app/'.$this->cert_key) : '', + // 必填-商户公钥证书路径 + 'mch_public_cert_path' => $this->cert_client ? storage_path('app/'.$this->cert_client) : '', + 'logger' => [ + 'file' => storage_path('logs/wechat/'.$this->log['file'].'.log'), + 'level' => $this->log['level'], + 'type' => $this->log['type'], + 'max_file' => (int) $this->log['max_file'], + ], + ]; + } + +} diff --git a/modules/Payment/Payment.php b/modules/Payment/Payment.php new file mode 100644 index 0000000..e206c28 --- /dev/null +++ b/modules/Payment/Payment.php @@ -0,0 +1,123 @@ + + */ + public static function install() + { + Artisan::call('migrate', [ + '--path' => 'modules/Payment/Database/Migrations', + ]); + + self::createAdminMenu(); + } + + /** + * Notes : 卸载模块的一些操作 + * + * @Date : 2021/3/12 11:35 上午 + * @Author : < Jason.C > + */ + public static function uninstall() + { + $menu = config('admin.database.menu_model'); + + $settingMenu = $menu::where('title', '支付设置')->get(); + + foreach ($settingMenu as $main) { + $main->delete(); + } + + $mains = $menu::where('title', self::$mainTitle)->get(); + + foreach ($mains as $main) { + $main->delete(); + } + } + + protected static function createAdminMenu() + { + $menu = config('admin.database.menu_model'); + + $main = $menu::create([ + 'parent_id' => 0, + 'order' => 30, + 'title' => self::$mainTitle, + 'icon' => 'fa-wordpress', + ]); + + $main->children()->createMany([ + [ + 'order' => 1, + 'title' => '支付订单', + 'icon' => 'fa-bars', + 'uri' => 'payments', + ], + [ + 'order' => 2, + 'title' => '退款订单', + 'icon' => 'fa-bars', + 'uri' => 'payments/refunds', + ], + [ + 'order' => 3, + 'title' => '转账订单', + 'icon' => 'fa-edit', + 'uri' => 'payments/transfers', + ], + [ + 'order' => 4, + 'title' => '现金红包', + 'icon' => 'fa-folder', + 'uri' => 'payments/redpacks', + ], + [ + 'order' => 5, + 'title' => '日账单', + 'icon' => 'fa-bars', + 'uri' => 'payments/bills', + ], + ]); + + $settingMenu = $main->children()->create([ + 'order' => 99, + 'title' => '支付设置', + 'icon' => 'fa-cogs', + 'uri' => '', + ]); + + $settingMenu->children()->createMany([ + [ + 'order' => 13, + 'title' => '支付配置', + 'icon' => 'fa-cogs', + 'uri' => 'payments/settings', + ], + [ + 'order' => 14, + 'title' => '微信支付', + 'icon' => 'fa-wechat', + 'uri' => 'payments/wechats', + ], + [ + 'order' => 15, + 'title' => '支付宝', + 'icon' => 'fa-adn', + 'uri' => 'payments/alipays', + ], + ]); + } + +} diff --git a/modules/Payment/Providers/PaymentServiceProvider.php b/modules/Payment/Providers/PaymentServiceProvider.php new file mode 100644 index 0000000..d827a42 --- /dev/null +++ b/modules/Payment/Providers/PaymentServiceProvider.php @@ -0,0 +1,98 @@ +loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations')); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->registerConfig(); + $this->app->register(RouteServiceProvider::class); + + + $this->app->singleton('pay.alipay', function () { + $alipay = Setting::getDefaultConfig('alipay'); + + return Pay::alipay([ + 'alipay' => [ + 'default' => $alipay, + ], + 'logger' => array_merge($alipay['logger'], [ + 'enable' => config('payment.logger', true), + ]), + 'notify_url' => route('api.payment.notify.alipay') + ]); + }); + + $this->app->singleton('pay.wechat', function () { + $wechat = Setting::getDefaultConfig('wechat'); + return Pay::wechat([ + 'wechat' => [ + 'default' => Setting::getDefaultConfig('wechat'), + ], + 'logger' => array_merge($wechat['logger'], [ + 'enable' => config('payment.logger', true), + ]), + 'notify_url' => route('api.payment.notify.alipay') + ]); + }); + + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides(): array + { + return ['pay.alipay', 'pay.wechat']; + } + + /** + * Register config. + * + * @return void + */ + protected function registerConfig() + { + $this->publishes([ + module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower.'.php'), + ], 'payment-config'); + + $this->mergeConfigFrom( + module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower + ); + } + +} diff --git a/modules/Payment/Providers/RouteServiceProvider.php b/modules/Payment/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..858e961 --- /dev/null +++ b/modules/Payment/Providers/RouteServiceProvider.php @@ -0,0 +1,63 @@ +mapApiRoutes(); + + $this->mapAdminRoutes(); + } + + protected function mapApiRoutes() + { + Route::as(config('api.route.as')) + ->domain(config('api.route.domain')) + ->middleware(config('api.route.middleware')) + ->namespace($this->moduleNamespace) + ->prefix(config('api.route.prefix')) + ->group(module_path($this->moduleName, 'Routes/api.php')); + } + + protected function mapAdminRoutes() + { + Route::as(config('admin.route.as')) + ->domain(config('admin.route.domain')) + ->middleware(config('admin.route.middleware')) + ->namespace($this->moduleNamespace) + ->prefix(config('admin.route.prefix')) + ->group(module_path($this->moduleName, 'Routes/admin.php')); + } + +} diff --git a/modules/Payment/README.md b/modules/Payment/README.md new file mode 100644 index 0000000..a56f4fd --- /dev/null +++ b/modules/Payment/README.md @@ -0,0 +1,46 @@ +# 支付模块 + +## 1. 安装 + +```shell +composer require yansongda/pay +``` + +## 2. 文档参考 + +> https://github.com/yansongda/pay + +## 3. 生成支付订单 +```php +use Modules\Payment\Traits\WithPayments; + +$order = Order::first(); + +$payment = $order->createWechatPayment(User::first(), -1, 'mp'); + +$payment->getPaymentParams(); +``` + + +## 4. 支付渠道 GATEWAY 参考值 +// 支付宝 +//web 电脑支付 array $order Response +//wap 手机网站支付 array $order Response +//app APP 支付 array $order Response +//mini 小程序支付 array $order Collection +//scan 扫码支付 array $order Collection +// 不支持的 +//pos 刷卡支付 array $order Collection +//transfer 账户转账 array $order Collection + +// 微信支付 +//mp 公众号支付 array $order Collection +//wap 手机网站支付 array $order Response +//app APP 支付 array $order JsonResponse +//miniapp 小程序支付 array $order Collection +//scan 扫码支付 array $order Collection +// 不支持的 +//pos 刷卡支付 array $order Collection +//transfer 账户转账 array $order Collection +//redpack 普通红包 array $order Collection +//groupRedpack 裂变红包 array $order Collection \ No newline at end of file diff --git a/modules/Payment/Routes/admin.php b/modules/Payment/Routes/admin.php new file mode 100644 index 0000000..b2898a8 --- /dev/null +++ b/modules/Payment/Routes/admin.php @@ -0,0 +1,23 @@ + 'payments', + 'namespace' => 'Admin', + 'as' => 'payment.', +], function (Router $router) { + $router->get('', 'IndexController@index')->name('index'); + $router->get('refunds', 'RefundController@index')->name('refunds.index'); + $router->get('bills', 'BillController@index'); + $router->resource('settings', 'SettingController'); + + $router->get('wechats/ajax', 'WechatController@ajax')->name('wechats.ajax'); + $router->resource('wechats', 'WechatController'); + $router->get('alipays/ajax', 'AlipayController@ajax')->name('alipays.ajax'); + $router->resource('alipays', 'AlipayController'); + + $router->get('redpacks', 'RedpackController@index'); + $router->get('transfers', 'TransferController@index'); +}); diff --git a/modules/Payment/Routes/api.php b/modules/Payment/Routes/api.php new file mode 100644 index 0000000..8bbf5ca --- /dev/null +++ b/modules/Payment/Routes/api.php @@ -0,0 +1,14 @@ + 'payments', + 'namespace' => 'Api', +], function (Router $router) { + $router->get('gateways', 'GatewayController@index'); + + $router->any('notify/wechat', 'NotifyController@wechat')->name('payment.notify.wechat'); + $router->any('notify/alipay', 'NotifyController@alipay')->name('payment.notify.alipay'); +}); diff --git a/modules/Payment/Traits/WithPayments.php b/modules/Payment/Traits/WithPayments.php new file mode 100644 index 0000000..de031fc --- /dev/null +++ b/modules/Payment/Traits/WithPayments.php @@ -0,0 +1,160 @@ + + * @throws \Exception + */ + protected function getTotalPayAmount() + { + if (! in_array('amount_field', array_keys(get_class_vars(__CLASS__))) || empty($this->amount_field)) { + throw new Exception('Model need [amount_field] property'); + } + + return $this->getOriginal($this->amount_field); + } + + /** + * Notes : 关联支付订单 + * + * @Date : 2021/4/21 1:49 下午 + * @Author : < Jason.C > + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ + public function payments(): MorphMany + { + return $this->morphMany(Payment::class, 'order'); + } + + /** + * Notes: 关联支付信息 + * + * @Author: 玄尘 + * @Date : 2021/5/17 9:26 + * @return \Illuminate\Database\Eloquent\Relations\MorphOne + */ + public function payment(): MorphOne + { + return $this->morphOne(Payment::class, 'order')->where('state', Payment::STATUS_SUCCESS); + } + + /** + * Notes: 是否支付 + * + * @Author: 玄尘 + * @Date : 2021/5/17 9:04 + */ + public function isPay(): bool + { + return $this->payments()->where('state', Payment::STATUS_SUCCESS)->exists(); + } + + /** + * Notes : 创建微信支付订单 + * + * @Date : 2021/4/21 1:55 下午 + * @Author : < Jason.C > + * @param \Modules\User\Models\User $user 下单用户 + * @param float $total 订单金额 + * @param string $gateway 支付渠道 + * @return \Illuminate\Database\Eloquent\Model + * @throws \Exception + */ + public function createWechatPayment(User $user, float $total = -1, string $gateway = 'mp'): Model + { + $gatewayAll = ['mp', 'wap', 'app', 'miniapp', 'scan']; + if (config('payment.version', 2) == 3) { + $gatewayAll = ['mp', 'wap', 'app', 'mini', 'scan']; + if ($gateway == 'miniapp') { + $gateway = 'mini'; + } + } + + if (! in_array($gateway, $gatewayAll)) { + throw new Exception('Unsupported Wechat gateway'); + } + + // 自动获取订单金额 + if ($total < 0) { + $total = $this->getTotalPayAmount(); + } + + return $this->payments()->create([ + 'user' => $user, + 'total' => $total, + 'driver' => Payment::DRIVER_WECHAT, + 'gateway' => $gateway, + ]); + } + + /** + * Notes : 创建支付宝订单 + * + * @Date : 2021/4/23 10:24 上午 + * @Author : < Jason.C > + * @param \Modules\User\Models\User $user 下单用户 + * @param float $total 订单金额 + * @param string $gateway 支付渠道 + * @return \Illuminate\Database\Eloquent\Model + * @throws \Exception + */ + public function createAlipayPayment(User $user, float $total = -1, string $gateway = 'web'): Model + { + if (! in_array($gateway, ['web', 'wap', 'app', 'mini', 'scan'])) { + throw new Exception('Unsupported Alipay gateway'); + } + // 自动获取订单金额 + if ($total < 0) { + $total = $this->getTotalPayAmount(); + } + + return $this->payments()->create([ + 'user' => $user, + 'total' => $total, + 'driver' => Payment::DRIVER_ALIPAY, + 'gateway' => $gateway, + ]); + } + + /** + * Notes: 水滴支付 + * + * @Author: 玄尘 + * @Date: 2022/9/5 15:50 + * @param User $user + * @param float $total + * @param string $gateway + * @return Model + * @throws Exception + */ + public function createScorePayment(User $user, float $total = -1, string $gateway = 'web'): Model + { + // 自动获取订单金额 + if ($total < 0) { + $total = $this->getTotalPayAmount(); + } + + return $this->payments()->create([ + 'user' => $user, + 'total' => $total, + 'driver' => Payment::DRIVER_SCORE, + 'gateway' => $gateway, + ]); + } + +} \ No newline at end of file diff --git a/modules/Payment/composer.json b/modules/Payment/composer.json new file mode 100644 index 0000000..bf69f61 --- /dev/null +++ b/modules/Payment/composer.json @@ -0,0 +1,23 @@ +{ + "name": "jasonc/payment-module", + "description": "支付模块", + "type": "laravel-module", + "authors": [ + { + "name": "Jason.Chen", + "email": "chenjxlg@163.com" + } + ], + "require": { + "genealabs/laravel-model-caching": "^0.11.3", + "yansongda/pay": "^v2.10.0" + }, + "extra": { + "module-dir": "modules" + }, + "autoload": { + "psr-4": { + "Modules\\Payment\\": "" + } + } +} \ No newline at end of file diff --git a/modules/Payment/module.json b/modules/Payment/module.json new file mode 100644 index 0000000..2148e07 --- /dev/null +++ b/modules/Payment/module.json @@ -0,0 +1,15 @@ +{ + "name": "Payment", + "alias": "payment", + "description": "支付管理模块", + "keywords": [], + "priority": 0, + "providers": [ + "Modules\\Payment\\Providers\\PaymentServiceProvider" + ], + "aliases": {}, + "files": [], + "requires": [], + "version": "1.0.0", + "author": "Jason.Chen" +} \ No newline at end of file diff --git a/modules/README.md b/modules/README.md new file mode 100644 index 0000000..b492f2a --- /dev/null +++ b/modules/README.md @@ -0,0 +1,81 @@ +# 模块存放目录 + +## 1. 模块列表 + +### 1. User 【用户模块】 + +> 模块地址:http://git.yuzhankeji.cn/UzTech/laravel-user-module.git +> +> 后台菜单排序:10 + +### 2. Cms 【内容管理】 + +> 模块地址:https://git.yuzhankeji.cn/UzTech/laravel-cms-module +> +> 后台菜单排序:15 + +### 3. Mall 【商城模块】 + +> 模块地址:https://git.yuzhankeji.cn/UzTech/laravel-mall-module +> +> 后台菜单排序:20 + +### 4. Payment 【支付中心】 + +> 模块地址:https://git.yuzhankeji.cn/UzTech/laravel-payment-module +> +> 后台菜单排序:25 + +### 5. Settlement 【结算中心】 + +> 模块地址: +> +> 后台菜单排序:30 + +### 6. Chain 【区块链管理】 + +> 模块地址: +> +> 后台菜单排序:35 + +### 7. Notification 【消息中心】 + +> 模块地址: +> +> 后台菜单排序:40 + +### 8. Linker 【应用链接管理】 + +> 模块地址: +> +> 后台菜单排序:45 + +### 9. AppVersion 【APP版本管理】 + +> 模块地址: +> +> 后台菜单排序:50 + +### 10. OmniForm 【万能表单】 + +> 模块地址: +> +> 后台菜单排序:55 + +### 11. Tao 【淘宝客联盟】 + +> 模块地址: +> +> 后台菜单排序:60 + +### 12. Advert 【广告联盟】 + +> 模块地址: +> +> 后台菜单排序:65 + +### 13. Aliyun 【阿里云管理】 + +> 模块地址: +> +> 后台菜单排序:90 \ No newline at end of file diff --git a/modules/Storage/.gitignore b/modules/Storage/.gitignore new file mode 100644 index 0000000..4b6cb11 --- /dev/null +++ b/modules/Storage/.gitignore @@ -0,0 +1,4 @@ +.idea +vendor +.DS_Store +composer.lock \ No newline at end of file diff --git a/modules/Storage/Config/config.php b/modules/Storage/Config/config.php new file mode 100644 index 0000000..f5e27d3 --- /dev/null +++ b/modules/Storage/Config/config.php @@ -0,0 +1,45 @@ + 5 * 1024 * 1024, + + 'upload_path' => 'uploads', + + 'driver' => env('FILESYSTEM_DRIVER', 'public'), + + 'disks' => [ + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL').'/storage', + 'visibility' => 'public', + ], + + 'oss' => [ + 'driver' => 'oss', + 'root' => '', // 设置上传时根前缀 + 'access_key' => env('OSS_ACCESS_KEY'), + 'secret_key' => env('OSS_SECRET_KEY'), + 'endpoint' => env('OSS_ENDPOINT'), + 'bucket' => env('OSS_BUCKET'), + 'isCName' => env('OSS_IS_CNAME', false), + 'cdnHost' => env('OSS_CDN_HOST'), + 'buckets' => [ + 'test' => [ + 'access_key' => env('OSS_ACCESS_KEY'), + 'secret_key' => env('OSS_SECRET_KEY'), + 'bucket' => env('OSS_TEST_BUCKET'), + 'endpoint' => env('OSS_TEST_ENDPOINT'), + 'isCName' => env('OSS_TEST_IS_CNAME', false), + 'cdnHost' => env('OSS_TEXT_CDN_HOST'), + ], + ], + // 以下是STS用的,不使用STS直传可不用配置 + 'RegionId' => env('OSS_STS_REGION_ID', 'cn-beijing'), + 'RoleArn' => env('OSS_STS_ROLE_ARN', ''), + ], + ], + +]; diff --git a/modules/Storage/Database/Migrations/0000_00_00_000000_create_file_storages_table.php b/modules/Storage/Database/Migrations/0000_00_00_000000_create_file_storages_table.php new file mode 100644 index 0000000..4be3bc0 --- /dev/null +++ b/modules/Storage/Database/Migrations/0000_00_00_000000_create_file_storages_table.php @@ -0,0 +1,36 @@ +id(); + $table->string('driver', 16); + $table->string('hash', 32)->index(); + $table->string('type', 32); + $table->unsignedInteger('size'); + $table->string('path'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('file_storages'); + } + +} diff --git a/modules/Storage/Http/Controllers/OssController.php b/modules/Storage/Http/Controllers/OssController.php new file mode 100644 index 0000000..e82d29f --- /dev/null +++ b/modules/Storage/Http/Controllers/OssController.php @@ -0,0 +1,133 @@ +uploadPath = Config::get('storage.upload_path').date('/Y/m/d'); + } + + /** + * Notes : 普通文件上传 + * + * @Date : 2021/4/25 5:25 下午 + * @Author : + * @param Request $request + * @return mixed + */ + public function upload(Request $request) + { + $upload = $request->file('upload'); + + $size = File::size($upload->path()); + + if ($size > Config::get('storage.max_upload_size')) { + return $this->failed('超过最大允许上传大小', 422); + } + $exists = true; + $hash = File::hash($upload->path()); + + $existFile = StorageModel::where('driver', Config::get('storage.driver'))->where('hash', $hash)->first(); + + if ($existFile) { + $fullName = $existFile->path; + } else { + $path = $this->uploadPath; + $fileName = $hash.'.'.$upload->getClientOriginalExtension(); + $fullName = $path.'/'.$fileName; + + $uploaded = Storage::putFileAs($path, $upload, $fileName); + + if (! $uploaded) { + return $this->failed('文件上传失败', 422); + } + $exists = false; + StorageModel::create([ + 'hash' => $hash, + 'driver' => Config::get('storage.driver'), + 'type' => $upload->getClientMimeType(), + 'size' => $size, + 'path' => $fullName, + ]); + } + + return $this->success([ + 'exists' => $exists, + 'size' => $size, + 'path' => $fullName, + 'url' => Storage::url($fullName), + ]); + } + + /** + * Notes : 多图片统一上传 + * + * @Date : 2021/8/23 13:49 + * @Author : Mr.wang + * @param Request $request + * @return mixed + */ + public function uploads(Request $request) + { + $fullFile = []; + $files = []; + if ($request->file()) { + foreach ($request->file() as $key => $upload) { + $size = File::size($upload->path()); + + if ($size > Config::get('storage.max_upload_size')) { + $message = '第'.$key + 1 .'张图片超过最大允许上传大小'; + + return $this->failed($message, 422); + } + $hash = File::hash($upload->path()); + + $existFile = StorageModel::where('driver', Config::get('storage.driver')) + ->where('hash', $hash) + ->first(); + if ($existFile) { + $fullName = $existFile->path; + } else { + $path = $this->uploadPath; + $fileName = $hash.'.'.$upload->getClientOriginalExtension(); + $fullName = $path.'/'.$fileName; + + $uploaded = Storage::putFileAs($path, $upload, $fileName); + + if (! $uploaded) { + return $this->failed('文件上传失败', 422); + } + StorageModel::create([ + 'hash' => $hash, + 'driver' => Config::get('storage.driver'), + 'type' => $upload->getClientMimeType(), + 'size' => $size, + 'path' => $fullName, + ]); + } + $fullFile[] = Storage::url($fullName); + $files[] = $fullName; + } + + return $this->success([ + 'path' => $files, + 'url' => $fullFile, + ]); + } else { + return $this->failed('没有图片'); + } + } + +} diff --git a/modules/Storage/Http/Controllers/StsController.php b/modules/Storage/Http/Controllers/StsController.php new file mode 100644 index 0000000..65efaec --- /dev/null +++ b/modules/Storage/Http/Controllers/StsController.php @@ -0,0 +1,80 @@ + + * @return mixed + */ + public function config() + { + $config = Config::get('storage.disks.oss'); + + try { + AlibabaCloud::accessKeyClient( + $config['access_key'], + $config['secret_key'], + )->regionId($config['RegionId'])->asDefaultClient(); + } catch (ClientException $e) { + return $this->failed($e->getErrorMessage()); + } + + try { + $Policy = [ + 'Version' => "1", + 'Statement' => [ + [ + 'Effect' => 'Allow', + 'Action' => 'oss:*', + 'Resource' => [ + "*", + ], + ], + ], + ]; + + $result = AlibabaCloud::rpc() + ->product('Sts') + ->scheme('https') // https | http + ->version('2015-04-01') + ->action('AssumeRole') + ->method('POST') + ->host('sts.aliyuncs.com') + ->options([ + 'query' => [ + 'RegionId' => $config['RegionId'], + 'RoleArn' => $config['RoleArn'], + 'RoleSessionName' => 'RoleSessionName', + // 'Policy' => json_encode($Policy), + 'DurationSeconds' => 3600, + ], + ]) + ->request(); + + return $this->success([ + 'secure' => true, + 'bucket' => $config['bucket'], + 'region' => $config['RegionId'], // 前缀带 oss- ? + 'accessKeyId' => $result->Credentials->AccessKeyId, + 'accessKeySecret' => $result->Credentials->AccessKeySecret, + 'stsToken' => $result->Credentials->SecurityToken, + 'endpoint' => $config['endpoint'], + 'cname' => $config['isCName'], + ]); + } catch (ClientException | ServerException $e) { + return $this->failed($e->getErrorMessage()); + } + } +} diff --git a/modules/Storage/Models/Storage.php b/modules/Storage/Models/Storage.php new file mode 100644 index 0000000..d195d96 --- /dev/null +++ b/modules/Storage/Models/Storage.php @@ -0,0 +1,12 @@ +mapApiRoutes(); + } + + protected function mapApiRoutes() + { + Route::as(config('api.route.as')) + ->domain(config('api.route.domain')) + ->middleware(config('api.route.middleware')) + ->namespace($this->moduleNamespace) + ->prefix(config('api.route.prefix')) + ->group(module_path($this->moduleName, 'Routes/api.php')); + } + +} diff --git a/modules/Storage/Providers/StorageServiceProvider.php b/modules/Storage/Providers/StorageServiceProvider.php new file mode 100644 index 0000000..00873b6 --- /dev/null +++ b/modules/Storage/Providers/StorageServiceProvider.php @@ -0,0 +1,73 @@ +loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations')); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->registerConfig(); + + $driver = Config::get('storage.driver'); + Config::set('filesystems.default', $driver); + $disks = Config::get('storage.disks'); + Config::set('filesystems.disks.'.$driver, data_get($disks, $driver)); + + $this->app->register(RouteServiceProvider::class); + } + + /** + * Register config. + * + * @return void + */ + protected function registerConfig() + { + $this->publishes([ + module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower.'.php'), + ], 'config'); + $this->mergeConfigFrom( + module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower + ); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides(): array + { + return []; + } + +} diff --git a/modules/Storage/README.md b/modules/Storage/README.md new file mode 100644 index 0000000..0d2f29c --- /dev/null +++ b/modules/Storage/README.md @@ -0,0 +1,70 @@ +# 文件存储模块 + +## 1. 前端上传接口 + +## 2. 配置STS直传 + +### 步骤一:创建RAM用户 + +- 登录RAM控制台。 +- 在左侧导航栏的人员管理菜单下,单击用户。 +- 单击新建用户。 +- 输入登录名称和显示名称。 +- 在访问方式区域下,选择编程访问,然后单击确定。 +- 单击复制,保存访问密钥(AccessKey ID 和 AccessKey Secret)。 + +### 步骤二:为RAM用户授予请求AssumeRole的权限 + +- 单击已创建RAM用户右侧对应的添加权限。 +- 在添加权限页面,选择AliyunSTSAssumeRoleAccess权限。 +- 单击确定。 + +### 步骤三:创建用于获取临时访问凭证的角色 + +- 在左侧导航栏,单击RAM角色管理。 +- 单击新建RAM角色,选择可信实体类型为阿里云账号,单击下一步。 +- 在新建RAM角色页面,RAM角色名称填写为RamOssTest,选择云账号为当前云账号。 +- 单击完成。 +- 单击复制,保存角色的ARN。 + +### 步骤四:为角色授予上传文件的权限 + +- 在左侧导航栏的权限管理菜单下,单击权限策略管理。 +- 单击创建权限策略。 +- 在新建自定义权限策略页面,填写策略名称,配置模式选择脚本配置,并在策略内容中赋予角色向目标存储空间examplebucket下的目录exampledir上传文件的权限。 +```json +{ + "Version": "1", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "oss:PutObject" + ], + "Resource": [ + "acs:oss:*:*:examplebucket/exampledir", + "acs:oss:*:*:examplebucket/exampledir/*" + ] + } + ] +} +``` +- 单击确定。 + +### 步骤五:获取临时访问凭证 + +- 您可以通过调用STS服务接口AssumeRole 或者使用各语言STS SDK来获取临时访问凭证。 +> 说明 临时访问凭证有效时间单位为秒,最小值为900,最大值以当前角色设定的最大会话时间为准。详情请参见设置角色最大会话时间。 + +### 步骤六:使用临时访问凭证上传文件至OSS + +### 常见问题 + +- 报错The security token you provided is invalid.如何处理? +> 请确保完整填写步骤五获取到的SecurityToken。 + +- 报错The OSS Access Key Id you provided does not exist in our records.如何处理? +> 临时访问凭证已过期,过期后自动失效。请使用临时访问密钥(AccessKeyId和AccessKeySecret)向App服务器申请新的临时访问凭证。具体操作,请参见步骤五。 + +- 获取STS时报错NoSuchBucket如何处理? +> 出现这种报错通常是STS的endpoint填写错误。请根据您的地域,填写正确的STS接入地址。各地域的STS接入地址请参见接入地址。 \ No newline at end of file diff --git a/modules/Storage/Routes/api.php b/modules/Storage/Routes/api.php new file mode 100644 index 0000000..225f985 --- /dev/null +++ b/modules/Storage/Routes/api.php @@ -0,0 +1,12 @@ + 'storage', +], function (Router $router) { + $router->post('upload', 'OssController@upload'); + $router->post('uploads', 'OssController@uploads'); + $router->get('sts', 'StsController@config'); +}); diff --git a/modules/Storage/Storage.php b/modules/Storage/Storage.php new file mode 100644 index 0000000..7e8c82f --- /dev/null +++ b/modules/Storage/Storage.php @@ -0,0 +1,34 @@ + + */ + public static function install() + { + Artisan::call('migrate', [ + '--path' => 'modules/Storage/Database/Migrations', + ]); + } + + /** + * Notes : 卸载模块的一些操作 + * @Date : 2021/3/12 11:35 上午 + * @Author : < Jason.C > + */ + public static function uninstall() + { + + } + +} diff --git a/modules/Storage/composer.json b/modules/Storage/composer.json new file mode 100644 index 0000000..03e4118 --- /dev/null +++ b/modules/Storage/composer.json @@ -0,0 +1,23 @@ +{ + "name": "uztech/storage-module", + "description": "文件存储模块", + "type": "laravel-module", + "authors": [ + { + "name": "Jason.Chen", + "email": "chenjxlg@163.com" + } + ], + "require": { + "alibabacloud/sts": "^1.8", + "jasonc/laravel-filesystem-oss": "^3.0" + }, + "extra": { + "module-dir": "modules" + }, + "autoload": { + "psr-4": { + "Modules\\Storage\\": "" + } + } +} diff --git a/modules/Storage/module.json b/modules/Storage/module.json new file mode 100644 index 0000000..2c12094 --- /dev/null +++ b/modules/Storage/module.json @@ -0,0 +1,15 @@ +{ + "name": "Storage", + "alias": "storage", + "description": "文件存储模块,文件上传,文件管理", + "keywords": [], + "priority": 0, + "providers": [ + "Modules\\Storage\\Providers\\StorageServiceProvider" + ], + "aliases": {}, + "files": [], + "requires": [], + "version": "1.0.0", + "author": "Jason.Chen" +} diff --git a/modules/Task/Config/config.php b/modules/Task/Config/config.php new file mode 100644 index 0000000..3c99eb9 --- /dev/null +++ b/modules/Task/Config/config.php @@ -0,0 +1,5 @@ + 'Task' +]; diff --git a/modules/Task/Database/Migrations/0000_00_00_000000_create_task_categories_table.php b/modules/Task/Database/Migrations/0000_00_00_000000_create_task_categories_table.php new file mode 100644 index 0000000..3685fd2 --- /dev/null +++ b/modules/Task/Database/Migrations/0000_00_00_000000_create_task_categories_table.php @@ -0,0 +1,37 @@ +id(); + $table->string('title')->comment('分类标题'); + $table->string('remark')->comment('分类描述'); + $table->string('cover')->nullable()->comment('展示图片'); + $table->boolean('status')->default(0)->comment('状态'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('task_categories'); + } + +} diff --git a/modules/Task/Database/Migrations/0000_00_00_000000_create_task_logs_table.php b/modules/Task/Database/Migrations/0000_00_00_000000_create_task_logs_table.php new file mode 100644 index 0000000..d195c23 --- /dev/null +++ b/modules/Task/Database/Migrations/0000_00_00_000000_create_task_logs_table.php @@ -0,0 +1,34 @@ +id(); + $table->unsignedBigInteger('user_id')->index(); + $table->unsignedBigInteger('task_id')->index(); + $table->json('source')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('task_logs'); + } +} diff --git a/modules/Task/Database/Migrations/0000_00_00_000000_create_task_users_table.php b/modules/Task/Database/Migrations/0000_00_00_000000_create_task_users_table.php new file mode 100644 index 0000000..fcb759f --- /dev/null +++ b/modules/Task/Database/Migrations/0000_00_00_000000_create_task_users_table.php @@ -0,0 +1,36 @@ +id(); + $table->unsignedBigInteger('user_id')->index(); + $table->unsignedBigInteger('task_id')->index(); + $table->timestamp('task_at')->index()->comment('做任务的日期'); + $table->integer('total')->default(1)->comment('任务量'); + $table->boolean('status')->default(0)->index()->comment('状态'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('task_users'); + } +} diff --git a/modules/Task/Database/Migrations/0000_00_00_000000_create_tasks_table.php b/modules/Task/Database/Migrations/0000_00_00_000000_create_tasks_table.php new file mode 100644 index 0000000..2977267 --- /dev/null +++ b/modules/Task/Database/Migrations/0000_00_00_000000_create_tasks_table.php @@ -0,0 +1,52 @@ +id(); + $table->unsignedBigInteger('category_id')->default(0)->index(); + $table->string('title', 100)->nullable()->comment('任务标题'); + $table->string('sub_title')->nullable()->comment('副标题'); + $table->string('key', 100)->nullable()->comment('任务标识'); + $table->string('ico', 200)->nullable()->comment('任务图标'); + $table->string('cover')->nullable()->comment('图片'); + $table->integer('position')->default(0)->comment('定位'); + $table->json('identity')->nullable()->comment('可用身份'); + $table->string('remark', 100)->nullable()->comment('任务描述'); + $table->string('tips', 100)->nullable()->comment('任务奖励提示'); + $table->text('description')->nullable()->comment('任务详细说明'); + $table->string('url', 200)->nullable()->comment('任务目标连接'); + $table->integer('rule_id')->nullable()->comment('对应账变规则'); + $table->decimal('rule_number', 8, 2)->default(0)->comment('任务发放的数值'); + $table->string('type', 20)->nullable()->comment('任务计量类型'); + $table->unsignedInteger('task_number')->default(0)->comment('任务数额'); + $table->boolean('company_certify')->nullable()->comment('是否企业认证'); + $table->string('cycle', 20)->nullable()->comment('周期'); + $table->boolean('status')->default(0)->comment('状态'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('tasks'); + } + +} diff --git a/modules/Task/Facades/TaskFacade.php b/modules/Task/Facades/TaskFacade.php new file mode 100644 index 0000000..7e15191 --- /dev/null +++ b/modules/Task/Facades/TaskFacade.php @@ -0,0 +1,105 @@ +shown()->where('key', $key)->first(); + + if ($task) { + $timeRange = self::getTimeInterval($task->cycle); + + $task->taskLogs()->create([ + 'user_id' => $user_id, + 'source' => $source, + ]); + + $taskUser = $task->taskUsers() + ->when($task->cycle != 'one', function ($q) use ($timeRange) { + $q->whereBetween('task_at', $timeRange); + }) + ->byUserId($user_id) + ->first(); + + if (! $taskUser) { + $taskUser = $task->taskUsers()->create([ + 'user_id' => $user_id, + 'task_at' => now(), + 'total' => $total, + ]); + } else { + $taskUser->incrementTotal($total);//增加数值 + } + + //存在任务 + if ($taskUser->total >= $task->task_number && $taskUser->status == User::STATUS_INTI) { + $taskUser->status = User::STATUS_FINISH; + $taskUser->save(); + $variable = $task->getRuleNumber(); + info('key '.$key.' variable'.$variable); + + if ($variable > 0) { + //发放水滴 + $taskUser->user->account->rule($task->rule_id, $variable, false, $source ?? []); + } + } + } + + } + + /** + * Notes: 获取时间区间 + * + * @Author: 玄尘 + * @Date: 2022/10/20 10:37 + * @param $type + * @param string $date + * @return array|void + */ + public static function getTimeInterval($type, string $date = '') + { + switch ($type) { + case 'day': + return [ + 'start_at' => Carbon::parse($date)->startOfDay(), + 'end_at' => Carbon::parse($date)->endOfDay(), + ]; + case 'week': + return [ + 'start_at' => Carbon::parse($date)->startOfWeek(), + 'end_at' => Carbon::parse($date)->endOfWeek(), + ]; + case 'month': + return [ + 'start_at' => Carbon::parse($date)->startOfMonth(), + 'end_at' => Carbon::parse($date)->endOfMonth(), + ]; + case 'year': + return [ + 'start_at' => Carbon::parse($date)->startOfYear(), + 'end_at' => Carbon::parse($date)->endOfYear(), + ]; + case 'one': + return [ + 'start_at' => '', + 'end_at' => Carbon::parse($date)->endOfDay(), + ]; + } + } +} \ No newline at end of file diff --git a/modules/Task/Http/Controllers/Admin/CategoryController.php b/modules/Task/Http/Controllers/Admin/CategoryController.php new file mode 100644 index 0000000..dddaa67 --- /dev/null +++ b/modules/Task/Http/Controllers/Admin/CategoryController.php @@ -0,0 +1,54 @@ +column('id', '#ID#'); + $grid->column('cover', '分类图片')->image('', 50, 100); + $grid->column('title', '分类名称'); + $grid->column('remark', '描述'); + $grid->column('status', '状态')->switch([ + 'on' => ['value' => 1, 'text' => '打开', 'color' => 'success'], + 'off' => ['value' => 0, 'text' => '关闭', 'color' => 'danger'], + ]); + $grid->column('created_at', '创建时间'); + $grid->column('updated_at', '更新时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Category()); + $form->text('title', '分类名称')->required(); + $form->text('remark', '描述')->required(); + + $this->cover($form); + + $this->withUrl($form); + + $form->switch('status', '是否开启')->default(1)->states([ + 'on' => ['value' => 1, 'text' => '打开', 'color' => 'success'], + 'off' => ['value' => 0, 'text' => '关闭', 'color' => 'danger'], + ]); + + return $form; + } + +} diff --git a/modules/Task/Http/Controllers/Admin/TaskController.php b/modules/Task/Http/Controllers/Admin/TaskController.php new file mode 100644 index 0000000..648fc51 --- /dev/null +++ b/modules/Task/Http/Controllers/Admin/TaskController.php @@ -0,0 +1,139 @@ +filter(function (Grid\Filter $filter) { + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('title', '任务名称'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('category_id', '分类')->select(Category::shown()->pluck('title', 'id')); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('status', '状态')->select((new Task())->status_map); + }); + }); + + $grid->column('id', '#ID#'); + $grid->column('ico', '任务图标')->image('', 40, 40); + $grid->column('cover', '图片')->image('', 40, 40); + $grid->column('title', '任务名称'); + $grid->column('key', '任务关键字'); + $grid->column('category.title', '所属分类'); + $grid->column('rule.title', '账变名称'); + $grid->column('remark', '任务描述'); + $grid->column('tips', '任务奖励提示'); + $grid->column('type', '任务计量类型')->using(Task::TYPES); + $grid->column('cycle', '任务计量周期')->using(Task::CYCLES); +// $grid->column('company_certify', '企业认证') +// ->using(Task::COMPANY_CERTIFICATIONS) +// ->label(Task::COMPANY_CERTIFICATION_LABEL); + + $status = [ + 'on' => ['value' => 1, 'text' => '打开', 'color' => 'success'], + 'off' => ['value' => 0, 'text' => '关闭', 'color' => 'danger'], + ]; + +// $grid->column('展示位置') +// ->display(function () { +// $data = []; +// foreach ($this->position as $position) { +// $data[] = $this->position_map[$position]; +// } +// +// return $data; +// }) +// ->label(); + + $grid->column('身份') + ->display(function () { + return $this->getIdentities(); + })->label(); + $grid->column('status', '状态')->switch($status); + + $grid->column('created_at', '创建时间'); + $grid->column('updated_at', '更新时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Task()); + + + $form->text('title', '任务标题')->required(); +// $form->text('sub_title', '副标题'); +// $form->text('remark', '任务描述'); + $form->text('tips', '任务奖励提示')->required(); + $form->text('key', '任务关键字')->required(); + $form->select('category_id', '所属分类') + ->options(Category::query()->shown()->pluck('title', 'id')) + ->required(); + $this->cover($form, 'ico', 'ICO图标'); + $this->cover($form, 'cover', '图片'); +// $form->multipleSelect('position', '展示位置') +// ->options($form->model()->position_map); + $form->multipleSelect('identity', '可用身份') + ->options(Identity::pluck('name', 'id')); + + $form->select('rule_id', '对应账变规则') + ->options(function () { + return AccountRule::where('type', 'score')->pluck('title', 'id'); + }) + ->required(); + $form->currency('rule_number', '账变数值') + ->default(0) + ->help('0为获取账变设置的数值') + ->required(); + + $this->withUrl($form); + +// $form->radio('company_certify', '企业认证') +// ->options(Task::COMPANY_CERTIFICATIONS) +// ->default(Task::COMPANY_CERTIFICATION_NO) +// ->required(); + $form->radio('type', '任务计量类型')->default(Task::TYPE_COUNT)->options(Task::TYPES)->required(); + $form->radio('cycle', '任务计量周期')->default(Task::CYCLE_DAY)->options(Task::CYCLES)->required(); + $form->number('task_number', '任务量')->default(1)->required(); +// $form->ueditor('description', '任务详情')->required(); + $form->switch('status', '是否开启')->states([ + 'on' => ['value' => 1, 'text' => '打开', 'color' => 'success'], + 'off' => ['value' => 0, 'text' => '关闭', 'color' => 'danger'], + ]); + $form->saving(function (Form $form) { + $task = Task::query()->where('key', $form->key)->first(); + if ($task && $form->isCreating()) { + $error = new MessageBag([ + 'title' => '错误', + 'message' => '任务关键字已经存在', + ]); + return back()->withInput()->with(compact('error')); + } + }); + return $form; + } + +} diff --git a/modules/Task/Http/Controllers/Admin/UserController.php b/modules/Task/Http/Controllers/Admin/UserController.php new file mode 100644 index 0000000..4a69320 --- /dev/null +++ b/modules/Task/Http/Controllers/Admin/UserController.php @@ -0,0 +1,50 @@ +disableCreateButton(); + $grid->disableActions(); + + $grid->filter(function (Grid\Filter $filter) { + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('user_id', '下单用户')->select()->ajax(route('admin.user.users.ajax')); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('task_id', '任务')->select(Task::query()->pluck('title', 'id')); + }); + }); + + $grid->column('id', '#ID#'); + $grid->column('task.title', '任务'); + $grid->column('user.username', '用户'); + $grid->column('task.task_number', '任务量'); + $grid->column('total', '完成次数'); + $grid->column('task_at', '时间'); + + return $grid; + } +} diff --git a/modules/Task/Http/Controllers/Api/CategoryController.php b/modules/Task/Http/Controllers/Api/CategoryController.php new file mode 100644 index 0000000..48ca567 --- /dev/null +++ b/modules/Task/Http/Controllers/Api/CategoryController.php @@ -0,0 +1,21 @@ +get(); + + return $this->success(CategoryResource::collection($tasks)); + } + +} diff --git a/modules/Task/Http/Controllers/Api/TaskController.php b/modules/Task/Http/Controllers/Api/TaskController.php new file mode 100644 index 0000000..fc14cea --- /dev/null +++ b/modules/Task/Http/Controllers/Api/TaskController.php @@ -0,0 +1,39 @@ +category_id ?? ''; + + $tasks = Task::query() + ->when($category_id, function ($q) use ($category_id) { + $q->where('category_id', $category_id); + }) + ->Shown() + ->when($user, function ($q) use ($user) { + $q->ofUser($user); + }) + ->get(); + + return $this->success(TaskBaseResource::collection($tasks)); + } + + public function show(Task $task) + { + return $this->success(new TaskResource($task)); + } + +} diff --git a/modules/Task/Http/Controllers/Api/UserController.php b/modules/Task/Http/Controllers/Api/UserController.php new file mode 100644 index 0000000..c4436ce --- /dev/null +++ b/modules/Task/Http/Controllers/Api/UserController.php @@ -0,0 +1,76 @@ +where('key', $key)->first(); + if ($task && $task->status != Task::STATUS_UP) { + throw new \Exception('任务关闭'); + } + + $toDayStartTimestamp = Carbon::now()->startOfDay()->timestamp;//当天开始的时间戳 + + $app = Factory::miniProgram([ + 'app_id' => env('WECHAT_MINI_PROGRAM_APPID'), + 'secret' => env('WECHAT_MINI_PROGRAM_SECRET'), + 'response_type' => 'collection', + ]); + + $session = $app->auth->session($request->code); + if ($session->errcode) { + return $this->failed($session->errmsg); + } + + $decryptedData = $app->encryptor->decryptData( + $session->session_key, + $request->iv, + $request->encryptData + ); + + $list = collect($decryptedData['stepInfoList']); + + if ($list->isNotEmpty()) { + $toDay = $list->where('timestamp', $toDayStartTimestamp)->first(); + if ($toDay) { + TaskFacade::do($key, Api::userId(), [ + 'steps' => $toDay['step'] + ], $toDay['step']); + } + + if ($toDay['step'] >= $task->task_number) { + return $this->success('任务完成'); + } else { + throw new \Exception('您今天的步数未达标'); + } + } else { + throw new \Exception('未获取到微信运动数据'); + } + + + } catch (\Exception $exception) { + return $this->failed($exception->getMessage()); + } + } + +} diff --git a/modules/Task/Http/Resources/CategoryResource.php b/modules/Task/Http/Resources/CategoryResource.php new file mode 100644 index 0000000..d563b7c --- /dev/null +++ b/modules/Task/Http/Resources/CategoryResource.php @@ -0,0 +1,21 @@ + $this->id, + 'title' => $this->title, + 'remark' => $this->remark, + 'cover' => $this->cover_url, + 'url' => $this->linker, + ]; + } + +} diff --git a/modules/Task/Http/Resources/TaskBaseResource.php b/modules/Task/Http/Resources/TaskBaseResource.php new file mode 100644 index 0000000..dffa506 --- /dev/null +++ b/modules/Task/Http/Resources/TaskBaseResource.php @@ -0,0 +1,27 @@ + $this->id, + 'ico' => $this->ico_url, + 'key' => $this->key, + 'cover' => $this->cover_url, + 'title' => $this->title, + 'tips' => $this->tips, + 'linker' => $this->linker, + 'user' => $this->getUserTask($user), + ]; + } + +} \ No newline at end of file diff --git a/modules/Task/Http/Resources/TaskResource.php b/modules/Task/Http/Resources/TaskResource.php new file mode 100644 index 0000000..29fc35f --- /dev/null +++ b/modules/Task/Http/Resources/TaskResource.php @@ -0,0 +1,36 @@ + $this->id, + 'ico' => $this->ico_url, + 'key' => $this->key, + 'cover' => $this->cover_url, + 'title' => $this->title, + 'sub_title' => $this->sub_title, + 'remark' => $this->remark, + 'tips' => $this->tips, + 'url' => $this->linker, + 'category' => new CategoryResource($this->category), + 'rule' => [ + 'id' => $this->rule->id, + 'name' => $this->rule->name, + 'title' => $this->rule->title, + ], + 'user' => $this->getUserTask($user), + 'content' => $this->description, + ]; + } + +} \ No newline at end of file diff --git a/modules/Task/Models/Category.php b/modules/Task/Models/Category.php new file mode 100644 index 0000000..e9fad45 --- /dev/null +++ b/modules/Task/Models/Category.php @@ -0,0 +1,21 @@ + 'json', + ]; + +} diff --git a/modules/Task/Models/Task.php b/modules/Task/Models/Task.php new file mode 100644 index 0000000..64344b7 --- /dev/null +++ b/modules/Task/Models/Task.php @@ -0,0 +1,203 @@ + '日', + self::CYCLE_WEEK => '周', + self::CYCLE_MONTH => '月', + self::CYCLE_YEAR => '年', + self::CYCLE_ONE => '一次', + ]; + + const TYPE_SUM = 'sum'; + const TYPE_COUNT = 'count'; + + const TYPES = [ + self::TYPE_SUM => '和值', + self::TYPE_COUNT => '计数', + ]; + + const COMPANY_CERTIFICATION_YES = 1; + const COMPANY_CERTIFICATION_NO = 0; + + const COMPANY_CERTIFICATIONS = [ + self::COMPANY_CERTIFICATION_YES => '是', + self::COMPANY_CERTIFICATION_NO => '否', + ]; + + const COMPANY_CERTIFICATION_LABEL = [ + self::COMPANY_CERTIFICATION_YES => 'info', + self::COMPANY_CERTIFICATION_NO => 'success', + ]; + + const POSITION_RECOMMENDED = 1; + + public array $position_map = [ + self::POSITION_RECOMMENDED => '推荐', + ]; + + public string $cover_field = 'cover'; + + public $casts = [ + 'identity' => 'json', + ]; + + + const STATUS_DOWN = 0; + const STATUS_UP = 1; + + public array $status_map = [ + self::STATUS_UP => '开启', + self::STATUS_DOWN => '关闭', + ]; + + public function getIcoUrlAttribute(): string + { + return $this->parseImageUrl($this->ico); + } + + /** + * Notes: 关联分类 + * + * @Author: 玄尘 + * @Date: 2022/10/19 16:45 + * @return BelongsTo + */ + public function category(): BelongsTo + { + return $this->belongsTo(Category::class); + } + + /** + * Notes: 关联规则 + * + * @Author: 玄尘 + * @Date : 2021/9/30 14:30 + * @return BelongsTo + */ + public function rule(): BelongsTo + { + return $this->belongsTo(AccountRule::class, 'rule_id'); + } + + /** + * Notes: 获取账变值 + * + * @Author: 玄尘 + * @Date: 2022/10/20 11:05 + */ + public function getRuleNumber() + { + $variable = $this->rule_number; + if (! $variable) { + $variable = $this->rule ? $this->rule->variable : 0; + } + + return $variable; + } + + /** + * Notes: description + * + * @Author: 玄尘 + * @Date : 2021/9/29 11:53 + * @param Builder $query + * @param User $user + * @return Builder + */ + public function scopeOfUser(Builder $query, User $user): Builder + { + $identity = $user->identities->first(); + return $query->where('identity', 'like', "%$identity->id%"); + } + + /** + * Notes: description + * + * @Author: 玄尘 + * @Date : 2021/9/29 11:53 + * @param Builder $query + * @param Identity $identity + * @return Builder + */ + public function scopeOfIdentity(Builder $query, Identity $identity): Builder + { + return $query->where('identity', 'like', "%$identity->id%"); + } + + /** + * Notes: 是否完成 + * + * @Author: 玄尘 + * @Date : 2021/9/30 13:25 + */ + public function getFinish($user): bool + { + $rule = $this->rule; + if ($rule) { + if (method_exists($this, $rule->name)) { + return $this->{$rule->name}($user); + } + } + + return false; + } + + /** + * Notes: 任务数据 + * + * @Author: 玄尘 + * @Date: 2022/10/20 10:43 + */ + public function taskUsers(): HasMany + { + return $this->hasMany(TaskUser::class); + } + + /** + * Notes: 任务记录 + * + * @Author: 玄尘 + * @Date: 2022/10/20 10:45 + */ + public function taskLogs(): HasMany + { + return $this->hasMany(Log::class); + } + + public function getIdentities() + { + return Identity::query()->whereIn('id',$this->identity)->pluck('name'); + } + +} diff --git a/modules/Task/Models/Traits/BelongsToTask.php b/modules/Task/Models/Traits/BelongsToTask.php new file mode 100644 index 0000000..a016867 --- /dev/null +++ b/modules/Task/Models/Traits/BelongsToTask.php @@ -0,0 +1,20 @@ +belongsTo(Task::class); + } +} diff --git a/modules/Task/Models/Traits/TaskAttribute.php b/modules/Task/Models/Traits/TaskAttribute.php new file mode 100644 index 0000000..66f97b1 --- /dev/null +++ b/modules/Task/Models/Traits/TaskAttribute.php @@ -0,0 +1,52 @@ + $this->task_number, + ]; + if ($user) { + $timeRange = TaskFacade::getTimeInterval($this->cycle); + + $taskUser = $this->taskUsers() + ->when($this->cycle != 'one', function ($q) use ($timeRange) { + $q->whereBetween('task_at', $timeRange); + }) + ->byUser($user) + ->first(); + + if ($taskUser) { + return array_merge($data, [ + 'task_id' => $taskUser->id, + 'finish' => $taskUser->status, + 'total' => min($taskUser->total, $data['all']), + ]); + } else { + return array_merge($data, [ + 'finish' => 0, + 'total' => 0, + ]); + } + } else { + return array_merge($data, [ + 'finish' => 0, + 'total' => 0, + ]); + } + } + +} diff --git a/modules/Task/Models/User.php b/modules/Task/Models/User.php new file mode 100644 index 0000000..52d5bb7 --- /dev/null +++ b/modules/Task/Models/User.php @@ -0,0 +1,41 @@ + '进行中', + self::STATUS_FINISH => '完成', + ]; + + /** + * Notes: 增加任务数 + * + * @Author: 玄尘 + * @Date: 2022/10/20 11:10 + * @param int $step + */ + public function incrementTotal(int $step = 1) + { + if ($this->task->key == 'steps') { + $this->total = $step; + $this->save(); + } else { + $this->increment('total', $step); + } + } +} diff --git a/modules/Task/Providers/RouteServiceProvider.php b/modules/Task/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..4761122 --- /dev/null +++ b/modules/Task/Providers/RouteServiceProvider.php @@ -0,0 +1,78 @@ +mapApiRoutes(); + + $this->mapAdminRoutes(); + } + + /** + * Define the "web" routes for the application. + * These routes all receive session state, CSRF protection, etc. + * + * @return void + */ + protected function mapAdminRoutes() + { + Route::as(config('admin.route.as')) + ->domain(config('admin.route.domain')) + ->middleware(config('admin.route.middleware')) + ->namespace($this->moduleNamespace.'\\Admin') + ->prefix(config('admin.route.prefix')) + ->group(module_path($this->moduleName, 'Routes/admin.php')); + } + + /** + * Define the "api" routes for the application. + * These routes are typically stateless. + * + * @return void + */ + protected function mapApiRoutes() + { + Route::as(config('api.route.as')) + ->domain(config('api.route.domain')) + ->middleware(config('api.route.middleware')) + ->namespace($this->moduleNamespace.'\\Api') + ->prefix(config('api.route.prefix').'/tasks') + ->group(module_path($this->moduleName, 'Routes/api.php')); + } + +} diff --git a/modules/Task/Providers/TaskServiceProvider.php b/modules/Task/Providers/TaskServiceProvider.php new file mode 100644 index 0000000..92ff57a --- /dev/null +++ b/modules/Task/Providers/TaskServiceProvider.php @@ -0,0 +1,67 @@ +registerConfig(); + $this->loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations')); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->app->register(RouteServiceProvider::class); + } + + /** + * Register config. + * + * @return void + */ + protected function registerConfig() + { + $this->publishes([ + module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower.'.php'), + ], 'config'); + $this->mergeConfigFrom( + module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower + ); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides(): array + { + return []; + } + +} diff --git a/modules/Task/Routes/admin.php b/modules/Task/Routes/admin.php new file mode 100644 index 0000000..c48ef08 --- /dev/null +++ b/modules/Task/Routes/admin.php @@ -0,0 +1,11 @@ +resource('tasks/categories', 'CategoryController'); + $router->resource('tasks/users', 'UserController'); + $router->resource('tasks', 'TaskController'); +}); diff --git a/modules/Task/Routes/api.php b/modules/Task/Routes/api.php new file mode 100644 index 0000000..b6ec12d --- /dev/null +++ b/modules/Task/Routes/api.php @@ -0,0 +1,21 @@ + config('api.route.middleware_guess'), +], function (Router $router) { + $router->get('', 'TaskController@index'); + $router->get('{task}', 'TaskController@show')->where('task', '[0-9]+');; + $router->get('categories', 'CategoryController@index'); + $router->get('wechat_step', 'TaskController@wechatStep'); +}); + +Route::group([ + 'middleware' => config('api.route.middleware_auth'), +], function (Router $router) { + $router->get('wechat_step', 'UserController@wechatStep'); +}); + + diff --git a/modules/Task/Task.php b/modules/Task/Task.php new file mode 100644 index 0000000..cdd8fc4 --- /dev/null +++ b/modules/Task/Task.php @@ -0,0 +1,81 @@ + 'modules/Task/Database/Migrations', + ]); + + self::createAdminMenu(); + } + + protected static function createAdminMenu() + { + $menu = config('admin.database.menu_model'); + + $exists = $menu::where('title', self::$mainTitle)->exists(); + if (! $exists) { + $main = $menu::create([ + 'parent_id' => 0, + 'order' => 3, + 'title' => '任务管理', + 'icon' => 'fa-archive', + ]); + $main->children() + ->create([ + 'order' => 0, + 'title' => '任务列表', + 'icon' => 'fa-align-left', + 'uri' => 'tasks', + ]); + + $main->children() + ->create([ + 'order' => 1, + 'title' => '分类管理', + 'icon' => 'fa-align-left', + 'uri' => 'tasks/categories', + ]); + $main->children() + ->create([ + 'order' => 3, + 'title' => '任务记录', + 'icon' => 'fa-align-left', + 'uri' => 'tasks/users', + ]); + } + } + + /** + * Notes: 卸载模块的一些操作 + * + * @Author: 玄尘 + * @Date : 2021/9/26 11:24 + */ + public static function uninstall() + { + $menu = config('admin.database.menu_model'); + + $mains = $menu::where('title', self::$mainTitle)->get(); + + foreach ($mains as $main) { + $main->delete(); + } + } + +} diff --git a/modules/Task/Traits/HasTasks.php b/modules/Task/Traits/HasTasks.php new file mode 100644 index 0000000..4167c42 --- /dev/null +++ b/modules/Task/Traits/HasTasks.php @@ -0,0 +1,52 @@ +hasMany(User::class); + } + + /** + * Notes: 获取任务数据 + * + * @Author: 玄尘 + * @Date: 2022/10/21 9:54 + */ + public function getTaskCount($task_id): array + { + $task = Task::find($task_id); + + return $task->getUserTask($this); + } + + /** + * Notes: 所有任务数据 + * + * @Author: 玄尘 + * @Date: 2022/10/21 9:59 + * @param $task_id + * @return array + */ + public function getTasksCount(): array + { + return $this->tasks()->map(function ($taskUser) { + return $taskUser->task->getUserTask($this); + }); + } + +} \ No newline at end of file diff --git a/modules/Task/composer.json b/modules/Task/composer.json new file mode 100644 index 0000000..1d93905 --- /dev/null +++ b/modules/Task/composer.json @@ -0,0 +1,18 @@ +{ + "name": "xuanchen/task_module", + "description": "", + "type": "laravel-module", + "authors": [ + { + "name": "Chen.Xuan", + "email": "122383162@qq.com" + } + ], + "require": { + }, + "autoload": { + "psr-4": { + "Modules\\Task\\": "" + } + } +} diff --git a/modules/Task/module.json b/modules/Task/module.json new file mode 100644 index 0000000..2992b4f --- /dev/null +++ b/modules/Task/module.json @@ -0,0 +1,15 @@ +{ + "name": "Task", + "alias": "task", + "description": "任务模块", + "keywords": [], + "priority": 0, + "providers": [ + "Modules\\Task\\Providers\\TaskServiceProvider" + ], + "aliases": {}, + "files": [], + "version": "1.0.0", + "author": "玄尘", + "requires": [] +} diff --git a/modules/Task/package.json b/modules/Task/package.json new file mode 100644 index 0000000..4599509 --- /dev/null +++ b/modules/Task/package.json @@ -0,0 +1,17 @@ +{ + "private": true, + "scripts": { + "dev": "npm run development", + "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", + "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", + "watch-poll": "npm run watch -- --watch-poll", + "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", + "prod": "npm run production", + "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" + }, + "devDependencies": { + "cross-env": "^7.0", + "laravel-mix": "^5.0.1", + "laravel-mix-merge-manifest": "^0.1.2" + } +} diff --git a/modules/User/.gitignore b/modules/User/.gitignore new file mode 100644 index 0000000..4b6cb11 --- /dev/null +++ b/modules/User/.gitignore @@ -0,0 +1,4 @@ +.idea +vendor +.DS_Store +composer.lock \ No newline at end of file diff --git a/modules/User/.styleci.yml b/modules/User/.styleci.yml new file mode 100644 index 0000000..1d210e6 --- /dev/null +++ b/modules/User/.styleci.yml @@ -0,0 +1,17 @@ +risky: false + +version: 7.4 + +preset: laravel + +tab-width: 4 + +use-tabs: false + +#enabled: +# - "align_phpdoc" + +disabled: + - "unalign_equals" + - "no_blank_lines_after_class_opening" + - "no_blank_lines_after_phpdoc" diff --git a/modules/User/Config/config.php b/modules/User/Config/config.php new file mode 100644 index 0000000..df3b2b0 --- /dev/null +++ b/modules/User/Config/config.php @@ -0,0 +1,98 @@ + 'User', + + /* + |-------------------------------------------------------------------------- + | 后台是否显示新增用户按钮 + |-------------------------------------------------------------------------- + */ + 'create_user_by_admin' => env('USER_CREATE_BY_ADMIN', true), + + /* + |-------------------------------------------------------------------------- + | 开启用户基础数据的缓存 + |-------------------------------------------------------------------------- + */ + 'cache_user' => env('USER_USE_CACHE', false), + + /* + |-------------------------------------------------------------------------- + | 缓存用户的配置 + |-------------------------------------------------------------------------- + */ + 'cache_config' => [ + 'cache_ttl' => env('CACHE_USER_TTL', 3600), + 'cache_channel' => env('CACHE_USER_CHANNEL', 'every'), + 'model_with' => env('CACHE_USER_MODEL_WITH'), + ], + + /* + |-------------------------------------------------------------------------- + | 用户默认的头像地址 + |-------------------------------------------------------------------------- + */ + 'avatar' => env('USER_DEFAULT_AVATAR', ''), + + /* + |-------------------------------------------------------------------------- + | 默认推荐用ID + |-------------------------------------------------------------------------- + */ + 'default_parent_id' => 0, + + /* + |-------------------------------------------------------------------------- + | 推广码的加密配置 + |-------------------------------------------------------------------------- + */ + 'invite_code' => [ + 'salt' => 'Uz.Tech', + 'length' => 10, + ], + + /* + |-------------------------------------------------------------------------- + | 社会化登录平台配置(暂时加入支付宝,微信,抖音的配置信息) + |-------------------------------------------------------------------------- + */ + 'socialite' => [ + 'alipay' => [ + 'client_id' => env('ALIPAY_CLIENT_ID', ''), + 'rsa_private_key' => env('RSA_PRIVATE_KEY', ''), + 'redirect' => '', + ], + 'douyin' => [ + 'client_id' => env('DOUYIN_CLIENT_ID', ''), + 'client_secret' => env('DOUYIN_CLIENT_SECRET', ''), + 'redirect' => '', + ], + 'wechat' => [ + 'client_id' => env('WECHAT_CLIENT_ID', ''), + 'client_secret' => env('WECHAT_CLIENT_SECRET', ''), + 'redirect' => '', + // 开放平台 - 第三方平台所需 + 'component' => [ + // or 'app_id', 'component_app_id' as key + 'id' => '', + // or 'app_token', 'access_token', 'component_access_token' as key + 'token' => '', + ], + ], + 'unicloud' => [ + 'key' => env('UNICLOUD_API_KEY', ''), + 'secret' => env('UNICLOUD_API_SECRET', ''), + 'self_secret' => env('UNICLOUD_SELF_SECRET', ''), + 'domain' => env('UNICLOUD_DOMAIN', ''), + 'cloud_function_url' => [ + 'one_key' => env('UNICLOUD_FUNCTION_URL_ONE_KEY', ''), + ], + ], + ], +]; diff --git a/modules/User/Config/identity.php b/modules/User/Config/identity.php new file mode 100644 index 0000000..1c14a79 --- /dev/null +++ b/modules/User/Config/identity.php @@ -0,0 +1,19 @@ + false, + 'conditions' => [ + 'price' => '开通金额', + ], + 'rules' => [ + 'buy_crystal' => '购物贡献比例', + 'give_crystal' => '开通贡献值', + 'store_sales' => '店铺销售', + 'barter_in' => '易货(进)', + 'barter_out' => '易货(出)', + ], +]; \ No newline at end of file diff --git a/modules/User/Console/Commands/UserIdentityOver.php b/modules/User/Console/Commands/UserIdentityOver.php new file mode 100644 index 0000000..5ade631 --- /dev/null +++ b/modules/User/Console/Commands/UserIdentityOver.php @@ -0,0 +1,57 @@ +where('default', 1)->first(); + if ($identity) { + IdentityMiddle::query() + ->whereNotNull('ended_at') + ->where('ended_at', '<', now()) + ->chunkById(1000, function ($middles) use ($identity) { + foreach ($middles as $middle) { + $middle->user->joinIdentity($identity->id, IdentityLog::CHANNEL_SYSTEM); + } + }); + } + + } +} diff --git a/modules/User/Console/Kernel.php b/modules/User/Console/Kernel.php new file mode 100644 index 0000000..ec5d5c7 --- /dev/null +++ b/modules/User/Console/Kernel.php @@ -0,0 +1,19 @@ +command('xuanchen:auto-set-user-identity-over')->everyMinute(); + $schedule->command('xuanchen:auto-remind-user-case')->dailyAt('11:00'); + $schedule->command('xuanchen:auto-remind-user-case')->dailyAt('19:00'); + $schedule->command('xuanchen:auto-remind-user-sign')->dailyAt('11:00'); + $schedule->command('xuanchen:auto-remind-user-sign')->dailyAt('19:00'); + } + +} \ No newline at end of file diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_account_logs_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_account_logs_table.php new file mode 100644 index 0000000..64bae54 --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_account_logs_table.php @@ -0,0 +1,41 @@ +id(); + $table->unsignedBigInteger('account_id'); + $table->unsignedBigInteger('rule_id'); + $table->string('type', 50)->nullable(); + $table->decimal('amount'); + $table->decimal('balance'); + $table->boolean('frozen')->default(0); + $table->string('remark')->nullable(); + $table->json('source')->nullable(); + $table->timestamp('settle_at')->nullable()->comment('列入结算日期'); + $table->timestamp('frozen_at')->nullable()->comment('可解冻时间'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_account_logs'); + } + +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_account_rules_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_account_rules_table.php new file mode 100644 index 0000000..745a839 --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_account_rules_table.php @@ -0,0 +1,39 @@ +id(); + $table->string('title', 50); + $table->string('name', 50); + $table->string('type', 50); + $table->decimal('variable')->default(0); + $table->integer('trigger')->default(0); + $table->boolean('deductions'); + $table->string('remark', 200); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_account_rules'); + } + +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_accounts_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_accounts_table.php new file mode 100644 index 0000000..70b43cd --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_accounts_table.php @@ -0,0 +1,35 @@ +unsignedBigInteger('user_id')->primary(); + $table->decimal('balance', 20, 2)->default(0); + $table->decimal('score', 20, 2)->default(0); + $table->decimal('coins', 20, 2)->default(0); + $table->decimal('other', 20, 2)->default(0); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_accounts'); + } + +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_certifications_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_certifications_table.php new file mode 100644 index 0000000..2a61b4f --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_certifications_table.php @@ -0,0 +1,37 @@ +id(); + $table->unsignedBigInteger('user_id')->index(); + $table->boolean('verified')->default(0); + $table->string('name', 32); + $table->string('id_card', 32)->index(); + $table->string('front_card')->nullable(); + $table->string('back_card')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_certifications'); + } +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_identities_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_identities_table.php new file mode 100644 index 0000000..fe92e91 --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_identities_table.php @@ -0,0 +1,53 @@ +id(); + $table->string('name')->comment('身份名称'); + $table->string('cover')->nullable()->comment('身份的LOGO'); + $table->string('description')->nullable()->comment('简介'); + $table->integer('stock')->default(0)->comment('水的库存'); + $table->unsignedInteger('order')->default(0)->comment('展示排序'); + $table->json('conditions')->nullable()->comment('升级的条件,JSON'); + $table->json('rules')->nullable()->comment('身份权益,JSON'); + $table->json('rights')->nullable()->comment('前台显示的权益'); + $table->json('ruleshows')->nullable()->comment('前台显示的权益'); + $table->tinyInteger('can_buy')->default(0)->comment('是否可以前台购买'); + $table->tinyInteger('years')->default(0)->comment('有效期(年)'); + $table->boolean('status')->default(0); + $table->boolean('serial_open')->default(0)->comment('是否开启身份编号'); + $table->unsignedInteger('serial_places')->default(0)->comment('身份编号位数'); + $table->string('serial_prefix', 4)->nullable()->comment('身份编号前缀'); + $table->string('protocol_url')->nullable()->comment('协议地址'); + $table->boolean('default')->default(0)->comment('是否默认身份'); + $table->tinyInteger('channel')->default(1)->comment('缴费渠道'); + $table->integer('total')->default(0)->comment('可开通总数'); + $table->tinyInteger('job')->default(0)->comment('职位'); + $table->timestamp('end_at')->nullable()->comment('结束时间'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_identities'); + } + +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_identity_logs_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_identity_logs_table.php new file mode 100644 index 0000000..ae563a0 --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_identity_logs_table.php @@ -0,0 +1,37 @@ +id(); + $table->unsignedBigInteger('user_id')->comment('用户ID'); + $table->unsignedBigInteger('before')->comment('变化前身份'); + $table->unsignedBigInteger('after')->comment('变化后身份'); + $table->string('channel', 20)->nullable()->comment('变化渠道'); + $table->string('remark', 200)->nullable()->comment('备注'); + $table->json('source')->nullable()->comment('附加溯源信息'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_identity_logs'); + } + +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_identity_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_identity_table.php new file mode 100644 index 0000000..fb3ff89 --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_identity_table.php @@ -0,0 +1,37 @@ +unsignedBigInteger('user_id')->index(); + $table->unsignedBigInteger('identity_id')->index(); + $table->dateTime('started_at')->nullable()->comment('身份的开始时间'); + $table->dateTime('ended_at')->nullable()->comment('角色的结束时间'); + $table->string('serial', '25')->nullable()->comment('身份生成的编号'); + $table->timestamps(); + + $table->primary(['user_id', 'identity_id']); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_identitie'); + } + +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_infos_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_infos_table.php new file mode 100644 index 0000000..459528f --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_infos_table.php @@ -0,0 +1,34 @@ +unsignedInteger('user_id')->primary(); + $table->string('nickname')->nullable(); + $table->string('avatar')->nullable(); + $table->json('extends')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_infos'); + } + +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_orders_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_orders_table.php new file mode 100644 index 0000000..b004d1b --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_orders_table.php @@ -0,0 +1,42 @@ +id(); + $table->unsignedBigInteger('user_id'); + $table->unsignedBigInteger('identity_id'); + $table->decimal('price'); + $table->tinyInteger('year')->default(1)->comment('开通年限'); + $table->string('state'); + $table->string('name')->comment('姓名'); + $table->string('card_no')->comment('银行卡号'); + $table->string('cover')->comment('凭证'); + $table->tinyInteger('type')->comment('1 开通 2 续费'); + $table->tinyInteger('channel')->default(1)->comment('1 身份 2 体验官'); + $table->timestamps(); + $table->index(['user_id', 'identity_id']); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_orders'); + } + +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_relations_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_relations_table.php new file mode 100644 index 0000000..16f2566 --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_relations_table.php @@ -0,0 +1,34 @@ +unsignedInteger('user_id')->primary(); + $table->unsignedInteger('parent_id')->default(0)->comment('上级用户ID'); + $table->unsignedInteger('layer')->default(1)->comment('所在层级'); + $table->longText('bloodline')->nullable()->comment('血缘线'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_relations'); + } + +} \ No newline at end of file diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_service_identity_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_service_identity_table.php new file mode 100644 index 0000000..0979471 --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_service_identity_table.php @@ -0,0 +1,33 @@ +unsignedBigInteger('service_id')->index(); + $table->unsignedBigInteger('identity_id')->index(); + $table->timestamps(); + $table->primary(['service_id', 'identity_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_service_identity'); + } +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_services_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_services_table.php new file mode 100644 index 0000000..cd69112 --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_services_table.php @@ -0,0 +1,36 @@ +id(); + $table->string('name'); + $table->string('mobile'); + $table->string('cover'); + $table->boolean('status'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_services'); + } + +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_configs_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_configs_table.php new file mode 100644 index 0000000..e11fc2e --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_configs_table.php @@ -0,0 +1,33 @@ +id(); + $table->json('params')->nullable()->comment('参数'); + $table->json('tasks')->nullable()->comment('参数'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_sign_configs'); + } + +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_logs_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_logs_table.php new file mode 100644 index 0000000..d56a23d --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_logs_table.php @@ -0,0 +1,37 @@ +id(); + $table->unsignedBigInteger('user_id')->index(); + $table->date('date'); + $table->boolean('type')->default(0)->comment('0:签到,1:补签'); + $table->string('remark')->nullable()->comment('预留'); + $table->timestamp('created_at'); + + $table->unique(['user_id', 'date']); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_sign_logs'); + } + +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_signs_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_signs_table.php new file mode 100644 index 0000000..915861a --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_signs_table.php @@ -0,0 +1,38 @@ +id(); + $table->unsignedBigInteger('user_id')->index(); + $table->unsignedInteger('continue_days')->default(0)->comment('连续签到天数'); + $table->unsignedInteger('counts')->default(0)->comment('累计签到天数'); + $table->timestamp('last_sign_at')->nullable(); + $table->tinyInteger('need_case')->default(0)->comment('是否需要上传报告'); + $table->timestamp('reset_at')->nullable()->comment('重置连续次数日期'); + $table->tinyInteger('is_finish')->default(0)->comment('是否完成'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_signs'); + } + +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_configs_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_configs_table.php new file mode 100644 index 0000000..d59d2ec --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_configs_table.php @@ -0,0 +1,38 @@ +id(); + $table->boolean('debug')->default(1); + $table->string('debug_code', 16); + $table->tinyInteger('length')->default(4); + $table->tinyInteger('expires')->default(5)->comment('有效期,单位:分钟'); + $table->json('template')->nullable(); + $table->string('default_gateway', 16); + $table->boolean('in_use')->default(0); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_sms_configs'); + } + +} \ No newline at end of file diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_gateways_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_gateways_table.php new file mode 100644 index 0000000..a09e3d5 --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_gateways_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('name', 16); + $table->string('slug', 16); + $table->json('configs'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_sms_gateways'); + } + +} \ No newline at end of file diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_table.php new file mode 100644 index 0000000..9ff4324 --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_table.php @@ -0,0 +1,38 @@ +id(); + $table->string('mobile', 16); + $table->string('channel', 16)->comment('验证渠道,一般对应TEMPLATE_ID'); + $table->string('gateway', 16)->nullable()->comment('发送网关'); + $table->string('content')->comment('短信内容,验证或通知'); + $table->boolean('used')->default(0); + $table->timestamps(); + + $table->index(['mobile', 'channel']); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_sms'); + } + +} \ No newline at end of file diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_socialite_configs_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_socialite_configs_table.php new file mode 100644 index 0000000..4dec94d --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_socialite_configs_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('name', 100); + $table->string('slug', 100); + $table->json('configs')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_socialite_configs'); + } + +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_apps_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_apps_table.php new file mode 100644 index 0000000..e1f70d3 --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_apps_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('user_wechat_id'); + $table->string('openid')->unique(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_wechat_apps'); + } +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_minis_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_minis_table.php new file mode 100644 index 0000000..a4693d5 --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_minis_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('user_wechat_id'); + $table->string('openid')->unique(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_wechat_minis'); + } +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_officials_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_officials_table.php new file mode 100644 index 0000000..762c1ad --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_officials_table.php @@ -0,0 +1,36 @@ +id(); + $table->foreignId('user_wechat_id'); + $table->string('openid'); + $table->boolean('subscribe')->comment('关注公众平台')->default(0); + $table->timestamp('subscribed_at')->nullable(); + $table->string('subscribe_scene', 32)->comment('关注场景')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_wechat_officials'); + } +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechats_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechats_table.php new file mode 100644 index 0000000..ff0307f --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechats_table.php @@ -0,0 +1,39 @@ +id(); + $table->foreignId('user_id'); + $table->string('unionid')->comment('unionid')->nullable(); + $table->string('nickname')->comment('微信昵称')->nullable(); + $table->string('avatar')->comment('微信头像')->nullable(); + $table->boolean('sex')->comment('性别')->default(0); + $table->string('country')->nullable(); + $table->string('province')->nullable(); + $table->string('city')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('user_wechats'); + } + +} diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_users_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_users_table.php new file mode 100644 index 0000000..c3b716e --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_users_table.php @@ -0,0 +1,35 @@ +id()->startingValue(10000); + $table->string('username')->unique(); + $table->string('password')->nullable(); + $table->boolean('status')->default(1)->comment('״̬'); + $table->string('remember_token')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::dropIfExists('users'); + } + +} diff --git a/modules/User/Database/Migrations/2021_12_07_101850_create_user_certification_configs_table.php b/modules/User/Database/Migrations/2021_12_07_101850_create_user_certification_configs_table.php new file mode 100644 index 0000000..8827074 --- /dev/null +++ b/modules/User/Database/Migrations/2021_12_07_101850_create_user_certification_configs_table.php @@ -0,0 +1,45 @@ +id(); + $table->boolean('is_open')->default(0)->comment('开通第三方认证'); + $table->boolean('is_ocr_open')->default(0)->comment('开通OCR认证,第三方认证开启时有效'); + $table->boolean('type')->default(1); + $table->string('code')->nullable()->comment('个人认证接口code'); + $table->string('url')->nullable()->comment('个人认证接口地址'); + $table->string('ocr_appid')->nullable()->comment('ocr认证配置'); + $table->string('ocr_secretkey')->nullable()->comment('ocr认证配置'); + $table->boolean('status')->default(0); + $table->timestamps(); + }); + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_certification_configs'); + Artisan::call('db:seed', [ + '--class' => UserCertificationConfigsSeeder::class, + ]); + } +} diff --git a/modules/User/Database/Migrations/2022_07_26_160901_create_user_stocks_table.php b/modules/User/Database/Migrations/2022_07_26_160901_create_user_stocks_table.php new file mode 100644 index 0000000..1ade178 --- /dev/null +++ b/modules/User/Database/Migrations/2022_07_26_160901_create_user_stocks_table.php @@ -0,0 +1,34 @@ +id(); + $table->unsignedBigInteger('user_id'); + $table->integer('stock')->comment('总数'); + $table->integer('hold')->default(0)->comment('持有数量'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_stocks'); + } +} diff --git a/modules/User/Database/Migrations/2022_08_02_143704_create_user_stock_logs_table.php b/modules/User/Database/Migrations/2022_08_02_143704_create_user_stock_logs_table.php new file mode 100644 index 0000000..0eae415 --- /dev/null +++ b/modules/User/Database/Migrations/2022_08_02_143704_create_user_stock_logs_table.php @@ -0,0 +1,35 @@ +id(); + $table->unsignedBigInteger('user_stock_id')->index(); + $table->unsignedBigInteger('identity_id')->index(); + $table->string('type')->comment('类型'); + $table->integer('variable'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_stock_logs'); + } +} diff --git a/modules/User/Database/Migrations/2022_09_07_141907_create_user_logs_table.php b/modules/User/Database/Migrations/2022_09_07_141907_create_user_logs_table.php new file mode 100644 index 0000000..10dcb29 --- /dev/null +++ b/modules/User/Database/Migrations/2022_09_07_141907_create_user_logs_table.php @@ -0,0 +1,34 @@ +id(); + $table->unsignedBigInteger('user_id')->index(); + $table->unsignedBigInteger('admin_id')->index(); + $table->string('remark')->comment('备注内容'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_logs'); + } +} diff --git a/modules/User/Database/Migrations/2022_09_07_170415_add_stock_to_user_orders_table.php b/modules/User/Database/Migrations/2022_09_07_170415_add_stock_to_user_orders_table.php new file mode 100644 index 0000000..ffe740f --- /dev/null +++ b/modules/User/Database/Migrations/2022_09_07_170415_add_stock_to_user_orders_table.php @@ -0,0 +1,32 @@ +integer('stock')->default(0)->comment('水数量')->after('year'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('user_orders', function (Blueprint $table) { + + }); + } +} diff --git a/modules/User/Database/Migrations/2022_09_08_111009_add_scource_to_user_orders_table.php b/modules/User/Database/Migrations/2022_09_08_111009_add_scource_to_user_orders_table.php new file mode 100644 index 0000000..502cf6b --- /dev/null +++ b/modules/User/Database/Migrations/2022_09_08_111009_add_scource_to_user_orders_table.php @@ -0,0 +1,32 @@ +json('source')->nullable()->after('cover'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('user_orders', function (Blueprint $table) { + + }); + } +} diff --git a/modules/User/Database/Migrations/2022_09_28_142001_create_user_subscribes_table.php b/modules/User/Database/Migrations/2022_09_28_142001_create_user_subscribes_table.php new file mode 100644 index 0000000..bede40d --- /dev/null +++ b/modules/User/Database/Migrations/2022_09_28_142001_create_user_subscribes_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('unionid')->index(); + $table->string('openid')->index(); + $table->string('subscribe')->default(0); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_subscribes'); + } +} diff --git a/modules/User/Database/Migrations/2022_09_28_142001_create_user_wechat_subscribes_table.php b/modules/User/Database/Migrations/2022_09_28_142001_create_user_wechat_subscribes_table.php new file mode 100644 index 0000000..9a1e823 --- /dev/null +++ b/modules/User/Database/Migrations/2022_09_28_142001_create_user_wechat_subscribes_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('unionid')->index(); + $table->string('openid')->index(); + $table->string('subscribe')->default(0); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_subscribes'); + } +} diff --git a/modules/User/Database/Seeders/UserCertificationConfigsSeeder.php b/modules/User/Database/Seeders/UserCertificationConfigsSeeder.php new file mode 100644 index 0000000..fd132a6 --- /dev/null +++ b/modules/User/Database/Seeders/UserCertificationConfigsSeeder.php @@ -0,0 +1,23 @@ + 0, + 'is_ocr_open' => 0, + 'type' => 1, + 'code' => '', + 'url' => '', + 'ocr_appid' => '', + 'ocr_secretkey' => '', + 'status' => 0, + ]); + } +} \ No newline at end of file diff --git a/modules/User/Events/UserCertificationSuccess.php b/modules/User/Events/UserCertificationSuccess.php new file mode 100644 index 0000000..a015ddd --- /dev/null +++ b/modules/User/Events/UserCertificationSuccess.php @@ -0,0 +1,27 @@ +certification = $certification; + } + +} diff --git a/modules/User/Events/UserJoinIdentity.php b/modules/User/Events/UserJoinIdentity.php new file mode 100644 index 0000000..d1e006f --- /dev/null +++ b/modules/User/Events/UserJoinIdentity.php @@ -0,0 +1,38 @@ +user = $user; + $this->identity = $identity; + } + +} diff --git a/modules/User/Events/UserLoginSuccess.php b/modules/User/Events/UserLoginSuccess.php new file mode 100644 index 0000000..995a7c7 --- /dev/null +++ b/modules/User/Events/UserLoginSuccess.php @@ -0,0 +1,51 @@ +user = $user; + $this->request = $request; + $this->gateway = $gateway; + } + +} diff --git a/modules/User/Events/UserOrderPaid.php b/modules/User/Events/UserOrderPaid.php new file mode 100644 index 0000000..222cf5f --- /dev/null +++ b/modules/User/Events/UserOrderPaid.php @@ -0,0 +1,29 @@ +order = $order; + } + +} diff --git a/modules/User/Events/UserRemoveIdentity.php b/modules/User/Events/UserRemoveIdentity.php new file mode 100644 index 0000000..089ea14 --- /dev/null +++ b/modules/User/Events/UserRemoveIdentity.php @@ -0,0 +1,38 @@ +user = $user; + $this->identity = $identity; + } + +} diff --git a/modules/User/Events/UserSignReplenishSuccess.php b/modules/User/Events/UserSignReplenishSuccess.php new file mode 100644 index 0000000..afa3d07 --- /dev/null +++ b/modules/User/Events/UserSignReplenishSuccess.php @@ -0,0 +1,25 @@ +log = $log; + } + +} diff --git a/modules/User/Events/UserSignSuccess.php b/modules/User/Events/UserSignSuccess.php new file mode 100644 index 0000000..01d7de2 --- /dev/null +++ b/modules/User/Events/UserSignSuccess.php @@ -0,0 +1,25 @@ +log = $log; + } + +} diff --git a/modules/User/Events/UserUpdateIdentity.php b/modules/User/Events/UserUpdateIdentity.php new file mode 100644 index 0000000..8eeb273 --- /dev/null +++ b/modules/User/Events/UserUpdateIdentity.php @@ -0,0 +1,42 @@ +user = $user; + $this->before = $before; + $this->after = $after; + } + +} diff --git a/modules/User/Facades/Calendar.php b/modules/User/Facades/Calendar.php new file mode 100644 index 0000000..67cfa5f --- /dev/null +++ b/modules/User/Facades/Calendar.php @@ -0,0 +1,109 @@ +startOfMonth()->startOfWeek(0)->toDateString(); + $end = Carbon::parse($date)->endOfMonth()->endOfWeek(6)->toDateString(); + $data = self::getBetweenDate($start, $end); + + $calendar = Carbon::parse($date); + + $days = $data->map(function ($today) use ($calendar) { + return [ + 'today' => $today->toDateString(), + 'day' => (int) $today->format('j'), + 'isPast' => $today->isPast(),//是否为之前的日期 + 'isMonth' => $today->isSameMonth($calendar),//当前月 + ]; + }); + + return [ + 'start' => $start, + 'end' => $end, + 'calendar' => $days + ]; + + } + + /** + * Notes: 返回日期区间 + * + * @Author: 玄尘 + * @Date: 2022/2/8 16:52 + * @param string $start_at 开始日期 + * @param string $end_at 结束日期 + */ + public static function getBetweenDate(string $start_at, string $end_at) + { + return collect(CarbonPeriod::create($start_at, $end_at)); + } + + /** + * Notes: 获取日期 某一天的前一天后一天 + * + * @Author: 玄尘 + * @Date: 2022/1/21 11:03 + * @param string $today + * @param string $type + * @return string + */ + public static function day(string $today = '', string $type = ''): string + { + $variate = ['before' => -1, 'after' => 1, '' => 0][$type]; + + return Carbon::parse($today)->addDays($variate)->toDateString(); + } + + /** + * Notes: 获取日期 某一天的前一天后一天 + * + * @Author: 玄尘 + * @Date: 2022/1/21 11:03 + * @param string $month + * @param string $type + * @return string + */ + public static function getFirstDayByMonth(string $month = '', string $type = ''): string + { + $month = Carbon::parse($month); + if ($type == 'before') { + $month = $month->subMonth(); + } + + if ($type == 'after') { + $month = $month->addMonth(); + } + + return $month->startOfMonth()->toDateString(); + } + + /** + * Notes: 获取年份 + * + * @Author: 玄尘 + * @Date: 2022/8/10 9:30 + * @param $start + */ + public static function getYears($startYear) + { + $years = CarbonPeriod::years($startYear); + return $years; + } +} \ No newline at end of file diff --git a/modules/User/Facades/Sms.php b/modules/User/Facades/Sms.php new file mode 100644 index 0000000..f3bf4b7 --- /dev/null +++ b/modules/User/Facades/Sms.php @@ -0,0 +1,122 @@ + + * @param string|null $key 配置的主键 + * @return mixed + */ + public static function getConfig(string $key = null) + { + $model = SmsConfig::orderByDesc('in_use')->first(); + + $config = [ + 'debug' => $model->debug ?? true, + 'debug_code' => $model->debug_code ?? '0000', + 'length' => $model->length ?? 4, + 'template' => $model->template ?? [], + 'default' => [ + 'strategy' => OrderStrategy::class, + 'gateways' => [ + $model->default_gateway ?? 'aliyun', + ], + ], + 'gateways' => [ + $model->default_gateway => $model->getGateway(), + ], + ]; + +// dd($config); + + if (isset($config['gateways']['aliyun'])) { + $config['gateways']['aliyun']['access_key_id'] = $config['gateways']['aliyun']['APP_ID']; + $config['gateways']['aliyun']['access_key_secret'] = $config['gateways']['aliyun']['APP_SECRET']; + $config['gateways']['aliyun']['sign_name'] = $config['gateways']['aliyun']['SIGN_NAME']; + } + + return Arr::get($config, $key) ?? $config; + } + + /** + * Notes : 发送验证码短信 + * + * @Date : 2021/5/26 4:17 下午 + * @Author : + * @param string $mobile 手机号码 + * @param string $channel 验证通道 + * @throws InvalidArgumentException + * @throws NoGatewayAvailableException + */ + public static function sendVerificationCode(string $mobile, string $channel = 'DEFAULT') + { + $message = new VerificationCode(self::getConfig(), $channel); + + if (! self::getConfig('debug')) { + app('sms')->send($mobile, $message); + } + + SmsModel::create([ + 'mobile' => $mobile, + 'channel' => $channel, + 'gateway' => self::getConfig('debug') ? 'debug' : self::getConfig('default.gateways.0'), + 'content' => $message->code, + ]); + } + + /** + * 验证短信 + * + * @Author: + * @Date :2018-11-07T14:26:38+0800 + * @param string $mobile 手机号码 + * @param string $code 验证码 + * @param string $channel 验证通道 + * @return bool + */ + public static function checkCode(string $mobile, string $code, string $channel = 'DEFAULT'): bool + { + $Sms = SmsModel::where('mobile', $mobile)->where('channel', $channel)->first(); + + if ($Sms && $Sms->content == $code) { + if ($Sms->used && ! self::getConfig('debug')) { + return false; + } + # todo 有效期判定 + $Sms->used = 1; + $Sms->save(); + + return true; + } else { + return false; + } + } + + /** + * Notes : 发送通知短信 + * + * @Date : 2021/5/26 5:08 下午 + * @Author : + * @param string|array $mobile 接受短信的手机号 + * @param string $content 短信内容 + */ + public static function sendNotification($mobile, string $content) + { + # todo. 这里要实现批量发送,短信通道等操作,待测试 + } + +} diff --git a/modules/User/Facades/UserSign.php b/modules/User/Facades/UserSign.php new file mode 100644 index 0000000..964fe8b --- /dev/null +++ b/modules/User/Facades/UserSign.php @@ -0,0 +1,308 @@ +sign->continue_days; + $counts = $user->sign->counts; + if (SignLog::where('user_id', $user->id) + ->whereDate('date', (clone $date)->subDay()->toDateString()) + ->exists()) { + $continue_days++; + } else { + $continue_days = 1; + } + + $crystals = self::getSignCrystal($continue_days); + $rule_name = $params['rule_name']; + $str = "第{$continue_days}天签到"; + if ($crystals > 0) { + $user->account->rule($rule_name, 0, false, [ + 'frozen_at' => now()->toDateTimeString(), + 'settle_at' => now()->toDateTimeString(), + 'remark' => '第'.$continue_days.'天签到', + ]); + $str .= ",获得{$crystals}水滴"."\n"; +// $str .= '第'.$continue_days.'天签到'; + } + + + $task_crystals = self::getTaskCrystal($continue_days); + if ($task_crystals > 0) { +// $user->account->rule($rule_name, $task_crystals, true, [ +// 'frozen_at' => now()->toDateTimeString(), +// 'settle_at' => now()->toDateTimeString(), +// 'remark' => '连续签到'.$continue_days.'天', +// ]); +// $str .= '连续签到'.$continue_days.'天,获得水晶'.$task_crystals; + $str .= '连续签到'.$continue_days.'天'; + } + + $user->sign->update([ + 'continue_days' => $continue_days, + 'counts' => ++$counts, + 'last_sign_at' => $date, + ]); + SignLog::create([ + 'user_id' => $user->id, + 'date' => $date, + 'type' => 0, + ]); + + return [true, $str]; + } catch (Exception $e) { + return [false, $e->getMessage()]; + } + } + + /** + * 返回连续签到额外任务 + * + * @param int $continue_days + * @return int + */ + public static function getTaskCrystal(int $continue_days = 1): int + { + $tasks = SignConfig::getTasks(); + $task = $tasks->firstWhere('day', $continue_days); + + return $task['number'] ?? 0; + } + + public static function getNextTaskCrystal($continue_days = 1) + { + $tasks = SignConfig::getTasks(); + + return $tasks->where('day', ' > ', $continue_days) + ->sortBy('day') + ->first(); + } + + /** + * 返回签到奖励 + * + * @param int $continue_days + * @return HigherOrderCollectionProxy|int|mixed + */ + public static function getSignCrystal(int &$continue_days = 1) + { + $params = SignConfig::getParams(); + $crystals = 0; + + switch ($params['type']) { + case 'single': + $crystals = $params['single_number']; + break; + case 'continuous': + $crystals = (int) $params['continuous_base'] + (int) bcmul($continue_days - 1, + $params['continuous_incremental'], + 0); + break; + case 'cycle': + if ($continue_days > $params['cycle_day']) { + $continue_days -= $params['cycle_day']; + } + $crystals = (int) $params['cycle_base'] + (int) bcmul($continue_days - 1, $params['cycle_incremental'], + 0); + break; + } + + return $crystals; + } + + /** + * Notes : 某一天是否可签到 + * + * @Date : 2021/5/28 3:25 下午 + * @Author : + * @param User $user + * @param Carbon $date + * @return bool + */ + public static function canSign(User $user, DateTime $date): bool + { + $identityMiddle = $user->identityMiddle()->first(); + $start_at = $identityMiddle->started_at; + if (! $start_at) { + $start_at = $identityMiddle->created_at; + } + + return $identityMiddle->identity->order > 1 && + Carbon::parse($date)->endOfDay()->gt($start_at) && + ! $user->IsSign($date) && + $date->isToday(); + } + + /** + * Notes: 是否可以补签 + * + * @Author: 玄尘 + * @Date: 2022/8/12 8:51 + * @param User $user + * @param DateTime $date + * @return bool + */ + public static function canReSign(User $user, DateTime $date): bool + { + $identityMiddle = $user->identityMiddle()->first(); + $start_at = $identityMiddle->started_at; + if (! $start_at) { + $start_at = $identityMiddle->created_at; + } + + return ! $user->IsSign($date) && + now()->endOfDay()->gt($date) && + ! $date->isToday() && + Carbon::parse($date)->endOfDay()->gt($start_at); + } + + /** + * Notes: 获取不可打卡的信息 + * + * @Author: 玄尘 + * @Date: 2022/8/10 13:18 + */ + public static function getNotSignText(User $user, DateTime $date) + { + $identityMiddle = $user->identityMiddle()->first(); + if (SignLog::where('user_id', $user->id)->whereDate('date', $date)->exists()) { + return '今日已打卡'; + } + + if ($identityMiddle->identity->order < 2) { + return '未开通会员'; + } + + $started_at = $identityMiddle->started_at; + if (! $started_at) { + $started_at = $identityMiddle->created_at; + } + + if ($started_at->gt(Carbon::parse($date)->startOfDay())) { + return '当前时间还未开通会员'; + } + + + } + + /** + * Notes : 补签 + * + * @Date : 2021/5/28 3:15 下午 + * @Author : + * @param User $user + * @param DateTime $date + * @return bool + * @throws Exception + */ + public static function replenish(User $user, DateTime $date): array + { + if (! self::canReSign($user, $date)) { + throw new Exception(self::getNotSignText($user, $date)); + } + $params = SignConfig::getParams(); + + $continue_days = $user->sign->continue_days; + + $crystals = self::getSignCrystal($continue_days); + $rule_name = $params['rule_name']; + $str = $date->format('Y-m-d')."号补签"; + + if ($crystals > 0) { + $user->account->rule($rule_name, 0, false, [ + 'frozen_at' => now()->toDateTimeString(), + 'settle_at' => now()->toDateTimeString(), + 'remark' => now()->toDateTimeString().'天补签到', + ]); + $str .= ",获得{$crystals}水滴"."\n"; + } + + $counts = $user->sign->counts; + + + $user->sign->update([ + 'counts' => ++$counts, + ]); + + $log = SignLog::create([ + 'user_id' => $user->id, + 'date' => $date, + 'type' => 1, + ]); + + self::recalculateContinueDays($user); + + return [true, $str]; + } + + /** + * Notes: 重新计算连续签到日期 + * + * @Author: 玄尘 + * @Date: 2022/8/4 9:34 + */ + public static function recalculateContinueDays(User $user) + { + $reset_at = $user->sign->reset_at; + $days = SignLog::query() + ->byUser($user) + ->when($reset_at, function ($q) use ($reset_at) { + $q->where('date', '>=', $reset_at); + }) + ->latest('date') + ->pluck('date'); + + $continue_days = 1; + foreach ($days as $key => $day) { + if (isset($days[$key + 1])) { + if ($day->subDay()->isSameDay($days[$key + 1])) { + $continue_days++; + } else { + break; + } + } + } + + $user->sign->update([ + 'continue_days' => max($continue_days, 1), + 'counts' => $user->signLogs()->count() + ]); + return true; + } + +} diff --git a/modules/User/Http/Controllers/Admin/AccountController.php b/modules/User/Http/Controllers/Admin/AccountController.php new file mode 100644 index 0000000..712f185 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/AccountController.php @@ -0,0 +1,80 @@ +model()->orderByDesc('user_id'); + + $grid->filter(function (Grid\Filter $filter) { + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('user.username', '用户名'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('user.info.nickname', '用户昵称'); + }); + }); + + $grid->disableActions(); + $grid->disableCreateButton(); + + $grid->model()->with(['user.info']); + + $grid->column('user.username', '用户名'); + $grid->column('用户昵称')->display(function () { + return $this->user->info->nickname; + }); + $grid->column('balance', '余额'); + $grid->column('score', '水滴'); +// $grid->column('coins'); +// $grid->column('other'); + $grid->column('updated_at', '更新时间'); + $grid->column('账户日志')->display(function () { + return '账户日志'; + })->link(function () { + return route('admin.user.account.logs', $this); + }, '_self'); + + return $grid; + } + + public function detail($id): Grid + { + $grid = new Grid(new AccountLog()); + + $grid->disableCreateButton(); + $grid->disableActions(); + + if (is_numeric($id)) { + $grid->model()->where('account_id', $id); + } else { + $grid->column('用户') + ->display(function () { + return $this->account->user->username."({$this->account->user->info->nickname})"; + }); + } + + $grid->column('type', '账户类型'); + $grid->column('rule.title', '账变规则'); + $grid->column('amount', '账变金额'); + $grid->column('balance', '当期余额'); + $grid->column('remark', '备注'); + $grid->column('source', '详情')->hide(); + $grid->column('created_at', '账变时间'); + + return $grid; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/Actions/AddUserRemark.php b/modules/User/Http/Controllers/Admin/Actions/AddUserRemark.php new file mode 100644 index 0000000..3f722bc --- /dev/null +++ b/modules/User/Http/Controllers/Admin/Actions/AddUserRemark.php @@ -0,0 +1,32 @@ +addLog(Admin::user(), $request->remark); + + return $this->response()->success('添加备注成功')->refresh(); + } catch (\Exception $exception) { + return $this->response()->error($exception->getMessage())->refresh(); + } + } + + public function form(User $user) + { + $this->textarea('remark', '备注')->required(); + } + +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/Actions/Certification/ConfigPublish.php b/modules/User/Http/Controllers/Admin/Actions/Certification/ConfigPublish.php new file mode 100644 index 0000000..2763277 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/Actions/Certification/ConfigPublish.php @@ -0,0 +1,26 @@ +', $model->id)->update(['status' => 0]); + $model->status = 1; + $model->save(); + + return $this->response()->success('发布配置完成')->refresh(); + } + + public function dialog() + { + $this->confirm('确定使用配置么?'); + } +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/Actions/Certification/Replicate.php b/modules/User/Http/Controllers/Admin/Actions/Certification/Replicate.php new file mode 100644 index 0000000..33e97af --- /dev/null +++ b/modules/User/Http/Controllers/Admin/Actions/Certification/Replicate.php @@ -0,0 +1,33 @@ +replicate(); + + $new->status = 0; + $new->save(); + }); + + return $this->response()->success('复制菜单完成')->refresh(); + } catch (\Exception $exception) { + return $this->response()->error('复制菜单出错了')->refresh(); + } + } + + public function dialog() + { + $this->confirm('确定复制配置么?'); + } +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/Actions/JoinIdentity.php b/modules/User/Http/Controllers/Admin/Actions/JoinIdentity.php new file mode 100644 index 0000000..c8758ac --- /dev/null +++ b/modules/User/Http/Controllers/Admin/Actions/JoinIdentity.php @@ -0,0 +1,69 @@ +join_identity_id; + $remark = $request->remark; + $identity = Identity::find($identity_id); + $price = $identity->getCondition('price', '0'); + + $data = [ + 'user_id' => $user->id, + 'identity_id' => $identity_id, + 'year' => 1, + 'type' => 1, + 'stock' => $identity->stock, + 'name' => '', + 'card_no' => '', + 'cover' => '', + 'state' => Order::STATE_INIT, + 'price' => $price, + ]; + + $order = Order::create($data); + $order->pay(); + +// +// $user->joinIdentity($identity_id, 'System', [ +// 'remark' => $remark, +// ]); + + return $this->response()->success('加入身份成功')->refresh(); + } catch (Exception $e) { + return $this->response()->error($e->getMessage())->refresh(); + } + } + + public function form(User $user) + { + $userIdentity = $user->identityFirst(); + if (empty($userIdentity)) { + $identities = Identity::whereIn('order', [2, 3, 4, 5])->pluck('name', 'id'); + } elseif ($userIdentity->job == Identity::JOB_YK) { + $identities = Identity::whereIn('order', [2, 3, 4, 5])->pluck('name', 'id'); + } else { + $identities = Identity::where('order', '>', $userIdentity->order)->pluck('name', 'id'); + } + $this->select('join_identity_id', '加入身份') + ->options($identities) + ->required(); + $this->text('remark', '加入备注'); + $this->confirm('确认加入该身份?'); + } + +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/Actions/Pay.php b/modules/User/Http/Controllers/Admin/Actions/Pay.php new file mode 100644 index 0000000..21c25fd --- /dev/null +++ b/modules/User/Http/Controllers/Admin/Actions/Pay.php @@ -0,0 +1,25 @@ +pay(); + + return $this->response()->success('支付状态调整成功')->refresh(); + } catch (\Exception $exception) { + return $this->response()->error($exception->getMessage())->refresh(); + } + } + +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/Actions/Refund.php b/modules/User/Http/Controllers/Admin/Actions/Refund.php new file mode 100644 index 0000000..490b753 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/Actions/Refund.php @@ -0,0 +1,25 @@ +refund(); + + return $this->response()->success('退款成功')->refresh(); + } catch (\Exception $exception) { + return $this->response()->error($exception->getMessage())->refresh(); + } + } + +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/Actions/RemoveIdentity.php b/modules/User/Http/Controllers/Admin/Actions/RemoveIdentity.php new file mode 100644 index 0000000..a0b6234 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/Actions/RemoveIdentity.php @@ -0,0 +1,48 @@ +remove_identity_id; + $remark = $request->remark; + $user->removeIdentity($identity_id, 'System', [ + 'remark' => $remark, + ]); + + $defaultIdentity = Identity::where('default', 1)->first(); + if ($defaultIdentity) { + $user->joinIdentity($defaultIdentity->id, IdentityLog::CHANNEL_SYSTEM); + } + + return $this->response()->success('移除身份成功')->refresh(); + } catch (Exception $e) { + return $this->response()->error($e->getMessage())->refresh(); + } + } + + public function form(Model $model) + { + $this->select('remove_identity_id', '移除身份') + ->options(Identity::whereIn('id', $model->identities()->get()->pluck('id')) + ->pluck('name', 'id')) + ->required(); + $this->text('remark', '移除说明'); + $this->confirm('确认移除该身份?'); + } + +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/Actions/UpdateRelation.php b/modules/User/Http/Controllers/Admin/Actions/UpdateRelation.php new file mode 100644 index 0000000..f4705fb --- /dev/null +++ b/modules/User/Http/Controllers/Admin/Actions/UpdateRelation.php @@ -0,0 +1,35 @@ +updateParent($request->parent_id)) { + return $this->response()->success('变更成功')->refresh(); + } else { + return $this->response()->error('变更失败')->refresh(); + } + } catch (Exception $exception) { + return $this->response()->error($exception->getMessage())->refresh(); + } + } + + public function form() + { + $this->text('parent_id', '目标ID'); + $this->confirm('确认变更隶属?'); + } + +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/Actions/UserStatusInit.php b/modules/User/Http/Controllers/Admin/Actions/UserStatusInit.php new file mode 100644 index 0000000..e46319c --- /dev/null +++ b/modules/User/Http/Controllers/Admin/Actions/UserStatusInit.php @@ -0,0 +1,27 @@ +update([ + 'status' => User::STATUS_INIT + ]); + + return $this->response()->success('设置成功')->refresh(); + } catch (\Exception $exception) { + return $this->response()->error($exception->getMessage())->refresh(); + } + } + +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/Actions/UserStatusRefund.php b/modules/User/Http/Controllers/Admin/Actions/UserStatusRefund.php new file mode 100644 index 0000000..16d3837 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/Actions/UserStatusRefund.php @@ -0,0 +1,27 @@ +update([ + 'status' => User::STATUS_REFUND + ]); + + return $this->response()->success('设置成功')->refresh(); + } catch (\Exception $exception) { + return $this->response()->error($exception->getMessage())->refresh(); + } + } + +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/CertificationConfigController.php b/modules/User/Http/Controllers/Admin/CertificationConfigController.php new file mode 100644 index 0000000..d83043c --- /dev/null +++ b/modules/User/Http/Controllers/Admin/CertificationConfigController.php @@ -0,0 +1,68 @@ +disableFilter(); + $grid->model()->orderBy('id', 'desc'); + $grid->actions(function ($action) { + $action->disableView(); + $action->add(new Replicate); + + if ($this->row->status == 0) { + $action->add(new ConfigPublish); + } else { + $action->disableEdit(); + $action->disableDelete(); + } + }); + $grid->column('id', 'ID'); + $grid->column('配置HASH')->display(function () { + return md5($this->created_at); + }); + $grid->column('is_open', '开通网络认证')->bool(); + $grid->column('is_ocr_open', '开通OCR认证')->bool(); + $grid->column('type', '认证类型') + ->using(UserCertificationConfig::TYPE) + ->label(); + $grid->column('status', '状态')->bool(); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + protected function form(): Form + { + $form = new Form(new UserCertificationConfig()); + + $states = [ + 'on' => ['value' => 1, 'text' => '开启', 'color' => 'success'], + 'off' => ['value' => 0, 'text' => '关闭', 'color' => 'danger'], + ]; + $form->switch('is_open', '开通网络认证')->states($states); + $form->switch('is_ocr_open', '开通OCR认证')->states($states); + $form->radioButton('type', '认证类型') + ->options(UserCertificationConfig::TYPE) + ->required(); + $form->password('code', '阿里云code')->help('开启认证时必填'); + $form->url('url', '阿里云接口地址')->help('开启OCR认证时必填'); + $form->text('ocr_appid', 'OCRAPPID')->help('开启OCR认证时必填'); + $form->password('ocr_secretkey', 'OCRSecret')->help('开启OCR认证时必填'); + + return $form; + } +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/CertificationController.php b/modules/User/Http/Controllers/Admin/CertificationController.php new file mode 100644 index 0000000..48676b3 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/CertificationController.php @@ -0,0 +1,35 @@ +disableCreateButton(); + $grid->disableActions(); + $grid->filter(function (Grid\Filter $filter) { + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('name', '姓名'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('id_card', '身份证号'); + }); + }); + + $grid->column('id', '#ID#'); + $grid->column('name', '姓名'); + $grid->column('id_card', '身份证号'); + $grid->column('created_at', '申请时间'); + + return $grid; + } +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/GatewayController.php b/modules/User/Http/Controllers/Admin/GatewayController.php new file mode 100644 index 0000000..b91d85e --- /dev/null +++ b/modules/User/Http/Controllers/Admin/GatewayController.php @@ -0,0 +1,42 @@ +disableFilter(); + + $grid->column('name', '网关名称'); + $grid->column('slug', '网关标识'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new SmsGateway()); + + $form->text('name', '网关名称')->required(); + $form->text('slug', '网关标识')->required(); + $form->keyValue('configs', '网关配置')->value([ + 'APP_ID' => '', + 'APP_SECRET' => '', + 'SIGN_NAME' => '', + ]); + + return $form; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/IdentitiesController.php b/modules/User/Http/Controllers/Admin/IdentitiesController.php new file mode 100644 index 0000000..6c1bd61 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/IdentitiesController.php @@ -0,0 +1,226 @@ +model()->oldest('id')->oldest('order'); + $grid->filter(function (Grid\Filter $filter) { + $filter->scope('trashed', '回收站')->onlyTrashed(); + + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('name', '身份名称'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('status', '状态')->select([ + 0 => '禁用', + 1 => '正常', + ]); + }); + }); + + $grid->model()->oldest()->withCount('users'); + + $grid->column('id', '#ID#'); + $grid->column('cover', '展示图片')->image('', 40); + $grid->column('name', '身份名称'); + $grid->column('order', '排序'); + $grid->column('status', '状态')->bool(); + $grid->column('serial_open', '是否开启编号')->bool(); + $grid->column('serial_places', '编号长度'); + $grid->column('serial_prefix', '编号前缀'); + $grid->column('years', '有效期(月)') + ->display(function () { + return $this->years > 0 ? $this->years : '永久'; + }); + $grid->column('stock', '送水(箱)'); + $grid->column('users_count', '组内用户'); + $grid->column('job', '身份')->using(Identity::JOBS)->label(); + $grid->column('total', '可开通总数'); + $grid->column('channel', '缴费渠道') + ->using(Identity::CHANNELS) + ->label([ + Identity::CHANNEL_ONLINE => 'primary', + Identity::CHANNEL_OFFLINE => 'success', + ]); + foreach (config('identity.conditions') as $key => $title) { + $grid->column($title)->display(function () use ($key) { + return $this->getCondition($key, '无'); + }); + } + foreach (config('identity.rules') as $key => $title) { + $grid->column($title)->display(function () use ($key) { + return $this->getRule($key, '无'); + }); + } + + $states = [ + 'on' => ['value' => 1, 'text' => '是', 'color' => 'primary'], + 'off' => ['value' => 0, 'text' => '否', 'color' => 'default'], + ]; + $grid->column('default', '默认身份')->switch($states); + $grid->column('can_buy', '前台开通')->switch($states); + + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Identity()); + $form->text('name', '身份名称') + ->required() + ->rules([ + 'unique:Modules\User\Models\Identity,name,{{id}}', + ], [ + 'unique' => '身份名称已经存在', + ]); + $this->cover($form, 'cover', '展示图片'); + $form->number('order', '排序') + ->required() + ->default(0); + $form->textarea('description', '身份简介') + ->rules(['max:255'], [ + 'max' => '简介内容最大不能超过:max个字符', + ]); + $form->switch('default', '默认身份') + ->default(0); + $form->switch('can_buy', '前台开通') + ->help('是否可以在前台开通身份') + ->default(0); + $form->number('stock', '库存') + ->help('开通身份送多少水') + ->default(0); + + $form->number('years', '有效期(月)') + ->help('0未长期有效') + ->default(0); + + $form->switch('status', '状态') + ->required() + ->default(1); + $form->select('serial_open', '是否开启编号') + ->options([ + false => '关闭', + true => '开启', + ]) + ->required(); + $form->number('serial_places', '编号长度') + ->rules([ + 'exclude_if:serial_open,true', + 'required', + ], [ + 'required' => '编号开启必填', + ]); + $form->text('serial_prefix', '编号前缀'); + $form->text('protocol_url', '协议地址')->help('显示开通身份签署的协议'); + $form->radioButton('channel', '缴费渠道') + ->options(Identity::CHANNELS) + ->default(1); + $form->radioButton('job', '身份') + ->options(Identity::JOBS) + ->default(0) + ->when(Identity::JOB_TY, function (Form $form) { + $form->number('total', '可开通总数')->default(100); + $form->date('end_at', '结束日期'); + }); + $form->divider(); + + $form->table('conditions', '升级条件', function (NestedForm $form) { + $form->select('name', '条件') + ->required() + ->options(config('identity.conditions')) + ->rules('required'); + $form->select('compare', '规则') + ->options([ + '>' => '大于', + '>=' => '大于等于', + '=' => '等于', + '<' => '小于', + '<=' => '小于等于', + ]) + ->required() + ->rules('required'); + $form->text('value', '值') + ->required() + ->rules('required'); + }); +// + $form->table('rules', '身份权益', function (NestedForm $form) { + $form->select('name', '权益') + ->required() + ->options(config('identity.rules')) + ->rules('required'); + $form->select('compare', '规则') + ->options([ + '>' => '大于', + '>=' => '大于等于', + '=' => '等于', + '<' => '小于', + '<=' => '小于等于', + ]) + ->required() + ->rules('required'); + $form->text('value', '值') + ->required() + ->rules('required'); + }); + $form->table('ruleshows', '身份权益(前台展示)', function (NestedForm $form) { + $form->image('icon', '权益图片'); + $form->select('name', '权益') + ->options(config('identity.show_rules')) + ->required() + ->rules('required'); + $form->text('value', '值') + ->required() + ->rules('required'); + }); +// + $form->table('rights', '可享受权益', function (NestedForm $form) { + $form->text('name', '名称') + ->required() + ->rules('required'); + $form->image('cover', '权益图片'); + $form->text('remark', '备注') + ->required() + ->rules('required'); + $form->number('order', '排序'); + }); + +// $form->saving(function ($form) { +//// dd(request()->all()); +// }); + + + $form->saved(function ($form) { + $model = $form->model(); + if ($model->default == 1) { + Identity::where('id', '!=', $model->id) + ->where('default', 1) + ->update([ + 'default' => 0, + ]); + } + }); + + return $form; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/IdentityLogController.php b/modules/User/Http/Controllers/Admin/IdentityLogController.php new file mode 100644 index 0000000..e2cd205 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/IdentityLogController.php @@ -0,0 +1,60 @@ +disableActions(); + $grid->disableCreateButton(); + + $grid->filter(function (Grid\Filter $filter) { + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('user.username', '用户名'); + }); + + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('user.info.nickname', '用户昵称'); + }); + + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('channel', '变更方式')->select(IdentityLog::CHANNEL_MAP); + }); + }); + + $grid->column('user.username', '用户名'); + $grid->column('用户昵称')->display(function () { + return $this->user->info->nickname; + }); + $grid->column('before_identity.name', '变更前身份')->display(function () { + if ($this->before == 0) { + return '无'; + } + + return $this->before_identity->name; + }); + $grid->column('after_identity.name', '变更后身份')->display(function () { + if ($this->after == 0) { + return '无'; + } + + return $this->after_identity->name; + }); + $grid->column('channel', '变更方式')->using(IdentityLog::CHANNEL_MAP); + $grid->column('remark', '备注'); + $grid->column('created_at', '变更时间'); + + return $grid; + } + +} diff --git a/modules/User/Http/Controllers/Admin/IndexController.php b/modules/User/Http/Controllers/Admin/IndexController.php new file mode 100644 index 0000000..0d4df03 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/IndexController.php @@ -0,0 +1,156 @@ + + * @return Grid + */ + public function grid(): Grid + { + $grid = new Grid(new User()); + + if (config('user.create_user_by_admin')) { + $grid->quickCreate(function (Grid\Tools\QuickCreate $create) { + $create->text('username', '用户名')->required(); + $create->password('password', '登录密码')->required(); + $create->text('info.nickname', '用户昵称')->required(); + }); + } else { + $grid->disableCreateButton(); + } + + $grid->actions(function (Grid\Displayers\Actions $actions) { + ! config('user.create_user_by_admin') && $actions->disableEdit(); + $actions->disableDelete(); + $actions->disableView(); + $actions->add(new JoinIdentity()); + $actions->add(new RemoveIdentity()); + $actions->add(new UpdateRelation()); + }); + + $grid->quickSearch('username')->placeholder('快速搜索用户名'); + + $grid->filter(function (Grid\Filter $filter) { + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('username', '用户名'); + }); + + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('info.nickname', '用户昵称'); + $filter->like('identityMiddle.serial', '会员编号(数字)'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('identities.id', '身份')->select(Identity::pluck('name', 'id')); + $filter->equal('parent.username', '推荐人'); + }); + }); + + $grid->model()->withCount('addresses')->with(['info', 'parent', 'identities', 'addresses']); + + $grid->column('info.avatar', '头像')->image('', 32); + $grid->column('id', '用户ID'); + $grid->column('username', '用户名'); + $grid->column('info.nickname', '用户昵称'); + +// $grid->column('addresses_count', '收货地址') +// ->link(function () { +// return route('admin.mall.addresses.index', ['user_id' => $this->id]); +// }, '_self'); + $grid->column('推荐人')->display(function () { + return $this->parent->username ?? '无'; + }); + $grid->column('identities', '用户身份') + ->display(function () { + $data = []; + foreach ($this->identities as $identity) { + $data[] = $identity->name.' : '.$identity->serial_prefix.$identity->getOriginal('pivot_serial'); + } + + return $data; + }) + ->label(); + + + $grid->column('是否关注') + ->display(function () { + return $this->isOfficialSubscribe(); + }) + ->bool(); + $grid->column('邀请码') + ->display(function () { + return Hashids::connection('code')->encode($this->id); + }); + $grid->column('created_at', '注册时间'); + + return $grid; + } + + /** + * Notes : 编辑表单 + * + * @Date : 2021/7/15 5:09 下午 + * @Author : + * @return Form + * @throws Exception + */ + public function form(): Form + { + $form = new Form(new User()); + + if ($form->isCreating() && ! config('user.create_user_by_admin')) { + throw new Exception('不运允许添加用户'); + } + + if ($form->isCreating() && ! config('user.edit_user_by_admin')) { + throw new Exception('不运允许编辑用户'); + } + + $form->text('username', '用户名') + ->required() + ->rules('unique:users,username,{{id}}'); + $form->password('password', '登录密码') + ->required() + ->rules('min:6'); + $form->text('info.nickname', '用户昵称'); + + return $form; + } + + /** + * Notes : User 列表选择, 这里没有判断,用户是否已经有店铺了,如果判断的情况,可能导致当前用户 无法被选中 + * + * @Date : 2021/5/6 4:35 下午 + * @Author : + */ + public function ajax(Request $request) + { + $q = $request->get('q'); + + return User::leftJoin('user_infos as info', 'users.id', '=', 'info.user_id') + ->where('username', 'like', "%$q%") + ->orWhere('info.nickname', 'like', "%$q%") + ->select('id', DB::raw('CONCAT(username, " [", info.nickname, "]") as text')) + ->paginate(); + } + +} diff --git a/modules/User/Http/Controllers/Admin/OrderController.php b/modules/User/Http/Controllers/Admin/OrderController.php new file mode 100644 index 0000000..6ceed49 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/OrderController.php @@ -0,0 +1,88 @@ +model()->latest()->where('identity_id', '>', 2); + $grid->disableCreateButton(); + + $grid->actions(function (Grid\Displayers\Actions $actions) { + $actions->disableEdit(); + $actions->disableDelete(); + $actions->disableView(); + if ($actions->row->canPay()) { + $actions->add(new Pay()); + } + + if ($actions->row->canRefund()) { + $actions->add(new Refund()); + } + + }); + + $grid->filter(function (Grid\Filter $filter) { + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->like('user.username', '用户名'); + $filter->like('user.info.nickname', '用户昵称'); + }); + + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->equal('identity.id', '身份')->select(function () { + return Identity::query()->where('order', '>', 1)->pluck('name', 'id'); + }); + $filter->equal('state', '状态')->select(Order::STATES); + }); + + }); + + $grid->column('id', '用户ID'); + $grid->column('升级用户')->display(function () { + return $this->user->username."({$this->user->info->nickname})"; + }); + $grid->column('name', '打款人姓名'); + $grid->column('cover', '打款凭证')->gallery(['width' => 60, 'height' => 60]); + $grid->column('identity.name', '开通身份'); + $grid->column('price', '应打款额')->editable(); + $grid->column('state', '状态')->using(Order::STATES)->label(); + $grid->column('type', '类型')->using(Order::TYPES)->label(); + + $grid->column('created_at', '升级时间'); + + return $grid; + } catch (Exception $exception) { + dd('Payment 模块不存在,无法加载订单数据'); + } + } + + public function form(): Form + { + $form = new Form(new Order()); + + $form->decimal('price', '金额')->required(); + + return $form; + } + +} diff --git a/modules/User/Http/Controllers/Admin/RuleController.php b/modules/User/Http/Controllers/Admin/RuleController.php new file mode 100644 index 0000000..d753226 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/RuleController.php @@ -0,0 +1,60 @@ +disableTools(); + $grid->actions(function (Grid\Displayers\Actions $actions) { + $actions->disableView(); + $actions->disableDelete(); + }); + $grid->column('id', '#ID#'); + $grid->column('title', '规则名称'); + $grid->column('name', '规则关键字'); + $grid->column('type', '规则账户')->using(Account::TYPES); + $grid->column('variable', '固定额度'); + $grid->column('created_at', '创建时间'); + $grid->column('updated_at', '更新时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new AccountRule()); + $form->text('title', '规则名称')->required(); + if ($form->isCreating()) { + $form->text('name', '规则关键字')->required(); + } else { + $form->text('name', '规则关键字') + ->readonly() + ->required(); + } + $form->select('type', '规则账户') + ->options(Account::TYPES) + ->required(); + $form->number('variable', '固定额度')->default(0); + $states = [ + 'on' => ['value' => 1, 'text' => '是', 'color' => 'success'], + 'off' => ['value' => 0, 'text' => '否', 'color' => 'danger'], + ]; + $form->switch('deductions', '立即扣款')->states($states); + $form->textarea('remark', '描述')->required(); + + return $form; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/Selectable/Identities.php b/modules/User/Http/Controllers/Admin/Selectable/Identities.php new file mode 100644 index 0000000..073b128 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/Selectable/Identities.php @@ -0,0 +1,38 @@ +identities)->name; + }; + } + + public function make() + { + $this->column('id', '#ID#'); + $this->column('name', '身份名称'); + $this->column('status', '状态')->bool(); + $this->column('created_at', '时间'); + + $this->filter(function (Filter $filter) { + $filter->like('name', '身份名称'); + }); + } + +} diff --git a/modules/User/Http/Controllers/Admin/ServiceController.php b/modules/User/Http/Controllers/Admin/ServiceController.php new file mode 100644 index 0000000..fe822ee --- /dev/null +++ b/modules/User/Http/Controllers/Admin/ServiceController.php @@ -0,0 +1,51 @@ +disableTools(); + + $grid->model()->with(['identities']); + + $grid->column('id', '#ID#'); + $grid->column('name', '姓名'); + $grid->column('mobile', '联系方式'); + $grid->column('status', '状态')->bool(); + $grid->column('identities', '服务身份') + ->belongsToMany(Identities::class); + $grid->column('created_at', '创建时间'); + $grid->column('updated_at', '更新时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Service()); + $form->text('name', '姓名')->required(); + $form->mobile('mobile', '联系方式')->required(); + $this->cover($form, 'cover', '客服二维码'); + $form->switch('status', '显示')->default(1); + $form->belongsToMany('identities', Identities::class, '服务身份'); + + return $form; + } + +} diff --git a/modules/User/Http/Controllers/Admin/SignBannerController.php b/modules/User/Http/Controllers/Admin/SignBannerController.php new file mode 100644 index 0000000..082e560 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/SignBannerController.php @@ -0,0 +1,40 @@ +column('id', '#ID#'); + $grid->column('title', '标题'); + $grid->column('cover', '封面图')->image('', 100, 100); + $grid->column('status', '状态')->bool(); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new SignBanner()); + + $form->text('title', '标题')->required(); + $this->cover($form); + $form->switch('status', '状态')->default(1); + + return $form; + } +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/SignConfigController.php b/modules/User/Http/Controllers/Admin/SignConfigController.php new file mode 100644 index 0000000..78e9f6c --- /dev/null +++ b/modules/User/Http/Controllers/Admin/SignConfigController.php @@ -0,0 +1,61 @@ + 1]); + + $form = new Form(new SignConfig()); + $form->disableCreatingCheck(); + $form->disableEditingCheck(); + $form->tools(function (Form\Tools $tools) { + $tools->disableDelete(); + $tools->disableView(); + $tools->disableList(); + }); + $form->embeds('params', '配置', function (EmbeddedForm $form) { + $form->radio('open', '是否开启')->options([ + 0 => '关闭', + 1 => '开启', + ])->required(); + $form->text('rule_name', '签到账变关键字')->default(''); + $form->radio('show_type', '签到展示类型') + ->options(SignConfig::SHOWTYPES) + ->default(SignConfig::SHOWTYPES_DAY) + ->required(); + $form->radio('type', '签到类型') + ->options(SignConfig::TYPES) + ->required(); + $form->number('single_number', '单次奖励')->default(0); + $form->number('continuous_base', '连续基础值')->default(0); + $form->number('continuous_incremental', '连续增量')->default(0); + $form->number('cycle_day', '周期天数')->default(0); + $form->number('cycle_base', '周期基础值')->default(0); + $form->number('cycle_incremental', '周期增量')->default(0); + }); + $form->table('tasks', '特殊奖励', function ($form) { + $form->number('day', '天数')->default(0); + $form->number('number', '奖励')->default(0); + }); + + return $form; + } + +} diff --git a/modules/User/Http/Controllers/Admin/SignTextController.php b/modules/User/Http/Controllers/Admin/SignTextController.php new file mode 100644 index 0000000..e4528f1 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/SignTextController.php @@ -0,0 +1,38 @@ +column('id', '#ID#'); + $grid->column('title', '标题'); + $grid->column('description', '一级描述'); + $grid->column('sub_description', '二级描述'); + $grid->column('status', '状态')->bool(); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new SignText()); + + $form->text('title', '标题')->required(); + $form->text('description', '一级描述')->required(); + $form->text('sub_description', '二级描述')->required(); + $form->switch('status', '状态')->default(1); + + return $form; + } +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/SmsConfigController.php b/modules/User/Http/Controllers/Admin/SmsConfigController.php new file mode 100644 index 0000000..29978c9 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/SmsConfigController.php @@ -0,0 +1,54 @@ +disableFilter(); + $grid->model()->orderByDesc('in_use'); + + $grid->column('debug', '调试模式')->bool(); + $grid->column('debug_code', '调试验证码'); + $grid->column('length', '验证码位数'); + $grid->column('expires', '有效期/分钟'); + $grid->column('default_gateway', '默认网关'); + $grid->column('in_use', '使用中')->bool(); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new SmsConfig()); + + $form->select('default_gateway', '默认网关')->options(function () { + return SmsGateway::pluck('name', 'slug'); + })->required(); + $form->switch('debug', '调试模式'); + $form->text('debug_code', '调试验证码')->setWidth(2)->required(); + $form->number('length', '验证码位数')->default(4)->required(); + $form->number('expires', '有效期/分钟')->default(5)->required(); + $form->keyValue('template', '渠道模板')->value([ + 'DEFAULT' => '', + 'LOGIN' => '', + 'REGISTER' => '', + ])->required(); + $form->switch('in_use', '使用中'); + + return $form; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/SmsController.php b/modules/User/Http/Controllers/Admin/SmsController.php new file mode 100644 index 0000000..6d32b93 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/SmsController.php @@ -0,0 +1,37 @@ +disableCreateButton(); + $grid->disableActions(); + + $grid->filter(function (Grid\Filter $filter) { + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('mobile', '接收手机'); + }); + }); + + $grid->column('mobile', '接收手机'); + $grid->column('channel', '短信渠道'); + $grid->column('gateway', '发送网关'); + $grid->column('content', '短信内容'); + $grid->column('used', '使用')->bool(); + $grid->column('created_at', '发送时间'); + + return $grid; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Admin/StockController.php b/modules/User/Http/Controllers/Admin/StockController.php new file mode 100644 index 0000000..0812cf4 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/StockController.php @@ -0,0 +1,60 @@ + + * @return Grid + */ + public function grid(): Grid + { + $grid = new Grid(new UserStock()); + + + $grid->disableCreateButton(); + $grid->disableActions(); + + + $grid->filter(function (Grid\Filter $filter) { + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('user.username', '用户名'); + }); + }); + + $grid->model()->withCount('logs')->with(['user.info']); + + $grid->column('user.username', '用户名'); + $grid->column('用户昵称') + ->display(function () { + return $this->user->info->nickname; + }); + + $grid->column('stock', '总数'); + $grid->column('hold', '提货数') + ->link(function () { + return admin_url('/users/stocks/'.$this->id.'/logs'); + }, '_blank'); + $grid->column('剩余') + ->display(function () { + return $this->residue; + }); + + $grid->column('created_at', '注册时间'); + + return $grid; + } + +} diff --git a/modules/User/Http/Controllers/Admin/StockLogController.php b/modules/User/Http/Controllers/Admin/StockLogController.php new file mode 100644 index 0000000..2275369 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/StockLogController.php @@ -0,0 +1,58 @@ +header($stock->user->info->nickname) + ->description('库存记录') + ->body($this->grid($stock)); + } + + /** + * Notes : 用户管理列表 + * + * @Date : 2021/3/11 1:59 下午 + * @Author : + * @return Grid + */ + public function grid($stock): Grid + { + $grid = new Grid(new UserStockLog()); + + $grid->disableCreateButton(); + $grid->disableActions(); + + $grid->model()->where('user_stock_id', $stock->id)->with(['userStock']); + + $grid->column('id', 'ID'); + $grid->column('身份') + ->display(function () { + if ($this->identity) { + return $this->identity->name; + } else { + return '---'; + } + }); + $grid->column('type', '类型') + ->using(UserStockLog::TYPES) + ->label(); + $grid->column('variable', '数量'); + $grid->column('created_at', '操作时间'); + return $grid; + } + +} diff --git a/modules/User/Http/Controllers/Api/Account/LogController.php b/modules/User/Http/Controllers/Api/Account/LogController.php new file mode 100644 index 0000000..1cc1808 --- /dev/null +++ b/modules/User/Http/Controllers/Api/Account/LogController.php @@ -0,0 +1,61 @@ +account->logs() + ->where('type', 'score') + ->paginate(); + $data = [ + 'score' => $user->account->score, + 'logs' => new UserAccountLogCollection($logs) + ]; + return $this->success($data); + } + + /** + * Notes:现金账户 + * + * @Author: 玄尘 + * @Date: 2022/8/19 8:37 + */ + public function balance(): \Illuminate\Http\JsonResponse + { + $user = Api::user(); + $logs = $user->account->logs()->where('type', 'balance')->paginate(); + + $yesterday = $user->account->logs() + ->whereDate('created_at', now()->subDay()->toDateTime()) + ->where('type', 'balance') + ->where('amount', '>', 0) + ->sum('amount') ?? 0; + + $withdraw = $user->withdraws()->sum('amount') ?? 0; + + $data = [ + 'account' => [ + 'balance' => floatval($user->account->balance), + 'yesterday' => floatval($yesterday), + 'withdraw' => floatval($withdraw), + ], + 'logs' => new UserAccountLogCollection($logs) + ]; + return $this->success($data); + } + +} diff --git a/modules/User/Http/Controllers/Api/Auth/LoginController.php b/modules/User/Http/Controllers/Api/Auth/LoginController.php new file mode 100644 index 0000000..26cb16a --- /dev/null +++ b/modules/User/Http/Controllers/Api/Auth/LoginController.php @@ -0,0 +1,63 @@ + + * @param LoginRequest $request + * @return JsonResponse + */ + public function index(LoginRequest $request): JsonResponse + { + $credentials = [ + 'username' => $request->username, + 'password' => $request->password, + ]; + + $token = Api::attempt($credentials); + + if ($token) { + $user = User::where('username', $request->username)->first(); + event(new UserLoginSuccess($user, $request, '账号密码')); + + return $this->success([ + 'access_token' => $token, + 'token_type' => 'Bearer', + ]); + } else { + return $this->failed('用户名或密码错误'); + } + } + + /** + * Notes : 退出登录,撤销所有令牌,这个需要配合 api:^5.0 使用 + * + * @Date : 2021/9/22 11:07 上午 + * @Author : + * @return JsonResponse + */ + public function logout(): JsonResponse + { + $user = Api::user(); + + if ($user) { + $user->tokens()->delete(); + } + + return $this->success(''); + } + +} diff --git a/modules/User/Http/Controllers/Api/Auth/RegisterController.php b/modules/User/Http/Controllers/Api/Auth/RegisterController.php new file mode 100644 index 0000000..40becee --- /dev/null +++ b/modules/User/Http/Controllers/Api/Auth/RegisterController.php @@ -0,0 +1,43 @@ + + * @param RegisterRequest $request + * @return JsonResponse + */ + public function index(RegisterRequest $request): JsonResponse + { + $username = $request->username; + $password = $request->password; + + $user = User::create([ + 'username' => $username, + 'password' => $password, + ]); + + $token = Api::login($user); + + event(new UserLoginSuccess($user, $request, '用户注册')); + + return $this->success([ + 'access_token' => $token, + 'token_type' => 'Bearer', + ]); + } + +} diff --git a/modules/User/Http/Controllers/Api/Auth/SmsController.php b/modules/User/Http/Controllers/Api/Auth/SmsController.php new file mode 100644 index 0000000..d3111d7 --- /dev/null +++ b/modules/User/Http/Controllers/Api/Auth/SmsController.php @@ -0,0 +1,114 @@ + + * @param SmsRequest $request + * @return JsonResponse + */ + public function send(SmsRequest $request): JsonResponse + { + $mobile = $request->mobileNo; + + try { + Sms::sendVerificationCode($mobile); + $isExists = User::where('username', $mobile)->exists(); + return $this->success([ + 'new' => ! $isExists, + 'message' => '短信发送成功', + ]); + } catch (Exception $exception) { + return $this->failed($exception->getException('aliyun')->getMessage()); + } + } + + /** + * Notes : 短信验证码登录 + * + * @Date : 2021/7/20 10:15 上午 + * @Author : + * @param LoginSmsRequest $request + * @return JsonResponse + */ + public function login(LoginSmsRequest $request): JsonResponse + { + $mobileNo = $request->mobileNo; + $code = $request->code; + $invite_code = $request->invite ?? ''; + $channel_code = $request->channel ?? '';//渠道 + $channel = ''; + if ($channel_code) { + $channel = UserChannel::query() + ->where('code', $channel_code) + ->first(); + } + + $parent = 0; + + if ($invite_code) { + $invite = Hashids::connection('code')->decode($invite_code); + + if (empty($invite)) { + return $this->failed('邀请码不正确'); + } + $parent = $invite[0]; + $parentUser = User::find($parent); + + //普通用户无法分享 + if ($parentUser && $parentUser->identityFirst()->id == 1) { + $parent = 0; + } + } + + $check = Sms::checkCode($mobileNo, $code); + if ($check == false) { + return $this->failed('验证码不正确', 422); + } + $user = User::firstOrCreate([ + 'username' => $mobileNo, + ], [ + 'parent_id' => $parent, + 'password' => 111111, + ]); + + $is_new = $user->wasRecentlyCreated; + + $message = ''; + if ($is_new) { + if ($user->parent && $parent && $user->parent->id != $parent) { + $message = "您已与用户{$user->parent->info->nickname}绑定隶属关系,此次邀请码无效"; + } + + } + + + $token = Api::login($user); + + event(new UserLoginSuccess($user, $request, '手机验证码')); + + return $this->success([ + 'token_type' => 'Bearer', + 'access_token' => $token, + 'message' => $message, + 'is_new' => $is_new, + ]); + } + +} diff --git a/modules/User/Http/Controllers/Api/Auth/WechatController.php b/modules/User/Http/Controllers/Api/Auth/WechatController.php new file mode 100644 index 0000000..df47b12 --- /dev/null +++ b/modules/User/Http/Controllers/Api/Auth/WechatController.php @@ -0,0 +1,154 @@ +url ?? ''; + $scopes = $request->scopes ?? ['snsapi_userinfo']; + + $web_url = config('user.web.base'); + $redirect = $web_url.$pages; + if (! is_array($scopes)) { + $scopes = [$scopes]; + } + $response = $app->oauth->scopes($scopes)->redirect($redirect); + + return $this->success($response); + } + + /** + * Notes: 获取jssdk + * + * @Author: 玄尘 + * @Date : 2021/7/1 11:08 + * @param Request $request + * @return JsonResponse + */ + public function getJsSdk(Request $request) + { + $url = $request->url; + $jsApiList = $request->jsApiList ?? []; + $openTagList = $request->openTagList ?? []; + $app = app('wechat.official_account'); + + if ($url) { + $app->jssdk->setUrl($url); + } + + if (! is_array($jsApiList)) { + $jsApiList = [$jsApiList]; + } + + if (! is_array($openTagList)) { + $openTagList = [$openTagList]; + } + + return $this->success($app->jssdk->buildConfig($jsApiList, false, false, true, $openTagList)); + } + + /** + * Notes: 微信分享 + * + * @Author: 玄尘 + * @Date : 2021/10/26 16:52 + * @param Request $request + * @return JsonResponse|mixed + * @throws InvalidArgumentException + * @throws InvalidConfigException + * @throws RuntimeException + * @throws GuzzleException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function officialShare(Request $request) + { + $url = $request->url ?? ''; + if (empty($url)) { + return $this->failed('地址必须传'); + } + + $app = app('wechat.official_account'); + + $apis = [ + 'updateAppMessageShareData', + 'updateTimelineShareData', + // 'showOptionMenu', + // 'showMenuItems', + 'showAllNonBaseMenuItem', + ]; + + $app->jssdk->setUrl($url); + $cog = $app->jssdk->buildConfig($apis); + + return $this->success($cog); + } + + + /** + * Notes: 获取小程序openid + * + * @Author: 玄尘 + * @Date: 2022/9/26 14:17 + * @return mixed|void + */ + public function getMiniOpenid(Request $request) + { + $validator = \Validator::make($request->all(), [ + 'code' => 'required', + ], [ + 'code.required' => '缺少参数:code', + ]); + + if ($validator->fails()) { + return $this->failed($validator->errors()->first()); + } + + $weChat = app('wechat.mini_program'); + $session = $weChat->auth->session($request->code); + + if (isset($session->errcode)) { + return $this->failed($session->errmsg); + } + + return $this->success($session->openid); + } + + /** + * Notes: 获取公众号openid + * + * @Author: 玄尘 + * @Date: 2022/9/26 14:21 + */ + public function getOfficialOpenid() + { + try { + $weUser = $this->getUserFromCode(); + return $weUser->getId(); + } catch (\Exception $exception) { + return $this->failed($exception->getMessage()); + } + } + + +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Api/Certification/IndexController.php b/modules/User/Http/Controllers/Api/Certification/IndexController.php new file mode 100644 index 0000000..4c5a5dd --- /dev/null +++ b/modules/User/Http/Controllers/Api/Certification/IndexController.php @@ -0,0 +1,90 @@ +certification) { + return $this->failed('暂无认证记录'); + } + + return $this->success(new UserCertificationResource($user->certification)); + } + + /** + * Notes : 判定认证与秘钥 + * + * @Date : 2021/5/17 16:52 + * @Author : Mr.wang + * @return JsonResponse + */ + public function certified(): JsonResponse + { + $user = Api::user(); + if (! $user) { + return $this->success(false); + } + + return $this->success((bool) $user->certification); + } + + /** + * Notes : 保存认证信息 + * + * @Date : 2021/7/20 10:24 上午 + * @Author : + * @param CertificationRequest $request + * @return mixed + */ + public function store(CertificationRequest $request) + { + $user = Api::user(); + if ($user->certification) { + return $this->failed('用户已存在认证信息'); + } + $keys = [ + 'name' => $request->name, + 'idcard' => $request->id_card, + 'front_card' => $request->front_card ?? '', + 'back_card' => $request->back_card ?? '', + ]; + $config = new UserCertificationConfig(); + $data = $config->autoVerified($keys); + if ($data['code'] == 1) { + $verified = 1; + } else { + return $this->failed($data['message']); + } + $result = UserCertification::updateOrCreate([ + 'user_id' => $user->id, + ], [ + 'name' => $request->name, + 'id_card' => $request->id_card, + 'front_card' => $request->front_card ?? '', + 'back_card' => $request->back_card ?? '', + 'verified' => $verified, + ]); + if ($result) { + $user->info()->update([ + 'nickname' => $request->name, + ]); + + return $this->success('操作成功'); + } else { + return $this->failed('操作失败'); + } + } + +} diff --git a/modules/User/Http/Controllers/Api/Favorite/IndexController.php b/modules/User/Http/Controllers/Api/Favorite/IndexController.php new file mode 100644 index 0000000..80d027c --- /dev/null +++ b/modules/User/Http/Controllers/Api/Favorite/IndexController.php @@ -0,0 +1,45 @@ +category_id ?? 0; + $favorteArticles = $user->favorites() + ->when($category_id, function ($q) use ($category_id) { + $q->whereHasMorph('favoriteable', [Article::class], function ($query) use ($category_id) { + $query->whereHas('categories', function ($q) use ($category_id) { + $q->where('cms_categories.id', $category_id); + }); + }); + }) + ->paginate(); + + $data = [ + 'categories' => new CategoryCollection(Category::query()->get()), + 'favorites' => new FavoriteCollection($favorteArticles), + ]; + return $this->success($data); + } + +} diff --git a/modules/User/Http/Controllers/Api/Identity/IndexController.php b/modules/User/Http/Controllers/Api/Identity/IndexController.php new file mode 100644 index 0000000..3903a18 --- /dev/null +++ b/modules/User/Http/Controllers/Api/Identity/IndexController.php @@ -0,0 +1,409 @@ +type ?? ''; + $user = Api::user(); + $list = Identity::where('order', '>', 1)->where('can_buy', 1)->get(); + $data = [ + 'user' => [ + 'username' => $user->username, + 'nickname' => $user->info->nickname, + 'avatar' => $user->info->avatar, + 'identity' => $user->identities->count() + ? new IdentityMiddleResource($user->identityMiddle()->first()) + : '', + ], + 'identities' => UserIdentityResource::collection($list), + ]; + + return $this->success($data); + } + + /** + * Notes: 获取可开通的身份 + * + * @Author: 玄尘 + * @Date : 2021/6/4 9:37 + * @param Identity $identity + * @return JsonResponse + */ + public function create(Identity $identity): JsonResponse + { + $user = Api::user(); + $ended_at = ''; + //可开通的身份ids + $ids = Identity::where('can_buy', 1)->pluck('id')->toArray(); + $identity_middles = $user->identityMiddle; + + //是否可以前台开通 + if (in_array($identity->id, $ids)) { + //有有效期 + if ($identity->years) { + //已经开通的身份ids + $identity_ids = $identity_middles->pluck('identity_id')->toArray(); + //是否已经开通了此身份 + if (in_array($identity->id, $identity_ids)) { + $ended_at = $identity_middles->where('identity_id', $identity->id) + ->first()->ended_at ?? now()->format('Y-m-d H:i:s'); + + $ended_at = Carbon::parse($ended_at)->addMonths($identity->years)->format('Y/m/d'); + } else { + $ended_at = now()->addMonths($identity->years)->format('Y/m/d'); + } + } else { + $ended_at = '永久'; + } + + } + +// $price = $identity->getCondition('price', '0'); +// if ($identity->job == Identity::JOB_JK) { +// $coupon = +// } + + $price = $identity->getCondition('price', '0'); + $cost = $identity->getCondition('cost', '0'); + if (! $cost) { + $cost = $price; + } + + $coupon_price = 0; + if ($identity->job == Identity::JOB_JK) { + $coupons = $this->getCreateOrderCoupon($user, $identity, 0); + if ($coupons->isNotEmpty()) { + $user_coupon = $coupons->first(); + $coupon_price = $user_coupon->price; + if ($user_coupon->coupon->type == Coupon::TYPE_REDUCTION) { + $price = $price > $user_coupon->price ? bcsub($price, $coupon_price, 2) : 0; + } + } + } + + + $data = [ + 'identity' => [ + 'identity_id' => $identity->id, + 'name' => $identity->name, + 'cover' => $identity->cover_url, + 'description' => $ended_at ? '有效期至'.$ended_at : $identity->description, + 'coupon_price' => $coupon_price, + 'cost' => $cost, + 'price' => $price, + 'can_buy' => (bool) $identity->can_buy, + 'ended_at' => $ended_at, + 'service' => config('user.experience.service'), + 'protocol_url' => (string) $identity->protocol_url,//协议地址 + ], + ]; + + return $this->success($data); + + } + + /** + * Notes: 开通会员 + * + * @Author: 玄尘 + * @Date : 2021/6/4 10:02 + * @param Identity $identity + * @param Request $request + * @return JsonResponse + */ + public function store(Identity $identity, Request $request): JsonResponse + { + $user = Api::user(); + + $price = $request->price ?? 0; + if ($identity->job != Identity::JOB_TY) { + $validator = Validator::make($request->all(), [ + 'name' => 'required', + // 'card_no' => 'required|numeric', + 'cover' => 'required', + 'price' => 'required', + ], [ + 'name.required' => '缺少姓名', + 'card_no.required' => '缺少银行卡号', + 'card_no.numeric' => '银行卡号只能是数字', + 'cover.required' => '缺少打款凭证', + 'price.required' => '缺少打款金额', + ]); + + if ($validator->fails()) { + return $this->failed($validator->errors()->first()); + } + + +// if ($identity->job == Identity::JOB_NK && ! $user->parent) { +// return $this->failed('没有推荐人不可开通年卡会员'); +// } + } else { + $total = $identity->users()->count(); + if ($total >= 100) { + return $this->failed('体验官最多可开通100人'); + } + $end_at = $identity->end_at; + if ($end_at) { + $end_at = Carbon::parse($end_at)->endOfDay(); + if (now()->gt($end_at)) { + return $this->failed('体验官活动已过期'); + } + } + + + $tencentMap = app('xuanchen.tencent.map'); + $res = $tencentMap->api()->ip(request()->ip())->toArray(); + if (! in_array($res['ad_info']['city'], ['深圳', '深圳市'])) { +// return $this->failed('体验官活动只限于深圳用户参加'); + } + } + + + if (! $identity->can_buy) { + return $this->failed('当前身份不可开通,请联系客服开通。'); + } + + $type = Order::TYPE_OPEN; + $year = $request->year ?? 1; + //是否可以开通多身份 false不可 + if (config('identity.can_has_many_identity') == false) { + if ($user->identities->isNotEmpty()) { + $this_identity = $user->identities->first(); + + if ($this_identity->order > $identity->order) { + return $this->failed('不可降级开通'); + } + + if ($this_identity->id == $identity->id) { + $type = Order::TYPE_RENEW; + } + + } + } + + + $hasOne = Order::query() + ->byUser($user) + ->where('identity_id', $identity->id) + ->where('state', Order::STATE_INIT) + ->exists(); + + if ($hasOne) { + return $this->failed('您已经提交过了,请等待审核'); + } + + + + if (! $price) { + $price = $identity->getCondition('price', '0'); + $price = $price * $year; + } + + $data = [ + 'user_id' => $user->id, + 'identity_id' => $identity->id, + 'year' => $year, + 'type' => $type, + 'stock' => $identity->stock, + 'name' => $request->name, + 'card_no' => $request->card_no, + 'cover' => $request->cover, + 'state' => Order::STATE_INIT, + 'price' => $price, + ]; + + $order = Order::create($data); + + if ($order) { + if ($coupon_price > 0) { + $order->useCouponLog()->create([ + 'sourceable_type' => get_class($order), + 'sourceable_id' => $order->id, + 'coupon_grant_id' => $user_coupon->id, + ]); + } + if ($identity->job == Identity::JOB_TY) { + return $this->success([ + 'order_id' => $order->id, + 'openids' => $user->wechat->getOpenids() + ]); + } else { + return $this->success('提交成功,请等待后台审核'); + } + } else { + return $this->failed('创建订单失败,请稍后再试'); + } + } + + /** + * Notes: 微信支付 + * + * @Author: 玄尘 + * @Date : 2021/6/7 14:42 + * @param Order $order + * @return JsonResponse + * @throws Exception + */ + public function wechat(Order $order, Request $request): JsonResponse + { + $channel = $request->channel ?? 'mp'; + $openid = $request->openid ?? ''; + + $user = Api::user(); + + if (! $order->canPay()) { + return $this->failed('支付失败,订单不可支付'); + } + + if ($order->user()->isNot($user)) { + return $this->failed('支付失败,您没有权限支付'); + } + + $extends = [ + 'notify_url' => route('api.payment.notify.wechat'), + ]; + + if (! $openid) { + $openid = $user->wechat->getUserOpenid($channel); + } + + $payment = $order->createWechatPayment($user, $order->price, $channel); + if (! $openid) { + return $this->failed('支付失败,缺少openid'); + } + + if (config('payment.version', 2) == 2) { + $extends = array_merge($extends, [ + 'openid' => $openid, + ]); + } else { + $extends = array_merge($extends, [ + 'payer' => [ + 'openid' => $openid, + ], + ]); + } + + $notify = $payment->getPaymentParams('商品下单', $extends); + + if (config('payment.version', 2) == 2) { + $notify = $notify->getContent(); + } else { + $notify = $notify->toJson(); + } + + $data = [ + 'wechat' => $notify, + 'identity' => [ + 'identity_id' => $order->identity_id, + 'name' => $order->identity->name, + ], + ]; + + return $this->success($data); + } + + /** + * Notes: 支付宝支付 + * + * @Author: 玄尘 + * @Date : 2021/6/7 14:42 + * @param Order $order + * @return JsonResponse + * @throws Exception + */ + public function alipay(Order $order): JsonResponse + { + $user = Api::user(); + + if (! $order->canPay()) { + return $this->failed('支付失败,订单不可支付'); + } + + if ($order->user()->isNot($user)) { + return $this->failed('支付失败,您没有权限支付'); + } + + $payment = $order->createAlipayPayment($user, $order->price, 'app'); + + $notify = $payment->getPaymentParams('商品下单', [ + 'notify_url' => route('api.payment.notify.alipay'), + ]); + + $data = [ + 'wechat' => $notify->getContent(), + 'identity' => [ + 'identity_id' => $order->identity_id, + 'name' => $order->identity->name, + ], + ]; + + return $this->success($data); + + } + + /** + * Notes: 根据不同身份判定用户折扣 + * + * @Author: Mr.wang + * @Date : 2021/6/10 11:00 + * @return JsonResponse + */ + public function rule(): JsonResponse + { + $user = Api::user(); + $defaultIdentity = Identity::orderBy('order', 'desc')->first(); + $defaultDiscount = $defaultIdentity->getRule('buy_discount', 0); + if (! $user) { + return $this->success([ + 'message' => '开通节点 最高享'.($defaultDiscount / 10).'折优惠', + 'not_vip' => true, + 'discount' => $defaultDiscount / 100, + ]); + } + + $identity = $user->identities->first(); + $discount = $identity->getRule('buy_discount', 0); + if ($discount >= 100) { + $message = '开通节点 最高享'.($defaultDiscount / 10).'折优惠'; + } else { + $message = $identity->name.'尊享'.($discount / 10).'折优惠'; + } + + return $this->success([ + 'message' => $message, + 'not_vip' => (bool) $identity->default, + 'discount' => $discount / 100, + ]); + } + + /** + * Notes: 获取身份权益 + * + * @Author: 玄尘 + * @Date : 2021/6/8 8:39 + * @param Identity $identity + * @return JsonResponse + */ + public function show(Identity $identity): JsonResponse + { + return $this->success(new UserIdentityResource($identity)); + } + +} diff --git a/modules/User/Http/Controllers/Api/IndexController.php b/modules/User/Http/Controllers/Api/IndexController.php new file mode 100644 index 0000000..02e2b93 --- /dev/null +++ b/modules/User/Http/Controllers/Api/IndexController.php @@ -0,0 +1,200 @@ + + * @return JsonResponse + */ + public function index(): JsonResponse + { + $user = Api::user(); + + return $this->success(new UserInfoResource($user)); + } + + /** + * Notes : 用户信息 + * + * @Date : 2021/6/16 2:14 下午 + * @Author : Mr.wang + * @return JsonResponse + */ + public function info(): JsonResponse + { + $user = Api::user(); + + return $this->success(new UserInfoBaseResource($user)); + } + + /** + * Notes : 用户基础信息修改 + * + * @Date : 2021/5/27 14:02 + * @Author : Mr.wang + * @param Request $request + * @param string $key + * @return JsonResponse + */ + public function update(Request $request, string $key): JsonResponse + { + $user = Api::user(); + + switch ($key) { + case 'nickname': + $validator = Validator::make($request->all(), [ + 'value' => 'required', + ], [ + 'value.required' => '用户昵称必须填写', + ]); + break; + case 'avatar': + $validator = Validator::make($request->all(), [ + 'value' => ['required', 'regex:/[^\s]*\.(jpg|jpeg|gif|png)$/i'], + ], [ + 'value.required' => '用户头像必须上传', + 'value.regex' => '头像地址格式不正确', + ]); + break; + default: + return $this->failed('路径不合法'); + } + if ($validator->fails()) { + return $this->failed($validator->errors()->first(), 422); + } + $result = $user->info()->update([$key => $request->value]); + if ($result) { + return $this->success('操作成功'); + } else { + return $this->failed('失败'); + } + + } + + /** + * Notes : 用户邀请码接口 + * + * @Date : 2021/5/27 14:15 + * @Author : Mr.wang + * @return JsonResponse + */ + public function invite(): JsonResponse + { + $user = Api::user(); + if ($user->identityFirst()->id > 1) { + $invite = Hashids::connection('code')->encode($user->id); + } else { + $invite = ''; + } + $url = Config::get('user.invite_code.url').'?invite='.$invite; + + $code = 'data:image/png;base64,'.base64_encode(QrCode::format('png') + ->size(100) + ->margin(3) + ->generate($url)); + + return $this->success([ + 'code' => $code, + 'invite' => $invite, + 'children' => $user->children()->count(), + 'user_info' => new UserInfoBaseResource($user), + ]); + } + + /** + * Notes : 绑定邀请码 + * + * @Date : 2021/7/20 10:30 上午 + * @Author : wang + * @param Request $request + * @return JsonResponse + */ + public function bind(Request $request): JsonResponse + { + $invite = $request->invite ?? ''; + if (empty($invite)) { + return $this->failed('邀请码不能为空'); + } + $invite = Hashids::connection('code')->decode($invite); + if (empty($invite)) { + return $this->failed('邀请码不正确'); + } + + $user = Api::user(); + if ($user->parent) { + return $this->failed('用户已有上级,不能重复绑定'); + } + + if ($user->updateParent($invite[0])) { + return $this->success('绑定成功'); + } else { + return $this->failed('绑定失败'); + } + } + + /** + * Notes: 获取小程序码 + * + * @Author: 玄尘 + * @Date: 2022/9/28 13:13 + */ + public function getMiniCode(Request $request) + { + $user = Api::user(); + $size = $request->size ?? '300'; + $url = $request->url ?? 'pages/mall/index/index'; + + $name = "share_user_mini_{$user->id}.png"; + $path = "share/".$name; + $file_path = storage_path("app/public/".$path); + + $data = [ + 'username' => $user->username, + 'nickname' => $user->info->nickname ?? '', + 'avatar' => $user->info->avatar ?? '', + ]; + + $invite = Hashids::connection('code')->encode($user->id); + + $parse = parse_url($url); + + $arr['invite'] = $invite; + $str = $parse['path'].'?'.http_build_query($arr); + if (! file_exists($file_path)) { + $app = app('wechat.mini_program'); + $response = $app->app_code + ->get($str, [ + 'width' => $size, + 'is_hyaline' => true, + ]); + if ($response instanceof \EasyWeChat\Kernel\Http\StreamResponse) { + $response->saveAs(storage_path("app/public/share"), $name); + } + } + + $data = array_merge($data, [ + 'qrcode' => Storage::url($path), + ]); + + return $this->success($data); + } + +} diff --git a/modules/User/Http/Controllers/Api/Rank/IndexController.php b/modules/User/Http/Controllers/Api/Rank/IndexController.php new file mode 100644 index 0000000..977370c --- /dev/null +++ b/modules/User/Http/Controllers/Api/Rank/IndexController.php @@ -0,0 +1,131 @@ +toDateTimeString(), + ]; + + $vipUsers = $this->getRankDataTrait($dateBetween); + $data = []; + $i = 1; + foreach ($vipUsers as $user) { + $data[] = [ + 'rank' => $i, + 'avatar' => $user->info->avatar, //头像 + 'nickname' => $user->info->nickname, + 'rate' => '---', + 'number' => $user->countChild, + ]; + } + return $this->success([ + 'page_id' => 6, + 'rand' => $data + ]); + } + + /** + * Notes: 推荐用户总排行 + * + * @Author: 玄尘 + * @Date: 2022/8/17 15:11 + * @return JsonResponse + */ + public function totalUser(): JsonResponse + { + $user = Api::user(); + + $users = User::query() + ->withCount('children') + ->take(10) + ->latest('children_count') + ->get() + ->map(function ($info) use ($user) { + return [ + 'user' => [ + 'user_id' => $info->id, + 'username' => $info->username, + 'nickname' => $info->info->nickname ?? '', + 'avatar' => $info->info->avatar ?? '', + ], + 'is_my' => $info->id == $user->id, + 'total' => $info->children_count, + ]; + }) + ->pad(10, [ + 'user' => [ + 'user_id' => 0, + 'username' => '---', + 'nickname' => '---', + 'avatar' => '---', + ], + 'is_my' => false, + 'total' => 0, + ]); + + $users = $users->toArray(); + + $i = 1; + $top = $other = []; + $myRank = 0; + foreach ($users as &$rank) { + $rank['rank'] = $i; + if ($rank['is_my']) { + $myRank = $i; + } + + if ($i < 4) { + $top[] = $rank; + } else { + $other[] = $rank; + } + $i++; + } + + $childrenCount = $user->children()->count(); + if (! $myRank) { + $myRank = User::query() + ->withCount('children') + ->latest('children_count') + ->where('children_count'.'>', $childrenCount) + ->count(); + } + + $data = [ + 'user' => [ + 'username' => $user->username, + 'nickname' => $user->info->nickname, + 'avatar' => $user->info->avatar, + 'total' => $user->sign->counts, + 'rank' => $myRank ?: $myRank + 1, + ], + 'top' => $top, + 'other' => $other, + ]; + + return $this->success($data); + } + +} diff --git a/modules/User/Http/Controllers/Api/Relation/IndexController.php b/modules/User/Http/Controllers/Api/Relation/IndexController.php new file mode 100644 index 0000000..e38e1dd --- /dev/null +++ b/modules/User/Http/Controllers/Api/Relation/IndexController.php @@ -0,0 +1,46 @@ +larer ?? 1; + + $user = Api::user(); + $layer = bcadd($user->relation->layer, $layer); + + $users = Relation::query() + ->where('bloodline', 'like', "%,".$user->id.",%") + ->when($layer, function ($q) use ($layer) { + $q->where('layer', $layer); + }, function ($q) { + $q->whereIn('layer', [2, 3]); + }) + ->paginate(); + $data = [ + 'count' => $user->getRelationCount(), + 'users' => RelationResource::collection($users), + ]; + + return $this->success($data); + } + +} \ No newline at end of file diff --git a/modules/User/Http/Controllers/Api/Service/IndexController.php b/modules/User/Http/Controllers/Api/Service/IndexController.php new file mode 100644 index 0000000..08eed96 --- /dev/null +++ b/modules/User/Http/Controllers/Api/Service/IndexController.php @@ -0,0 +1,32 @@ +identity_id ?? 0; + if ($identityId) { + $identity = Identity::findOrFail($identityId); + $service = $identity->identities()->shown()->inRandomOrder()->first(); + } else { + $user = Api::user(); + $service = $user->identities->first()->identities()->shown()->inRandomOrder()->first(); + } + if (empty($service)) { + return $this->success([]); + } + + return $this->success(new UserServiceResource($service)); + } + +} diff --git a/modules/User/Http/Controllers/Api/Setting/IndexController.php b/modules/User/Http/Controllers/Api/Setting/IndexController.php new file mode 100644 index 0000000..f119d47 --- /dev/null +++ b/modules/User/Http/Controllers/Api/Setting/IndexController.php @@ -0,0 +1,96 @@ +id)->first(); + if ($wechat) { + if ($wechat->mini) { + $bool = true; + } else { + $bool = false; + } + } else { + $bool = false; + } + + return $this->success([ + 'avatar' => $user->info->avatar ?? '', + 'nickname' => $user->info->nickname ?? '', + 'is_bind' => $bool, + 'certification' => [ + 'is_true' => (bool) $user->certification, + 'message' => $user->certification ? [ + 'name' => $user->certification->name, + 'idcard' => $user->certification->id_card, + ] : [], + ], + ]); + } + + /** + * Notes : 用户基础信息修改 + * + * @Date : 2021/5/27 14:02 + * @Author : Mr.wang + * @param Request $request + * @param string $key + * @return JsonResponse + */ + public function update(Request $request, string $key): JsonResponse + { + $user = Api::user(); + + switch ($key) { + case 'nickname': + $validator = Validator::make($request->all(), [ + 'value' => 'required', + ], [ + 'value.required' => '用户昵称必须填写', + ]); + break; + case 'avatar': + $validator = Validator::make($request->all(), [ + 'value' => ['required', 'regex:/[^\s]*\.(jpg|jpeg|gif|png)$/i'], + ], [ + 'value.required' => '用户头像必须上传', + 'value.regex' => '头像地址格式不正确', + ]); + break; + default: + return $this->failed('路径不合法'); + } + + if ($validator->fails()) { + return $this->failed($validator->errors()->first(), 422); + } + + $result = $user->info()->update([$key => $request->value]); + if ($result) { + return $this->success('操作成功'); + } else { + return $this->failed('失败'); + } + + } + +} diff --git a/modules/User/Http/Controllers/Api/Sign/IndexController.php b/modules/User/Http/Controllers/Api/Sign/IndexController.php new file mode 100644 index 0000000..1cac67a --- /dev/null +++ b/modules/User/Http/Controllers/Api/Sign/IndexController.php @@ -0,0 +1,363 @@ + + * @param Request $request + * @return JsonResponse + */ + public function index(Request $request): JsonResponse + { + $type = SignConfig::getParams('show_type'); + + $user = Api::user(); + + switch ($type) { + case 'week': + ##todo 根据自然周进行展示,等待完善 + break; + case 'month': + //获取年 + $year = $request->year ?? now()->year; + //获取月份 + $month = $request->month ?? now()->month; + $yearMonth = sprintf("%d-%s", $year, $month); + $data = $this->month($yearMonth, $user); + break; + default: + $days = $request->days ?? 7; + $data = $this->sevenDay($days, $user); + break; + } + return $this->success($data); + } + + /** + * Notes : 7天签到图 + * + * @Date : 2022/1/7 15:07 + * @Author : Mr.wang + * @param $days + * @param $user + * @return array + */ + private function sevenDay($days, $user) + { + $continue_days = $user->sign->continue_days; + $modDay = bcmod($continue_days, $days); + $cycle = bcdiv($continue_days, $days, 0); + $data = [ + 'can_sign' => ! UserSign::canSign($user, now()), + 'next_task' => '', + ]; + $task = UserSign::getNextTaskCrystal($continue_days); + if ($task) { + $data['next_task'] = [ + 'day' => $task['day'], + 'diff' => $task['day'] - $continue_days, + 'crystal' => $task['number'] ?? 0, + ]; + } + for ($i = 1; $i <= $days; $i++) { + $day = $cycle * $days + $i; + $text = '第'.$day.'天'; + if ($i == $modDay) { + $text = '今天'; + } + $data['lists'][$i] = [ + 'sign' => ($i <= $modDay), + 'crystal' => UserSign::getSignCrystal($day), + 'text' => $text, + ]; + + } + if ($user->sign) { + return $data; + } else { + return []; + } + } + + /** + * Notes : 一月签到图 + * + * @Date : 2022/1/7 15:11 + * @Author : Mr.wang + * @param $yearMonth + * @param $user + * @return array[] + */ + private function month($yearMonth, $user) + { + $base = [ + 'month' => $yearMonth, + 'continue' => $user->sign->continue_days, + 'total' => $user->sign->counts, + 'isSign' => $user->isSign(), + ]; + + $calendar = Calendar::show($yearMonth)['calendar']; + $calendar = $calendar->map(function ($info) use ($user) { + $canSign = UserSign::canSign($user, Carbon::parse($info['today'])); + return array_merge($info, [ + 'isSign' => $user->isSign($info['today']), + 'canSign' => $canSign, + 'canReSign' => UserSign::canReSign($user, Carbon::parse($info['today'])) + ]); + }); + +// //获取月份第一天所在的星期 +// $firstDayOfWeek = Carbon::parse("$yearMonth-01")->dayOfWeek; +// //补全 +// $day = 0; +// $calendar = []; +// for ($i = 0; $i < 6; $i++) { +// for ($j = 0; $j < 7; $j++) { +// if ($firstDayOfWeek != 0 and $i == 0) { +// //根据月初第一天所在的星期,计算出之前几天的日子 +// $day = Carbon::parse("$yearMonth-01")->subDays($firstDayOfWeek - $j)->day; +// $date = Carbon::parse("$yearMonth-01")->subDays($firstDayOfWeek - $j); +// $hidden = $date->lt(Carbon::parse("$yearMonth-01")->startOfMonth()); +// } else { +// $day++; +// $date = Carbon::parse("$yearMonth-01")->addDays($day - 1); +// $hidden = $date->gt(Carbon::parse("$yearMonth-01")->endOfMonth()); +// } +// $calen = [ +// 'date' => $date->format("j"), +// 'isPast' => $date->isPast(), +// 'isHidden' => $hidden, +// 'isSign' => $user->isSign($date->format('Y-m-d')), +// ]; +// $calendar[$i][] = $calen; +// } +// } + + return [ + 'base' => $base, + 'calendar' => $calendar->splitIn(5), + ]; + } + + /** + * Notes : 签到 + * + * @Date : 2021/5/28 3:14 下午 + * @Author : + * @return JsonResponse + */ + public function sign(): JsonResponse + { + $user = Api::user(); + try { + [$res, $message] = UserSign::signIn($user); + + if ($res) { +// $logs = $user->account->logs() +// ->where('rule_id', 10) +// ->whereDate('created_at', now()) +// ->get(); + + + return $this->success($message); + + } else { + return $this->failed($message); + } + } catch (Exception $exception) { + return $this->failed($exception->getMessage()); + } + } + + /** + * Notes : 预留补签功能 + * + * @Date : 2021/5/28 3:14 下午 + * @Author : + * @param Request $request + * @return JsonResponse + */ + public function replenish(Request $request): JsonResponse + { + $user = Api::user(); + $date = $request->date; + $date = Carbon::parse($date); + + try { + + [$res, $message] = UserSign::replenish($user, $date); + + if ($res) { + return $this->success($message); + } else { + return $this->failed('补签失败'); + } + } catch (Exception $exception) { + return $this->failed($exception->getMessage()); + } + } + + /** + * Notes: 月日历 + * + * @Author: 玄尘 + * @Date: 2022/8/3 11:44 + * @return JsonResponse + */ + public function date() + { + return $this->success(Calendar::show('2022-07')); + } + + /** + * Notes: 签到封面 + * + * @Author: 玄尘 + * @Date: 2022/8/9 8:15 + * @return JsonResponse + */ + public function backgrounds(): JsonResponse + { + $user = Api::user(); + + $signTexts = SignText::shown()->get(); + $signBanner = SignBanner::query()->shown()->inRandomOrder()->first(); + $calendar = new OvertrueCalendar(); + $lunarMonth = $calendar->toChinaMonth(now()->month); + $lunarDay = $calendar->toChinaDay(now()->day); + + $invite = Hashids::connection('code')->encode($user->id); + $url = Config::get('user.invite_code.url').'?invite='.$invite; + $code = 'data:image/png;base64,'.base64_encode(QrCode::format('png') + ->size(100) + ->margin(3) + ->generate($url)); + + //锶源昆仑 + return $this->success([ + 'texts' => SignTextResource::collection($signTexts), + 'banner' => new SignBannerResource($signBanner), + 'time' => [ + 'yearMonth' => now()->format('Y-m'), + 'time' => now()->format('H:i'), + 'day' => now()->format('d'), + 'lunar' => '农历'.$lunarMonth.$lunarDay, + ], + 'user' => [ + 'username' => $user->username, + 'nickname' => $user->info->nickname ?? '', + 'avatar' => $user->info->avatar ?? '', + 'code' => $code, + ], + 'sign' => $user->getSignData() + ]); + } + + /** + * Notes: 签到排行 + * + * @Author: 玄尘 + * @Date: 2022/8/17 13:43 + * @param Request $request + * @return JsonResponse + */ + public function rank(Request $request): JsonResponse + { + $user = Api::user(); + $ranks = Sign::query() + ->latest('counts') + ->take(10) + ->get() + ->map(function ($info) use ($user) { + return [ + 'user' => [ + 'user_id' => $info->user_id, + 'username' => $info->user->username, + 'nickname' => $info->user->info->nickname ?? '', + 'avatar' => $info->user->info->avatar ?? '', + ], + 'is_my' => $info->user_id == $user->id, + 'total' => $info->counts, + ]; + }) + ->pad(10, [ + 'user' => [ + 'user_id' => 0, + 'username' => '---', + 'nickname' => '---', + 'avatar' => '---', + ], + 'is_my' => false, + 'total' => 0, + ]); + + $ranks = $ranks->toArray(); + $i = 1; + $top = $other = []; + $myRank = 0; + foreach ($ranks as &$rank) { + $rank['rank'] = $i; + if ($rank['user']['user_id'] == $user->id) { + $myRank = $i; + } + + if ($i < 4) { + $top[] = $rank; + } else { + $other[] = $rank; + } + $i++; + } + + if (! $myRank) { + $myRank = Sign::query()->where('counts', '>', $user->sign->counts)->count(); + } + + $data = [ + 'user' => [ + 'username' => $user->username, + 'nickname' => $user->info->nickname, + 'avatar' => $user->info->avatar, + 'total' => $user->sign->counts, + 'rank' => $myRank ?: $myRank + 1, + ], + 'top' => $top, + 'other' => $other, + ]; + + return $this->success($data); + + + } + +} diff --git a/modules/User/Http/Controllers/Api/Socialite/Controller.php b/modules/User/Http/Controllers/Api/Socialite/Controller.php new file mode 100644 index 0000000..ea0a2f0 --- /dev/null +++ b/modules/User/Http/Controllers/Api/Socialite/Controller.php @@ -0,0 +1,14 @@ +all(), [ + 'access_token' => 'required', + 'openid' => 'required', + ], [ + 'access_token.required' => 'access_token必须填写', + 'openid.required' => 'openid必须填写', + ]); + if ($validator->fails()) { + return $this->failed($validator->errors()->first(), 422); + } + $data = [ + 'access_token' => $request->access_token, + 'openid' => $request->openid, + ]; + $secret = config('user.socialite.unicloud.self_secret', ''); + $domain = config('user.socialite.unicloud.domain', ''); + $url = config('user.socialite.unicloud.cloud_function_url.one_key', ''); + if (! $secret || ! $domain || ! $url) { + return $this->failed('过程参数存在错误'); + } + ksort($data); + $signString = urldecode(http_build_query($data)); + $sign = hash_hmac('sha256', $signString, $secret); + $params = $signString.'&sign='.$sign; + $url = $domain.$url.'?'.$params; + $res = json_decode(file_get_contents($url)); + + if ($res->success) { + $user = User::firstOrCreate([ + 'username' => $res->phoneNumber, + ], [ + 'password' => 111111, + ]); + + $token = Api::login($user); + + event(new UserLoginSuccess($user, $request, '本机一键登录')); + + return $this->success([ + 'token_type' => 'Bearer', + 'access_token' => $token, + 'is_new' => $user->wasRecentlyCreated, + ]); + + } else { + return $this->failed('一键登录失败'); + } + + } + + public function query(Request $request) + { + $validator = Validator::make($request->all(), [ + 'mobile' => 'required', + ], [ + 'mobile.required' => '请传入手机号', + ]); + if ($validator->fails()) { + return $this->failed($validator->errors()->first(), 422); + } + $user = User::firstOrCreate([ + 'username' => $request->mobile, + ], [ + 'password' => 111111, + ]); + + $token = Api::login($user); + + event(new UserLoginSuccess($user, $request, '本机一键登录')); + + return $this->success([ + 'token_type' => 'Bearer', + 'access_token' => $token, + 'is_new' => $user->wasRecentlyCreated, + ]); + } + +} diff --git a/modules/User/Http/Controllers/Api/Socialite/WeChatController.php b/modules/User/Http/Controllers/Api/Socialite/WeChatController.php new file mode 100644 index 0000000..2af3d58 --- /dev/null +++ b/modules/User/Http/Controllers/Api/Socialite/WeChatController.php @@ -0,0 +1,368 @@ +all(), [ + 'union_id' => 'required', + 'mobileNo' => 'required|phone:CN,mobile', + 'code' => 'required', + ], [ + 'union_id.required' => 'unionId必须填写', + 'mobileNo.required' => '手机号码必须填写', + 'mobileNo.phone' => '手机号码格式不正确', + 'code.required' => '验证码必须填写', + ]); + if ($validator->fails()) { + return $this->failed($validator->errors()->first(), 422); + } + $unionId = $request->union_id; + $wechat = UserWechat::where('unionid', $unionId)->first(); + if (empty($wechat)) { + $mobileNo = $request->mobileNo; + $code = $request->code; + $check = Sms::checkCode($mobileNo, $code); + if ($check == false) { + return $this->failed('验证码不正确', 422); + } + $user = User::firstOrCreate([ + 'username' => $mobileNo, + ], [ + 'password' => 111111, + ]); + $wechat = $user->wechat()->firstOrcreate([ + 'unionid' => $unionId, + 'nickname' => $request->nickname ?? '', + 'avatar' => $request->avatar ?? '', + 'sex' => $request->gender ?? 0, + 'country' => $request->country ?? '', + 'province' => $request->province ?? '', + 'city' => $request->city ?? '', + ]); + $wechat->app()->firstOrcreate([ + 'openid' => $request->open_id ?? '', + ]); + $user->info()->update([ + 'nickname' => $request->nickname ?? $user->info->nickname, + 'avatar' => $request->avatar ?? '', + ]); + $token = Api::login($user); + + return $this->success([ + 'token_type' => 'Bearer', + 'access_token' => $token, + 'is_new' => $user->wasRecentlyCreated, + ]); + } else { + $token = Api::login($wechat->user); + + event(new UserLoginSuccess(Api::user(), $request, '微信授权')); + + return $this->success([ + 'token_type' => 'Bearer', + 'access_token' => $token, + 'is_new' => false, + ]); + } + } + + /** + * Notes : 小程序手机号登录 + * + * @Date : 2021/7/29 17:21 + * @Author : Mr.wang + * @param WechatMiniRequest $request + * @return JsonResponse + * @throws InvalidConfigException + */ + public function mini(WechatMiniRequest $request): JsonResponse + { + $code = $request->code; + $this->getConfig(); + $app = Factory::miniProgram($this->config); + $session = $app->auth->session($code); + if ($session->errcode) { + return $this->failed($session->errmsg); + } + + try { + $decryptedData = $app->encryptor->decryptData( + $session->session_key, + $request->iv, + $request->encryptedData + ); + $mobile = $decryptedData['purePhoneNumber']; + $user = User::where('username', $mobile)->first(); + if (! $user) { + $user = User::create([ + 'username' => $mobile, + 'password' => 111111, + ]); + $user->info()->create([ + 'nickname' => $request->nickname ?? '', + 'avatar' => $request->avatar ?? '', + ]); + } + $token = Api::login($user); + + event(new UserLoginSuccess(Api::user(), $request, '微信授权')); + + return $this->success([ + 'token_type' => 'Bearer', + 'access_token' => $token, + 'is_new' => $user->wasRecentlyCreated, + ]); + } catch (\Exception $exception) { + return $this->failed($exception->getMessage()); + } + } + + protected function getConfig() + { + $this->config = [ + 'app_id' => env('WECHAT_MINI_APPID'), + 'secret' => env('WECHAT_MINI_SECRET'), + 'response_type' => 'collection', + ]; + } + + /** + * Notes : 公众号登录 + * + * @Date : 2021/5/26 3:14 下午 + * @Author : < Jason.C > + */ + public function official() + { + + } + + public function query(Request $request): JsonResponse + { + $unionId = $request->union_id ?? ''; + if (empty($unionId)) { + return $this->failed('', 404); + } + $wechat = UserWechat::where('unionid', $unionId)->first(); + if (empty($wechat)) { + return $this->success([ + 'union_id' => $unionId, + ]); + } else { + $token = Api::login($wechat->user); + + event(new UserLoginSuccess(Api::user(), $request, '微信授权')); + + return $this->success([ + 'token_type' => 'Bearer', + 'access_token' => $token, + ]); + } + } + + /** + * Notes: 入库微信信息 + * + * @Author: 玄尘 + * @Date: 2022/8/2 11:16 + */ + public function addWechatUser(): JsonResponse + { + try { + $user = Api::user(); + if (! $user) { + throw new \Exception('操作失败请登录。'); + } + + $wechatUser = $user->wechat; + + $weUser = $this->getUserFromCode(); + $openid = $weUser->getId(); + $baseUser = $this->getWechatUser($openid); + $unionid = ''; + $raw = $weUser->getRaw(); + if (isset($raw['unionid'])) { + $unionid = $raw['unionid']; + } + + if (! $openid) { + if ($raw && $raw['errmsg']) { + throw new \Exception($raw['errmsg']); + } + + throw new \Exception('code解密失败'); + } + + if (! $user->info->avatar) { + //更新头像 + $user->info->update([ + 'avatar' => $weUser->getAvatar(), + ]); + } + + + if (! $wechatUser) { + $wechatUser = $user->wechat() + ->firstOrCreate([ + 'unionid' => $unionid, + 'nickname' => $weUser->getNickname(), + 'avatar' => $weUser->getAvatar(), + ]); + } elseif (empty($wechatUser['unionid']) && ! empty($unionid)) { + $wechatUser->update([ + 'unionid' => $unionid, + ]); + } + + if (! $wechatUser->official) { + $wechatUser->official()->firstOrCreate([ + 'openid' => $weUser->getId(), + 'subscribe' => $baseUser->subscribe + ]); + } else { + $wechatUser->official()->update([ + 'subscribe' => $baseUser->subscribe + ]); + } + + //设置关注状态 + if ($unionid) { + $this->setUserSubscribe($unionid, $openid, $baseUser->subscribe); + } + + return $this->success([ + 'openid' => $openid, + 'subscribe' => $baseUser->subscribe, + ]); + } catch (\Exception $exception) { + return $this->failed($exception->getMessage()); + } + } + + /** + * Notes: 小程序入库 + * + * @Author: 玄尘 + * @Date: 2022/9/23 14:46 + * @param Request $request + * @return JsonResponse|mixed + */ + public function miniAddWechatUser(Request $request) + { + $user = Api::user(); + + $validator = \Validator::make($request->all(), [ + 'code' => 'required', + 'iv' => 'required', + 'encryptedData' => 'required', + ], [ + 'code.required' => '缺少参数:code', + 'iv.required' => '缺少参数:iv', + 'encryptedData.mobile' => '缺少参数:encryptedData', + ]); + + if ($validator->fails()) { + return $this->failed($validator->errors()->first()); + } + + try { + $code = $request->code; + + $unionid = ''; + $weChat = app('wechat.mini_program'); + $session = $weChat->auth->session($code); + if (isset($session->unionid)) { + $unionid = $session->unionid; + } + + if (isset($session->errcode)) { + return $this->failed($session->errmsg); + } + + $openid = $session->openid; + $wechatUser = $user->wechat; + + $decryptedData = $weChat->encryptor->decryptData( + $session->session_key, + $request->iv, + $request->encryptedData + ); + + if (! $wechatUser) { + $wechatUser = $user->wechat() + ->firstOrCreate([ + 'unionid' => $unionid, + 'nickname' => $decryptedData['nickName'] ?? '', + 'avatar' => $decryptedData['avatarUrl'] ?? '', + ]); + } elseif (empty($wechatUser['unionid']) && ! empty($unionid)) { + $wechatUser->update([ + 'unionid' => $unionid, + ]); + } + + + if (! $wechatUser->mini) { + $wechatUser->mini()->firstOrCreate([ + 'openid' => $openid, + ]); + } + + $userSubscribe = UserSubscribe::query() + ->where('unionid', $unionid) + ->where('subscribe', 1) + ->first(); + + if ($userSubscribe && ! $wechatUser->official) { + if (! $wechatUser->official) { + $wechatUser->official() + ->firstOrCreate([ + 'openid' => $userSubscribe->openid, + 'subscribe' => $userSubscribe->subscribe + ]); + } else { + $wechatUser->official()->update([ + 'subscribe' => $userSubscribe->subscribe + ]); + } + } + + return $this->success([ + 'openid' => $openid, + 'subscribe' => $userSubscribe ? 1 : 0, + ]); + + } catch (\Exception $e) { + return $this->failed($e->getMessage()); + } + } +} diff --git a/modules/User/Http/Controllers/Api/Stock/IndexController.php b/modules/User/Http/Controllers/Api/Stock/IndexController.php new file mode 100644 index 0000000..f6bda9a --- /dev/null +++ b/modules/User/Http/Controllers/Api/Stock/IndexController.php @@ -0,0 +1,180 @@ + + * @param Request $request + * @return JsonResponse + */ + public function index(Request $request): JsonResponse + { + $state = $request->state ?? ''; + + $list = Order::byUser(Api::user()) + ->TypeSample() + ->when($state, function ($query) use ($state) { + switch ($state) { + case 'init': + $query->paid()->unPay(); + break; + case 'delivered': + $query->delivered(); + break; + } + }, function ($query) { + $query->common(); + }) + ->paginate(8); + + return $this->success(new OrderCollection($list)); + } + + /** + * Notes: 提货前置 + * + * @Author: 玄尘 + * @Date: 2022/8/1 11:32 + * @return JsonResponse + */ + public function create(): JsonResponse + { + $user = Api::user(); + $addresses = Address::where('user_id', $user->id)->orderBy('is_default', 'desc')->get(); + $address = Address::where('user_id', $user->id)->orderBy('is_default', 'desc')->first(); + + $data = [ + 'addresses' => AddressResource::collection($addresses), + 'address' => $address ? new AddressResource($address) : '', + 'stockData' => $user->getStockData(), + 'dientity' => new UserIdentityBaseResource($user->identityFirst()) + ]; + + return $this->success($data); + } + + /** + * Notes: 提货 + * + * @Author: 玄尘 + * @Date: 2022/8/1 11:33 + * @param Request $request + * @return JsonResponse|mixed + * @throws \Exception + */ + public function store(Request $request) + { + $user = Api::user(); + + $validator = Validator::make($request->all(), [ + 'address_id' => 'required|integer', + 'qty' => 'required|integer', + ], [ + 'qty.required' => '缺少提货数量', + 'qty.integer' => '数量必须是数字', + 'address_id.required' => '缺少收货地址', + 'address_id.integer' => '收货地址必须是数字', + ]); + + if ($validator->fails()) { + return $this->failed($validator->errors()->first()); + } + + + $remark = $request->remark ?? ''; + $address_id = $request->address_id; + $qty = $request->qty; + + $address = Address::find($address_id); + + $userIdentity = $user->identityFirst(); + if ($userIdentity->job == Identity::JOB_TY) { + $shenzhen = Region::query()->where('name', '深圳市')->first(); + if ($address->city_id != $shenzhen->id) { + return $this->failed('体验官收货地址只能选择深圳'); + } + } + + $goods_sku = GoodsSku::query() + ->whereHas('goods', function ($q) { + $q->where('channel', Goods::CHANNEL_FREE)->where('status', Goods::STATUS_UP); + }) + ->first(); + + if (! $goods_sku) { + return $this->failed('缺少商品'); + } + + $stockData = $user->getStockData(); + + if ($qty > $stockData['residue']) { + return $this->failed('用户库存不足'); + } + + $detail = collect(); + $item = new Item($goods_sku, $address, $qty); + $detail->push($item); + + $orders = (new OrderFacade)->user($user) + ->remark($remark) + ->type(Order::TYPE_SAMPLE) + ->items($detail) + ->address($address) + ->create(); + + //提货自动完成 + foreach ($orders as $order) { + if ($order->type == Order::TYPE_SAMPLE) { + $order->pay(); + } + } + + return $this->success('提货成功'); + + } + + /** + * Notes: 账变记录 + * + * @Author: 玄尘 + * @Date: 2022/8/3 10:43 + */ + public function logs() + { + $user = Api::user(); + $logs = UserStockLog::query() + ->ByUser($user) + ->oldest() + ->paginate(); + + return $this->success(new UserStockLogCollection($logs)); + } + +} diff --git a/modules/User/Http/Requests/CertificationRequest.php b/modules/User/Http/Requests/CertificationRequest.php new file mode 100644 index 0000000..c01453c --- /dev/null +++ b/modules/User/Http/Requests/CertificationRequest.php @@ -0,0 +1,34 @@ + 'required|min:2|max:5', + 'id_card' => ['required', new IdCardRule(), 'unique:user_certifications'], + // 'front_card' => 'required', + // 'back_card' => 'required', + ]; + } + + public function messages(): array + { + return [ + 'name.required' => '认证用户姓名必须填写', + 'name.min' => '认证用户姓名至少:min个字符', + 'name.max' => '认证用户姓名最多:max个字符', + 'id_card.required' => '身份证号必须填写', + 'id_card.unique' => '身份证号已存在', + 'front_card.required' => '身份证正面图片必须上传', + 'back_card.required' => '身份证背面图片必须上传', + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Requests/LoginRequest.php b/modules/User/Http/Requests/LoginRequest.php new file mode 100644 index 0000000..befa217 --- /dev/null +++ b/modules/User/Http/Requests/LoginRequest.php @@ -0,0 +1,41 @@ + + * @return string[] + */ + public function rules(): array + { + return [ + 'username' => 'required', + 'password' => 'required|min:6', + ]; + } + + /** + * Notes : 验证错误提示消息 + * + * @Date : 2021/3/11 4:59 下午 + * @Author : + * @return string[] + */ + public function messages(): array + { + return [ + 'username.required' => '用户名必须填写', + 'password.required' => '密码必须填写', + 'password.min' => '密码最少为:min个字符', + ]; + } + +} diff --git a/modules/User/Http/Requests/LoginSmsRequest.php b/modules/User/Http/Requests/LoginSmsRequest.php new file mode 100644 index 0000000..09cac4d --- /dev/null +++ b/modules/User/Http/Requests/LoginSmsRequest.php @@ -0,0 +1,27 @@ + 'required|phone:CN,mobile', + 'code' => 'required', + ]; + } + + public function messages(): array + { + return [ + 'mobileNo.required' => '手机号码必须填写', + 'mobileNo.phone' => '手机号码格式不正确', + 'code.required' => '验证码必须填写', + ]; + } + +} diff --git a/modules/User/Http/Requests/RegisterRequest.php b/modules/User/Http/Requests/RegisterRequest.php new file mode 100644 index 0000000..31b879a --- /dev/null +++ b/modules/User/Http/Requests/RegisterRequest.php @@ -0,0 +1,42 @@ + + * @return string[] + */ + public function rules(): array + { + return [ + 'username' => 'required|unique:users', + 'password' => 'required|min:6', + ]; + } + + /** + * Notes : 验证错误提示消息 + * + * @Date : 2021/3/11 4:59 下午 + * @Author : + * @return string[] + */ + public function messages(): array + { + return [ + 'username.required' => '用户名必须填写', + 'username.unique' => '用户名已经被使用', + 'password.required' => '密码必须填写', + 'password.min' => '密码最少为:min个字符', + ]; + } + +} diff --git a/modules/User/Http/Requests/SmsRequest.php b/modules/User/Http/Requests/SmsRequest.php new file mode 100644 index 0000000..ddf8b03 --- /dev/null +++ b/modules/User/Http/Requests/SmsRequest.php @@ -0,0 +1,25 @@ + 'required|phone:CN,mobile', + ]; + } + + public function messages(): array + { + return [ + 'mobileNo.required' => '手机号码必须填写', + 'mobileNo.phone' => '手机号码格式不正确', + ]; + } + +} diff --git a/modules/User/Http/Requests/ThawOneRequest.php b/modules/User/Http/Requests/ThawOneRequest.php new file mode 100644 index 0000000..b5d3c96 --- /dev/null +++ b/modules/User/Http/Requests/ThawOneRequest.php @@ -0,0 +1,26 @@ + 'required', + 'all_ids' => 'required', + ]; + } + + public function messages(): array + { + return [ + 'thaw_id.required' => '请传入要领取的ID', + 'all_ids.required' => '传入当前总的ID', + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Requests/UpdateUserInfoRequest.php b/modules/User/Http/Requests/UpdateUserInfoRequest.php new file mode 100644 index 0000000..f8bbefe --- /dev/null +++ b/modules/User/Http/Requests/UpdateUserInfoRequest.php @@ -0,0 +1,26 @@ + 'required', + 'avatar' => ['regex:/[^\s]*\.(jpg|jpeg|gif|png)$/i'], + ]; + } + + public function messages(): array + { + return [ + 'nickname.required' => '用户昵称必须填写', + 'avatar.regex' => '头像地址格式不正确', + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Requests/WechatMiniRequest.php b/modules/User/Http/Requests/WechatMiniRequest.php new file mode 100644 index 0000000..6a2fc55 --- /dev/null +++ b/modules/User/Http/Requests/WechatMiniRequest.php @@ -0,0 +1,28 @@ + 'required', + 'iv' => 'required', + 'encryptedData' => 'required', + ]; + } + + public function messages(): array + { + return [ + 'code.required' => '缺失CODE', + 'iv.required' => '缺失向量', + 'encryptedData.required' => '缺失内容', + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Resources/Account/CrystalResource.php b/modules/User/Http/Resources/Account/CrystalResource.php new file mode 100644 index 0000000..918d37f --- /dev/null +++ b/modules/User/Http/Resources/Account/CrystalResource.php @@ -0,0 +1,19 @@ + $this->id, + 'title' => $this->rule->title, + 'amount' => $this->amount, + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Resources/Account/UserAccountLogCollection.php b/modules/User/Http/Resources/Account/UserAccountLogCollection.php new file mode 100644 index 0000000..9789cdb --- /dev/null +++ b/modules/User/Http/Resources/Account/UserAccountLogCollection.php @@ -0,0 +1,20 @@ + $this->collection->map(function ($log) { + return new UserAccountLogResource($log); + }), + 'page' => $this->page(), + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Resources/Account/UserAccountLogResource.php b/modules/User/Http/Resources/Account/UserAccountLogResource.php new file mode 100644 index 0000000..2765ed4 --- /dev/null +++ b/modules/User/Http/Resources/Account/UserAccountLogResource.php @@ -0,0 +1,30 @@ + $this->id, + 'frozen' => [ + 'value' => $this->frozen, + 'text' => $this->frozen ? '待发放' : '已发放', + ], + 'rule' => [ + 'rule_id' => $this->rule->id, + 'name' => $this->rule->name, + 'title' => $this->rule->title, + 'remark' => $this->rule->remark, + ], + 'remark' => $this->source['remark'] ?? $this->rule->remark, + 'amount' => $this->amount > 0 ? '+'.floatval($this->amount) : floatval($this->amount), + 'created_at' => $this->created_at->format('Y-m-d H:i:s'), + ]; + } + +} diff --git a/modules/User/Http/Resources/Account/UserAccountResource.php b/modules/User/Http/Resources/Account/UserAccountResource.php new file mode 100644 index 0000000..79a3ef3 --- /dev/null +++ b/modules/User/Http/Resources/Account/UserAccountResource.php @@ -0,0 +1,22 @@ + floatval($this->balance),//现金 + 'score' => [ + 'surplus' => floatval($this->score),//水滴余量 + 'use' => 0,//水滴使用 + 'plan' => 300,//水滴赠送 + ], + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Resources/Favorite/FavoriteCollection.php b/modules/User/Http/Resources/Favorite/FavoriteCollection.php new file mode 100644 index 0000000..64ab46c --- /dev/null +++ b/modules/User/Http/Resources/Favorite/FavoriteCollection.php @@ -0,0 +1,20 @@ + $this->collection->map(function ($info) { + return new FavoriteResource($info); + }), + 'page' => $this->page(), + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Resources/Favorite/FavoriteResource.php b/modules/User/Http/Resources/Favorite/FavoriteResource.php new file mode 100644 index 0000000..add9bf9 --- /dev/null +++ b/modules/User/Http/Resources/Favorite/FavoriteResource.php @@ -0,0 +1,19 @@ + $this->id, + 'favoriteable' => new ArticleBaseResource($this->favoriteable) + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Resources/IdentityMiddleResource.php b/modules/User/Http/Resources/IdentityMiddleResource.php new file mode 100644 index 0000000..8d48345 --- /dev/null +++ b/modules/User/Http/Resources/IdentityMiddleResource.php @@ -0,0 +1,27 @@ +identity->order == 1) { + $ended_at = '---'; + } else { + $ended_at = $this->identity->years ? Carbon::parse($this->ended_at)->format('Y-m-d') : '永久'; + } + + return [ + 'name' => $this->identity->name, + 'serial' => $this->identity->serial_prefix.$this->serial, + 'started_at' => $this->started_at ?? '', + 'ended_at' => $ended_at, + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Resources/RelationResource.php b/modules/User/Http/Resources/RelationResource.php new file mode 100644 index 0000000..95ce652 --- /dev/null +++ b/modules/User/Http/Resources/RelationResource.php @@ -0,0 +1,24 @@ + $this->user->id, +// 'username' => substr_replace($this->user->username, '****', 3, 4), + 'username' => $this->user->username, + 'nickname' => $this->user->info->nickname ?? '', + 'avatar' => $this->user->info->avatar ?? '', + 'sex' => $this->user->case ? $this->user->case->sex_text : '--', + 'identity' => new UserIdentityBaseResource($this->user->identities->first()), + 'created_at' => (string) $this->user->created_at, + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Resources/Sign/SignBannerResource.php b/modules/User/Http/Resources/Sign/SignBannerResource.php new file mode 100644 index 0000000..636189a --- /dev/null +++ b/modules/User/Http/Resources/Sign/SignBannerResource.php @@ -0,0 +1,16 @@ + $this->id, + 'cover' => $this->cover_url, + ]; + } +} \ No newline at end of file diff --git a/modules/User/Http/Resources/Sign/SignTextResource.php b/modules/User/Http/Resources/Sign/SignTextResource.php new file mode 100644 index 0000000..45b1851 --- /dev/null +++ b/modules/User/Http/Resources/Sign/SignTextResource.php @@ -0,0 +1,18 @@ + $this->id, + 'title' => $this->title, + 'h1' => $this->description, + 'h2' => $this->sub_description, + ]; + } +} \ No newline at end of file diff --git a/modules/User/Http/Resources/UserCertificationResource.php b/modules/User/Http/Resources/UserCertificationResource.php new file mode 100644 index 0000000..fcb7743 --- /dev/null +++ b/modules/User/Http/Resources/UserCertificationResource.php @@ -0,0 +1,28 @@ + $this->id, + 'name' => $this->name, + 'verified' => (bool) $this->verified, + 'id_card' => $this->id_card, + 'front_card' => $this->when($this->front_card, function () { + return Storage::url($this->front_card); + }), + 'back_card' => $this->when($this->back_card, function () { + return Storage::url($this->back_card); + }), + 'created_at' => (string) $this->created_at, + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Resources/UserIdentityBaseResource.php b/modules/User/Http/Resources/UserIdentityBaseResource.php new file mode 100644 index 0000000..a7cf4d4 --- /dev/null +++ b/modules/User/Http/Resources/UserIdentityBaseResource.php @@ -0,0 +1,20 @@ + $this->id, + 'name' => $this->name, + 'cover' => $this->cover_url, + 'order' => $this->order, + ]; + } + +} diff --git a/modules/User/Http/Resources/UserIdentityResource.php b/modules/User/Http/Resources/UserIdentityResource.php new file mode 100644 index 0000000..c74079d --- /dev/null +++ b/modules/User/Http/Resources/UserIdentityResource.php @@ -0,0 +1,103 @@ +getCondition('price', '0'); + $cost = $this->getCondition('cost', '0'); + $identityName = $this->name; + $coupon_price = 0; + + if ($user) { + $identity = $user->identityFirst(); + if ($identity) { + //当前身份 可以续费 + if ($identity->id == $this->id) { + $renew = (bool) $identity->years; + $text = '续费'; + } elseif ($identity->order > $this->order) { + $text = '不可降级'; + } else { + $identityOrder = Order::ByUser($user) + ->where('identity_id', $this->id) + ->where('state', Order::STATE_INIT) + ->first(); + if ($identityOrder) { + $text = '等待审核'; + } else { + $open = true; + } + + } + + if ($this->job == Identity::JOB_JK) { + $openIdentity = Identity::find($this->id); + $coupons = $this->getCreateOrderCoupon($user, $openIdentity, 0); + if ($coupons->isNotEmpty()) { + $user_coupon = $coupons->first(); + $coupon_price = $user_coupon->price; + if ($user_coupon->coupon->type == Coupon::TYPE_REDUCTION) { + $price = $price > $user_coupon->price ? bcsub($price, $coupon_price, 2) : 0; + } + } + } + + if ($this->job == Identity::JOB_HH && $identity->job == Identity::JOB_HH) { + $star = $identity->getOriginal('pivot_star', 0); + if ($star > 0) { + $identityName = config('identity.stars.'.$star, '').$identityName; + } + } + } else { + $open = true; + } + } + + return [ + 'identity_id' => $this->id, + 'name' => $identityName, + 'stock' => $this->stock, + 'years' => $this->years, + 'times' => $this->when($user && $this->id == $user->identityFirst()->id, function () use ($user) { + return new IdentityMiddleResource($user->identityMiddle()->first()); + }, [ + 'name' => '---', + 'serial' => '---', + 'started_at' => '---', + 'ended_at' => '---', + ]), + 'cover' => $this->cover_url, + 'order' => $this->order, + 'description' => $this->description ?? "", + 'coupon_price' => floatval($coupon_price),//开通金额 + 'cost' => floatval($cost),//开通金额 + 'price' => floatval($price),//开通金额 + 'can' => [ + 'buy' => (bool) $this->can_buy, + 'open' => $this->can_buy ? $open : false,//开通 + 'renew' => $this->can_buy ? $renew : false,//续费 + ], + 'buttonText' => $text, + 'rights' => $this->rights, + 'rules' => $this->getRules(), + 'not_rules' => $this->getNotRules(), + 'is_open' => $user && $this->id == $user->identityFirst()->id + ]; + } + +} diff --git a/modules/User/Http/Resources/UserIdentityRightsResource.php b/modules/User/Http/Resources/UserIdentityRightsResource.php new file mode 100644 index 0000000..93913c5 --- /dev/null +++ b/modules/User/Http/Resources/UserIdentityRightsResource.php @@ -0,0 +1,35 @@ +rights; + $data = []; + if ($rights){ + for ($i = 1; $i <= count($rights); $i++) { + $data[$i] = [ + 'name' => $rights['new_'.$i]['name'] ?? '', + 'cover' => ! empty($rights['new_'.$i]['cover']) ? Storage::url($rights['new_'.$i]['cover']) : '', + 'order' => $rights['new_'.$i]['order'] ?? '', + 'remark' => $rights['new_'.$i]['remark'] ?? '', + ]; + } + } + + return [ + 'identity_id' => $this->id, + 'name' => $this->name, + 'cover' => $this->cover_url, + 'order' => $this->order, + 'rights' => $data, + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Resources/UserInfoBaseResource.php b/modules/User/Http/Resources/UserInfoBaseResource.php new file mode 100644 index 0000000..3cc3ce4 --- /dev/null +++ b/modules/User/Http/Resources/UserInfoBaseResource.php @@ -0,0 +1,26 @@ + $this->id, + 'username' => $this->username, + 'nickname' => $this->info->nickname ?? '', + 'avatar' => $this->info->avatar ?? '', + 'sign' => $this->getSignData(), + 'identity' => new UserIdentityBaseResource($this->identities->first()), + 'status' => $this->getStateData(), + 'canPick' => $this->canPick(), + 'nowStatus' => $this->getNowStatus(), + 'tag' => $this->tag + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Resources/UserInfoResource.php b/modules/User/Http/Resources/UserInfoResource.php new file mode 100644 index 0000000..5dabc26 --- /dev/null +++ b/modules/User/Http/Resources/UserInfoResource.php @@ -0,0 +1,60 @@ +identityFirst(); + if ($identity->id > 2) { + $order = true; + } else { + $order = false; + } + + return [ + 'user_id' => $this->id, + 'username' => $this->username, + 'nickname' => $this->info->nickname ?? '', + 'avatar' => $this->info->avatar ?? '', + 'account' => new UserAccountResource($this->account), + 'identity' => new UserIdentityBaseResource($identity), + 'service' => new UserServiceResource($identity->identities()->first()), + 'water_mobile' => app('Conf_mall')['water_mobile'] ?? '', + 'invite' => Hashids::connection('code')->encode($this->id), + 'case' => $this->getStockData(), + 'user_wechat' => new UserWechatResource($this->wechat), + 'created_at' => (string) $this->created_at, + 'sign' => $this->getSignData(), + 'status' => $this->getStateData(), + 'nowStatus' => $this->getNowStatus(), + 'canPick' => $this->canPick(), + 'count' => [ + 'coupon' => $this->couponGrants()->count(),//Żȯ + 'relation' => $this->getRelationCount(),//¼ + 'invites' => $this->invites()->count(),// + 'notifications' => $this->unreadNotifications()->count(),//Ϣ + 'orders' => Order::byUser(Api::user())->common()->count(),// + 'refunds' => RefundModel::byUser(Api::user())->count(),//˿ + ], + 'identityShow' => $this->when(true, function () use ($identity) { + return [ + 'right' => new UserIdentityRightsResource($identity), + 'times' => new IdentityMiddleResource($this->identityMiddle()->first()), + 'is_top' => $identity->id == 4, + ]; + }), + ]; + } + +} diff --git a/modules/User/Http/Resources/UserServiceResource.php b/modules/User/Http/Resources/UserServiceResource.php new file mode 100644 index 0000000..f8b9659 --- /dev/null +++ b/modules/User/Http/Resources/UserServiceResource.php @@ -0,0 +1,20 @@ + $this->id, + 'name' => $this->name, + 'code' => $this->cover_url, + 'mobile' => $this->mobile, + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Resources/UserSignResource.php b/modules/User/Http/Resources/UserSignResource.php new file mode 100644 index 0000000..ff1f5c3 --- /dev/null +++ b/modules/User/Http/Resources/UserSignResource.php @@ -0,0 +1,22 @@ + $this->continue_days, + 'counts' => $this->counts, + 'last_sign_at' => (string) $this->last_sign_at, + 'today_signed' => !UserSign::canSign($this->user, Carbon::today()), + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Resources/UserWechatResource.php b/modules/User/Http/Resources/UserWechatResource.php new file mode 100644 index 0000000..645c4d3 --- /dev/null +++ b/modules/User/Http/Resources/UserWechatResource.php @@ -0,0 +1,22 @@ + $this->id, + 'unionid' => $this->unionid, + 'nickname' => $this->nickname, + 'avatar' => $this->avatar, + 'mini' => $this->mini, + 'official' => $this->official, + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Resources/stock/UserStockLogCollection.php b/modules/User/Http/Resources/stock/UserStockLogCollection.php new file mode 100644 index 0000000..aa497a1 --- /dev/null +++ b/modules/User/Http/Resources/stock/UserStockLogCollection.php @@ -0,0 +1,21 @@ + $this->collection->map(function ($log) { + return new UserStockLogResource($log); + }), + 'page' => $this->page(), + ]; + + } + +} \ No newline at end of file diff --git a/modules/User/Http/Resources/stock/UserStockLogResource.php b/modules/User/Http/Resources/stock/UserStockLogResource.php new file mode 100644 index 0000000..1905678 --- /dev/null +++ b/modules/User/Http/Resources/stock/UserStockLogResource.php @@ -0,0 +1,22 @@ + $this->id, + 'variable' => $this->variable, + 'type' => $this->type_text, + 'created_at' => $this->created_at->format('Y-m-d H:i:s'), + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Listeners/UserOrderPaidListeners.php b/modules/User/Listeners/UserOrderPaidListeners.php new file mode 100644 index 0000000..09df97d --- /dev/null +++ b/modules/User/Listeners/UserOrderPaidListeners.php @@ -0,0 +1,51 @@ +order->refresh(); + $type = UserStockLog::TYPE_INIT; + if ($order->user->hasIdentity($order->identity->id)) { + $type = UserStockLog::TYPE_IN; + } + + $channel = IdentityLog::CHANNEL_AUTO; + if (isset($order->source['channel'])) { + $channel = $order->source['channel']; + } + + $order->user->joinIdentity($order->identity->id, $channel, [ + 'serial' => $order->source['serial'] ?? '' + ]);//开通身份 + + $order->user->notify(new SystemOpenVip($order->user->identityMiddle()->first())); + + if ($order->stock > 0) { + $order->user->addStock($order->identity->id, $type, $order->stock);//赠水 + } + } catch (Exception $exception) { + throw new Exception($exception->getMessage()); + } + } + +} diff --git a/modules/User/Listeners/UserSignContinueDays.php b/modules/User/Listeners/UserSignContinueDays.php new file mode 100644 index 0000000..49c6680 --- /dev/null +++ b/modules/User/Listeners/UserSignContinueDays.php @@ -0,0 +1,32 @@ +log; + + # todo .连续签到天数的一个计算,用最后的签到日期,是否可行? + $sign = Sign::updateOrCreate([ + 'user_id' => $log->user_id, + ], [ + 'counts' => SignLog::query()->distinct()->where('user_id', $log->user_id)->count('date'), + 'last_sign_at' => Carbon::now(), + ]); + if ($sign->wasRecentlyCreated) { + $sign->continue_days = 1; + } else { + $sign->continue_days = $sign->getContinueDays(); + } + $sign->save(); + } + +} diff --git a/modules/User/Models/Account.php b/modules/User/Models/Account.php new file mode 100644 index 0000000..0c9afc0 --- /dev/null +++ b/modules/User/Models/Account.php @@ -0,0 +1,175 @@ + '现金余额', + 'score' => '水滴', + 'coins' => '未使用', + 'other' => '其他', + ]; + + /** + * Notes : 账户日志 + * + * @Date : 2021/4/27 11:22 上午 + * @Author : < Jason.C > + * @return HasMany + */ + public function logs(): HasMany + { + return $this->hasMany(AccountLog::class, 'account_id', 'user_id'); + } + + /** + * Notes: 执行账户规则 + * + * @Author: + * @Date : 2019/11/28 1:24 下午 + * @param $rule string|int + * @param float $variable + * @param bool $frozen + * @param array $source + * @return bool + * @throws Exception + */ + public function rule($rule, float $variable = 0, bool $frozen = true, array $source = []): bool + { + if (is_numeric($rule)) { + $rule = AccountRule::findOrFail($rule); + } else { + $rule = AccountRule::where('name', $rule)->firstOrFail(); + } + + if ($rule->trigger == 0) { + // 不限制执行的 + return $this->accountExecute($rule, $variable, $frozen, $source); + } elseif ($rule->trigger > $this->logs() + ->where('rule_id', $rule->id) + ->whereDate('created_at', Carbon::today()) + ->count()) { + // 每日执行 trigger 次 + return $this->accountExecute($rule, $variable, $frozen, $source); + } elseif ($rule->trigger < 0 && ! $this->logs()->where('rule_id', $rule->id)->first()) { + // 终身只能执行一次 + return $this->accountExecute($rule, $variable, $frozen, $source); + } + + throw new Exception('达到最大可执行次数'); + } + + /** + * Notes: 增加账户余额 + * + * @Author: + * @Date : 2019/11/28 1:25 下午 + * @param $type + * @param $variable + * @return bool + */ + public function increase($type, $variable): bool + { + DB::transaction(function () use ($type, $variable) { + $this->increment($type, $variable); + $log = [ + 'rule_id' => 0, + 'type' => $type, + 'variable' => $variable, + 'frozen' => 0, + 'balance' => $this->{$type}, + 'source' => ['type' => 'increase'], + ]; + $this->logs()->create($log); + }); + + return true; + } + + /** + * Notes: 扣除账户金额 + * + * @Author: + * @Date : 2019/11/28 1:25 下午 + * @param $type + * @param $variable + * @return bool + * @throws Exception + */ + public function decrease($type, $variable): bool + { + + DB::transaction(function () use ($type, $variable) { + $this->decrement($type, $variable); + $log = [ + 'rule_id' => 0, + 'type' => $type, + 'variable' => -$variable, + 'frozen' => 0, + 'balance' => $this->{$type}, + 'source' => ['type' => 'deduct'], + ]; + $this->logs()->create($log); + }); + + return true; + } + + /** + * Notes: 执行账户规则 + * + * @Author: + * @Date : 2019/11/28 1:41 下午 + * @param AccountRule $rule + * @param $variable + * @param $frozen + * @param $source + * @return bool + * @throws Exception + */ + protected function accountExecute(AccountRule $rule, $variable, $frozen, $source): bool + { + if ($variable != 0) { + $rule->variable = $variable; + } + + DB::transaction(function () use ($rule, $frozen, $source) { + // 如果是扣款,立即执行,如果非冻结,也立即执行 + if ($rule->variable < 0 || $rule->deductions == 1 || $frozen === false) { + $this->increment($rule->type, $rule->variable); + $frozen = false; + } + $log = [ + 'rule_id' => $rule->id, + 'type' => $rule->type, + 'amount' => $rule->variable, + 'frozen' => $frozen, + 'balance' => $this->{$rule->type}, + 'source' => $source ?: [], + 'remark' => $source['remark'] ?? $rule->remark, + 'settle_at' => $source['settle_at'] ?? null, + 'frozen_at' => $source['frozen_at'] ?? null, + ]; + // 写入记录 + $this->logs()->create($log); + }); + + return true; + } + +} diff --git a/modules/User/Models/AccountLog.php b/modules/User/Models/AccountLog.php new file mode 100644 index 0000000..efb9a0f --- /dev/null +++ b/modules/User/Models/AccountLog.php @@ -0,0 +1,91 @@ + 'json', + ]; + + /** + * Notes : 账户 + * + * @Date : 2021/4/27 11:22 上午 + * @Author : < Jason.C > + * @return BelongsTo + */ + public function account(): BelongsTo + { + return $this->belongsTo(Account::class, 'account_id'); + } + + public function rule(): BelongsTo + { + return $this->belongsTo(AccountRule::class, 'rule_id'); + } + + /** + * Notes: 冻结一条账户记录 + * + * @Author: + * @Date : 2019/12/1 10:48 上午 + * @return bool + * @throws Exception + */ + public function freeze(): bool + { + if ($this->frozen == 0) { + $this->account->decrement($this->type, $this->amount); + $this->frozen = 1; + $this->balance = $this->account->{$this->type}; + $this->save(); + + return true; + } else { + throw new Exception('账目已冻结'); + } + } + + /** + * Notes: 解冻一条记录 + * + * @Author: + * @Date : 2019/12/1 10:48 上午 + * @return bool + * @throws Exception + */ + public function thaw(): bool + { + if ($this->frozen == 1) { + $this->account->increment($this->type, $this->amount); + $this->frozen = 0; + $this->balance = $this->account->{$this->type}; + $this->save(); + + return true; + } else { + throw new Exception('已经领取'); + } + } + + public function getAmountFormatAttribute(): string + { + return ($this->amount > 0 ? '+' : '').$this->amount; + } + +} diff --git a/modules/User/Models/AccountRule.php b/modules/User/Models/AccountRule.php new file mode 100644 index 0000000..e762bee --- /dev/null +++ b/modules/User/Models/AccountRule.php @@ -0,0 +1,43 @@ +hasMany(AccountLog::class, 'rule_id'); + } + + /** + * Notes : 获取可执行次数的文本显示 + * @Date : 2021/5/21 2:37 下午 + * @Author : < Jason.C > + * @return string + */ + protected function getTriggerTextAttribute(): string + { + switch ($this->trigger <=> 0) { + case -1: + return '仅一次'; + case 0: + return '不限制'; + case 1: + return $this->trigger . ' 次/日'; + default: + return ''; + } + } + +} \ No newline at end of file diff --git a/modules/User/Models/Identity.php b/modules/User/Models/Identity.php new file mode 100644 index 0000000..2dc1c21 --- /dev/null +++ b/modules/User/Models/Identity.php @@ -0,0 +1,271 @@ + '原价', + 'price' => '开通金额', + ]; + + const Rules = [ + 'give_crystal' => '开通赠水滴', + 'recommend_rate' => '推荐比例', + ]; + + const CHANNEL_ONLINE = 1; + const CHANNEL_OFFLINE = 2; + + const CHANNELS = [ + self::CHANNEL_ONLINE => '线上', + self::CHANNEL_OFFLINE => '线下' + ]; + + const EXPERIENCE_YES = 1; + const EXPERIENCE_NO = 0; + + const EXPERIENCES = [ + self::EXPERIENCE_YES => '是', + self::EXPERIENCE_NO => '否' + ]; + + const JOB_YK = 0; + const JOB_TY = 1; + const JOB_JK = 2; + const JOB_NK = 3; + const JOB_CS = 4; + const JOB_HH = 5; + + const JOBS = [ + self::JOB_YK => '游客', + self::JOB_TY => '体验官', + self::JOB_JK => '季卡', + self::JOB_NK => '年卡', + self::JOB_CS => '创始', + self::JOB_HH => '合伙人', + ]; + + protected $table = 'user_identities'; + + protected $casts = [ + 'conditions' => 'json', + 'rules' => 'json', + 'rights' => 'json', + 'ruleshows' => 'json', + ]; + + public function setConditionsAttribute($value) + { + $this->attributes['conditions'] = json_encode(array_values($value)); + } + + public function setRulesAttribute($value) + { + $rules = collect($this->rules); + + foreach ($value as &$item) { + $info = $rules->where('name', $item['name'])->first(); + if (! isset($item['icon']) && $info && isset($info['icon'])) { + $item['icon'] = $info['icon']; + } + } + $this->attributes['rules'] = json_encode(array_values($value)); + } + + public function setRuleshowsAttribute($value) + { + $rules = collect($this->ruleshows); + + foreach ($value as &$item) { + $info = $rules->where('name', $item['name'])->first(); + if (! isset($item['icon']) && $info && isset($info['icon'])) { + $item['icon'] = $info['icon']; + } + } + $this->attributes['ruleshows'] = json_encode(array_values($value)); + } + + public function setRightsAttribute($value) + { + $rights = collect($this->rights); + + foreach ($value as &$item) { + $info = $rights->where('name', $item['name'])->first(); + if (! isset($item['cover']) && $info && isset($info['cover'])) { + $item['cover'] = $info['cover']; + } + } + + $this->attributes['rights'] = json_encode(array_values($value)); + } + + /** + * Notes: 获取所有规则 + * + * @Author: 玄尘 + * @Date: 2022/8/21 13:03 + */ + public function getRules() + { + $rules = $this->ruleshows; + foreach ($rules as $key => $rule) { + if (isset($rule['icon'])) { + $rules[$key]['cover'] = $this->parseImageUrl($rule['icon']); + } + $rules[$key]['text'] = Arr::get(config('identity.show_rules'), $rule['name']); + } + return $rules; + } + + /** + * Notes: 获取未开通身份时反馈的数据 + * + * @Author: 玄尘 + * @Date: 2022/8/26 14:16 + * @return array|mixed + */ + public function getNotRules() + { + $rules = $this->rules; + if ($this->job == Identity::JOB_JK) { + return [ + 'give_crystal' => [ + 'cover' => $this->getRuleIcon('give_crystal'), + 'value' => $this->getRule('give_crystal'), + 'text' => '赠送水滴' + ], + 'recommend_coupon' => [ + 'cover' => $this->getRuleIcon('recommend_coupon'), + 'value' => $this->getRule('recommend_coupon'), + 'text' => '赠送抵值券' + ], + 'stock' => [ + 'value' => $this->stock, + 'text' => "赠送{$this->stock}箱水" + ], + 'year' => [ + 'value' => $this->years, + 'text' => $this->years."个月有效期" + ], + ]; + } + + if ($this->job == Identity::JOB_NK) { + return [ + 'stock' => [ + 'value' => $this->stock, + 'text' => "赠送{$this->stock}箱水" + ], + 'year' => [ + 'value' => $this->years, + 'text' => $this->years."个月有效期" + ], + ]; + } + + return $rules; + } + + + /** + * Notes : 组内用户 + * + * @Date : 2021/5/6 12:06 下午 + * @Author : < Jason.C > + * @return BelongsToMany + */ + public function users(): BelongsToMany + { + return $this->belongsToMany(User::class, 'user_identity') + ->withTimestamps(); + } + + /** + * Notes : 不同的客服匹配不同的身份 + * + * @Date : 2021/6/07 10:50 + * @Author : Mr.wang + * @return BelongsToMany + */ + public function identities(): BelongsToMany + { + return $this->belongsToMany(Service::class, 'user_service_identity') + ->withTimestamps(); + } + + /** + * 返回身份中的某一个规则 + * + * @param string $key + * @param mixed $default + * @return mixed|string + */ + public function getRule(string $key = '', $default = '') + { + $values = collect($this->rules); + $value = $values->where('name', $key)->first(); + if ($value) { + return $value['value']; + } else { + return $default; + } + } + + /** + * Notes: 获取规则图标 + * + * @Author: 玄尘 + * @Date: 2022/8/26 14:24 + * @param string $key + * @param $default + * @return mixed|string + */ + public function getRuleIcon(string $key = '', $default = '') + { + $values = collect($this->rules); + $value = $values->where('name', $key)->first(); + if ($value && isset($value['icon'])) { + return $this->parseImageUrl($value['icon']); + } else { + return $default; + } + } + + /** + * 返回条件中的某一个字段 + * + * @param string $key + * @param mixed $default + * @return mixed|string + */ + public function getCondition(string $key = '', $default = '') + { + $values = collect($this->conditions); + $value = $values->where('name', $key)->first(); + if ($value) { + return $value['value']; + } else { + return $default; + } + } + +} diff --git a/modules/User/Models/IdentityLog.php b/modules/User/Models/IdentityLog.php new file mode 100644 index 0000000..b4cec38 --- /dev/null +++ b/modules/User/Models/IdentityLog.php @@ -0,0 +1,46 @@ + 'json', + ]; + + const CHANNEL_AUTO = 'Auto'; + const CHANNEL_REG = 'Reg'; + + const CHANNEL_SYSTEM = 'System'; + + const CHANNEL_MAP = [ + self::CHANNEL_AUTO => '自动变更', + self::CHANNEL_REG => '注册默认', + self::CHANNEL_SYSTEM => '后台变更', + ]; + + public function before_identity(): BelongsTo + { + return $this->belongsTo(Identity::class, 'before')->withDefault([ + 'name' => '已删除', + ]); + } + + public function after_identity(): BelongsTo + { + return $this->belongsTo(Identity::class, 'after')->withDefault([ + 'name' => '已删除', + ]); + } + +} \ No newline at end of file diff --git a/modules/User/Models/IdentityMiddle.php b/modules/User/Models/IdentityMiddle.php new file mode 100644 index 0000000..717e714 --- /dev/null +++ b/modules/User/Models/IdentityMiddle.php @@ -0,0 +1,36 @@ +identity->order > 1) { + $identity->addTimeline(); + } + }); + } + + public function identity(): BelongsTo + { + return $this->belongsTo(Identity::class, 'identity_id'); + } + +} \ No newline at end of file diff --git a/modules/User/Models/Order.php b/modules/User/Models/Order.php new file mode 100644 index 0000000..3462fe6 --- /dev/null +++ b/modules/User/Models/Order.php @@ -0,0 +1,96 @@ + '待审核', + self::STATE_SUCCESS => '已支付', + self::STATE_REFUND => '已退款', + ]; + + const TYPE_OPEN = 1; + const TYPE_RENEW = 2; + + const TYPES = [ + self::TYPE_OPEN => '开通', + self::TYPE_RENEW => '续费', + ]; + + const CHANNEL_IDENTITY = 1; + const CHANNEL_EXPERIENCE = 2; + const CHANNEL_PARTNER = 3; + + const CHANNELS = [ + self::CHANNEL_IDENTITY => '开通身份', + self::CHANNEL_EXPERIENCE => '开通体验官', + self::CHANNEL_PARTNER => '开通合伙人', + ]; + + public $casts = [ + 'source' => 'json' + ]; + + public function identity(): BelongsTo + { + return $this->belongsTo(Identity::class, 'identity_id'); + } + + /** + * Notes: 设置订单支付 + * @Author: 玄尘 + * @Date : 2020/11/12 11:19 + */ + public function pay() + { + $this->state = self::STATE_SUCCESS; + $this->save(); + + event(new UserOrderPaid($this)); + } + + /** + * Notes: 是否可以支付 + * @Author: 玄尘 + * @Date : 2021/6/4 10:19 + * @return bool + */ + public function canPay(): bool + { + return $this->state == self::STATE_INIT; + } + + /** + * Notes: 是否可以退款 + * + * @Author: 玄尘 + * @Date: 2022/8/22 13:18 + * @return bool + */ + public function canRefund(): bool + { + return $this->isPay(); + } + + + + +} diff --git a/modules/User/Models/Relation.php b/modules/User/Models/Relation.php new file mode 100644 index 0000000..774bfea --- /dev/null +++ b/modules/User/Models/Relation.php @@ -0,0 +1,75 @@ +setParentColumn('parent_id'); + $this->setOrderColumn('created_at'); + $this->setTitleColumn('user_id'); + } + + /** + * 上级用户 + * + * @return BelongsTo + */ + public function parent(): BelongsTo + { + return $this->belongsTo(User::class, 'parent_id'); + } + + /** + * Notes: 关联中间表 + * + * @Author: 玄尘 + * @Date: 2022/8/19 13:16 + */ + public function identities(): BelongsToMany + { + return $this->belongsToMany( + Identity::class, + 'user_identity', + 'user_id', + 'identity_id', + ); + } + + + /** + * 所有下级用户 + * + * @return HasManyThrough + */ + public function children(): HasManyThrough + { + return $this->hasManyThrough( + User::class, + Relation::class, + 'parent_id', + 'id', + 'user_id', + 'user_id' + ); + } + +} diff --git a/modules/User/Models/Service.php b/modules/User/Models/Service.php new file mode 100644 index 0000000..28e8546 --- /dev/null +++ b/modules/User/Models/Service.php @@ -0,0 +1,34 @@ +belongsToMany(Identity::class, 'user_service_identity') + ->withTimestamps(); + } + +} diff --git a/modules/User/Models/Sign.php b/modules/User/Models/Sign.php new file mode 100644 index 0000000..170b3f6 --- /dev/null +++ b/modules/User/Models/Sign.php @@ -0,0 +1,61 @@ + '进行中', + self::FINISH_SIGN => '打卡完成', + self::FINISH_LOG => '报告完成', + ]; + + protected static function boot() + { + parent::boot(); + + self::saved(function ($sign) { + $params = SignConfig::getParams(); + + if ($params['open'] == 1 && $sign->need_case == 0 && $sign->continue_days >= $params['cycle_day']) { + $sign->update(['need_case' => 1, 'is_finish' => 1]); + } + + }); + } + + /** + * Notes : 获取最新连续签到数 + * + * @Date : 2021/5/28 12:00 + * @Author : Mr.wang + * @return int|mixed + */ + public function getContinueDays() + { + if ($this->last_sign_at->diffInDays() > 1) { + $continue = 1; + } else { + $continue = $this->continue_days + 1; + } + + return $continue; + } + +} diff --git a/modules/User/Models/SignBanner.php b/modules/User/Models/SignBanner.php new file mode 100644 index 0000000..ed7755b --- /dev/null +++ b/modules/User/Models/SignBanner.php @@ -0,0 +1,16 @@ + '单日', + self::TYPE_CONTINUOUS => '连续', + self::TYPE_CYCLE => '周期连续', + ]; + const SHOWTYPES_DAY = 'day'; + const SHOWTYPES_WEEK = 'week'; + const SHOWTYPES_MONTH = 'month'; + const SHOWTYPES = [ + self::SHOWTYPES_DAY => '7天展示', + self::SHOWTYPES_WEEK => '一周展示', + self::SHOWTYPES_MONTH => '一月展示', + ]; + protected $table = 'user_sign_configs'; + protected $casts = [ + 'params' => 'json', + 'tasks' => 'json', + ]; + + public static function getParams($key = '') + { + $model = SignConfig::find(1); + if (! $key) { + return collect($model->params); + } else { + return $model->params[$key] ?? ''; + } + } + + public static function getTasks($key = '') + { + $model = SignConfig::find(1); + if (! $key) { + return collect($model->tasks); + } else { + return $model->tasks[$key] ?? ''; + } + } + +} \ No newline at end of file diff --git a/modules/User/Models/SignLog.php b/modules/User/Models/SignLog.php new file mode 100644 index 0000000..027ca87 --- /dev/null +++ b/modules/User/Models/SignLog.php @@ -0,0 +1,30 @@ + 'date', + ]; + + protected static function boot() + { + parent::boot(); + + self::created(function ($model) { + + }); + } + +} diff --git a/modules/User/Models/SignText.php b/modules/User/Models/SignText.php new file mode 100644 index 0000000..0c806ed --- /dev/null +++ b/modules/User/Models/SignText.php @@ -0,0 +1,13 @@ + 'json', + ]; + + public static function boot() + { + parent::boot(); + + self::saved(function ($model) { + if ($model->in_use && $model->id) { + self::where('id', '<>', $model->id) + ->where('in_use', 1) + ->update(['in_use' => 0]); + } + }); + } + + /** + * Notes : 默认网关配置 + * @Date : 2021/5/27 5:18 下午 + * @Author : < Jason.C > + * @return array + */ + public function getGateway(): array + { + return SmsGateway::where('slug', $this->default_gateway)->value('configs'); + } + +} \ No newline at end of file diff --git a/modules/User/Models/SmsGateway.php b/modules/User/Models/SmsGateway.php new file mode 100644 index 0000000..cdd347a --- /dev/null +++ b/modules/User/Models/SmsGateway.php @@ -0,0 +1,19 @@ + 'json', + ]; + +} \ No newline at end of file diff --git a/modules/User/Models/Traits/CertificationTrait.php b/modules/User/Models/Traits/CertificationTrait.php new file mode 100644 index 0000000..441e4c7 --- /dev/null +++ b/modules/User/Models/Traits/CertificationTrait.php @@ -0,0 +1,283 @@ +ocrVerified(Storage::url($keys['front_card']), 'front'); + if (! $verified) { + return [ + 'code' => 0, + 'message' => $this->getErrorMessage(), + ]; + } else { + if ($verified['words_result']['姓名']['words'] != $keys['name']) { + return [ + 'code' => 0, + 'message' => '正面图片与填写信息不符', + ]; + } + if ($verified['words_result']['公民身份号码']['words'] != $keys['idcard']) { + return [ + 'code' => 0, + 'message' => '正面图片与填写信息不符', + ]; + } + } + $verified = $this->ocrVerified(Storage::url($keys['back_card']), 'back'); + + if (! $verified) { + return [ + 'code' => 0, + 'message' => $this->getErrorMessage(), + ]; + } else { + if ($verified['image_status'] != 'normal') { + return [ + 'code' => 0, + 'message' => '背面图片与填写信息不符', + ]; + } + } + } + $base = [ + 'name' => $keys['name'], + 'idcard' => $keys['idcard'], + ]; + $data = $this->check($base); + if ($data === false) { + return [ + 'code' => 0, + 'message' => $this->getErrorMessage(), + ]; + } else { + return [ + 'code' => 1, + ]; + } + } else { + return [ + 'code' => 1, + ]; + } + } + + /** + * Notes : ocr认证 + * + * @Author: Mr.wang + * @Date : 2021/12/7 13:54 + * @param $image + * @param $type + * @return bool + */ + public function ocrVerified($image, $type): bool + { + $AppID = config('userCertification.ocr_appid'); + $SecretKey = config('userCertification.ocr_secretkey'); + + if (empty($AppID)) { + $this->setErrorMessage('请配置接口AppID'); + + return false; + } + if (empty($SecretKey)) { + $this->setErrorMessage('请配置接口SecretKey'); + + return false; + } + $access = $this->getAccess($AppID, $SecretKey); + if ($access === false) { + $this->setErrorMessage('access_token不正确'); + + return false; + } + $token = $access->access_token; + $apiUrl = 'https://aip.baidubce.com/rest/2.0/ocr/v1/idcard'; + $result = $this->getOcr($apiUrl, $token, $image, $type); + if (($result['error_code'] ?? 0) == 100) { + $this->setErrorMessage($result['error_msg'] ?? '未知错误'); + + return false; + } else { + if (empty($result['words_result']) || ! isset($result['words_result'])) { + $this->setErrorMessage('图片未识别'); + + return false; + } + + return $result; + } + } + + protected function getAccess($AppID, $SecretKey) + { + $authUrl = 'https://aip.baidubce.com/oauth/2.0/token'; + try { + $Client = new Client(); + $response = $Client->post($authUrl, [ + 'query' => [ + 'grant_type' => 'client_credentials', + 'client_id' => $AppID, + 'client_secret' => $SecretKey, + ], + ]); + $result = json_decode($response->getBody()->getContents()); + + return $result; + } catch (\Exception $e) { + return false; + } + } + + protected function getOcr($url, $token, $image, $type) + { + $url = $url.'?access_token='.$token; + $params = [ + 'url' => $image, + 'id_card_side' => $type, + ]; + try { + $Client = new Client(); + $response = $Client->post($url, ['form_params' => $params]); + $result = json_decode($response->getBody()->getContents(), true); + + return $result; + } catch (\Exception $e) { + return false; + } + } + + public function getErrorMessage() + { + return $this->errorMessage; + } + + protected function setErrorMessage($message) + { + $this->errorMessage = $message; + } + + /** + * Notes : 网络验证 + * + * @Date : 2021/9/25 15:42 + * @Author : Mr.wang + * @param $keys + * @return bool|mixed + */ + protected function check($keys): bool + { + $apiUrl = config('userCertification.url'); + if (empty($apiUrl)) { + $this->setErrorMessage('请配置接口地址'); + + return false; + } + $apiCode = config('userCertification.code'); + if (empty($apiCode)) { + $this->setErrorMessage('请配置接口Code'); + + return false; + } + $this->setParams($keys); + $this->setHeaders(); + $result = $this->dopost($apiUrl); + try { + if (config('userCertification.type') == 2) { + if ($result->code == 0 && $result->message == '成功') { + if ($result->result->res == 1) { + return true; + } else { + $this->setErrorMessage('信息'.$result->result->description); + + return false; + } + } else { + $this->setErrorMessage('信息'.$result->result->description); + + return false; + } + } else { + if ($result->code == 200 && $result->success === true) { + if ($result->data->result == 0) { + return true; + } else { + $this->setErrorMessage($result->data->desc); + + return false; + } + } else { + $this->setErrorMessage($result->msg); + + return false; + } + } + } catch (\Exception $e) { + return $result; + } + } + + protected function setParams($keys) + { + $this->params = $keys; + } + + protected function setHeaders() + { + $this->header = [ + "Authorization" => 'APPCODE '.config('userCertification.code'), + "Accept" => "application/json", + ]; + } + + protected function dopost($url) + { + try { + $Client = new Client(); + $response = $Client->get($url, ['query' => $this->params, 'headers' => $this->header]); + // switch (config('usercertification.request_method')) { + // case 'get': + // $response = $Client->get($url, ['query' => $this->params, 'headers' => $this->header]); + // break; + // case 'post': + // $response = $Client->post($url, ['query' => $this->params, 'headers' => $this->header]); + // break; + // default: + // $this->setErrorMessage('不允许的请求方式'); + // + // return false; + // break; + // } + $result = json_decode($response->getBody()->getContents()); + + return $result; + } catch (\Exception $e) { + preg_match_all('/[\x{4e00}-\x{9fff}]+/u', $e->getmessage(), $cn_name); + $this->setErrorMessage($cn_name[0][0]); + + return false; + } + } +} \ No newline at end of file diff --git a/modules/User/Models/Traits/HasIdentityScopes.php b/modules/User/Models/Traits/HasIdentityScopes.php new file mode 100644 index 0000000..4af6a39 --- /dev/null +++ b/modules/User/Models/Traits/HasIdentityScopes.php @@ -0,0 +1,77 @@ +where('job', Identity::JOB_TY); + } + + /** + * Notes: 季卡 + * + * @Author: 玄尘 + * @Date: 2022/8/18 15:26 + * @param Builder $query + * @return Builder + */ + public function scopeJk(Builder $query): Builder + { + return $query->where('job', Identity::JOB_JK); + } + + /** + * Notes: 年卡 + * + * @Author: 玄尘 + * @Date: 2022/8/18 15:27 + * @param Builder $query + * @return Builder + */ + public function scopeNk(Builder $query): Builder + { + return $query->where('job', Identity::JOB_NK); + } + + /** + * Notes: 创始 + * + * @Author: 玄尘 + * @Date: 2022/8/18 15:27 + * @param Builder $query + * @return Builder + */ + public function scopeCs(Builder $query): Builder + { + return $query->where('job', Identity::JOB_CS); + } + + /** + * Notes: 合伙人 + * + * @Author: 玄尘 + * @Date: 2022/8/18 15:27 + * @param Builder $query + * @return Builder + */ + public function scopeHh(Builder $query): Builder + { + return $query->where('job', Identity::JOB_HH); + } + + +} diff --git a/modules/User/Models/Traits/HasLog.php b/modules/User/Models/Traits/HasLog.php new file mode 100644 index 0000000..cce3e3f --- /dev/null +++ b/modules/User/Models/Traits/HasLog.php @@ -0,0 +1,44 @@ +hasMany(UserLog::class); + } + + /** + * Notes: + * + * @Author: 玄尘 + * @Date: 2022/9/7 14:22 + * @param $admin + * @param $remark + */ + public function addLog($admin, $remark) + { + $this->logs()->create([ + 'user_id' => $this->getKey(), + 'admin_id' => $admin->getKey(), + 'remark' => $remark + ]); + } + + +} \ No newline at end of file diff --git a/modules/User/Models/Traits/HasRelations.php b/modules/User/Models/Traits/HasRelations.php new file mode 100644 index 0000000..ccf8fa6 --- /dev/null +++ b/modules/User/Models/Traits/HasRelations.php @@ -0,0 +1,192 @@ + + * @Date : 2020/1/13 5:52 下午 + */ + public static function bootHasRelations() + { + self::created(function ($model) { + if (isset($model->parent_id) && is_numeric($model->parent_id) && $model->parent_id != 0) { + $parent = User::find($model->parent_id); + if ($parent && $model->id != $model->parent_id) { + $model->relation()->create([ + 'parent_id' => $parent->id, + 'bloodline' => $parent->relation->bloodline.$parent->id.',', + 'layer' => $parent->relation->layer + 1, + ]); + } else { + $model->relation()->create([ + 'parent_id' => config('user.default_parent_id'), + 'bloodline' => config('user.default_parent_id').',', + 'layer' => 1, + ]); + } + } else { + $model->relation()->create([ + 'parent_id' => config('user.default_parent_id'), + 'bloodline' => config('user.default_parent_id').',', + 'layer' => 1, + ]); + } + }); + } + + /** + * Notes: 这个方法,是为了给用户创建事件监听模型使用的 + * 目的是去除attribute里面的parent_id参数,防止数据库写入错误 + * + * @Author: + * @Date : 2020/1/13 5:58 下午 + * @param int $parentID + */ + protected function setParentIdAttribute(int $parentID) + { + $this->parent_id = $parentID; + } + + /** + * Notes: 用户关联关系 + * + * @Author: + * @Date : 2020/1/13 5:51 下午 + * @return HasOne + */ + public function relation(): HasOne + { + return $this->hasOne(Relation::class)->withDefault(); + } + + /** + * Notes: 上级用户 + * + * @Author: + * @Date : 2020/1/13 5:51 下午 + * @return HasOneThrough + */ + public function parent(): HasOneThrough + { + return $this->hasOneThrough( + User::class, + Relation::class, + 'user_id', + 'id', + 'id', + 'parent_id' + ); + } + + /** + * Notes: 所有下级用户 + * + * @Author: + * @Date : 2020/1/13 5:51 下午 + * @return mixed + */ + public function children(): HasManyThrough + { + return $this->hasManyThrough( + User::class, + Relation::class, + 'parent_id', + 'id', + 'id', + 'user_id' + ); + } + + /** + * 调整隶属 + * + * @param int $parent_id + * @return bool + * @throws Exception + */ + public function updateParent(int $parent_id = 0): bool + { + if ($parent_id == $this->id) { + throw new Exception('不能绑定自己'); + } + if (Relation::where('user_id', $parent_id) + ->where('bloodline', 'like', "%,".$this->id.",%") + ->exists()) { + throw new Exception('不能绑定自己的下级用户'); + } + + try { + $relation = $this->relation; + + $new_blood = '0,'; + $new_layer = 1; + $new_parent_id = 0; + + $blood = $relation->bloodline; + $layer = $relation->layer; + $parent = User::find($parent_id); + if ($parent) { + $new_parent_id = $parent->id; + $new_blood = $parent->relation->bloodline.$new_parent_id.','; + $new_layer = $parent->relation->layer + 1; + } + $relation->parent_id = $new_parent_id; + $relation->bloodline = $new_blood; + $relation->layer = $new_layer; + if ($relation->save()) { + $diffLayer = $layer - $new_layer; + DB::update("UPDATE `user_relations` SET `bloodline`=CONCAT(?,SUBSTRING(bloodline,LENGTH(?)+1)),`layer`=`layer`-? WHERE `bloodline` LIKE ?", + [$new_blood, $blood, $diffLayer, "%,".$this->id.",%"]); + } + + return true; + } catch (Exception $e) { + return false; + } + } + + /** + * Notes: 获取下级数量 + * + * @Author: 玄尘 + * @Date : 2021/9/24 11:42 + */ + public function getRelationCount(): array + { + return [ + 'all' => Relation::query() + ->whereIn('layer', [$this->relation->layer + 1, $this->relation->layer + 2]) + ->where('bloodline', 'like', "%,".$this->id.",%") + ->count(), + 'one' => Relation::query() + ->where('layer', $this->relation->layer + 1) + ->where('bloodline', 'like', "%,".$this->id.",%") + ->count(), + 'two' => Relation::query() + ->where('layer', $this->relation->layer + 2) + ->where('bloodline', 'like', "%,".$this->id.",%") + ->count(), + ]; + } + +} diff --git a/modules/User/Models/Traits/HasSign.php b/modules/User/Models/Traits/HasSign.php new file mode 100644 index 0000000..8890d1b --- /dev/null +++ b/modules/User/Models/Traits/HasSign.php @@ -0,0 +1,77 @@ +hasOne(Sign::class); + } + + /** + * Notes : 当前日期是否签到 + * + * @Date : 2022/1/6 13:25 + * @Author : Mr.wang + * @param string $date + * @return bool + */ + public function isSign($date = null): bool + { + $date = $date ?? Carbon::now()->format('Y-m-d'); + return $this->signLogs()->whereDate('date', $date)->exists(); + } + + /** + * Notes : 用户签到日志 + * + * @Date : 2022/1/6 13:23 + * @Author : Mr.wang + * @return HasMany + */ + public function signLogs(): HasMany + { + return $this->hasMany(SignLog::class); + } + + /** + * Notes: 获取签到数据 + * + * @Author: 玄尘 + * @Date: 2022/8/3 15:35 + */ + public function getSignData(): array + { + $all = Arr::get(SignConfig::getParams(), 'cycle_day', 0); + $data = [ + 'continue' => 0, + 'total' => 0, + 'all' => $all + ]; + if ($this->sign) { + $data = [ + 'continue' => $this->sign->continue_days, + 'total' => $this->sign->counts, + 'all' => $all, + ]; + } + $data = array_merge($data, [ + 'text' => '第'.$data['continue'].'/'.$data['all'].'天' + ]); + return $data; + } +} \ No newline at end of file diff --git a/modules/User/Models/Traits/HasStock.php b/modules/User/Models/Traits/HasStock.php new file mode 100644 index 0000000..5c2a8c9 --- /dev/null +++ b/modules/User/Models/Traits/HasStock.php @@ -0,0 +1,153 @@ +hasOne(UserStock::class)->withDefault([ + 'stock' => 0, + 'hold' => 0, + 'residue' => 0, + ]); + } + + /** + * Notes: 获取库存 + * + * @Author: 玄尘 + * @Date: 2022/7/29 13:58 + */ + public function getStockData(): array + { + $identity = $this->identityFirst(); + $min = 1; + if ($identity->job == Identity::JOB_TY) { + $min = $identity->stock; + } + + $deliver = OrderItem::query() + ->whereHas('order', function ($q) { + $q->TypeSample()->ByUser($this)->paid(); + })->sum('qty'); + + $deliverd = OrderItem::query() + ->whereHas('order', function ($q) { + $q->TypeSample()->ByUser($this)->whereIn('state', [ + Order::STATUS_SIGNED, + Order::STATUS_DELIVERED, + ]); + })->sum('qty'); + + return [ + 'case_id' => $this->case ? $this->case->id : 0, + 'stock' => $this->userStock->stock,//总库存 + 'hold' => $this->userStock->hold,//已发货 + 'residue' => (int) bcsub($this->userStock->residue, $deliver),//未提货 + 'deliver' => $deliver,//待发货 + 'deliverd' => $deliverd,//已发货 + 'min_pick' => $min,//最少提货数量 + ]; + } + + /** + * Notes: 增加库存 + * + * @Author: 玄尘 + * @Date: 2022/7/26 16:18 + */ + public function addStock($identity_id, $type = null, $addStock = 0) + { + try { + $identity = Identity::find($identity_id); + + $stock = $identity->stock; + if ($addStock) { + $stock = $addStock; + } + + $userStock = $this->userStock; + if (! $type) { + $type = UserStockLog::TYPE_IN; + } + + if (isset($userStock->id)) { + $userStock->increment('stock', $stock); + } else { + $userStock = UserStock::query()->updateOrCreate([ + 'user_id' => $this->id, + ], [ + 'stock' => $stock, + ]); + } + + $this->addStockLog($type, $stock, $identity_id); + + return true; + } catch (\Exception $exception) { + return new \Exception($exception->getMessage()); + } + } + + + /** + * Notes: 增加销量 + * + * @Author: 玄尘 + * @Date: 2022/7/29 14:44 + */ + public function addHold($number) + { + $this->userStock->increment('hold', $number); + + $this->addStockLog(UserStockLog::TYPE_OUT, -$number); + + } + + /** + * Notes: 增加日志 + * + * @Author: 玄尘 + * @Date: 2022/8/2 14:55 + * @param $type + * @param $variable + * @param int $identity_id + */ + public function addStockLog($type, $variable, int $identity_id = 0) + { + $userStock = UserStock::where('user_id', $this->id)->first(); + UserStockLog::query() + ->create([ + 'user_stock_id' => $userStock->id, + 'type' => $type, + 'variable' => $variable, + 'identity_id' => $identity_id, + ]); + } + + +} \ No newline at end of file diff --git a/modules/User/Models/Traits/HasVipOrders.php b/modules/User/Models/Traits/HasVipOrders.php new file mode 100644 index 0000000..f5eb36f --- /dev/null +++ b/modules/User/Models/Traits/HasVipOrders.php @@ -0,0 +1,65 @@ + + * @return HasMany + */ + public function vipOrders(): HasMany + { + return $this->hasMany(Order::class); + } + + /** + * Notes: 开通会员缴费 + * + * @Author: 玄尘 + * @Date: 2022/9/7 13:59 + */ + public function getOpenVipPrices($type = 'all') + { + return $this->vipOrders + ->where('state', Order::STATE_SUCCESS) + ->where('price', '>', 0) + ->pluck('price'); + } + + /** + * Notes: 创建订单 + * + * @Author: 玄尘 + * @Date: 2022/9/7 16:55 + */ + public function createOrder($identity_id, $year, $price, $stock, $source = null) + { + $data = [ + 'user_id' => $this->id, + 'identity_id' => $identity_id, + 'year' => $year, + 'type' => 1, + 'name' => '', + 'card_no' => '', + 'cover' => '', + 'stock' => $stock, + 'state' => Order::STATE_INIT, + 'price' => $price, + 'source' => $source, + ]; + + $order = Order::create($data); + $order->pay(); + return $order; + } + +} \ No newline at end of file diff --git a/modules/User/Models/Traits/HasWechat.php b/modules/User/Models/Traits/HasWechat.php new file mode 100644 index 0000000..39c975a --- /dev/null +++ b/modules/User/Models/Traits/HasWechat.php @@ -0,0 +1,56 @@ +hasOne(UserWechat::class); + } + + /** + * Notes: 是否关注公众号 + * + * @Author: 玄尘 + * @Date: 2022/8/3 16:23 + */ + public function isOfficialSubscribe(): bool + { + return $this->wechat ? $this->wechat->isOfficialSubscribe() : false; + } + + /** + * Notes: 获取openids + * + * @Author: 玄尘 + * @Date: 2022/9/27 14:51 + * @return string[] + */ + public function getOpenids() + { + if ($this->wechat) { + $data = $this->wechat->getOpenids(); + } else { + $data = [ + 'mini' => '', + 'official' => '', + ]; + } + + return $data; + } + +} \ No newline at end of file diff --git a/modules/User/Models/Traits/JoinIdentity.php b/modules/User/Models/Traits/JoinIdentity.php new file mode 100644 index 0000000..858d8fc --- /dev/null +++ b/modules/User/Models/Traits/JoinIdentity.php @@ -0,0 +1,247 @@ +identityMiddle->count() >= 1) { + //续费 + if ($this->hasIdentity($identity_id)) { + $this->renewIdentity($identity_id); + } else { + //升级 + self::updateIdentity($identity_id, $channel, $source); + } + } else { + $identity = Identity::find($identity_id); + if ($identity) { + //未开通此身份 + if ($this->hasIdentity($identity_id) == false) { + $remark = Arr::get($source, 'remark', '加入身份'); + $res = self::identityLog(0, $identity_id, $channel, $remark, $source); + if ($res) { + $serial = $source['serial'] ?? ''; + if ($identity->serial_open && empty($serial)) { + $serial = self::getNewSerial($identity->serial_places); + } + + $value = []; + if ($identity->years) { + $value = [ + 'started_at' => now(), + 'ended_at' => now()->addMonths($identity->years), + ]; + } + + $this->identityMiddle() + ->updateOrCreate([ + 'identity_id' => $identity_id, + 'serial' => $serial, + ], $value); + + event(new UserJoinIdentity($this, $identity)); + } else { + throw new Exception('添加日志失败'); + } + } else { + //已开通此身份 + $this->renewIdentity($identity_id); + } + } else { + throw new Exception('身份信息不存在'); + } + + } + } + + /** + * Notes: 续费 + * + * @Author: 玄尘 + * @Date : 2021/6/8 9:49 + * @param int $identity_id + */ + public function renewIdentity(int $identity_id) + { + $identity = Identity::find($identity_id); + $before = $this->identityMiddle()->where('identity_id', $identity_id)->first(); + + //vip + if ($identity->years) { + IdentityMiddle::where('user_id', $this->id) + ->where('identity_id', $identity_id) + ->update([ + 'ended_at' => Carbon::parse($before->ended_at)->addMonths($identity->years), + ]); + } else { + IdentityMiddle::where('user_id', $this->id) + ->where('identity_id', $identity_id) + ->update([ + 'started_at' => null, + 'ended_at' => null, + ]); + } + + event(new UserJoinIdentity($this, $before->identity)); + } + + /** + * 用户身份调整,当多身份关闭时由join触发 + * + * @param int $identity_id + * @param string $channel + * @param array $source + * @throws Exception + */ + public function updateIdentity( + int $identity_id = 0, + string $channel = IdentityLog::CHANNEL_AUTO, + array $source = [] + ) { + $before = $this->identityMiddle()->first(); + if ($this->identityMiddle()->count() > 1) { + $this->identityMiddle()->where('identity_id', '!=', $before->identity_id)->delete(); + } + $identity = Identity::find($identity_id); + if ($identity) { + $remark = Arr::get($source, 'remark', '身份变更'); + $res = self::identityLog($before->identity_id, $identity_id, $channel, $remark, $source); + if ($res) { + $serial = $source['serial'] ?? ''; + if ($identity->serial_open && empty($serial)) { + $serial = self::getNewSerial($identity->serial_places); + } + + $data = [ + 'identity_id' => $identity_id, + 'serial' => $serial, + 'started_at' => null, + 'ended_at' => null, + ]; + + if ($identity->years) { + $data['started_at'] = now(); + $data['ended_at'] = now()->addMonths($identity->years); + } + + IdentityMiddle::where('user_id', $this->id) + ->where('identity_id', $before->identity_id) + ->update($data); + + event(new UserUpdateIdentity($this, $before->identity, $identity)); + } else { + throw new Exception('调整身份'); + } + } else { + throw new Exception('身份信息不存在'); + } + } + + /** + * 判断用户是否参与身份 + * + * @param int $identity_id + * @return bool + */ + public function hasIdentity(int $identity_id = 0): bool + { + if ($identity_id) { + $res = $this->identityMiddle()->where('identity_id', $identity_id)->first(); + + return (bool) $res; + } else { + return true; + } + } + + /** + * 用户移除身份 + * + * @param int $identity_id 身份ID + * @param string $channel 移除渠道 + * @param array $source 其他溯源信息 + * @throws Exception + */ + public function removeIdentity( + int $identity_id = 0, + string $channel = IdentityLog::CHANNEL_AUTO, + array $source = [] + ) { + if ($this->identityMiddle()->where('identity_id', $identity_id)->first()) { + $remark = Arr::get($source, 'remark', '身份移除'); + $res = self::identityLog($identity_id, 0, $channel, $remark, $source); + if ($res) { + $this->identityMiddle()->where('identity_id', $identity_id)->delete(); + event(new UserRemoveIdentity($this, Identity::find($identity_id))); + } else { + throw new Exception('移除记录失败'); + } + } else { + throw new Exception('用户不在身份组中'); + } + } + + /** + * 生成数据库中部存在的号码 + * + * @return int + */ + public function getNewSerial($places = 8) + { + try { + $min = pow(10, $places - 1); + $max = 9 * $min; + $query = 'SELECT code FROM (SELECT CEILING(RAND()*'.$max.'+'.$min.') AS code FROM user_identity UNION SELECT CEILING(RAND()*'.$max.'+'.$min.') AS code) AS ss WHERE "code" NOT IN (SELECT serial FROM user_identity where serial !=null) LIMIT 1'; + $res = DB::select($query); + + return (int) $res[0]->code; + } catch (Exception $e) { + return ''; + } + } + + public function identityLog( + $before = 0, + $after = 0, + $channel = '', + $remark = '', + $source = [] + ): Model { + return $this->identity_logs()->create([ + 'before' => $before, + 'after' => $after, + 'channel' => $channel, + 'remark' => $remark, + 'source' => $source, + ]); + } + +} diff --git a/modules/User/Models/Traits/OrderActions.php b/modules/User/Models/Traits/OrderActions.php new file mode 100644 index 0000000..f5627db --- /dev/null +++ b/modules/User/Models/Traits/OrderActions.php @@ -0,0 +1,107 @@ +state = self::STATE_SUCCESS; + $this->save(); + event(new UserOrderPaid($this)); + return true; + + } catch (\Exception $exception) { + return $exception->getMessage(); + } + + } + + /** + * Notes: 退款 + * + * @Author: 玄尘 + * @Date: 2022/8/22 13:09 + */ + public function refund(): bool + { + try { + $payment = $this->payment; + if (! $payment) { + throw new Exception("退款失败,未找到支付信息"); + } + + $refund = $payment->refunds()->create([ + 'total' => $this->price, + ]); + //微信支付 + if ($payment->driver == Payment::DRIVER_WECHAT) { + + $order = [ + 'out_trade_no' => $payment->trade_id, + 'out_refund_no' => $refund->refund_no, + 'amount' => [ + 'refund' => $payment->total * 100, + 'total' => $payment->total * 100, + 'currency' => 'CNY', + ], + ]; + + $result = app('pay.wechat')->refund($order); + + if (isset($result->code) && $result->code == 'PARAM_ERROR') { + throw new Exception("退款失败,".$result->message); + } + + $this->update([ + 'state' => Order::STATE_REFUND + ]); + + $refund->update([ + 'refund_at' => now() + ]); + + return true; + } elseif ($payment->driver == Payment::DRIVER_ALIPAY) {//支付宝支付 + + $order = [ + 'out_trade_no' => $this->order->order_no, + 'refund_amount' => $this->actual_total, + ]; + + $result = app('pay.alipay')->refund($order); + + if ($result->code != '10000') { + throw new Exception("退款失败,".$result->msg); + } + + $this->update([ + 'state' => Order::STATE_REFUND + ]); + + $refund->update([ + 'refund_at' => now() + ]); + + return true; + } else { + throw new Exception("退款失败,未找到支付路径"); + } + } catch (Exception $exception) { + throw new Exception($exception->getMessage()); + } + } +} diff --git a/modules/User/Models/Traits/WechatAttribute.php b/modules/User/Models/Traits/WechatAttribute.php new file mode 100644 index 0000000..f2966ab --- /dev/null +++ b/modules/User/Models/Traits/WechatAttribute.php @@ -0,0 +1,77 @@ +official->openid ?? ''; + } + + /** + * Notes: description + * + * @Author: 玄尘 + * @Date: 2022/8/3 16:22 + */ + public function isOfficialSubscribe(): bool + { + return $this->official()->where('subscribe', 1)->exists(); + } + + /** + * Notes : 获取微信小程序openid + * + * @Date : 2021/5/26 17:03 + * @Author : Mr.wang + * @return string + */ + public function getMiniOpenidAttribute(): string + { + return $this->mini->openid ?? ''; + } + + + /** + * Notes: 获取用户openid + * + * @Author: 玄尘 + * @Date: 2022/9/26 13:52 + * @param $channel + * @return mixed + */ + public function getUserOpenid($channel) + { + $channel = strtolower($channel); + if ($channel == 'mp') { + return Arr::get($this->getOpenids(), 'official'); + } else { + return Arr::get($this->getOpenids(), 'mini'); + } + } + + /** + * Notes: 获取openids + * + * @Author: 玄尘 + * @Date: 2022/9/26 15:06 + * @return array + */ + public function getOpenids(): array + { + return [ + 'mini' => $this->mini()->value('openid') ?? '', + 'official' => $this->official()->value('openid') ?? '', + ]; + } + +} diff --git a/modules/User/Models/User.php b/modules/User/Models/User.php new file mode 100644 index 0000000..72c41b4 --- /dev/null +++ b/modules/User/Models/User.php @@ -0,0 +1,306 @@ + '正常', + self::STATUS_REFUND => '退费', + ]; + + /** + * 禁止写入的字段 + * + * @var array + */ + protected $guarded = []; + + /** + * 模型隐藏字段 + * + * @var array + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; + + protected static function boot() + { + parent::boot(); + + self::created(function ($user) { + $user->info()->create([ + 'nickname' => '用户'.substr($user->username, -4), + ]); + $user->sign()->create([ + 'continue_days' => 0, + 'counts' => 0, + 'last_sign_at' => null, + ]); + $user->account()->create(); + + $defaultIdentity = Identity::where('default', 1)->first(); + if ($defaultIdentity) { + $user->joinIdentity($defaultIdentity->id, IdentityLog::CHANNEL_REG); + } + }); + } + + /** + * Notes : 用户资料 + * + * @Date : 2021/3/11 5:41 下午 + * @Author : < Jason.C > + * @return HasOne + */ + public function info(): HasOne + { + return $this->hasOne(UserInfo::class); + } + + + /** + * Notes : 用户账户 + * + * @Date : 2021/4/27 11:20 上午 + * @Author : < Jason.C > + * @return HasOne + */ + public function account(): HasOne + { + return $this->hasOne(Account::class); + } + + /** + * Notes : 当用户有密码的时候才加密 + * + * @Date : 2021/3/11 1:43 下午 + * @Author : < Jason.C > + * @param $password + */ + public function setPasswordAttribute($password) + { + if ($password) { + $this->attributes['password'] = bcrypt($password); + } + } + + /** + * 第一身份 + * + * @return mixed|null + */ + public function identityFirst() + { + return $this->identities()->first(); + } + + /** + * Notes : 用户身份 + * + * @Date : 2021/5/6 12:00 下午 + * @Author : < Jason.C > + */ + public function identities(): BelongsToMany + { + return $this->belongsToMany(Identity::class, 'user_identity') + ->withTimestamps() + ->withPivot(['started_at', 'ended_at', 'serial']); + } + + /** + * 用户中间表关联 + * + * @return HasMany + */ + public function identityMiddle(): HasMany + { + return $this->hasMany(IdentityMiddle::class); + } + + /** + * 身份变动表关联 + * + * @return HasMany + */ + public function identity_logs(): HasMany + { + return $this->hasMany(IdentityLog::class); + } + + /** + * Notes: 升级缴费订单 + * + * @Author: 玄尘 + * @Date: 2022/9/7 13:57 + */ + public function vipOrders(): HasMany + { + return $this->hasMany(UserOrder::class); + } + + /** + * 用户个人认证 + * + * @return HasOne + */ + public function certification(): HasOne + { + return $this->hasOne(UserCertification::class); + } + + /** + * Notes: 获取状态信息 + * + * @Author: 玄尘 + * @Date: 2022/8/3 16:30 + */ + public function getStateData(): array + { + $identity = $this->identities->first(); + + return [ + 'isSubscribe' => $this->isOfficialSubscribe(),//关注 + 'isVip' => $identity ? $identity->order : 0,//是否开会 + ]; + } + + /** + * Notes: 获取当前状态 + * + * @Author: 玄尘 + * @Date: 2022/8/3 16:38 + */ + public function getNowStatus(): array + { + $status = $this->getStateData(); + + if (! $status['isSubscribe']) { + return [ + 'value' => 'isSubscribe', + 'text' => '关注' + ]; + } + + if (! $status['isCase']) { + return [ + 'value' => 'isCase', + 'text' => '健康档案' + ]; + } + + if (! $status['isVip']) { + return [ + 'value' => 'isVip', + 'text' => '开通会员' + ]; + } + + return [ + 'value' => 'upCase', + 'text' => '上传档案' + ]; + + } + + + /** + * Notes: 开通季卡的次数 + * + * @Author: 玄尘 + * @Date: 2022/8/21 10:38 + */ + public function getOpenJkCount(): int + { + $jkIdentity = Identity::query()->Jk()->first(); + $num = 0; + if ($jkIdentity) { + $num = UserOrder::query() + ->byUser($this) + ->where('identity_id', $jkIdentity->id) + ->where('state', UserOrder::STATE_SUCCESS) + ->count(); + } + + return $num; + } + + /** + * Notes: description + * + * @Author: 玄尘 + * @Date: 2022/9/28 13:08 + */ + public function getCodeAttribute() + { + $userIdentity = $this->identityFirst(); + + if ($userIdentity && $userIdentity->id > 1) { + $invite = Hashids::connection('code')->encode($this->id); + } else { + $invite = ''; + } + + $url = Config::get('user.invite_code.url').'?invite='.$invite; + return 'data:image/png;base64,'.base64_encode(QrCode::format('png') + ->size(100) + ->margin(3) + ->generate($url)); + } + +} diff --git a/modules/User/Models/UserCertification.php b/modules/User/Models/UserCertification.php new file mode 100644 index 0000000..fc152f3 --- /dev/null +++ b/modules/User/Models/UserCertification.php @@ -0,0 +1,27 @@ + 'boolean', + ]; + + protected static function boot() + { + parent::boot(); + + self::created(function ($certification) { + event(new UserCertificationSuccess($certification)); + }); + } + +} \ No newline at end of file diff --git a/modules/User/Models/UserCertificationConfig.php b/modules/User/Models/UserCertificationConfig.php new file mode 100644 index 0000000..365a273 --- /dev/null +++ b/modules/User/Models/UserCertificationConfig.php @@ -0,0 +1,48 @@ + '姓名,身份证验证', + self::PHONECARD => '姓名,手机号,身份证号验证', + ]; + + public static function loading() + { + $config = Cache::rememberForever('userCertification', function () { + $conf = self::shown() + ->first(); + Artisan::call('queue:restart'); + + return $conf ? $conf->toArray() : ''; + }); + config(['userCertification' => $config]); + } + + protected static function boot() + { + parent::boot(); + + self::saved(function ($model) { + if ($model->status == 1) { + Cache::forget('userCertification'); + } + }); + } +} \ No newline at end of file diff --git a/modules/User/Models/UserInfo.php b/modules/User/Models/UserInfo.php new file mode 100644 index 0000000..63d402e --- /dev/null +++ b/modules/User/Models/UserInfo.php @@ -0,0 +1,48 @@ + + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + /** + * Notes : 获取头像的实际地址 + * + * @Date : 2021/3/12 10:53 上午 + * @Author : < Jason.C > + * @param $avatar + * @return string + */ + public function getAvatarAttribute($avatar): string + { + if (empty($avatar)) { + $avatar = config('user.avatar'); + } + + if (Str::startsWith($avatar, 'http')) { + return $avatar; + } + + return $avatar ? Storage::url($avatar) : ''; + } + +} diff --git a/modules/User/Models/UserLog.php b/modules/User/Models/UserLog.php new file mode 100644 index 0000000..e4c424f --- /dev/null +++ b/modules/User/Models/UserLog.php @@ -0,0 +1,18 @@ +belongsTo(Administrator::class); + } +} diff --git a/modules/User/Models/UserStock.php b/modules/User/Models/UserStock.php new file mode 100644 index 0000000..0d87dfe --- /dev/null +++ b/modules/User/Models/UserStock.php @@ -0,0 +1,93 @@ + '待提货', + self::STATUS_DELIVER => '待发货', + self::STATUS_DELIVERED => '待签收', + self::STATUS_SIGNED => '已签收', + ]; + + const STOCK_ORDER_STATUS_MAP = [ + '待提货' => 'primary', + '待发货' => 'success', + '待签收' => 'danger', + '已签收' => 'info', + ]; + + public $appends = ['residue']; + + /** + * Notes: 获取剩余 + * + * @Author: 玄尘 + * @Date: 2022/7/29 14:01 + * @return string + */ + public function getResidueAttribute() + { + return bcsub($this->stock, $this->hold); + } + + /** + * Notes: 日志 + * + * @Author: 玄尘 + * @Date: 2022/8/2 14:43 + * @return HasMany + */ + public function logs(): HasMany + { + return $this->hasMany(UserStockLog::class); + } + + /** + * Notes: 获取体验官提货订单状态 + * + * @Author: 玄尘 + * @Date: 2022/9/1 16:40 + */ + public function getStockOrderStatusAttribute() + { + $order = $this->user->orders()->first(); + if (empty($order)) { + return 1; + } elseif ($order->state == Order::STATUS_PAID) { + return 2; + } elseif ($order->state == Order::STATUS_DELIVERED) { + return 3; + } elseif ($order->state == Order::STATUS_SIGNED) { + return 4; + } + } + + + /** + * Notes: 获取体验官订单名称 + * + * @Author: 玄尘 + * @Date: 2022/9/1 16:45 + */ + public function getStockOrderStatusTextAttribute() + { + return Arr::get(self::STOCK_ORDER_STATUS, $this->stock_order_status, '未知'); + } + +} diff --git a/modules/User/Models/UserStockLog.php b/modules/User/Models/UserStockLog.php new file mode 100644 index 0000000..e58fc57 --- /dev/null +++ b/modules/User/Models/UserStockLog.php @@ -0,0 +1,53 @@ + '开通会员', + self::TYPE_IN => '续费', + self::TYPE_OUT => '提货', + ]; + + public function identity() + { + return $this->belongsTo(Identity::class); + } + + public function userStock(): BelongsTo + { + return $this->belongsTo(UserStock::class); + } + + public function getTypeTextAttribute(): string + { + return self::TYPES[$this->type]; + } + + /** + * Notes: description + * + * @Author: 玄尘 + * @Date: 2022/8/3 10:44 + * @param Builder $query + * @param User $user + * @return Builder + */ + public function scopeByUser(Builder $query, User $user): Builder + { + return $query->whereHas('userStock', function ($q) use ($user) { + return $q->ByUser($user); + }); + } + +} diff --git a/modules/User/Models/UserSubscribe.php b/modules/User/Models/UserSubscribe.php new file mode 100644 index 0000000..1e6c992 --- /dev/null +++ b/modules/User/Models/UserSubscribe.php @@ -0,0 +1,11 @@ + '未知', + '1' => '男', + '2' => '女', + ]; + + /** + * Notes : 小程序关联 + * + * @Date : 2021/6/10 3:40 下午 + * @Author : Mr.wang + * @return HasOne + */ + public function mini(): HasOne + { + return $this->hasOne(UserWechatMini::class); + } + + /** + * Notes : 公众号关联 + * + * @Date : 2021/6/10 3:40 下午 + * @Author : Mr.wang + * @return HasOne + */ + public function official(): HasOne + { + return $this->hasOne(UserWechatOfficial::class); + } + + /** + * Notes : app关联 + * + * @Date : 2021/6/10 3:40 下午 + * @Author : Mr.wang + * @return HasOne + */ + public function app(): HasOne + { + return $this->hasOne(UserWechatApp::class); + } + + /** + * Notes : 获取性别信息 + * + * @Date : 2021/12/1 14:35 + * @Author : Mr.wang + * @return string + */ + public function getSexAttribute(): string + { + return self::SEX[$this->sex] ?? '未知'; + } + +} diff --git a/modules/User/Models/UserWechatApp.php b/modules/User/Models/UserWechatApp.php new file mode 100644 index 0000000..abb82e8 --- /dev/null +++ b/modules/User/Models/UserWechatApp.php @@ -0,0 +1,16 @@ +belongsTo(UserWechat::class); + } + +} \ No newline at end of file diff --git a/modules/User/Models/UserWechatMini.php b/modules/User/Models/UserWechatMini.php new file mode 100644 index 0000000..62b1912 --- /dev/null +++ b/modules/User/Models/UserWechatMini.php @@ -0,0 +1,16 @@ +belongsTo(UserWechat::class); + } + +} \ No newline at end of file diff --git a/modules/User/Models/UserWechatOfficial.php b/modules/User/Models/UserWechatOfficial.php new file mode 100644 index 0000000..cda173a --- /dev/null +++ b/modules/User/Models/UserWechatOfficial.php @@ -0,0 +1,19 @@ +belongsTo(UserWechat::class); + } + +} \ No newline at end of file diff --git a/modules/User/Models/UserWechatSubscribe.php b/modules/User/Models/UserWechatSubscribe.php new file mode 100644 index 0000000..b15220e --- /dev/null +++ b/modules/User/Models/UserWechatSubscribe.php @@ -0,0 +1,10 @@ + [ + UserSignContinueDays::class, + ], + + //开通身份 + UserOrderPaid::class => [ + UserOrderPaidListeners::class, + ], + + UserSignReplenishSuccess::class => [ + ], + ]; + +} diff --git a/modules/User/Providers/RouteServiceProvider.php b/modules/User/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..63d66d0 --- /dev/null +++ b/modules/User/Providers/RouteServiceProvider.php @@ -0,0 +1,62 @@ +mapAdminRoutes(); + $this->mapApiRoutes(); + } + + protected function mapApiRoutes() + { + Route::as(config('api.route.as')) + ->domain(config('api.route.domain')) + ->middleware(config('api.route.middleware')) + ->namespace($this->moduleNamespace . '\\Api') + ->prefix(config('api.route.prefix') . '/user') + ->group(module_path($this->moduleName, 'Routes/api.php')); + } + + protected function mapAdminRoutes() + { + Route::as(config('admin.route.as')) + ->domain(config('admin.route.domain')) + ->middleware(config('admin.route.middleware')) + ->namespace($this->moduleNamespace . '\\Admin') + ->prefix(config('admin.route.prefix')) + ->group(module_path($this->moduleName, 'Routes/admin.php')); + } + +} diff --git a/modules/User/Providers/UserServiceProvider.php b/modules/User/Providers/UserServiceProvider.php new file mode 100644 index 0000000..276a756 --- /dev/null +++ b/modules/User/Providers/UserServiceProvider.php @@ -0,0 +1,120 @@ +loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations')); + + $this->registerValidatorExtend(); + + if ($this->app->runningInConsole()) { + $this->commands($this->runCommands); + } + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->registerConfig(); + + if (config('user.cache_user')) { + Config::set('auth.providers.users.driver', 'cache'); + Config::set('cache-user', Config::get('user.cache_config')); + } + + Config::set('auth.providers.users.model', User::class); + Config::set('hashids.connections.code', Config::get('user.invite_code')); + + // 注册短信到容器中 + $this->app->singleton('sms', function () { + return new EasySms(Sms::getConfig()); + }); + + $this->app->register(RouteServiceProvider::class); + } + + /** + * Register config. + * + * @return void + */ + protected function registerConfig() + { + $this->publishes([ + module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower.'.php'), + module_path($this->moduleName, 'Config/identity.php') => config_path('identity.php'), + ], 'config'); + + $this->mergeConfigFrom( + module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower + ); + $this->mergeConfigFrom( + module_path($this->moduleName, 'Config/identity.php'), 'identity' + ); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides(): array + { + return ['sms']; + } + + /** + * Notes : 注册短信验证码校验扩展 + * + * @Date : 2021/5/26 5:03 下午 + * @Author : + */ + public function registerValidatorExtend() + { + Validator::extend('sms_check', function ($attribute, $code, $parameters) { + if (empty($code)) { + return false; + } + $mobileFiled = $parameters[0] ?? 'mobile'; + $channel = $parameters[1] ?? 'DEFAULT'; + $mobile = request()->input($mobileFiled); + + return Sms::checkCode($mobile, $code, $channel); + }); + } + +} diff --git a/modules/User/README.md b/modules/User/README.md new file mode 100644 index 0000000..38cf711 --- /dev/null +++ b/modules/User/README.md @@ -0,0 +1,12 @@ +# 用户模块 + +## 1. 安装 + +```shell +composer require overtrue/wechat +composer require overtrue/easy-sms +composer require overtrue/socialite +composer require propaganistas/laravel-phone +composer require vinkla/hashids +composer require yangjisen/laravel-cache-provider +``` diff --git a/modules/User/Renderable/UserLog.php b/modules/User/Renderable/UserLog.php new file mode 100644 index 0000000..1083dc3 --- /dev/null +++ b/modules/User/Renderable/UserLog.php @@ -0,0 +1,31 @@ +logs as $log) { + $data[] = [ + 'admin' => $log->admin->name, + 'remark' => $log->remark, + 'created_at' => $log->created_at->toDateTimeString(), + ]; + } + + + $table = new Table(['操作人', '备注', '添加时间'], $data); + + return $table->render(); + + } + +} \ No newline at end of file diff --git a/modules/User/Routes/admin.php b/modules/User/Routes/admin.php new file mode 100644 index 0000000..9ce390d --- /dev/null +++ b/modules/User/Routes/admin.php @@ -0,0 +1,33 @@ + 'users', + 'as' => 'user.', +], function (Router $router) { + $router->get('ajax', 'IndexController@ajax')->name('users.ajax'); + + $router->get('accounts', 'AccountController@index'); + $router->get('accounts/{account}', 'AccountController@show')->name('account.logs'); + $router->resource('rules', 'RuleController'); + $router->resource('identities', 'IdentitiesController'); + $router->resource('identity_logs', 'IdentityLogController'); + $router->resource('services', 'ServiceController'); + $router->get('sms', 'SmsController@index'); + $router->resource('sms/configs', 'SmsConfigController'); + $router->resource('sms/gateways', 'GatewayController'); + $router->resource('orders', 'OrderController'); + $router->resource('certifications/configs', 'CertificationConfigController'); + $router->resource('certifications', 'CertificationController'); + + $router->resource('signs', 'SignConfigController'); + $router->resource('sign_texts', 'SignTextController'); + $router->resource('sign_banners', 'SignBannerController'); + $router->get('stocks', 'StockController@index'); + $router->get('stocks/{stock}/logs', 'StockLogController@index'); + +}); + +Route::resource('users', 'IndexController'); diff --git a/modules/User/Routes/api.php b/modules/User/Routes/api.php new file mode 100644 index 0000000..5de5c41 --- /dev/null +++ b/modules/User/Routes/api.php @@ -0,0 +1,188 @@ + 'Auth', + 'prefix' => 'auth', +], function (Router $router) { + $router->post('login', 'LoginController@index'); + $router->post('register', 'RegisterController@index'); + // 获取登录短信验证码 + $router->post('verify', 'SmsController@send'); + // 验证码登录 + $router->post('sms', 'SmsController@login'); + // 退出登录 + $router->post('logout', 'LoginController@logout')->middleware('token.guess'); + + //获取跳转地址 + $router->get('get_auth_url', 'WechatController@getAuthUrl'); + $router->get('get_jssdk', 'WechatController@getJsSdk'); + $router->get('official_share', 'WechatController@officialShare'); + $router->get('mini_openid', 'WechatController@getMiniOpenid'); + $router->get('official_openid', 'WechatController@getOfficialOpenid'); +}); + +// 社会化登录 +Route::group([ + 'namespace' => 'Socialite', + 'prefix' => 'socialite', + 'middleware' => config('api.route.middleware_guess'), +], function (Router $router) { + //微信APP登录 + $router->post('login/wechat/app', 'WeChatController@app'); + //微信小程序登录 + $router->post('login/wechat/mini', 'WeChatController@mini'); + //微信公众号登陆 + $router->post('login/wechat/official', 'WeChatController@official'); + //微信union_id 快速登录 + $router->post('login/wechat/query', 'WeChatController@query'); + //UniCloud本机一键登录(后端获取手机号) + $router->post('login/unicloud/app', 'UniCloudController@app'); + //UniCloud本机一键登录(前端获取手机号) + $router->post('login/unicloud/query', 'UniCloudController@query'); + //入库微信用户信息 + $router->post('login/wechat/add', 'WeChatController@addWechatUser'); + $router->post('login/wechat/mini/add', 'WeChatController@miniAddWechatUser'); + +}); + +Route::group([ + 'namespace' => 'Certification', + 'prefix' => 'certification', +], function (Router $router) { + // 个人认证 + $router->get('', 'IndexController@index'); + $router->post('', 'IndexController@store'); +}); + +Route::group([ + 'middleware' => config('api.route.middleware_guess'), +], function (Router $router) { + // 用户是否有身份认证 + $router->get('certified', 'Certification\IndexController@certified'); + // 获取身份对应的折扣 + $router->get('identity/rule', 'Identity\IndexController@rule'); +}); + +Route::group([ + 'namespace' => 'Identity', + 'prefix' => 'identities', + 'middleware' => config('api.route.middleware_auth'), +], function (Router $router) { + $router->get('', 'IndexController@index'); +// $router->get('{identity}', 'IndexController@show'); + $router->get('create/{identity}', 'IndexController@create'); + $router->post('create/{identity}', 'IndexController@store'); + //获取支付数据 + $router->get('pay/{order}/wechat', 'IndexController@wechat'); + $router->get('pay/{order}/alipay', 'IndexController@alipay'); +Route::group([ + 'namespace' => 'Identity', + 'prefix' => 'identities', + 'middleware' => config('api.route.middleware_guess'), +], function (Router $router) { + $router->get('{identity}', 'IndexController@show'); +}); + +Route::group([ + 'namespace' => 'Sign', + 'prefix' => 'sign', + 'middleware' => config('api.route.middleware_auth'), +], function (Router $router) { + $router->get('', 'IndexController@index'); + $router->post('', 'IndexController@sign'); + // 预留的补签接口 + $router->post('replenish', 'IndexController@replenish'); + // 测试日历接口 + $router->get('date', 'IndexController@date'); + $router->get('backgrounds', 'IndexController@backgrounds'); + $router->get('ranks', 'IndexController@rank'); + +}); + +// 账户 +Route::group([ + 'namespace' => 'Account', + 'prefix' => 'account', + 'middleware' => config('api.route.middleware_auth'), +], function (Router $router) { + $router->get('score', 'LogController@score'); + $router->get('balance', 'LogController@balance'); +}); + +// 用户中心 +Route::group([ + 'middleware' => config('api.route.middleware_auth'), +], function (Router $router) { + $router->get('', 'IndexController@index'); + // 资料和修改资料 + $router->get('info', 'IndexController@info'); + $router->put('{key}', 'IndexController@update'); + // 我的邀请码 + $router->get('invite', 'IndexController@invite'); + // 我的小程序码 + $router->get('mini_share', 'IndexController@getMiniCode'); + + // 绑定邀请码 + $router->post('bind', 'IndexController@bind'); + // 我的专属客服 + $router->get('services', 'Service\IndexController@index'); +}); + +//设置 +Route::group([ + 'namespace' => 'Setting', + 'prefix' => 'setting', + 'middleware' => config('api.route.middleware_auth'), +], function (Router $router) { + $router->get('', 'IndexController@index'); + $router->put('{key}', 'IndexController@update'); +}); + +Route::group([ + 'namespace' => 'Stock', + 'prefix' => 'stock', + 'middleware' => config('api.route.middleware_auth'), +], function (Router $router) { + // 个人认证 + $router->get('', 'IndexController@index'); + $router->get('pick', 'IndexController@create'); + $router->post('pick', 'IndexController@Store'); + $router->get('logs', 'IndexController@logs'); +}); + +//我的伙伴 +Route::group([ + 'namespace' => 'Relation', + 'prefix' => 'relations', + 'middleware' => config('api.route.middleware_auth'), +], function (Router $router) { + $router->get('', 'IndexController@index'); +}); + +//排行 +Route::group([ + 'namespace' => 'Rank', + 'prefix' => 'rank', + 'middleware' => config('api.route.middleware_auth'), +], function (Router $router) { + $router->get('total', 'IndexController@total'); + $router->get('totaluser', 'IndexController@totalUser'); + + $router->get('week', 'IndexController@week'); +}); + + + +//我的收藏 +Route::group([ + 'namespace' => 'Favorite', + 'middleware' => config('api.route.middleware_auth'), +], function (Router $router) { + $router->get('favorites', 'IndexController@index'); +}); + + diff --git a/modules/User/Rules/IdCardRule.php b/modules/User/Rules/IdCardRule.php new file mode 100644 index 0000000..bafcec3 --- /dev/null +++ b/modules/User/Rules/IdCardRule.php @@ -0,0 +1,68 @@ + + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function passes($attribute, $value): bool + { + if (strlen($value) !== 18) { + return false; + } + + return $this->isIdCard($value); + } + + private function isIdCard($id): bool + { + $id = strtoupper($id); + $regx = "/(^\d{17}([0-9]|X)$)/"; + $arr_split = []; + if (! preg_match($regx, $id)) { + return false; + } + + $regx = "/^(\d{6})+(\d{4})+(\d{2})+(\d{2})+(\d{3})([0-9]|X)$/"; + @preg_match($regx, $id, $arr_split); + $dtm_birth = $arr_split[2].'/'.$arr_split[3].'/'.$arr_split[4]; + if (! strtotime($dtm_birth)) { + return false; + } + + $arr_int = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]; + $arr_ch = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']; + $sign = 0; + for ($i = 0; $i < 17; $i++) { + $b = (int) $id[$i]; + $w = $arr_int[$i]; + $sign += $b * $w; + } + $n = $sign % 11; + $val_num = $arr_ch[$n]; + + return ! ($val_num !== substr($id, 17, 1)); + } + + /** + * 获取校验错误信息 + * + * @return string + */ + public function message(): string + { + return '身份证号码校验错误'; + } + +} diff --git a/modules/User/Services/VerificationCode.php b/modules/User/Services/VerificationCode.php new file mode 100644 index 0000000..99be783 --- /dev/null +++ b/modules/User/Services/VerificationCode.php @@ -0,0 +1,91 @@ +config = $config; + $this->channel = $channel; + + if ($config['debug']) { + $this->code = $config['debug_code']; + } else { + $this->code = sprintf("%0".$config['length']."d", mt_rand(1, pow(10, $config['length']) - 1)); + } + + parent::__construct(); + } + + /** + * Notes : 定义直接使用内容发送平台的内容 + * + * @Date : 2021/5/27 9:09 上午 + * @Author : + * @param GatewayInterface|null $gateway + * @return string + */ + public function getContent(GatewayInterface $gateway = null): string + { + return sprintf('验证码%s,您正在登录,若非本人操作,请勿泄露。', $this->code); + } + + /** + * Notes : 定义使用模板发送方式平台所需要的模板 ID + * + * @Date : 2021/5/27 9:10 上午 + * @Author : + * @param GatewayInterface|null $gateway + * @return string + * @throws Exception + */ + public function getTemplate(GatewayInterface $gateway = null): string + { + $templateId = $this->config['template'][$this->channel] ?? ''; + + if (! $templateId) { + throw new Exception('不合法的验证通道:'.$this->channel); + } + + return $templateId; + } + + /** + * Notes : 模板参数 + * + * @Date : 2021/5/27 9:10 上午 + * @Author : + * @param GatewayInterface|null $gateway + * @return string[] + */ + public function getData(GatewayInterface $gateway = null): array + { + return [ + 'code' => $this->code, + ]; + } + +} diff --git a/modules/User/Traits/BelongsToUser.php b/modules/User/Traits/BelongsToUser.php new file mode 100644 index 0000000..ffebd48 --- /dev/null +++ b/modules/User/Traits/BelongsToUser.php @@ -0,0 +1,52 @@ + + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + /** + * Notes : 用户 作用域查询 + * + * @Date : 2021/6/21 8:57 上午 + * @Author : + * @param Builder $query + * @param User $user + * @return Builder + */ + public function scopeByUser(Builder $query, User $user): Builder + { + return $query->where('user_id', $user->getKey()); + } + + /** + * Notes : 用户ID 作用域查询 + * + * @Date : 2021/6/21 9:04 上午 + * @Author : + * @param Builder $query + * @param int $userId + * @return Builder + */ + public function scopeByUserId(Builder $query, int $userId): Builder + { + return $query->where('user_id', $userId); + } + +} diff --git a/modules/User/Traits/RankDataTrait.php b/modules/User/Traits/RankDataTrait.php new file mode 100644 index 0000000..5095de0 --- /dev/null +++ b/modules/User/Traits/RankDataTrait.php @@ -0,0 +1,67 @@ +whereBetween('started_at', $timeBetween) + ->where('ended_at', '>=', now()) + ->select('user_id as middle_user_Id', + DB::raw('any_value(started_at) as started_at'), + DB::raw('any_value(identity_id) as identity_id')) + ->groupBy('middle_user_Id'); + + $vipRelationJion = DB::table('user_relations') + ->leftJoinSub($vipJion, 'vipJion', function ($join) { + $join->on('user_relations.user_id', '=', 'vipJion.middle_user_Id'); + }) + ->where('identity_id', '>', 1) + ->select('parent_id', + DB::raw('ifnull(count(user_id),0) as countChild'), + DB::raw('max(started_at) as lastOpenTime')) + ->groupBy('parent_id'); + + return User::query() + ->leftJoinSub($vipRelationJion, 'vipRelationJion', function ($join) { + $join->on('users.id', '=', 'vipRelationJion.parent_id'); + }) + ->where('countChild', '>', 0) + ->orderBy('countChild', 'desc') + ->orderBy('lastOpenTime', 'asc') + ->limit(10) + ->get(); + } + + public function getTotalUser(array $timeBetween) + { + $relationJion = DB::table('user_relations') + ->whereBetween('created_at', $timeBetween) + ->select('parent_id', + DB::raw('ifnull(count(user_id),0) as countChild'), + DB::raw('max(created_at) as lastRegTime') + ) + ->groupBy('parent_id'); + + return User::query() + ->leftJoinSub($relationJion, 'relationJion', function ($join) { + $join->on('users.id', '=', 'relationJion.parent_id'); + }) + ->where('countChild', '>', 0) + ->orderBy('countChild', 'desc') + ->orderBy('lastRegTime', 'asc') + ->limit(10) + ->get(); + } +} diff --git a/modules/User/Traits/WechatTrait.php b/modules/User/Traits/WechatTrait.php new file mode 100644 index 0000000..4cbd822 --- /dev/null +++ b/modules/User/Traits/WechatTrait.php @@ -0,0 +1,180 @@ +code; + if (! $code) { + throw new \Exception('缺少code'); + } + $weChat = app('wechat.official_account'); + + $weUser = $weChat->oauth->userFromCode(request()->code); + + return $weUser; + } catch (\Exception $exception) { + throw new \Exception($exception->getMessage()); + } + + } + + /** + * Notes: 获取用户信息 + * + * @Author: 玄尘 + * @Date: 2022/8/11 11:56 + * @param $openid + * @return mixed + */ + public function getWechatUser($openid) + { + $weChat = app('wechat.official_account'); + + return $weChat->user->get($openid); + } + + /** + * Notes: 设置已关注列表 + * + * @Author: 玄尘 + * @Date: 2022/9/28 14:18 + * @param $unionid + * @param $openid + * @param int $subscribe + */ + public function setUserSubscribe($unionid, $openid, int $subscribe = 1) + { + UserWechatSubscribe::query()->updateOrCreate([ + 'unionid' => $unionid, + 'openid' => $openid, + ], [ + 'subscribe' => $subscribe, + ]); + } + + /** + * Notes: 入库所有关注列表 + * + * @Author: 玄尘 + * @Date: 2022/9/28 14:41 + */ + public function getAllSubscribeUsers() + { + $app = app('wechat.official_account'); + $users = $app->user->list(); + collect($users->data['openid']) + ->split(3)->map(function ($datas) use ($app) { + $infos = $app->user->select($datas->toArray()); + foreach ($infos['user_info_list'] as $info) { + $this->setUserSubscribe($info['unionid'], $info['openid'], 1); + } + }); + } + + /** + * Notes: 用户关注公众号 + * + * @Author: 玄尘 + * @Date: 2022/11/1 14:19 + */ + public function userSubOfficial() + { + $app = app('wechat.official_account'); + $user = $app->user->get($this->payload->FromUserName); + + if ($user->unionid) { + $wechatUsers = UserWechat::query() + ->where('unionid', $user->unionid) + ->get(); + if ($wechatUsers->isNotEmpty()) { + foreach ($wechatUsers as $wechatUser) { + if ($wechatUser->official) { + if ($wechatUser->official->subscribe != 1) { + $wechatUser->official->update([ + 'subscribe' => 1, + 'subscribed_at' => Carbon::parse($user->subscribe_time)->toDateTimeString(), + ]); + } + } else { + $wechatUser->official()->create([ + 'openid' => $this->payload->FromUserName, + 'subscribe' => 1, + 'subscribed_at' => Carbon::parse($user->subscribe_time)->toDateTimeString(), + ]); + } + + } + } + + //插入关注数据 + $this->setUserSubscribe($user->unionid, $this->payload->FromUserName, 1); + + } else { + // 先查找用户是否存在,不存在再注册 + $officialUsers = UserWechatOfficial::query() + ->where('openid', $this->payload->FromUserName) + ->get(); + + //设置总表uniond + if ($officialUsers->isNotEmpty()) { + foreach ($officialUsers as $officialUser) { + if (! $officialUser->userWechat->unionid && $user->unionid) { + $officialUser->userWechat->update([ + 'unionid' => $user->unionid + ]); + } + + if ($officialUser->subscribe != 1) { + $officialUser->update([ + 'subscribe' => 1, + 'subscribed_at' => Carbon::parse($user->subscribe_time)->toDateTimeString(), + ]); + } + } + } + } + } + + /** + * Notes: 用户取消关注公众号 + * + * @Author: 玄尘 + * @Date: 2022/11/1 14:25 + */ + public function userUnSubOfficial() + { + $officialUsers = UserWechatOfficial::where('openid', $this->payload->FromUserName)->get(); + if ($officialUsers->isNotEmpty()) { + foreach ($officialUsers as $officialUser) { + $officialUser->update([ + 'subscribe' => 0, + 'subscribed_at' => null, + ]); + + //设置取消关注 + if ($officialUser->userWechat && $officialUser->userWechat->unionid) { + $this->setUserSubscribe($officialUser->userWechat->unionid, $officialUser->openid, 0); + } + } + } + } + +} \ No newline at end of file diff --git a/modules/User/User.php b/modules/User/User.php new file mode 100644 index 0000000..3978a1c --- /dev/null +++ b/modules/User/User.php @@ -0,0 +1,192 @@ + + */ + public static function install(): void + { + Artisan::call('migrate', [ + '--path' => 'modules/User/Database/Migrations', + ]); + + self::createAdminMenu(); + } + + /** + * Notes : 创建后台菜单 + * + * @Date : 2021/3/17 9:48 上午 + * @Author : + */ + protected static function createAdminMenu(): void + { + SignConfig::updateOrCreate([ + 'id' => 1, + ], [ + 'params' => [], + ]); + $menu = config('admin.database.menu_model'); + + $main = $menu::create([ + 'parent_id' => 0, + 'order' => 10, + 'title' => self::$mainTitle, + 'icon' => 'fa-group', + ]); + + $main->children()->createMany([ + [ + 'order' => 0, + 'title' => '用户列表', + 'icon' => 'fa-user', + 'uri' => 'users', + ], + [ + 'order' => 1, + 'title' => '用户账户', + 'icon' => 'fa-bars', + 'uri' => 'users/accounts', + ], + [ + 'order' => 2, + 'title' => '用户库存', + 'icon' => 'fa-bars', + 'uri' => 'users/stocks', + ], + // [ + // 'order' => 2, + // 'title' => '账变规则', + // 'icon' => 'fa-bars', + // 'uri' => 'users/rules', + // ], + [ + 'order' => 3, + 'title' => '签到规则管理', + 'icon' => 'fa-bars', + 'uri' => 'users/signs/1/edit', + ], + [ + 'order' => 4, + 'title' => '签到展示文字', + 'icon' => 'fa-bars', + 'uri' => 'users/sign_texts', + ], + [ + 'order' => 5, + 'title' => '签到背景图片', + 'icon' => 'fa-bars', + 'uri' => 'users/sign_banners', + ], + [ + 'order' => 6, + 'title' => '用户身份', + 'icon' => 'fa-graduation-cap', + 'uri' => 'users/identities', + ], + [ + 'order' => 7, + 'title' => '身份变动记录', + 'icon' => 'fa-graduation-cap', + 'uri' => 'users/identity_logs', + ], + // [ + // 'order' => 6, + // 'title' => '客服中心', + // 'icon' => 'fa-group', + // 'uri' => 'users/services', + // ], + [ + 'order' => 8, + 'title' => '升级订单', + 'icon' => 'fa-group', + 'uri' => 'users/orders', + ], + ]); + + $subMenu = $main->children()->create([ + 'order' => 10, + 'title' => '短信管理', + 'icon' => 'fa-commenting', + 'uri' => '', + ]); + +// $certify = $main->children()->create([ +// 'order' => 8, +// 'title' => '认证管理', +// 'icon' => 'fa-certificate', +// 'uri' => '', +// ]); + + + $subMenu->children()->createMany([ + [ + 'order' => 1, + 'title' => '短信记录', + 'icon' => 'fa-mobile', + 'uri' => 'users/sms', + ], + [ + 'order' => 2, + 'title' => '短信配置', + 'icon' => 'fa-cogs', + 'uri' => 'users/sms/configs', + ], + [ + 'order' => 3, + 'title' => '短信网关', + 'icon' => 'fa-map-signs', + 'uri' => 'users/sms/gateways', + ], + ]); + +// $certify->children()->createMany([ +// [ +// 'order' => 1, +// 'title' => '认证记录', +// 'icon' => 'fa-align-justify', +// 'uri' => 'users/certifications', +// ], +// [ +// 'order' => 2, +// 'title' => '认证配置', +// 'icon' => 'fa-cogs', +// 'uri' => 'users/certifications/configs', +// ], +// ]); + } + + /** + * Notes : 卸载模块的一些操作 + * + * @Date : 2021/3/12 11:35 上午 + * @Author : + */ + public static function uninstall(): void + { + $menu = config('admin.database.menu_model'); + + $msgMenu = $menu::where('title', '短信管理')->get(); + foreach ($msgMenu as $menu) { + $menu->delete(); + } + + $mains = $menu::where('title', self::$mainTitle)->get(); + foreach ($mains as $menu) { + $menu->delete(); + } + } + +} diff --git a/modules/User/composer.json b/modules/User/composer.json new file mode 100644 index 0000000..bdccd7f --- /dev/null +++ b/modules/User/composer.json @@ -0,0 +1,29 @@ +{ + "name": "uztech/user-module", + "description": "", + "type": "laravel-module", + "authors": [ + { + "name": "Jason.Chen", + "email": "chenjxlg@163.com" + } + ], + "require": { + "genealabs/laravel-model-caching": "^0.11.3", + "overtrue/wechat": "^5.0", + "overtrue/easy-sms": "^1.3", + "overtrue/socialite": "^3.2", + "overtrue/chinese-calendar": "^1.0", + "propaganistas/laravel-phone": "^4.3", + "vinkla/hashids": "^9.1", + "yangjisen/laravel-cache-provider": "^3.0" + }, + "extra": { + "module-dir": "modules" + }, + "autoload": { + "psr-4": { + "Modules\\User\\": "" + } + } +} diff --git a/modules/User/docs/READMD.md b/modules/User/docs/READMD.md new file mode 100644 index 0000000..5509ec7 --- /dev/null +++ b/modules/User/docs/READMD.md @@ -0,0 +1,13 @@ +# 文档 + +## 1. 后台 + +后台AJAX搜索,通过 用户名或昵称获取用户列表的内部接口 + +```php +route('admin.user.users.ajax'); +``` + +## 2. 内部接口 + +## 3. 接口文档,详见各文档 \ No newline at end of file diff --git a/modules/User/docs/用户鉴权.md b/modules/User/docs/用户鉴权.md new file mode 100644 index 0000000..846b5b3 --- /dev/null +++ b/modules/User/docs/用户鉴权.md @@ -0,0 +1,2 @@ +# 用户鉴权 + diff --git a/modules/User/module.json b/modules/User/module.json new file mode 100644 index 0000000..3aa7736 --- /dev/null +++ b/modules/User/module.json @@ -0,0 +1,18 @@ +{ + "name": "User", + "alias": "user", + "description": "用户基础模块", + "keywords": [], + "priority": 0, + "providers": [ + "Modules\\User\\Providers\\UserServiceProvider", + "Modules\\User\\Providers\\EventServiceProvider" + ], + "aliases": {}, + "files": [], + "requires": [], + "config": true, + "configName": "用户模块", + "version": "1.0.0", + "author": "Jason.Chen" +} diff --git a/modules/User/用户模块.apifox.json b/modules/User/用户模块.apifox.json new file mode 100644 index 0000000..2d2f9e3 --- /dev/null +++ b/modules/User/用户模块.apifox.json @@ -0,0 +1 @@ +{"apifoxProject":"1.0.0","info":{"name":"抖火","description":"","mockRule":{"rules":[],"enableSystemRule":true}},"responseCollection":[],"apiCollection":[{"name":"根目录","parentId":0,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"auth":{},"items":[{"name":"用户接口","parentId":0,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"auth":{},"items":[{"name":"消息中心","parentId":9409854,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"auth":{},"items":[{"name":"消息主页","api":{"id":"46914075","method":"get","path":"/api/notifications","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117540863","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":["用户接口/消息中心"],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":0,"cases":[{"id":53036155,"type":"http","path":null,"name":"成功","responseId":117540863,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"全部标记已读","api":{"id":"46914072","method":"put","path":"/api/notifications","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117540861","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":["用户接口/消息中心"],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":6,"cases":[{"id":53036153,"type":"http","path":null,"name":"成功","responseId":117540861,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"清空消息","api":{"id":"46914073","method":"delete","path":"/api/notifications","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117540862","name":"Successful Operation","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":["用户接口/消息中心"],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":12,"cases":[{"id":53036154,"type":"http","path":null,"name":"成功","responseId":117540862,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"分类列表","api":{"id":"46914076","method":"get","path":"/api/notifications/{type}/list","parameters":{"path":[{"name":"type","required":true,"description":"","type":"string","id":"type#0"}],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117540864","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":["用户接口/消息中心"],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":18,"cases":[{"id":53036156,"type":"http","path":null,"name":"成功","responseId":117540864,"parameters":{"path":[{"id":"type#0","relatedId":"type#0","relatedName":"type","name":"type","value":"","enable":true}],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"消息详情","api":{"id":"46914074","method":"get","path":"/api/notifications/{notification_id}","parameters":{"path":[{"name":"notification_id","required":true,"description":"","type":"string","id":"notification_id#0"}],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117540865","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":["用户接口/消息中心"],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":24,"cases":[{"id":53036157,"type":"http","path":null,"name":"成功","responseId":117540865,"parameters":{"path":[{"id":"notification_id#0","relatedId":"notification_id#0","relatedName":"notification_id","name":"notification_id","value":"","enable":true}],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"全部标记已读 (分类)","api":{"id":"46914077","method":"put","path":"/api/notifications/{type}","parameters":{"path":[{"name":"type","required":true,"description":"","type":"string","id":"type#0","example":"SystemNotification"}],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117540866","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":["用户接口/消息中心"],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":30,"cases":[{"id":53036158,"type":"http","path":null,"name":"成功","responseId":117540866,"parameters":{"path":[{"id":"type#0","relatedId":"type#0","relatedName":"type","name":"type","value":"SystemNotification","enable":true}],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"清空消息 (分类)","api":{"id":"46914078","method":"delete","path":"/api/notifications/{type}","parameters":{"path":[{"name":"type","required":true,"description":"","type":"string","id":"type#0"}],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117540867","name":"Successful Operation","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":["用户接口/消息中心"],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":36,"cases":[{"id":53036159,"type":"http","path":null,"name":"成功","responseId":117540867,"parameters":{"path":[{"id":"type#0","relatedId":"type#0","relatedName":"type","name":"type","value":"","enable":true}],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"未读消息数量","api":{"id":"46914079","method":"get","path":"/api/notifications/counts","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117540868","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":["用户接口/消息中心"],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":42,"cases":[{"id":53036160,"type":"http","path":null,"name":"成功","responseId":117540868,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}}]},{"name":"登录与注册","parentId":9409854,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"auth":{},"items":[{"name":"用户名密码登录","api":{"id":"46912419","method":"post","path":"/api/user/auth/login","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534018","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"application/x-www-form-urlencoded","parameters":[{"name":"username","required":true,"description":"","type":"text","id":"T2FZ7F6i5f","example":""},{"name":"password","required":true,"description":"","type":"text","id":"k8oOvEn87b"}]},"description":"","tags":[],"status":"released","serverId":"","operationId":"","sourceUrl":"","ordering":0,"cases":[{"id":53034417,"type":"http","path":null,"name":"成功","responseId":117534018,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[{"name":"username","value":"15663876870","enable":true,"id":"SBuaJ3V5MU","relatedName":"username"},{"name":"password","value":"111111","enable":true,"id":"W6XcQph1tO","relatedName":"password"}]},"preProcessors":[],"postProcessors":[{"type":"commonScript","data":[387699]}],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"uni本机一键登录(服务器获取手机号)","api":{"id":"46912420","method":"post","path":"/api/user/socialite/login/unicloud/app","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534019","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"access_token","required":true,"description":"uni前端获取内容","type":"text","id":"XYGfkloWzH","example":"321"},{"name":"openid","required":true,"description":"uni前端获取内容","type":"text","id":"M1B11ZW2yE","example":"321"}]},"description":"","tags":[],"status":"released","serverId":"","operationId":"","sourceUrl":"","ordering":6,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"uni本机一键登录(前端获取手机号)","api":{"id":"46912418","method":"post","path":"/api/user/socialite/login/unicloud/query","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534016","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"mobile","required":true,"description":"前端获取的手机号","type":"text","id":"awtVowiXai","example":"17645779673"}]},"description":"","tags":[],"status":"released","serverId":"","operationId":"","sourceUrl":"","ordering":12,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"获取登录短信验证码","api":{"id":"46912421","method":"post","path":"/api/user/auth/verify","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534017","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"mobileNo","required":true,"description":"手机号码","type":"text","id":"81ha5PW5lD"}]},"description":"","tags":[],"status":"released","serverId":"","operationId":"","sourceUrl":"","ordering":18,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"验证码登录","api":{"id":"46912427","method":"post","path":"/api/user/auth/sms","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534025","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"mobileNo","required":true,"description":"手机号","type":"text","id":"GecDdtZXvs"},{"name":"code","required":true,"description":"验证码","type":"text","id":"ykQUsGZhOv"},{"name":"parent_id","required":false,"description":"上级用户","type":"text","id":"Gvf9U3wX1E","example":"10045"}]},"description":"","tags":[],"status":"released","serverId":"","operationId":"","sourceUrl":"","ordering":24,"cases":[{"id":53034421,"type":"http","path":null,"name":"成功","responseId":117534025,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[{"name":"mobileNo","value":"14745798066","enable":true,"id":"imNAq03W7C","relatedName":"mobileNo"},{"name":"code","value":"0000","enable":true,"id":"wyPXQRqsvX","relatedName":"code"},{"name":"parent_id","value":"10045","id":"Yg9y03uxwa","relatedName":"parent_id"}]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"APP登录","api":{"id":"46912429","method":"post","path":"/api/user/socialite/login/wechat/app","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534027","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"union_id","required":true,"description":"unionId必须填写","type":"text","id":"N8kmLy1lpZ"},{"name":"open_id","required":false,"description":"","type":"text","id":"JAUyFqnyCy"},{"name":"mobileNo","required":true,"description":"手机号码必须填写","type":"text","id":"EBY5Llip56"},{"name":"code","required":true,"description":"验证码必须填写","type":"text","id":"dsAVqhj9yN"},{"name":"nickname","required":false,"description":"昵称","type":"text","id":"NUvaQyIRCK"},{"name":"avatar","required":false,"description":"头像","type":"text","id":"u1aDFc11Eo"},{"name":"gender","required":false,"description":"性别","type":"text","id":"YUIsw8Ol6F"},{"name":"country","required":false,"description":"国家","type":"text","id":"cteTMMCb05"},{"name":"province","required":false,"description":"省份","type":"text","id":"cvlwg0xjsP"},{"name":"city","required":false,"description":"城市","type":"text","id":"J3iJ7I4g4M"}]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":30,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"微信小程序手机号登录","api":{"id":"46912430","method":"post","path":"/api/user/socialite/login/wechat/mini","parameters":{"path":[],"query":[{"name":"code","required":true,"description":"微信code","type":"text","id":"yNjOWwVICn"},{"name":"iv","required":true,"description":"向量","type":"text","id":"DRMIcA6K9v"},{"name":"encryptedData","required":true,"description":"加密串","type":"text","id":"k6XGMCXF6o"},{"name":"nickname","required":false,"description":"微信昵称","type":"text","id":"D0iu5wCYyJ"},{"name":"avatar","required":false,"description":"微信头像","type":"text","id":"6dF4UxZz9X"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534028","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":36,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}}]},{"name":"身份相关","parentId":9409854,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"auth":{},"items":[{"name":"获取身份","api":{"id":"46912433","method":"get","path":"/api/user/identities","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534033","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"finish 条件是否完成\nstock 可开通总数\nsold 已有数量\nresidue 剩余数量\non_line true 可在线开通\nshow_button 是否显示按钮","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":0,"cases":[{"id":53034423,"type":"http","path":null,"name":"成功","responseId":117534033,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"获取身份详细信息","api":{"id":"46912438","method":"get","path":"/api/user/identities/{identity}","parameters":{"path":[{"name":"identity","required":true,"type":"text","id":"identity#0"}],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534038","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":6,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"获取可开通身份内容","api":{"id":"46912440","method":"get","path":"/api/user/identities/create/{identity}","parameters":{"path":[{"name":"identity","required":true,"type":"text","id":"identity#0","example":"2"}],"query":[{"name":"year","required":true,"description":"开通几年","type":"text","id":"mTQ2d21Dwc","example":"1"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534040","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":12,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"确认开通身份","api":{"id":"46912441","method":"post","path":"/api/user/identities/create/{identity}","parameters":{"path":[{"name":"identity","required":true,"type":"text","id":"identity#0","example":"2"}],"query":[{"name":"year","required":true,"description":"开通年限","type":"text","id":"1lq3bH2pSN","example":"1"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534041","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":18,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"开通身份支付(微信)","api":{"id":"46912445","method":"get","path":"/api/user/identities/pay/{order_id}/wechat","parameters":{"path":[{"name":"order_id","required":true,"type":"text","id":"order_id#0","example":"36"}],"query":[{"name":"type","required":true,"description":"支付渠道:miniapp 小程序 mp 公众号 app App","type":"text","id":"M0zVt3wEUm","example":"app"},{"name":"openid","required":true,"description":"用户openid,type=mp时必传","type":"text","id":"sqdUjfCCx0","example":"mp"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534042","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":24,"cases":[{"id":53034424,"type":"http","path":null,"name":"成功","responseId":117534042,"parameters":{"path":[{"id":"order#0","relatedId":"order#0","relatedName":"order","name":"order","value":"36","enable":true}],"query":[{"name":"type","value":"mp","enable":true,"id":"JxDYmeR4hg","relatedName":"type"},{"name":"openid","value":"111","enable":true,"id":"RCejxjJV6g","relatedName":"openid"}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"开通身份支付(支付宝)","api":{"id":"46912446","method":"get","path":"/api/user/identities/pay/{order_id}/alipay","parameters":{"path":[{"name":"order_id","required":true,"type":"text","id":"order_id#0","example":"1"}],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534043","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":30,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}}]},{"name":"用户信息","parentId":9409854,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"auth":{},"items":[{"name":"我的小程序码","api":{"id":"46914662","method":"get","path":"/api/user/mini_share","parameters":{"path":[],"query":[{"name":"code","required":true,"description":"大小","type":"string","id":"QsJLfs7XCa","example":"300"},{"name":"url","required":true,"description":"跳转地址","type":"string","id":"QjycaeyWZH","example":"pages/mall/index/index"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{"query":[],"body":[],"cookie":[],"header":[{"name":"Authorization"},{"name":"Accept"}]},"responses":[{"id":"117541884","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"tag ; 1 普通用户 2 大师 3 院士","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":0,"cases":[{"id":53036874,"type":"http","path":null,"name":"成功","responseId":0,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"获取用户信息","api":{"id":"46912452","method":"get","path":"/api/user/info","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534049","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":0,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"我的伙伴","api":{"id":"46914663","method":"get","path":"/api/user/relations","parameters":{"path":[],"query":[{"name":"larer","required":false,"description":"1 直接 2 间接","type":"string","id":"EKdNfxZgTv"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117541885","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":6,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"修改用户资料","api":{"id":"46912453","method":"put","path":"/api/user/setting/{key}","parameters":{"path":[{"name":"key","required":true,"type":"text","id":"key#0","example":"nickname"}],"query":[{"name":"value","required":true,"description":"","type":"text","id":"gJETQJf2Jf","example":"https://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83epT7dFGs0RNZERjeAl75GKTibmkrbAIH47dic43toYjTkSLUPegLDPicqGypp3mRVa7TKFBkbhUfaicqQ/132"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534050","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":6,"cases":[{"id":53034425,"type":"http","path":null,"name":"上传头像","responseId":117534050,"parameters":{"path":[{"id":"key#0","relatedId":"key#0","relatedName":"key","name":"key","value":"avatar","enable":true}],"query":[{"name":"value","value":"https://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83epT7dFGs0RNZERjeAl75GKTibmkrbAIH47dic43toYjTkSLUPegLDPicqGypp3mRVa7TKFBkbhUfaicqQ/132","enable":true,"id":"UsrrdScZa5","relatedName":"value"}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}},{"id":53034426,"type":"http","path":null,"name":"修改昵称","responseId":117534050,"parameters":{"path":[{"id":"key#0","relatedId":"key#0","relatedName":"key","name":"key","value":"nickname","enable":true}],"query":[{"name":"value","value":"玄尘","enable":true,"id":"Jd1drVR9T6","relatedName":"value"}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"获取jssdk","api":{"id":"46914664","method":"get","path":"/api/user/auth/official/jssdk","parameters":{"path":[],"query":[{"name":"url","required":true,"description":"域名:页面的url","type":"string","id":"9CfVBC3Bsz"},{"name":"jsApiList","required":true,"description":"","type":"string","id":"qlDDWLDoED"},{"name":"openTagList","required":false,"description":"","type":"string","id":"aHbb0nKKkN"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{"query":[],"body":[],"cookie":[],"header":[{"name":"Authorization"},{"name":"Accept"}]},"responses":[{"id":"117541886","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"微信支付、分享、调整到小程序App\njsApiList 查看地址:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#63\nopenTagList 查看地址:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_Open_Tag.html#%E6%A6%82%E8%BF%B0","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":12,"cases":[{"id":53036875,"type":"http","path":null,"name":"成功","responseId":117541886,"parameters":{"path":[],"query":[{"name":"url","value":"http://www.water.shangkelian.cn/user/code","enable":true,"id":"i1lHZTDvU9","relatedName":"url"},{"name":"jsApiList","enable":true,"id":"bO8fmBb2ma","relatedName":"jsApiList"},{"name":"openTagList","enable":true,"id":"oxz5c7EmLk","relatedName":"openTagList"}],"cookie":[],"header":[]},"commonParameters":{"query":[],"body":[],"header":[],"cookie":[]},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"获取邀请码","api":{"id":"46912454","method":"get","path":"/api/user/invite","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534051","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":12,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"绑定邀请码","api":{"id":"46912455","method":"post","path":"/api/user/bind","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534052","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"invite","required":false,"description":"","type":"text","id":"XUX77YKZMR"}]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":18,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"个人中心设置","api":{"id":"46912456","method":"get","path":"/api/user/setting","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534053","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":24,"cases":[{"id":53034427,"type":"http","path":null,"name":"成功","responseId":117534053,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"获取小程序openid","api":{"id":"46914666","method":"get","path":"/api/user/auth/mini/openid","parameters":{"path":[],"query":[{"name":"code","required":true,"description":"","type":"string","id":"2nmbNKMVEj"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{"query":[],"body":[],"cookie":[],"header":[{"name":"Authorization"},{"name":"Accept"}]},"responses":[{"id":"117541888","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":24,"cases":[{"id":53036877,"type":"http","path":null,"name":"成功","responseId":0,"parameters":{"path":[],"query":[{"name":"url","value":"http://www.gout.shangkelian.cn/user/code","enable":true,"id":"lMEYD90aFt","relatedName":"url"},{"name":"jsApiList","enable":true,"id":"5PatqFaHcn","relatedName":"jsApiList"},{"name":"openTagList","enable":true,"id":"YkKUQsQ5P0","relatedName":"openTagList"}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"获取授权页面","api":{"id":"46914667","method":"get","path":"/api/user/auth/official/url","parameters":{"path":[],"query":[{"name":"url","required":true,"description":"域名:页面的url /pages/index","type":"string","id":"pqMoW4te8P"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{"query":[],"body":[],"cookie":[],"header":[{"name":"Authorization"},{"name":"Accept"}]},"responses":[{"id":"117541889","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":30,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}}]},{"name":"用户签到","parentId":9409854,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"auth":{},"items":[{"name":"用户签到","api":{"id":"46912462","method":"post","path":"/api/user/sign","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534059","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":0,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"用户签到信息","api":{"id":"46912463","method":"get","path":"/api/user/sign","parameters":{"path":[],"query":[{"name":"days","required":false,"description":"","type":"text","id":"TaEOX6oVrd","example":"7"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534060","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":6,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}}]},{"name":"用户认证","parentId":9409854,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"auth":{},"items":[{"name":"用户认证","api":{"id":"46912464","method":"post","path":"/api/user/certification","parameters":{"path":[],"query":[{"name":"name","required":true,"description":"姓名","type":"text","id":"nueYaNtusJ","example":" 测试"},{"name":"id_card","required":true,"description":"身份证号","type":"text","id":"ISPMzfvS1f","example":"230102111111111111"},{"name":"front_card","required":true,"description":"身份证正面","type":"text","id":"SnYDZt1OxO","example":""},{"name":"back_card","required":true,"description":"身份证反面","type":"text","id":"A9XW8kuE0r","example":""}],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534061","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":0,"cases":[{"id":53034428,"type":"http","path":null,"name":"成功","responseId":117534061,"parameters":{"path":[],"query":[{"name":"name","value":" 测试","enable":true,"id":"7PtXSEn8Um","relatedName":"name"},{"name":"id_card","value":"230102198906032138","enable":true,"id":"92kil5yaKX","relatedName":"id_card"},{"name":"front_card","value":"1.jpg","enable":true,"id":"8GSaVo8NbU","relatedName":"front_card"},{"name":"back_card","value":"2.jpg","enable":true,"id":"7w7IDyCq7s","relatedName":"back_card"}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"查看认证信息","api":{"id":"46912465","method":"get","path":"/api/user/certification","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534062","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":6,"cases":[{"id":53034429,"type":"http","path":null,"name":"成功","responseId":117534062,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"是否实名认证","api":{"id":"46912466","method":"get","path":"/api/user/certified","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534063","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":12,"cases":[{"id":53034430,"type":"http","path":null,"name":"成功","responseId":117534063,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}}]}]}]}],"schemaCollection":[],"environments":[],"commonScripts":[{"name":"登录后保存token","description":"","content":"var jsonData = pm.response.json();\n\npm.environment.set('api_token', jsonData.data.access_token);","stickyOrdering":0,"ordering":0,"id":"387699"}],"globalVariables":[],"commonParameters":{"parameters":{"query":[],"body":[],"cookie":[],"header":[{"name":"Authorization","defaultEnable":true,"type":"string","defaultValue":"Bearer {{api_token}}","description":"Bearer {{api_token}}"},{"name":"Accept","defaultEnable":true,"type":"string","defaultValue":"application/json","description":"application/json"},{"name":"Authorization","defaultEnable":true,"type":"string","defaultValue":"Bearer {{api_token}}","description":"Bearer {{api_token}}"},{"name":"Accept","defaultEnable":true,"type":"string","defaultValue":"application/json","description":"application/json"}]},"id":394503,"projectId":1850830,"creatorId":366036,"editorId":366036,"createdAt":"2022-11-01T05:30:13.000Z","updatedAt":"2022-11-01T05:36:32.000Z","deletedAt":null},"projectSetting":{"apiStatuses":["developing","testing","released","deprecated"],"servers":[{"id":"default","name":"默认服务"}],"preProcessors":[],"postProcessors":[],"auth":{},"initialDisabledMockIds":[],"mockSettings":{},"gateway":[],"language":"zh-CN","id":"1444399","cloudMock":{"security":"free","enable":false,"tokenKey":"apifoxToken"}}} \ No newline at end of file diff --git a/modules/pull-all.sh b/modules/pull-all.sh new file mode 100644 index 0000000..5150557 --- /dev/null +++ b/modules/pull-all.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +dir=$(ls -d */) +for i in $dir +do + cd ./$i/ && git pull && cd .. +done diff --git a/package.json b/package.json new file mode 100644 index 0000000..00c6506 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "private": true, + "scripts": { + "dev": "npm run development", + "development": "mix", + "watch": "mix watch", + "watch-poll": "mix watch -- --watch-options-poll=1000", + "hot": "mix watch --hot", + "prod": "npm run production", + "production": "mix --production" + }, + "devDependencies": { + "axios": "^0.21", + "laravel-mix": "^6.0.6", + "lodash": "^4.17.19", + "postcss": "^8.1.14" + } +} diff --git a/parseComposer.php b/parseComposer.php new file mode 100644 index 0000000..a6ccb63 --- /dev/null +++ b/parseComposer.php @@ -0,0 +1,49 @@ + $version) { + if ($package !== 'php' && substr($package, 0,4) !== 'ext-') { + $origin[$package] = $version; + } +} + +foreach ($modules as $module) { + if ($module == '.' || $module == '..') { + continue; + } + + if (! is_dir('modules/'.$module)) { + continue; + } + + $needs = getNeeds(file_get_contents('modules/'.$module.'/composer.json')); + + foreach ($needs as $package => $version) { + if (isset($packages[$package])) { + if (version_compare($version, $packages[$package]) == 1) { + $packages[$package] = $version; + } + } else { + $packages[$package] = $version; + } + } +} + +$string = ''; + +foreach (array_diff_key($packages, $origin) as $package => $version) { + $string .= $package.':'.$version.' '; +} + +echo $string; + +function getNeeds($json): array +{ + return json_decode($json, true)['require']; +} + diff --git a/permission.sh b/permission.sh new file mode 100644 index 0000000..e0770d8 --- /dev/null +++ b/permission.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +user=www-data + +grep '^$user' /etc/passwd | wc -l > /dev/null +if [ $? -ne 0 ] +then + chown -R www-data:www-data storage bootstrap/cache modules.json +fi diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..3aec5e2 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,21 @@ + + + Options -MultiViews -Indexes + + + RewriteEngine On + + # Handle Authorization Header + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # Redirect Trailing Slashes If Not A Folder... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} (.+)/$ + RewriteRule ^ %1 [L,R=301] + + # Send Requests To Front Controller... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..a034365 --- /dev/null +++ b/public/index.php @@ -0,0 +1,55 @@ +make(Kernel::class); + +$response = $kernel->handle( + $request = Request::capture() +)->send(); + +$kernel->terminate($request, $response); diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..eb05362 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/public/vendor/laravel-admin/AdminLTE/bootstrap/css/bootstrap.min.css b/public/vendor/laravel-admin/AdminLTE/bootstrap/css/bootstrap.min.css new file mode 100644 index 0000000..a58698c --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/bootstrap/css/bootstrap.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')} .glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale} .glyphicon-asterisk:before{content:"\2a"} .glyphicon-plus:before{content:"\2b"} .glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"} .glyphicon-minus:before{content:"\2212"} .glyphicon-cloud:before{content:"\2601"} .glyphicon-envelope:before{content:"\2709"} .glyphicon-pencil:before{content:"\270f"} .glyphicon-glass:before{content:"\e001"} .glyphicon-music:before{content:"\e002"} .glyphicon-search:before{content:"\e003"} .glyphicon-heart:before{content:"\e005"} .glyphicon-star:before{content:"\e006"} .glyphicon-star-empty:before{content:"\e007"} .glyphicon-user:before{content:"\e008"} .glyphicon-film:before{content:"\e009"} .glyphicon-th-large:before{content:"\e010"} .glyphicon-th:before{content:"\e011"} .glyphicon-th-list:before{content:"\e012"} .glyphicon-ok:before{content:"\e013"} .glyphicon-remove:before{content:"\e014"} .glyphicon-zoom-in:before{content:"\e015"} .glyphicon-zoom-out:before{content:"\e016"} .glyphicon-off:before{content:"\e017"} .glyphicon-signal:before{content:"\e018"} .glyphicon-cog:before{content:"\e019"} .glyphicon-trash:before{content:"\e020"} .glyphicon-home:before{content:"\e021"} .glyphicon-file:before{content:"\e022"} .glyphicon-time:before{content:"\e023"} .glyphicon-road:before{content:"\e024"} .glyphicon-download-alt:before{content:"\e025"} .glyphicon-download:before{content:"\e026"} .glyphicon-upload:before{content:"\e027"} .glyphicon-inbox:before{content:"\e028"} .glyphicon-play-circle:before{content:"\e029"} .glyphicon-repeat:before{content:"\e030"} .glyphicon-refresh:before{content:"\e031"} .glyphicon-list-alt:before{content:"\e032"} .glyphicon-lock:before{content:"\e033"} .glyphicon-flag:before{content:"\e034"} .glyphicon-headphones:before{content:"\e035"} .glyphicon-volume-off:before{content:"\e036"} .glyphicon-volume-down:before{content:"\e037"} .glyphicon-volume-up:before{content:"\e038"} .glyphicon-qrcode:before{content:"\e039"} .glyphicon-barcode:before{content:"\e040"} .glyphicon-tag:before{content:"\e041"} .glyphicon-tags:before{content:"\e042"} .glyphicon-book:before{content:"\e043"} .glyphicon-bookmark:before{content:"\e044"} .glyphicon-print:before{content:"\e045"} .glyphicon-camera:before{content:"\e046"} .glyphicon-font:before{content:"\e047"} .glyphicon-bold:before{content:"\e048"} .glyphicon-italic:before{content:"\e049"} .glyphicon-text-height:before{content:"\e050"} .glyphicon-text-width:before{content:"\e051"} .glyphicon-align-left:before{content:"\e052"} .glyphicon-align-center:before{content:"\e053"} .glyphicon-align-right:before{content:"\e054"} .glyphicon-align-justify:before{content:"\e055"} .glyphicon-list:before{content:"\e056"} .glyphicon-indent-left:before{content:"\e057"} .glyphicon-indent-right:before{content:"\e058"} .glyphicon-facetime-video:before{content:"\e059"} .glyphicon-picture:before{content:"\e060"} .glyphicon-map-marker:before{content:"\e062"} .glyphicon-adjust:before{content:"\e063"} .glyphicon-tint:before{content:"\e064"} .glyphicon-edit:before{content:"\e065"} .glyphicon-share:before{content:"\e066"} .glyphicon-check:before{content:"\e067"} .glyphicon-move:before{content:"\e068"} .glyphicon-step-backward:before{content:"\e069"} .glyphicon-fast-backward:before{content:"\e070"} .glyphicon-backward:before{content:"\e071"} .glyphicon-play:before{content:"\e072"} .glyphicon-pause:before{content:"\e073"} .glyphicon-stop:before{content:"\e074"} .glyphicon-forward:before{content:"\e075"} .glyphicon-fast-forward:before{content:"\e076"} .glyphicon-step-forward:before{content:"\e077"} .glyphicon-eject:before{content:"\e078"} .glyphicon-chevron-left:before{content:"\e079"} .glyphicon-chevron-right:before{content:"\e080"} .glyphicon-plus-sign:before{content:"\e081"} .glyphicon-minus-sign:before{content:"\e082"} .glyphicon-remove-sign:before{content:"\e083"} .glyphicon-ok-sign:before{content:"\e084"} .glyphicon-question-sign:before{content:"\e085"} .glyphicon-info-sign:before{content:"\e086"} .glyphicon-screenshot:before{content:"\e087"} .glyphicon-remove-circle:before{content:"\e088"} .glyphicon-ok-circle:before{content:"\e089"} .glyphicon-ban-circle:before{content:"\e090"} .glyphicon-arrow-left:before{content:"\e091"} .glyphicon-arrow-right:before{content:"\e092"} .glyphicon-arrow-up:before{content:"\e093"} .glyphicon-arrow-down:before{content:"\e094"} .glyphicon-share-alt:before{content:"\e095"} .glyphicon-resize-full:before{content:"\e096"} .glyphicon-resize-small:before{content:"\e097"} .glyphicon-exclamation-sign:before{content:"\e101"} .glyphicon-gift:before{content:"\e102"} .glyphicon-leaf:before{content:"\e103"} .glyphicon-fire:before{content:"\e104"} .glyphicon-eye-open:before{content:"\e105"} .glyphicon-eye-close:before{content:"\e106"} .glyphicon-warning-sign:before{content:"\e107"} .glyphicon-plane:before{content:"\e108"} .glyphicon-calendar:before{content:"\e109"} .glyphicon-random:before{content:"\e110"} .glyphicon-comment:before{content:"\e111"} .glyphicon-magnet:before{content:"\e112"} .glyphicon-chevron-up:before{content:"\e113"} .glyphicon-chevron-down:before{content:"\e114"} .glyphicon-retweet:before{content:"\e115"} .glyphicon-shopping-cart:before{content:"\e116"} .glyphicon-folder-close:before{content:"\e117"} .glyphicon-folder-open:before{content:"\e118"} .glyphicon-resize-vertical:before{content:"\e119"} .glyphicon-resize-horizontal:before{content:"\e120"} .glyphicon-hdd:before{content:"\e121"} .glyphicon-bullhorn:before{content:"\e122"} .glyphicon-bell:before{content:"\e123"} .glyphicon-certificate:before{content:"\e124"} .glyphicon-thumbs-up:before{content:"\e125"} .glyphicon-thumbs-down:before{content:"\e126"} .glyphicon-hand-right:before{content:"\e127"} .glyphicon-hand-left:before{content:"\e128"} .glyphicon-hand-up:before{content:"\e129"} .glyphicon-hand-down:before{content:"\e130"} .glyphicon-circle-arrow-right:before{content:"\e131"} .glyphicon-circle-arrow-left:before{content:"\e132"} .glyphicon-circle-arrow-up:before{content:"\e133"} .glyphicon-circle-arrow-down:before{content:"\e134"} .glyphicon-globe:before{content:"\e135"} .glyphicon-wrench:before{content:"\e136"} .glyphicon-tasks:before{content:"\e137"} .glyphicon-filter:before{content:"\e138"} .glyphicon-briefcase:before{content:"\e139"} .glyphicon-fullscreen:before{content:"\e140"} .glyphicon-dashboard:before{content:"\e141"} .glyphicon-paperclip:before{content:"\e142"} .glyphicon-heart-empty:before{content:"\e143"} .glyphicon-link:before{content:"\e144"} .glyphicon-phone:before{content:"\e145"} .glyphicon-pushpin:before{content:"\e146"} .glyphicon-usd:before{content:"\e148"} .glyphicon-gbp:before{content:"\e149"} .glyphicon-sort:before{content:"\e150"} .glyphicon-sort-by-alphabet:before{content:"\e151"} .glyphicon-sort-by-alphabet-alt:before{content:"\e152"} .glyphicon-sort-by-order:before{content:"\e153"} .glyphicon-sort-by-order-alt:before{content:"\e154"} .glyphicon-sort-by-attributes:before{content:"\e155"} .glyphicon-sort-by-attributes-alt:before{content:"\e156"} .glyphicon-unchecked:before{content:"\e157"} .glyphicon-expand:before{content:"\e158"} .glyphicon-collapse-down:before{content:"\e159"} .glyphicon-collapse-up:before{content:"\e160"} .glyphicon-log-in:before{content:"\e161"} .glyphicon-flash:before{content:"\e162"} .glyphicon-log-out:before{content:"\e163"} .glyphicon-new-window:before{content:"\e164"} .glyphicon-record:before{content:"\e165"} .glyphicon-save:before{content:"\e166"} .glyphicon-open:before{content:"\e167"} .glyphicon-saved:before{content:"\e168"} .glyphicon-import:before{content:"\e169"} .glyphicon-export:before{content:"\e170"} .glyphicon-send:before{content:"\e171"} .glyphicon-floppy-disk:before{content:"\e172"} .glyphicon-floppy-saved:before{content:"\e173"} .glyphicon-floppy-remove:before{content:"\e174"} .glyphicon-floppy-save:before{content:"\e175"} .glyphicon-floppy-open:before{content:"\e176"} .glyphicon-credit-card:before{content:"\e177"} .glyphicon-transfer:before{content:"\e178"} .glyphicon-cutlery:before{content:"\e179"} .glyphicon-header:before{content:"\e180"} .glyphicon-compressed:before{content:"\e181"} .glyphicon-earphone:before{content:"\e182"} .glyphicon-phone-alt:before{content:"\e183"} .glyphicon-tower:before{content:"\e184"} .glyphicon-stats:before{content:"\e185"} .glyphicon-sd-video:before{content:"\e186"} .glyphicon-hd-video:before{content:"\e187"} .glyphicon-subtitles:before{content:"\e188"} .glyphicon-sound-stereo:before{content:"\e189"} .glyphicon-sound-dolby:before{content:"\e190"} .glyphicon-sound-5-1:before{content:"\e191"} .glyphicon-sound-6-1:before{content:"\e192"} .glyphicon-sound-7-1:before{content:"\e193"} .glyphicon-copyright-mark:before{content:"\e194"} .glyphicon-registration-mark:before{content:"\e195"} .glyphicon-cloud-download:before{content:"\e197"} .glyphicon-cloud-upload:before{content:"\e198"} .glyphicon-tree-conifer:before{content:"\e199"} .glyphicon-tree-deciduous:before{content:"\e200"} .glyphicon-cd:before{content:"\e201"} .glyphicon-save-file:before{content:"\e202"} .glyphicon-open-file:before{content:"\e203"} .glyphicon-level-up:before{content:"\e204"} .glyphicon-copy:before{content:"\e205"} .glyphicon-paste:before{content:"\e206"} .glyphicon-alert:before{content:"\e209"} .glyphicon-equalizer:before{content:"\e210"} .glyphicon-king:before{content:"\e211"} .glyphicon-queen:before{content:"\e212"} .glyphicon-pawn:before{content:"\e213"} .glyphicon-bishop:before{content:"\e214"} .glyphicon-knight:before{content:"\e215"} .glyphicon-baby-formula:before{content:"\e216"} .glyphicon-tent:before{content:"\26fa"} .glyphicon-blackboard:before{content:"\e218"} .glyphicon-bed:before{content:"\e219"} .glyphicon-apple:before{content:"\f8ff"} .glyphicon-erase:before{content:"\e221"} .glyphicon-hourglass:before{content:"\231b"} .glyphicon-lamp:before{content:"\e223"} .glyphicon-duplicate:before{content:"\e224"} .glyphicon-piggy-bank:before{content:"\e225"} .glyphicon-scissors:before{content:"\e226"} .glyphicon-bitcoin:before{content:"\e227"} .glyphicon-btc:before{content:"\e227"} .glyphicon-xbt:before{content:"\e227"} .glyphicon-yen:before{content:"\00a5"} .glyphicon-jpy:before{content:"\00a5"} .glyphicon-ruble:before{content:"\20bd"} .glyphicon-rub:before{content:"\20bd"} .glyphicon-scale:before{content:"\e230"} .glyphicon-ice-lolly:before{content:"\e231"} .glyphicon-ice-lolly-tasted:before{content:"\e232"} .glyphicon-education:before{content:"\e233"} .glyphicon-option-horizontal:before{content:"\e234"} .glyphicon-option-vertical:before{content:"\e235"} .glyphicon-menu-hamburger:before{content:"\e236"} .glyphicon-modal-window:before{content:"\e237"} .glyphicon-oil:before{content:"\e238"} .glyphicon-grain:before{content:"\e239"} .glyphicon-sunglasses:before{content:"\e240"} .glyphicon-text-size:before{content:"\e241"} .glyphicon-text-color:before{content:"\e242"} .glyphicon-text-background:before{content:"\e243"} .glyphicon-object-align-top:before{content:"\e244"} .glyphicon-object-align-bottom:before{content:"\e245"} .glyphicon-object-align-horizontal:before{content:"\e246"} .glyphicon-object-align-left:before{content:"\e247"} .glyphicon-object-align-vertical:before{content:"\e248"} .glyphicon-object-align-right:before{content:"\e249"} .glyphicon-triangle-right:before{content:"\e250"} .glyphicon-triangle-left:before{content:"\e251"} .glyphicon-triangle-bottom:before{content:"\e252"} .glyphicon-triangle-top:before{content:"\e253"} .glyphicon-console:before{content:"\e254"} .glyphicon-superscript:before{content:"\e255"} .glyphicon-subscript:before{content:"\e256"} .glyphicon-menu-left:before{content:"\e257"} .glyphicon-menu-right:before{content:"\e258"} .glyphicon-menu-down:before{content:"\e259"} .glyphicon-menu-up:before{content:"\e260"} *{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} :after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)} body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff} button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit} a{color:#337ab7;text-decoration:none} a:focus,a:hover{color:#23527c;text-decoration:underline} a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px} figure{margin:0} img{vertical-align:middle} .carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto} .img-rounded{border-radius:6px} .img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out} .img-circle{border-radius:50%} hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee} .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0} .sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} [role=button]{cursor:pointer} .h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit} .h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777} .h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px} .h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%} .h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px} .h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%} .h1,h1{font-size:36px} .h2,h2{font-size:30px} .h3,h3{font-size:24px} .h4,h4{font-size:18px} .h5,h5{font-size:14px} .h6,h6{font-size:12px} p{margin:0 0 10px} .lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}} .small,small{font-size:85%} .mark,mark{padding:.2em;background-color:#fcf8e3} .text-left{text-align:left} .text-right{text-align:right} .text-center{text-align:center} .text-justify{text-align:justify} .text-nowrap{white-space:nowrap} .text-lowercase{text-transform:lowercase} .text-uppercase{text-transform:uppercase} .text-capitalize{text-transform:capitalize} .text-muted{color:#777} .text-primary{color:#337ab7} a.text-primary:focus,a.text-primary:hover{color:#286090} .text-success{color:#3c763d} a.text-success:focus,a.text-success:hover{color:#2b542c} .text-info{color:#31708f} a.text-info:focus,a.text-info:hover{color:#245269} .text-warning{color:#8a6d3b} a.text-warning:focus,a.text-warning:hover{color:#66512c} .text-danger{color:#a94442} a.text-danger:focus,a.text-danger:hover{color:#843534} .bg-primary{color:#fff;background-color:#337ab7} a.bg-primary:focus,a.bg-primary:hover{background-color:#286090} .bg-success{background-color:#dff0d8} a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3} .bg-info{background-color:#d9edf7} a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee} .bg-warning{background-color:#fcf8e3} a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5} .bg-danger{background-color:#f2dede} a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9} .page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee} ol,ul{margin-top:0;margin-bottom:10px} ol ol,ol ul,ul ol,ul ul{margin-bottom:0} .list-unstyled{padding-left:0;list-style:none} .list-inline{padding-left:0;margin-left:-5px;list-style:none} .list-inline>li{display:inline-block;padding-right:5px;padding-left:5px} dl{margin-top:0;margin-bottom:20px} dd,dt{line-height:1.42857143} dt{font-weight:700} dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}} abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777} .initialism{font-size:90%;text-transform:uppercase} blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee} blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0} blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777} blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'} .blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0} .blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''} .blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'} address{margin-bottom:20px;font-style:normal;line-height:1.42857143} code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace} code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px} kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)} kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none} pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px} pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0} .pre-scrollable{max-height:340px;overflow-y:scroll} .container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}} .container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto} .row{margin-right:-15px;margin-left:-15px} .col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px} .col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left} .col-xs-12{width:100%} .col-xs-11{width:91.66666667%} .col-xs-10{width:83.33333333%} .col-xs-9{width:75%} .col-xs-8{width:66.66666667%} .col-xs-7{width:58.33333333%} .col-xs-6{width:50%} .col-xs-5{width:41.66666667%} .col-xs-4{width:33.33333333%} .col-xs-3{width:25%} .col-xs-2{width:16.66666667%} .col-xs-1{width:8.33333333%} .col-xs-pull-12{right:100%} .col-xs-pull-11{right:91.66666667%} .col-xs-pull-10{right:83.33333333%} .col-xs-pull-9{right:75%} .col-xs-pull-8{right:66.66666667%} .col-xs-pull-7{right:58.33333333%} .col-xs-pull-6{right:50%} .col-xs-pull-5{right:41.66666667%} .col-xs-pull-4{right:33.33333333%} .col-xs-pull-3{right:25%} .col-xs-pull-2{right:16.66666667%} .col-xs-pull-1{right:8.33333333%} .col-xs-pull-0{right:auto} .col-xs-push-12{left:100%} .col-xs-push-11{left:91.66666667%} .col-xs-push-10{left:83.33333333%} .col-xs-push-9{left:75%} .col-xs-push-8{left:66.66666667%} .col-xs-push-7{left:58.33333333%} .col-xs-push-6{left:50%} .col-xs-push-5{left:41.66666667%} .col-xs-push-4{left:33.33333333%} .col-xs-push-3{left:25%} .col-xs-push-2{left:16.66666667%} .col-xs-push-1{left:8.33333333%} .col-xs-push-0{left:auto} .col-xs-offset-12{margin-left:100%} .col-xs-offset-11{margin-left:91.66666667%} .col-xs-offset-10{margin-left:83.33333333%} .col-xs-offset-9{margin-left:75%} .col-xs-offset-8{margin-left:66.66666667%} .col-xs-offset-7{margin-left:58.33333333%} .col-xs-offset-6{margin-left:50%} .col-xs-offset-5{margin-left:41.66666667%} .col-xs-offset-4{margin-left:33.33333333%} .col-xs-offset-3{margin-left:25%} .col-xs-offset-2{margin-left:16.66666667%} .col-xs-offset-1{margin-left:8.33333333%} .col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}} table{background-color:transparent} caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left} th{text-align:left} .table{width:100%;max-width:100%;margin-bottom:20px} .table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd} .table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd} .table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0} .table>tbody+tbody{border-top:2px solid #ddd} .table .table{background-color:#fff} .table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px} .table-bordered{border:1px solid #ddd} .table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd} .table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px} .table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9} .table-hover>tbody>tr:hover{background-color:#f5f5f5} table col[class*=col-]{position:static;display:table-column;float:none} table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none} .table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5} .table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8} .table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8} .table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6} .table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7} .table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3} .table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3} .table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc} .table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede} .table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc} .table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}} fieldset{min-width:0;padding:0;margin:0;border:0} legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5} label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700} input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal} input[type=file]{display:block} input[type=range]{display:block;width:100%} select[multiple],select[size]{height:auto} input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px} output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555} .form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s} .form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)} .form-control::-moz-placeholder{color:#999;opacity:1} .form-control:-ms-input-placeholder{color:#999} .form-control::-webkit-input-placeholder{color:#999} .form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1} .form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed} textarea.form-control{height:auto} input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}} .form-group{margin-bottom:15px} .checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px} .checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer} .checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px} .checkbox+.checkbox,.radio+.radio{margin-top:-5px} .checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer} .checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px} fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed} .checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed} .checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed} .form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0} .form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0} .input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px} select.input-sm{height:30px;line-height:30px} select[multiple].input-sm,textarea.input-sm{height:auto} .form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px} .form-group-sm select.form-control{height:30px;line-height:30px} .form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto} .form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5} .input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px} select.input-lg{height:46px;line-height:46px} select[multiple].input-lg,textarea.input-lg{height:auto} .form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px} .form-group-lg select.form-control{height:46px;line-height:46px} .form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto} .form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333} .has-feedback{position:relative} .has-feedback .form-control{padding-right:42.5px} .form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none} .form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px} .form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px} .has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d} .has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)} .has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168} .has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d} .has-success .form-control-feedback{color:#3c763d} .has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b} .has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)} .has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b} .has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b} .has-warning .form-control-feedback{color:#8a6d3b} .has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442} .has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)} .has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483} .has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442} .has-error .form-control-feedback{color:#a94442} .has-feedback label~.form-control-feedback{top:25px} .has-feedback label.sr-only~.form-control-feedback{top:0} .help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}} .form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0} .form-horizontal .checkbox,.form-horizontal .radio{min-height:27px} .form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}} .form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}} .btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px} .btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px} .btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none} .btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)} .btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65} a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none} .btn-default{color:#333;background-color:#fff;border-color:#ccc} .btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c} .btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad} .btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad} .btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c} .btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none} .btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc} .btn-default .badge{color:#fff;background-color:#333} .btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4} .btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40} .btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74} .btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74} .btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40} .btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none} .btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4} .btn-primary .badge{color:#337ab7;background-color:#fff} .btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c} .btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625} .btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439} .btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439} .btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625} .btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none} .btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c} .btn-success .badge{color:#5cb85c;background-color:#fff} .btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da} .btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85} .btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc} .btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc} .btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85} .btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none} .btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da} .btn-info .badge{color:#5bc0de;background-color:#fff} .btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236} .btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d} .btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512} .btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512} .btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d} .btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none} .btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236} .btn-warning .badge{color:#f0ad4e;background-color:#fff} .btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a} .btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19} .btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925} .btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925} .btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19} .btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none} .btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a} .btn-danger .badge{color:#d9534f;background-color:#fff} .btn-link{font-weight:400;color:#337ab7;border-radius:0} .btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none} .btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent} .btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent} .btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none} .btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px} .btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px} .btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px} .btn-block{display:block;width:100%} .btn-block+.btn-block{margin-top:5px} input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%} .fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear} .fade.in{opacity:1} .collapse{display:none} .collapse.in{display:block} tr.collapse.in{display:table-row} tbody.collapse.in{display:table-row-group} .collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility} .caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent} .dropdown,.dropup{position:relative} .dropdown-toggle:focus{outline:0} .dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)} .dropdown-menu.pull-right{right:0;left:auto} .dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5} .dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap} .dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5} .dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0} .dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777} .dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)} .open>.dropdown-menu{display:block} .open>a{outline:0} .dropdown-menu-right{right:0;left:auto} .dropdown-menu-left{right:auto;left:0} .dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap} .dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990} .pull-right>.dropdown-menu{right:0;left:auto} .dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9} .dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}} .btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle} .btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left} .btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2} .btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px} .btn-toolbar{margin-left:-5px} .btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left} .btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px} .btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0} .btn-group>.btn:first-child{margin-left:0} .btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0} .btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0} .btn-group>.btn-group{float:left} .btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0} .btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0} .btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0} .btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0} .btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px} .btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px} .btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)} .btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none} .btn .caret{margin-left:0} .btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0} .dropup .btn-lg .caret{border-width:0 5px 5px} .btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%} .btn-group-vertical>.btn-group>.btn{float:none} .btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0} .btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0} .btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0} .btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px} .btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0} .btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0} .btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0} .btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate} .btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%} .btn-group-justified>.btn-group .btn{width:100%} .btn-group-justified>.btn-group .dropdown-menu{left:auto} [data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none} .input-group{position:relative;display:table;border-collapse:separate} .input-group[class*=col-]{float:none;padding-right:0;padding-left:0} .input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0} .input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px} select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px} select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto} .input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px} select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px} select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto} .input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell} .input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0} .input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle} .input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px} .input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px} .input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px} .input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0} .input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0} .input-group-addon:first-child{border-right:0} .input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0} .input-group-addon:last-child{border-left:0} .input-group-btn{position:relative;font-size:0;white-space:nowrap} .input-group-btn>.btn{position:relative} .input-group-btn>.btn+.btn{margin-left:-1px} .input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2} .input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px} .input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px} .nav{padding-left:0;margin-bottom:0;list-style:none} .nav>li{position:relative;display:block} .nav>li>a{position:relative;display:block;padding:10px 15px} .nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee} .nav>li.disabled>a{color:#777} .nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent} .nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7} .nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5} .nav>li>a>img{max-width:none} .nav-tabs{border-bottom:1px solid #ddd} .nav-tabs>li{float:left;margin-bottom:-1px} .nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0} .nav-tabs>li>a:hover{border-color:#eee #eee #ddd} .nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent} .nav-tabs.nav-justified{width:100%;border-bottom:0} .nav-tabs.nav-justified>li{float:none} .nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center} .nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}} .nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px} .nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}} .nav-pills>li{float:left} .nav-pills>li>a{border-radius:4px} .nav-pills>li+li{margin-left:2px} .nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7} .nav-stacked>li{float:none} .nav-stacked>li+li{margin-top:2px;margin-left:0} .nav-justified{width:100%} .nav-justified>li{float:none} .nav-justified>li>a{margin-bottom:5px;text-align:center} .nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}} .nav-tabs-justified{border-bottom:0} .nav-tabs-justified>li>a{margin-right:0;border-radius:4px} .nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}} .tab-content>.tab-pane{display:none} .tab-content>.active{display:block} .nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0} .navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}} .navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)} .navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}} .navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}} .container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}} .navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}} .navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}} .navbar-fixed-top{top:0;border-width:0 0 1px} .navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0} .navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px} .navbar-brand:focus,.navbar-brand:hover{text-decoration:none} .navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}} .navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px} .navbar-toggle:focus{outline:0} .navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px} .navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}} .navbar-nav{margin:7.5px -15px} .navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}} .navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}} .navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0} .navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0} .navbar-btn{margin-top:8px;margin-bottom:8px} .navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px} .navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px} .navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}} .navbar-default{background-color:#f8f8f8;border-color:#e7e7e7} .navbar-default .navbar-brand{color:#777} .navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent} .navbar-default .navbar-text{color:#777} .navbar-default .navbar-nav>li>a{color:#777} .navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent} .navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7} .navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent} .navbar-default .navbar-toggle{border-color:#ddd} .navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd} .navbar-default .navbar-toggle .icon-bar{background-color:#888} .navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7} .navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}} .navbar-default .navbar-link{color:#777} .navbar-default .navbar-link:hover{color:#333} .navbar-default .btn-link{color:#777} .navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333} .navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc} .navbar-inverse{background-color:#222;border-color:#080808} .navbar-inverse .navbar-brand{color:#9d9d9d} .navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent} .navbar-inverse .navbar-text{color:#9d9d9d} .navbar-inverse .navbar-nav>li>a{color:#9d9d9d} .navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent} .navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808} .navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent} .navbar-inverse .navbar-toggle{border-color:#333} .navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333} .navbar-inverse .navbar-toggle .icon-bar{background-color:#fff} .navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010} .navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}} .navbar-inverse .navbar-link{color:#9d9d9d} .navbar-inverse .navbar-link:hover{color:#fff} .navbar-inverse .btn-link{color:#9d9d9d} .navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff} .navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444} .breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px} .breadcrumb>li{display:inline-block} .breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"} .breadcrumb>.active{color:#777} .pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px} .pagination>li{display:inline} .pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd} .pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px} .pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px} .pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:3;color:#23527c;background-color:#eee;border-color:#ddd} .pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7} .pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd} .pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333} .pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px} .pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px} .pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5} .pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px} .pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px} .pager{padding-left:0;margin:20px 0;text-align:center;list-style:none} .pager li{display:inline} .pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px} .pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee} .pager .next>a,.pager .next>span{float:right} .pager .previous>a,.pager .previous>span{float:left} .pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff} .label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em} a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer} .label:empty{display:none} .btn .label{position:relative;top:-1px} .label-default{background-color:#777} .label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e} .label-primary{background-color:#337ab7} .label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090} .label-success{background-color:#5cb85c} .label-success[href]:focus,.label-success[href]:hover{background-color:#449d44} .label-info{background-color:#5bc0de} .label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5} .label-warning{background-color:#f0ad4e} .label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f} .label-danger{background-color:#d9534f} .label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c} .badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px} .badge:empty{display:none} .btn .badge{position:relative;top:-1px} .btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px} a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer} .list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff} .list-group-item>.badge{float:right} .list-group-item>.badge+.badge{margin-right:5px} .nav-pills>li>a>.badge{margin-left:3px} .jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee} .jumbotron .h1,.jumbotron h1{color:inherit} .jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200} .jumbotron>hr{border-top-color:#d5d5d5} .container .jumbotron,.container-fluid .jumbotron{border-radius:6px} .jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}} .thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out} .thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto} a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7} .thumbnail .caption{padding:9px;color:#333} .alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px} .alert h4{margin-top:0;color:inherit} .alert .alert-link{font-weight:700} .alert>p,.alert>ul{margin-bottom:0} .alert>p+p{margin-top:5px} .alert-dismissable,.alert-dismissible{padding-right:35px} .alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit} .alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6} .alert-success hr{border-top-color:#c9e2b3} .alert-success .alert-link{color:#2b542c} .alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1} .alert-info hr{border-top-color:#a6e1ec} .alert-info .alert-link{color:#245269} .alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc} .alert-warning hr{border-top-color:#f7e1b5} .alert-warning .alert-link{color:#66512c} .alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1} .alert-danger hr{border-top-color:#e4b9c0} .alert-danger .alert-link{color:#843534} @-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}} @-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}} @keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}} .progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)} .progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease} .progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px} .progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite} .progress-bar-success{background-color:#5cb85c} .progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} .progress-bar-info{background-color:#5bc0de} .progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} .progress-bar-warning{background-color:#f0ad4e} .progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} .progress-bar-danger{background-color:#d9534f} .progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} .media{margin-top:15px} .media:first-child{margin-top:0} .media,.media-body{overflow:hidden;zoom:1} .media-body{width:10000px} .media-object{display:block} .media-object.img-thumbnail{max-width:none} .media-right,.media>.pull-right{padding-left:10px} .media-left,.media>.pull-left{padding-right:10px} .media-body,.media-left,.media-right{display:table-cell;vertical-align:top} .media-middle{vertical-align:middle} .media-bottom{vertical-align:bottom} .media-heading{margin-top:0;margin-bottom:5px} .media-list{padding-left:0;list-style:none} .list-group{padding-left:0;margin-bottom:20px} .list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd} .list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px} .list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px} a.list-group-item,button.list-group-item{color:#555} a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333} a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5} button.list-group-item{width:100%;text-align:left} .list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee} .list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit} .list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777} .list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7} .list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit} .list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef} .list-group-item-success{color:#3c763d;background-color:#dff0d8} a.list-group-item-success,button.list-group-item-success{color:#3c763d} a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit} a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6} a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d} .list-group-item-info{color:#31708f;background-color:#d9edf7} a.list-group-item-info,button.list-group-item-info{color:#31708f} a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit} a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3} a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f} .list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3} a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b} a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit} a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc} a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b} .list-group-item-danger{color:#a94442;background-color:#f2dede} a.list-group-item-danger,button.list-group-item-danger{color:#a94442} a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit} a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc} a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442} .list-group-item-heading{margin-top:0;margin-bottom:5px} .list-group-item-text{margin-bottom:0;line-height:1.3} .panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)} .panel-body{padding:15px} .panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px} .panel-heading>.dropdown .dropdown-toggle{color:inherit} .panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit} .panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit} .panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px} .panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0} .panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0} .panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px} .panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px} .panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0} .panel-heading+.list-group .list-group-item:first-child{border-top-width:0} .list-group+.panel-footer{border-top-width:0} .panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0} .panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px} .panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px} .panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px} .panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px} .panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px} .panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px} .panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px} .panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px} .panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px} .panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd} .panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0} .panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0} .panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0} .panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0} .panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0} .panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0} .panel>.table-responsive{margin-bottom:0;border:0} .panel-group{margin-bottom:20px} .panel-group .panel{margin-bottom:0;border-radius:4px} .panel-group .panel+.panel{margin-top:5px} .panel-group .panel-heading{border-bottom:0} .panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd} .panel-group .panel-footer{border-top:0} .panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd} .panel-default{border-color:#ddd} .panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd} .panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd} .panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333} .panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd} .panel-primary{border-color:#337ab7} .panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7} .panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7} .panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff} .panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7} .panel-success{border-color:#d6e9c6} .panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6} .panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6} .panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d} .panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6} .panel-info{border-color:#bce8f1} .panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1} .panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1} .panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f} .panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1} .panel-warning{border-color:#faebcc} .panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc} .panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc} .panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b} .panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc} .panel-danger{border-color:#ebccd1} .panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1} .panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1} .panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442} .panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1} .embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden} .embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0} .embed-responsive-16by9{padding-bottom:56.25%} .embed-responsive-4by3{padding-bottom:75%} .well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)} .well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)} .well-lg{padding:24px;border-radius:6px} .well-sm{padding:9px;border-radius:3px} .close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2} .close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5} button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0} .modal-open{overflow:hidden} .modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0} .modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)} .modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)} .modal-open .modal{overflow-x:hidden;overflow-y:auto} .modal-dialog{position:relative;width:auto;margin:10px} .modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)} .modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000} .modal-backdrop.fade{filter:alpha(opacity=0);opacity:0} .modal-backdrop.in{filter:alpha(opacity=50);opacity:.5} .modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5} .modal-header .close{margin-top:-2px} .modal-title{margin:0;line-height:1.42857143} .modal-body{position:relative;padding:15px} .modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5} .modal-footer .btn+.btn{margin-bottom:0;margin-left:5px} .modal-footer .btn-group .btn+.btn{margin-left:-1px} .modal-footer .btn-block+.btn-block{margin-left:0} .modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}} .tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto} .tooltip.in{filter:alpha(opacity=90);opacity:.9} .tooltip.top{padding:5px 0;margin-top:-3px} .tooltip.right{padding:0 5px;margin-left:3px} .tooltip.bottom{padding:5px 0;margin-top:3px} .tooltip.left{padding:0 5px;margin-left:-3px} .tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px} .tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid} .tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000} .tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000} .tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000} .tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000} .tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000} .tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000} .tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000} .tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000} .popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto} .popover.top{margin-top:-10px} .popover.right{margin-left:10px} .popover.bottom{margin-top:10px} .popover.left{margin-left:-10px} .popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0} .popover-content{padding:9px 14px} .popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid} .popover>.arrow{border-width:11px} .popover>.arrow:after{content:"";border-width:10px} .popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0} .popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0} .popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0} .popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0} .popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)} .popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff} .popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)} .popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff} .carousel{position:relative} .carousel-inner{position:relative;width:100%;overflow:hidden} .carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left} .carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}} .carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block} .carousel-inner>.active{left:0} .carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%} .carousel-inner>.next{left:100%} .carousel-inner>.prev{left:-100%} .carousel-inner>.next.left,.carousel-inner>.prev.right{left:0} .carousel-inner>.active.left{left:-100%} .carousel-inner>.active.right{left:100%} .carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5} .carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x} .carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x} .carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9} .carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px} .carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px} .carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px} .carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1} .carousel-control .icon-prev:before{content:'\2039'} .carousel-control .icon-next:before{content:'\203a'} .carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none} .carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px} .carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff} .carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)} .carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}} .btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "} .btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both} .center-block{display:block;margin-right:auto;margin-left:auto} .pull-right{float:right!important} .pull-left{float:left!important} .hide{display:none!important} .show{display:block!important} .invisible{visibility:hidden} .text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0} .hidden{display:none!important} .affix{position:fixed}@-ms-viewport{width:device-width} .visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important} .visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}} .visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}} .visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}} .visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}} .visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.eot b/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000..b93a495 Binary files /dev/null and b/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.eot differ diff --git a/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.svg b/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 0000000..94fb549 --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.ttf b/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000..1413fc6 Binary files /dev/null and b/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.ttf differ diff --git a/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.woff b/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000..9e61285 Binary files /dev/null and b/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.woff differ diff --git a/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.woff2 b/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000..64539b5 Binary files /dev/null and b/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.woff2 differ diff --git a/public/vendor/laravel-admin/AdminLTE/bootstrap/js/bootstrap.min.js b/public/vendor/laravel-admin/AdminLTE/bootstrap/js/bootstrap.min.js new file mode 100644 index 0000000..c8f1c68 --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/bootstrap/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.4 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.4",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.4",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.4",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.4",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.4",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport),this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c&&c.$tip&&c.$tip.is(":visible")?void(c.hoverState="in"):(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.options.container?a(this.options.container):this.$element.parent(),p=this.getPosition(o);h="bottom"==h&&k.bottom+m>p.bottom?"top":"top"==h&&k.top-mp.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type)})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.4",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.4",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.4",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=a(document.body).height();"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/dist/css/AdminLTE.min.css b/public/vendor/laravel-admin/AdminLTE/dist/css/AdminLTE.min.css new file mode 100644 index 0000000..c5daa2e --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/dist/css/AdminLTE.min.css @@ -0,0 +1,7 @@ +/*! + * AdminLTE v2.3.2 + * Author: Almsaeed Studio + * Website: Almsaeed Studio + * License: Open source - MIT + * Please visit http://opensource.org/licenses/MIT for more information +!*/html,body{min-height:100%}.layout-boxed html,.layout-boxed body{height:100%}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:400;overflow-x:hidden;overflow-y:auto}.wrapper{min-height:100%;position:relative;overflow:hidden}.wrapper:before,.wrapper:after{content:" ";display:table}.wrapper:after{clear:both}.layout-boxed .wrapper{max-width:1250px;margin:0 auto;min-height:100%;box-shadow:0 0 8px rgba(0,0,0,0.5);position:relative}.layout-boxed{background:url('../img/boxed-bg.jpg') repeat fixed} .content-wrapper,.right-side,.main-footer{-webkit-transition:-webkit-transform .3s ease-in-out,margin .3s ease-in-out;-moz-transition:-moz-transform .3s ease-in-out,margin .3s ease-in-out;-o-transition:-o-transform .3s ease-in-out,margin .3s ease-in-out;transition:transform .3s ease-in-out,margin .3s ease-in-out;margin-left:230px;z-index:820} .layout-top-nav .content-wrapper,.layout-top-nav .right-side,.layout-top-nav .main-footer{margin-left:0}@media (max-width:767px){.content-wrapper,.right-side,.main-footer{margin-left:0}}@media (min-width:768px){.sidebar-collapse .content-wrapper,.sidebar-collapse .right-side,.sidebar-collapse .main-footer{margin-left:0}}@media (max-width:767px){.sidebar-open .content-wrapper,.sidebar-open .right-side,.sidebar-open .main-footer{-webkit-transform:translate(230px, 0);-ms-transform:translate(230px, 0);-o-transform:translate(230px, 0);transform:translate(230px, 0)}} .content-wrapper,.right-side{min-height:100%;background-color:#ecf0f5;z-index:800} .main-footer{background:#fff;padding:15px;color:#444;border-top:1px solid #d2d6de} .fixed .main-header,.fixed .main-sidebar,.fixed .left-side{position:fixed} .fixed .main-header{top:0;right:0;left:0} .fixed .content-wrapper,.fixed .right-side{padding-top:50px}@media (max-width:767px){.fixed .content-wrapper,.fixed .right-side{padding-top:100px}} .fixed.layout-boxed .wrapper{max-width:100%} body.hold-transition .content-wrapper,body.hold-transition .right-side,body.hold-transition .main-footer,body.hold-transition .main-sidebar,body.hold-transition .left-side,body.hold-transition .main-header>.navbar,body.hold-transition .main-header .logo{-webkit-transition:none;-o-transition:none;transition:none} .content{min-height:250px;padding:15px;margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px} h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:'Source Sans Pro',sans-serif} a{color:#3c8dbc} a:hover,a:active,a:focus{outline:none;text-decoration:none;color:#72afd2} .page-header{margin:10px 0 20px 0;font-size:22px} .page-header>small{color:#666;display:block;margin-top:5px} .main-header{position:relative;max-height:100px;z-index:1030} .main-header>.navbar{-webkit-transition:margin-left .3s ease-in-out;-o-transition:margin-left .3s ease-in-out;transition:margin-left .3s ease-in-out;margin-bottom:0;margin-left:230px;border:none;min-height:50px;border-radius:0} .layout-top-nav .main-header>.navbar{margin-left:0} .main-header #navbar-search-input.form-control{background:rgba(255,255,255,0.2);border-color:transparent} .main-header #navbar-search-input.form-control:focus,.main-header #navbar-search-input.form-control:active{border-color:rgba(0,0,0,0.1);background:rgba(255,255,255,0.9)} .main-header #navbar-search-input.form-control::-moz-placeholder{color:#ccc;opacity:1} .main-header #navbar-search-input.form-control:-ms-input-placeholder{color:#ccc} .main-header #navbar-search-input.form-control::-webkit-input-placeholder{color:#ccc} .main-header .navbar-custom-menu,.main-header .navbar-right{float:right}@media (max-width:991px){.main-header .navbar-custom-menu a,.main-header .navbar-right a{color:inherit;background:transparent}}@media (max-width:767px){.main-header .navbar-right{float:none}.navbar-collapse .main-header .navbar-right{margin:7.5px -15px}.main-header .navbar-right>li{color:inherit;border:0}} .main-header .sidebar-toggle{float:left;background-color:transparent;background-image:none;padding:15px 15px;font-family:fontAwesome} .main-header .sidebar-toggle:before{content:"\f0c9"} .main-header .sidebar-toggle:hover{color:#fff} .main-header .sidebar-toggle:focus,.main-header .sidebar-toggle:active{background:transparent} .main-header .sidebar-toggle .icon-bar{display:none} .main-header .navbar .nav>li.user>a>.fa,.main-header .navbar .nav>li.user>a>.glyphicon,.main-header .navbar .nav>li.user>a>.ion{margin-right:5px} .main-header .navbar .nav>li>a>.label{position:absolute;top:9px;right:7px;text-align:center;font-size:9px;padding:2px 3px;line-height:.9} .main-header .logo{-webkit-transition:width .3s ease-in-out;-o-transition:width .3s ease-in-out;transition:width .3s ease-in-out;display:block;float:left;height:50px;font-size:20px;line-height:50px;text-align:center;width:230px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;padding:0 15px;font-weight:300;overflow:hidden} .main-header .logo .logo-lg{display:block} .main-header .logo .logo-mini{display:none} .main-header .navbar-brand{color:#fff} .content-header{position:relative;padding:15px 15px 0 15px} .content-header>h1{margin:0;font-size:24px} .content-header>h1>small{font-size:15px;display:inline-block;padding-left:4px;font-weight:300} .content-header>.breadcrumb{float:right;background:transparent;margin-top:0;margin-bottom:0;font-size:12px;padding:7px 5px;position:absolute;top:15px;right:10px;border-radius:2px} .content-header>.breadcrumb>li>a{color:#444;text-decoration:none;display:inline-block} .content-header>.breadcrumb>li>a>.fa,.content-header>.breadcrumb>li>a>.glyphicon,.content-header>.breadcrumb>li>a>.ion{margin-right:5px} .content-header>.breadcrumb>li+li:before{content:'>\00a0'}@media (max-width:991px){.content-header>.breadcrumb{position:relative;margin-top:5px;top:0;right:0;float:none;background:#d2d6de;padding-left:10px}.content-header>.breadcrumb li:before{color:#97a0b3}} .navbar-toggle{color:#fff;border:0;margin:0;padding:15px 15px}@media (max-width:991px){.navbar-custom-menu .navbar-nav>li{float:left}.navbar-custom-menu .navbar-nav{margin:0;float:left}.navbar-custom-menu .navbar-nav>li>a{padding-top:15px;padding-bottom:15px;line-height:20px}}@media (max-width:767px){.main-header{position:relative}.main-header .logo,.main-header .navbar{width:100%;float:none}.main-header .navbar{margin:0}.main-header .navbar-custom-menu{float:right}}@media (max-width:991px){.navbar-collapse.pull-left{float:none !important}.navbar-collapse.pull-left+.navbar-custom-menu{display:block;position:absolute;top:0;right:40px}} .main-sidebar,.left-side{position:absolute;top:0;left:0;padding-top:50px;min-height:100%;width:230px;z-index:810;-webkit-transition:-webkit-transform .3s ease-in-out,width .3s ease-in-out;-moz-transition:-moz-transform .3s ease-in-out,width .3s ease-in-out;-o-transition:-o-transform .3s ease-in-out,width .3s ease-in-out;transition:transform .3s ease-in-out,width .3s ease-in-out}@media (max-width:767px){.main-sidebar,.left-side{padding-top:100px}}@media (max-width:767px){.main-sidebar,.left-side{-webkit-transform:translate(-230px, 0);-ms-transform:translate(-230px, 0);-o-transform:translate(-230px, 0);transform:translate(-230px, 0)}}@media (min-width:768px){.sidebar-collapse .main-sidebar,.sidebar-collapse .left-side{-webkit-transform:translate(-230px, 0);-ms-transform:translate(-230px, 0);-o-transform:translate(-230px, 0);transform:translate(-230px, 0)}}@media (max-width:767px){.sidebar-open .main-sidebar,.sidebar-open .left-side{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}} .sidebar{padding-bottom:10px} .sidebar-form input:focus{border-color:transparent} .user-panel{position:relative;width:100%;padding:10px;overflow:hidden} .user-panel:before,.user-panel:after{content:" ";display:table} .user-panel:after{clear:both} .user-panel>.image>img{width:100%;max-width:45px;height:auto} .user-panel>.info{padding:5px 5px 5px 15px;line-height:1;position:absolute;left:55px} .user-panel>.info>p{font-weight:600;margin-bottom:9px} .user-panel>.info>a{text-decoration:none;padding-right:5px;margin-top:3px;font-size:11px} .user-panel>.info>a>.fa,.user-panel>.info>a>.ion,.user-panel>.info>a>.glyphicon{margin-right:3px} .sidebar-menu{list-style:none;margin:0;padding:0} .sidebar-menu>li{position:relative;margin:0;padding:0} .sidebar-menu>li>a{padding:12px 5px 12px 15px;display:block} .sidebar-menu>li>a>.fa,.sidebar-menu>li>a>.glyphicon,.sidebar-menu>li>a>.ion{width:20px} .sidebar-menu>li .label,.sidebar-menu>li .badge{margin-top:3px;margin-right:5px} .sidebar-menu li.header{padding:10px 25px 10px 15px;font-size:12px} .sidebar-menu li>a>.fa-angle-left{width:auto;height:auto;padding:0;margin-right:10px;margin-top:3px} .sidebar-menu li.active>a>.fa-angle-left{-webkit-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)} .sidebar-menu li.active>.treeview-menu{display:block} .sidebar-menu .treeview-menu{display:none;list-style:none;padding:0;margin:0;padding-left:5px} .sidebar-menu .treeview-menu .treeview-menu{padding-left:20px} .sidebar-menu .treeview-menu>li{margin:0} .sidebar-menu .treeview-menu>li>a{padding:5px 5px 5px 15px;display:block;font-size:14px} .sidebar-menu .treeview-menu>li>a>.fa,.sidebar-menu .treeview-menu>li>a>.glyphicon,.sidebar-menu .treeview-menu>li>a>.ion{width:20px} .sidebar-menu .treeview-menu>li>a>.fa-angle-left,.sidebar-menu .treeview-menu>li>a>.fa-angle-down{width:auto}@media (min-width:768px){.sidebar-mini.sidebar-collapse .content-wrapper,.sidebar-mini.sidebar-collapse .right-side,.sidebar-mini.sidebar-collapse .main-footer{margin-left:50px !important;z-index:840}.sidebar-mini.sidebar-collapse .main-sidebar{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0);width:50px !important;z-index:850}.sidebar-mini.sidebar-collapse .sidebar-menu>li{position:relative}.sidebar-mini.sidebar-collapse .sidebar-menu>li>a{margin-right:0}.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>span{border-top-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li:not(.treeview)>a>span{border-bottom-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{padding-top:5px;padding-bottom:5px;border-bottom-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>a>span:not(.pull-right),.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>.treeview-menu{display:block !important;position:absolute;width:180px;left:50px}.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>a>span{top:0;margin-left:-3px;padding:12px 5px 12px 20px;background-color:inherit}.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>.treeview-menu{top:44px;margin-left:0}.sidebar-mini.sidebar-collapse .main-sidebar .user-panel>.info,.sidebar-mini.sidebar-collapse .sidebar-form,.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>span,.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu,.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>.pull-right,.sidebar-mini.sidebar-collapse .sidebar-menu li.header{display:none !important;-webkit-transform:translateZ(0)}.sidebar-mini.sidebar-collapse .main-header .logo{width:50px}.sidebar-mini.sidebar-collapse .main-header .logo>.logo-mini{display:block;margin-left:-15px;margin-right:-15px;font-size:18px}.sidebar-mini.sidebar-collapse .main-header .logo>.logo-lg{display:none}.sidebar-mini.sidebar-collapse .main-header .navbar{margin-left:50px}} .sidebar-menu,.main-sidebar .user-panel,.sidebar-menu>li.header{white-space:nowrap;overflow:hidden} .sidebar-menu:hover{overflow:visible} .sidebar-form,.sidebar-menu>li.header{overflow:hidden;text-overflow:clip} .sidebar-menu li>a{position:relative} .sidebar-menu li>a>.pull-right{position:absolute;right:10px;top:50%;margin-top:-7px} .control-sidebar-bg{position:fixed;z-index:1000;bottom:0} .control-sidebar-bg,.control-sidebar{top:0;right:-230px;width:230px;-webkit-transition:right .3s ease-in-out;-o-transition:right .3s ease-in-out;transition:right .3s ease-in-out} .control-sidebar{position:absolute;padding-top:50px;z-index:1010}@media (max-width:768px){.control-sidebar{padding-top:100px}} .control-sidebar>.tab-content{padding:10px 15px} .control-sidebar.control-sidebar-open,.control-sidebar.control-sidebar-open+.control-sidebar-bg{right:0} .control-sidebar-open .control-sidebar-bg,.control-sidebar-open .control-sidebar{right:0}@media (min-width:768px){.control-sidebar-open .content-wrapper,.control-sidebar-open .right-side,.control-sidebar-open .main-footer{margin-right:230px}} .nav-tabs.control-sidebar-tabs>li:first-of-type>a,.nav-tabs.control-sidebar-tabs>li:first-of-type>a:hover,.nav-tabs.control-sidebar-tabs>li:first-of-type>a:focus{border-left-width:0} .nav-tabs.control-sidebar-tabs>li>a{border-radius:0} .nav-tabs.control-sidebar-tabs>li>a,.nav-tabs.control-sidebar-tabs>li>a:hover{border-top:none;border-right:none;border-left:1px solid transparent;border-bottom:1px solid transparent} .nav-tabs.control-sidebar-tabs>li>a .icon{font-size:16px} .nav-tabs.control-sidebar-tabs>li.active>a,.nav-tabs.control-sidebar-tabs>li.active>a:hover,.nav-tabs.control-sidebar-tabs>li.active>a:focus,.nav-tabs.control-sidebar-tabs>li.active>a:active{border-top:none;border-right:none;border-bottom:none}@media (max-width:768px){.nav-tabs.control-sidebar-tabs{display:table}.nav-tabs.control-sidebar-tabs>li{display:table-cell}} .control-sidebar-heading{font-weight:400;font-size:16px;padding:10px 0;margin-bottom:10px} .control-sidebar-subheading{display:block;font-weight:400;font-size:14px} .control-sidebar-menu{list-style:none;padding:0;margin:0 -15px} .control-sidebar-menu>li>a{display:block;padding:10px 15px} .control-sidebar-menu>li>a:before,.control-sidebar-menu>li>a:after{content:" ";display:table} .control-sidebar-menu>li>a:after{clear:both} .control-sidebar-menu>li>a>.control-sidebar-subheading{margin-top:0} .control-sidebar-menu .menu-icon{float:left;width:35px;height:35px;border-radius:50%;text-align:center;line-height:35px} .control-sidebar-menu .menu-info{margin-left:45px;margin-top:3px} .control-sidebar-menu .menu-info>.control-sidebar-subheading{margin:0} .control-sidebar-menu .menu-info>p{margin:0;font-size:11px} .control-sidebar-menu .progress{margin:0} .control-sidebar-dark{color:#b8c7ce} .control-sidebar-dark,.control-sidebar-dark+.control-sidebar-bg{background:#222d32} .control-sidebar-dark .nav-tabs.control-sidebar-tabs{border-bottom:#1c2529} .control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a{background:#181f23;color:#b8c7ce} .control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:focus{border-left-color:#141a1d;border-bottom-color:#141a1d} .control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:focus,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:active{background:#1c2529} .control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover{color:#fff} .control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:focus,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:active{background:#222d32;color:#fff} .control-sidebar-dark .control-sidebar-heading,.control-sidebar-dark .control-sidebar-subheading{color:#fff} .control-sidebar-dark .control-sidebar-menu>li>a:hover{background:#1e282c} .control-sidebar-dark .control-sidebar-menu>li>a .menu-info>p{color:#b8c7ce} .control-sidebar-light{color:#5e5e5e} .control-sidebar-light,.control-sidebar-light+.control-sidebar-bg{background:#f9fafc;border-left:1px solid #d2d6de} .control-sidebar-light .nav-tabs.control-sidebar-tabs{border-bottom:#d2d6de} .control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a{background:#e8ecf4;color:#444} .control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:focus{border-left-color:#d2d6de;border-bottom-color:#d2d6de} .control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:focus,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:active{background:#eff1f7} .control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:focus,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:active{background:#f9fafc;color:#111} .control-sidebar-light .control-sidebar-heading,.control-sidebar-light .control-sidebar-subheading{color:#111} .control-sidebar-light .control-sidebar-menu{margin-left:-14px} .control-sidebar-light .control-sidebar-menu>li>a:hover{background:#f4f4f5} .control-sidebar-light .control-sidebar-menu>li>a .menu-info>p{color:#5e5e5e} .dropdown-menu{box-shadow:none;border-color:#eee} .dropdown-menu>li>a{color:#777} .dropdown-menu>li>a>.glyphicon,.dropdown-menu>li>a>.fa,.dropdown-menu>li>a>.ion{margin-right:10px} .dropdown-menu>li>a:hover{background-color:#e1e3e9;color:#333} .dropdown-menu>.divider{background-color:#eee} .navbar-nav>.notifications-menu>.dropdown-menu,.navbar-nav>.messages-menu>.dropdown-menu,.navbar-nav>.tasks-menu>.dropdown-menu{width:280px;padding:0 0 0 0;margin:0;top:100%} .navbar-nav>.notifications-menu>.dropdown-menu>li,.navbar-nav>.messages-menu>.dropdown-menu>li,.navbar-nav>.tasks-menu>.dropdown-menu>li{position:relative} .navbar-nav>.notifications-menu>.dropdown-menu>li.header,.navbar-nav>.messages-menu>.dropdown-menu>li.header,.navbar-nav>.tasks-menu>.dropdown-menu>li.header{border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0;background-color:#ffffff;padding:7px 10px;border-bottom:1px solid #f4f4f4;color:#444444;font-size:14px} .navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px;font-size:12px;background-color:#fff;padding:7px 10px;border-bottom:1px solid #eeeeee;color:#444 !important;text-align:center}@media (max-width:991px){.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a{background:#fff !important;color:#444 !important}} .navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a:hover,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a:hover,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a:hover{text-decoration:none;font-weight:normal} .navbar-nav>.notifications-menu>.dropdown-menu>li .menu,.navbar-nav>.messages-menu>.dropdown-menu>li .menu,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu{max-height:200px;margin:0;padding:0;list-style:none;overflow-x:hidden} .navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a{display:block;white-space:nowrap;border-bottom:1px solid #f4f4f4} .navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a:hover,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:hover,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a:hover{background:#f4f4f4;text-decoration:none} .navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a{color:#444444;overflow:hidden;text-overflow:ellipsis;padding:10px} .navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.glyphicon,.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.fa,.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.ion{width:20px} .navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a{margin:0;padding:10px 10px} .navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>div>img{margin:auto 10px auto auto;width:40px;height:40px} .navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>h4{padding:0;margin:0 0 0 45px;color:#444444;font-size:15px;position:relative} .navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>h4>small{color:#999999;font-size:10px;position:absolute;top:0;right:0} .navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>p{margin:0 0 0 45px;font-size:12px;color:#888888} .navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:before,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:after{content:" ";display:table} .navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:after{clear:both} .navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a{padding:10px} .navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a>h3{font-size:14px;padding:0;margin:0 0 10px 0;color:#666666} .navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a>.progress{padding:0;margin:0} .navbar-nav>.user-menu>.dropdown-menu{border-top-right-radius:0;border-top-left-radius:0;padding:1px 0 0 0;border-top-width:0;width:280px} .navbar-nav>.user-menu>.dropdown-menu,.navbar-nav>.user-menu>.dropdown-menu>.user-body{border-bottom-right-radius:4px;border-bottom-left-radius:4px} .navbar-nav>.user-menu>.dropdown-menu>li.user-header{height:175px;padding:10px;text-align:center} .navbar-nav>.user-menu>.dropdown-menu>li.user-header>img{z-index:5;height:90px;width:90px;border:3px solid;border-color:transparent;border-color:rgba(255,255,255,0.2)} .navbar-nav>.user-menu>.dropdown-menu>li.user-header>p{z-index:5;color:#fff;color:rgba(255,255,255,0.8);font-size:17px;margin-top:10px} .navbar-nav>.user-menu>.dropdown-menu>li.user-header>p>small{display:block;font-size:12px} .navbar-nav>.user-menu>.dropdown-menu>.user-body{padding:15px;border-bottom:1px solid #f4f4f4;border-top:1px solid #dddddd} .navbar-nav>.user-menu>.dropdown-menu>.user-body:before,.navbar-nav>.user-menu>.dropdown-menu>.user-body:after{content:" ";display:table} .navbar-nav>.user-menu>.dropdown-menu>.user-body:after{clear:both} .navbar-nav>.user-menu>.dropdown-menu>.user-body a{color:#444 !important}@media (max-width:991px){.navbar-nav>.user-menu>.dropdown-menu>.user-body a{background:#fff !important;color:#444 !important}} .navbar-nav>.user-menu>.dropdown-menu>.user-footer{background-color:#f9f9f9;padding:10px} .navbar-nav>.user-menu>.dropdown-menu>.user-footer:before,.navbar-nav>.user-menu>.dropdown-menu>.user-footer:after{content:" ";display:table} .navbar-nav>.user-menu>.dropdown-menu>.user-footer:after{clear:both} .navbar-nav>.user-menu>.dropdown-menu>.user-footer .btn-default{color:#666666}@media (max-width:991px){.navbar-nav>.user-menu>.dropdown-menu>.user-footer .btn-default:hover{background-color:#f9f9f9}} .navbar-nav>.user-menu .user-image{float:left;width:25px;height:25px;border-radius:50%;margin-right:10px;margin-top:-2px}@media (max-width:767px){.navbar-nav>.user-menu .user-image{float:none;margin-right:0;margin-top:-8px;line-height:10px}} .open:not(.dropup)>.animated-dropdown-menu{backface-visibility:visible !important;-webkit-animation:flipInX .7s both;-o-animation:flipInX .7s both;animation:flipInX .7s both} @keyframes flipInX{0%{transform:perspective(400px) rotate3d(1, 0, 0, 90deg);transition-timing-function:ease-in;opacity:0}40%{transform:perspective(400px) rotate3d(1, 0, 0, -20deg);transition-timing-function:ease-in}60%{transform:perspective(400px) rotate3d(1, 0, 0, 10deg);opacity:1}80%{transform:perspective(400px) rotate3d(1, 0, 0, -5deg)}100%{transform:perspective(400px)}} @-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, 90deg);-webkit-transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, -20deg);-webkit-transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, 10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, -5deg)}100%{-webkit-transform:perspective(400px)}} .navbar-custom-menu>.navbar-nav>li{position:relative} .navbar-custom-menu>.navbar-nav>li>.dropdown-menu{position:absolute;right:0;left:auto}@media (max-width:991px){.navbar-custom-menu>.navbar-nav{float:right}.navbar-custom-menu>.navbar-nav>li{position:static}.navbar-custom-menu>.navbar-nav>li>.dropdown-menu{position:absolute;right:5%;left:auto;border:1px solid #ddd;background:#fff}} .form-control{border-radius:0;box-shadow:none;border-color:#d2d6de} .form-control:focus{border-color:#3c8dbc;box-shadow:none} .form-control::-moz-placeholder,.form-control:-ms-input-placeholder,.form-control::-webkit-input-placeholder{color:#bbb;opacity:1} .form-control:not(select){-webkit-appearance:none;-moz-appearance:none;appearance:none} .form-group.has-success label{color:#00a65a} .form-group.has-success .form-control{border-color:#00a65a;box-shadow:none} .form-group.has-warning label{color:#f39c12} .form-group.has-warning .form-control{border-color:#f39c12;box-shadow:none} .form-group.has-error label{color:#dd4b39} .form-group.has-error .form-control{border-color:#dd4b39;box-shadow:none} .input-group .input-group-addon{border-radius:0;border-color:#d2d6de;background-color:#fff} .btn-group-vertical .btn.btn-flat:first-of-type,.btn-group-vertical .btn.btn-flat:last-of-type{border-radius:0} .icheck>label{padding-left:0} .form-control-feedback.fa{line-height:34px} .input-lg+.form-control-feedback.fa,.input-group-lg+.form-control-feedback.fa,.form-group-lg .form-control+.form-control-feedback.fa{line-height:46px} .input-sm+.form-control-feedback.fa,.input-group-sm+.form-control-feedback.fa,.form-group-sm .form-control+.form-control-feedback.fa{line-height:30px} .progress,.progress>.progress-bar{-webkit-box-shadow:none;box-shadow:none} .progress,.progress>.progress-bar,.progress .progress-bar,.progress>.progress-bar .progress-bar{border-radius:1px} .progress.sm,.progress-sm{height:10px} .progress.sm,.progress-sm,.progress.sm .progress-bar,.progress-sm .progress-bar{border-radius:1px} .progress.xs,.progress-xs{height:7px} .progress.xs,.progress-xs,.progress.xs .progress-bar,.progress-xs .progress-bar{border-radius:1px} .progress.xxs,.progress-xxs{height:3px} .progress.xxs,.progress-xxs,.progress.xxs .progress-bar,.progress-xxs .progress-bar{border-radius:1px} .progress.vertical{position:relative;width:30px;height:200px;display:inline-block;margin-right:10px} .progress.vertical>.progress-bar{width:100%;position:absolute;bottom:0} .progress.vertical.sm,.progress.vertical.progress-sm{width:20px} .progress.vertical.xs,.progress.vertical.progress-xs{width:10px} .progress.vertical.xxs,.progress.vertical.progress-xxs{width:3px} .progress-group .progress-text{font-weight:600} .progress-group .progress-number{float:right} .table tr>td .progress{margin:0} .progress-bar-light-blue,.progress-bar-primary{background-color:#3c8dbc} .progress-striped .progress-bar-light-blue,.progress-striped .progress-bar-primary{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)} .progress-bar-green,.progress-bar-success{background-color:#00a65a} .progress-striped .progress-bar-green,.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)} .progress-bar-aqua,.progress-bar-info{background-color:#00c0ef} .progress-striped .progress-bar-aqua,.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)} .progress-bar-yellow,.progress-bar-warning{background-color:#f39c12} .progress-striped .progress-bar-yellow,.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)} .progress-bar-red,.progress-bar-danger{background-color:#dd4b39} .progress-striped .progress-bar-red,.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)} .small-box{border-radius:2px;position:relative;display:block;margin-bottom:20px;box-shadow:0 1px 1px rgba(0,0,0,0.1)} .small-box>.inner{padding:10px} .small-box>.small-box-footer{position:relative;text-align:center;padding:3px 0;color:#fff;color:rgba(255,255,255,0.8);display:block;z-index:10;background:rgba(0,0,0,0.1);text-decoration:none} .small-box>.small-box-footer:hover{color:#fff;background:rgba(0,0,0,0.15)} .small-box h3{font-size:38px;font-weight:bold;margin:0 0 10px 0;white-space:nowrap;padding:0} .small-box p{font-size:15px} .small-box p>small{display:block;color:#f9f9f9;font-size:13px;margin-top:5px} .small-box h3,.small-box p{z-index:5px} .small-box .icon{-webkit-transition:all .3s linear;-o-transition:all .3s linear;transition:all .3s linear;position:absolute;top:-10px;right:10px;z-index:0;font-size:90px;color:rgba(0,0,0,0.15)} .small-box:hover{text-decoration:none;color:#f9f9f9} .small-box:hover .icon{font-size:95px}@media (max-width:767px){.small-box{text-align:center}.small-box .icon{display:none}.small-box p{font-size:12px}} .box{position:relative;border-radius:3px;background:#ffffff;border-top:3px solid #d2d6de;margin-bottom:20px;width:100%;box-shadow:0 1px 1px rgba(0,0,0,0.1)} .box.box-primary{border-top-color:#3c8dbc} .box.box-info{border-top-color:#00c0ef} .box.box-danger{border-top-color:#dd4b39} .box.box-warning{border-top-color:#f39c12} .box.box-success{border-top-color:#00a65a} .box.box-default{border-top-color:#d2d6de} .box.collapsed-box .box-body,.box.collapsed-box .box-footer{display:none} .box .nav-stacked>li{border-bottom:1px solid #f4f4f4;margin:0} .box .nav-stacked>li:last-of-type{border-bottom:none} .box.height-control .box-body{max-height:300px;overflow:auto} .box .border-right{border-right:1px solid #f4f4f4} .box .border-left{border-left:1px solid #f4f4f4} .box.box-solid{border-top:0} .box.box-solid>.box-header .btn.btn-default{background:transparent} .box.box-solid>.box-header .btn:hover,.box.box-solid>.box-header a:hover{background:rgba(0,0,0,0.1)} .box.box-solid.box-default{border:1px solid #d2d6de} .box.box-solid.box-default>.box-header{color:#444;background:#d2d6de;background-color:#d2d6de} .box.box-solid.box-default>.box-header a,.box.box-solid.box-default>.box-header .btn{color:#444} .box.box-solid.box-primary{border:1px solid #3c8dbc} .box.box-solid.box-primary>.box-header{color:#fff;background:#3c8dbc;background-color:#3c8dbc} .box.box-solid.box-primary>.box-header a,.box.box-solid.box-primary>.box-header .btn{color:#fff} .box.box-solid.box-info{border:1px solid #00c0ef} .box.box-solid.box-info>.box-header{color:#fff;background:#00c0ef;background-color:#00c0ef} .box.box-solid.box-info>.box-header a,.box.box-solid.box-info>.box-header .btn{color:#fff} .box.box-solid.box-danger{border:1px solid #dd4b39} .box.box-solid.box-danger>.box-header{color:#fff;background:#dd4b39;background-color:#dd4b39} .box.box-solid.box-danger>.box-header a,.box.box-solid.box-danger>.box-header .btn{color:#fff} .box.box-solid.box-warning{border:1px solid #f39c12} .box.box-solid.box-warning>.box-header{color:#fff;background:#f39c12;background-color:#f39c12} .box.box-solid.box-warning>.box-header a,.box.box-solid.box-warning>.box-header .btn{color:#fff} .box.box-solid.box-success{border:1px solid #00a65a} .box.box-solid.box-success>.box-header{color:#fff;background:#00a65a;background-color:#00a65a} .box.box-solid.box-success>.box-header a,.box.box-solid.box-success>.box-header .btn{color:#fff} .box.box-solid>.box-header>.box-tools .btn{border:0;box-shadow:none} .box.box-solid[class*='bg']>.box-header{color:#fff} .box .box-group>.box{margin-bottom:5px} .box .knob-label{text-align:center;color:#333;font-weight:100;font-size:12px;margin-bottom:0.3em} .box>.overlay,.overlay-wrapper>.overlay,.box>.loading-img,.overlay-wrapper>.loading-img{position:absolute;top:0;left:0;width:100%;height:100%} .box .overlay,.overlay-wrapper .overlay{z-index:50;background:rgba(255,255,255,0.7);border-radius:3px} .box .overlay>.fa,.overlay-wrapper .overlay>.fa{position:absolute;top:50%;left:50%;margin-left:-15px;margin-top:-15px;color:#000;font-size:30px} .box .overlay.dark,.overlay-wrapper .overlay.dark{background:rgba(0,0,0,0.5)} .box-header:before,.box-body:before,.box-footer:before,.box-header:after,.box-body:after,.box-footer:after{content:" ";display:table} .box-header:after,.box-body:after,.box-footer:after{clear:both} .box-header{color:#444;display:block;padding:10px;position:relative} .box-header.with-border{border-bottom:1px solid #f4f4f4} .collapsed-box .box-header.with-border{border-bottom:none} .box-header>.fa,.box-header>.glyphicon,.box-header>.ion,.box-header .box-title{display:inline-block;font-size:18px;margin:0;line-height:1} .box-header>.fa,.box-header>.glyphicon,.box-header>.ion{margin-right:5px} .box-header>.box-tools{position:absolute;right:10px;top:5px} .box-header>.box-tools [data-toggle="tooltip"]{position:relative} .box-header>.box-tools.pull-right .dropdown-menu{right:0;left:auto} .btn-box-tool{padding:5px;font-size:12px;background:transparent;color:#97a0b3} .open .btn-box-tool,.btn-box-tool:hover{color:#606c84} .btn-box-tool.btn:active{box-shadow:none} .box-body{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px;padding:10px} .no-header .box-body{border-top-right-radius:3px;border-top-left-radius:3px} .box-body>.table{margin-bottom:0} .box-body .fc{margin-top:5px} .box-body .full-width-chart{margin:-19px} .box-body.no-padding .full-width-chart{margin:-9px} .box-body .box-pane{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:3px} .box-body .box-pane-right{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:0} .box-footer{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px;border-top:1px solid #f4f4f4;padding:10px;background-color:#fff} .chart-legend{margin:10px 0}@media (max-width:991px){.chart-legend>li{float:left;margin-right:10px}} .box-comments{background:#f7f7f7} .box-comments .box-comment{padding:8px 0;border-bottom:1px solid #eee} .box-comments .box-comment:before,.box-comments .box-comment:after{content:" ";display:table} .box-comments .box-comment:after{clear:both} .box-comments .box-comment:last-of-type{border-bottom:0} .box-comments .box-comment:first-of-type{padding-top:0} .box-comments .box-comment img{float:left} .box-comments .comment-text{margin-left:40px;color:#555} .box-comments .username{color:#444;display:block;font-weight:600} .box-comments .text-muted{font-weight:400;font-size:12px} .todo-list{margin:0;padding:0;list-style:none;overflow:auto} .todo-list>li{border-radius:2px;padding:10px;background:#f4f4f4;margin-bottom:2px;border-left:2px solid #e6e7e8;color:#444} .todo-list>li:last-of-type{margin-bottom:0} .todo-list>li>input[type='checkbox']{margin:0 10px 0 5px} .todo-list>li .text{display:inline-block;margin-left:5px;font-weight:600} .todo-list>li .label{margin-left:10px;font-size:9px} .todo-list>li .tools{display:none;float:right;color:#dd4b39} .todo-list>li .tools>.fa,.todo-list>li .tools>.glyphicon,.todo-list>li .tools>.ion{margin-right:5px;cursor:pointer} .todo-list>li:hover .tools{display:inline-block} .todo-list>li.done{color:#999} .todo-list>li.done .text{text-decoration:line-through;font-weight:500} .todo-list>li.done .label{background:#d2d6de !important} .todo-list .danger{border-left-color:#dd4b39} .todo-list .warning{border-left-color:#f39c12} .todo-list .info{border-left-color:#00c0ef} .todo-list .success{border-left-color:#00a65a} .todo-list .primary{border-left-color:#3c8dbc} .todo-list .handle{display:inline-block;cursor:move;margin:0 5px} .chat{padding:5px 20px 5px 10px} .chat .item{margin-bottom:10px} .chat .item:before,.chat .item:after{content:" ";display:table} .chat .item:after{clear:both} .chat .item>img{width:40px;height:40px;border:2px solid transparent;border-radius:50%} .chat .item>.online{border:2px solid #00a65a} .chat .item>.offline{border:2px solid #dd4b39} .chat .item>.message{margin-left:55px;margin-top:-40px} .chat .item>.message>.name{display:block;font-weight:600} .chat .item>.attachment{border-radius:3px;background:#f4f4f4;margin-left:65px;margin-right:15px;padding:10px} .chat .item>.attachment>h4{margin:0 0 5px 0;font-weight:600;font-size:14px} .chat .item>.attachment>p,.chat .item>.attachment>.filename{font-weight:600;font-size:13px;font-style:italic;margin:0} .chat .item>.attachment:before,.chat .item>.attachment:after{content:" ";display:table} .chat .item>.attachment:after{clear:both} .box-input{max-width:200px} .modal .panel-body{color:#444} .info-box{display:block;min-height:90px;background:#fff;width:100%;box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:2px;margin-bottom:15px} .info-box small{font-size:14px} .info-box .progress{background:rgba(0,0,0,0.2);margin:5px -10px 5px -10px;height:2px} .info-box .progress,.info-box .progress .progress-bar{border-radius:0} .info-box .progress .progress-bar{background:#fff} .info-box-icon{border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px;display:block;float:left;height:90px;width:90px;text-align:center;font-size:45px;line-height:90px;background:rgba(0,0,0,0.2)} .info-box-icon>img{max-width:100%} .info-box-content{padding:5px 10px;margin-left:90px} .info-box-number{display:block;font-weight:bold;font-size:18px} .progress-description,.info-box-text{display:block;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .info-box-text{text-transform:uppercase} .info-box-more{display:block} .progress-description{margin:0} .timeline{position:relative;margin:0 0 30px 0;padding:0;list-style:none} .timeline:before{content:'';position:absolute;top:0;bottom:0;width:4px;background:#ddd;left:31px;margin:0;border-radius:2px} .timeline>li{position:relative;margin-right:10px;margin-bottom:15px} .timeline>li:before,.timeline>li:after{content:" ";display:table} .timeline>li:after{clear:both} .timeline>li>.timeline-item{-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px;margin-top:0;background:#fff;color:#444;margin-left:60px;margin-right:15px;padding:0;position:relative} .timeline>li>.timeline-item>.time{color:#999;float:right;padding:10px;font-size:12px} .timeline>li>.timeline-item>.timeline-header{margin:0;color:#555;border-bottom:1px solid #f4f4f4;padding:10px;font-size:16px;line-height:1.1} .timeline>li>.timeline-item>.timeline-header>a{font-weight:600} .timeline>li>.timeline-item>.timeline-body,.timeline>li>.timeline-item>.timeline-footer{padding:10px} .timeline>li>.fa,.timeline>li>.glyphicon,.timeline>li>.ion{width:30px;height:30px;font-size:15px;line-height:30px;position:absolute;color:#666;background:#d2d6de;border-radius:50%;text-align:center;left:18px;top:0} .timeline>.time-label>span{font-weight:600;padding:5px;display:inline-block;background-color:#fff;border-radius:4px} .timeline-inverse>li>.timeline-item{background:#f0f0f0;border:1px solid #ddd;-webkit-box-shadow:none;box-shadow:none} .timeline-inverse>li>.timeline-item>.timeline-header{border-bottom-color:#ddd} .btn{border-radius:3px;-webkit-box-shadow:none;box-shadow:none;border:1px solid transparent} .btn.uppercase{text-transform:uppercase} .btn.btn-flat{border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;border-width:1px} .btn:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)} .btn:focus{outline:none} .btn.btn-file{position:relative;overflow:hidden} .btn.btn-file>input[type='file']{position:absolute;top:0;right:0;min-width:100%;min-height:100%;font-size:100px;text-align:right;opacity:0;filter:alpha(opacity=0);outline:none;background:white;cursor:inherit;display:block} .btn-default{background-color:#f4f4f4;color:#444;border-color:#ddd} .btn-default:hover,.btn-default:active,.btn-default.hover{background-color:#e7e7e7} .btn-primary{background-color:#3c8dbc;border-color:#367fa9} .btn-primary:hover,.btn-primary:active,.btn-primary.hover{background-color:#367fa9} .btn-success{background-color:#00a65a;border-color:#008d4c} .btn-success:hover,.btn-success:active,.btn-success.hover{background-color:#008d4c} .btn-info{background-color:#00c0ef;border-color:#00acd6} .btn-info:hover,.btn-info:active,.btn-info.hover{background-color:#00acd6} .btn-danger{background-color:#dd4b39;border-color:#d73925} .btn-danger:hover,.btn-danger:active,.btn-danger.hover{background-color:#d73925} .btn-warning{background-color:#f39c12;border-color:#e08e0b} .btn-warning:hover,.btn-warning:active,.btn-warning.hover{background-color:#e08e0b} .btn-outline{border:1px solid #fff;background:transparent;color:#fff} .btn-outline:hover,.btn-outline:focus,.btn-outline:active{color:rgba(255,255,255,0.7);border-color:rgba(255,255,255,0.7)} .btn-link{-webkit-box-shadow:none;box-shadow:none} .btn[class*='bg-']:hover{-webkit-box-shadow:inset 0 0 100px rgba(0,0,0,0.2);box-shadow:inset 0 0 100px rgba(0,0,0,0.2)} .btn-app{border-radius:3px;position:relative;padding:15px 5px;margin:0 0 10px 10px;min-width:80px;height:60px;text-align:center;color:#666;border:1px solid #ddd;background-color:#f4f4f4;font-size:12px} .btn-app>.fa,.btn-app>.glyphicon,.btn-app>.ion{font-size:20px;display:block} .btn-app:hover{background:#f4f4f4;color:#444;border-color:#aaa} .btn-app:active,.btn-app:focus{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)} .btn-app>.badge{position:absolute;top:-3px;right:-10px;font-size:10px;font-weight:400} .callout{border-radius:3px;margin:0 0 20px 0;padding:15px 30px 15px 15px;border-left:5px solid #eee} .callout a{color:#fff;text-decoration:underline} .callout a:hover{color:#eee} .callout h4{margin-top:0;font-weight:600} .callout p:last-child{margin-bottom:0} .callout code,.callout .highlight{background-color:#fff} .callout.callout-danger{border-color:#c23321} .callout.callout-warning{border-color:#c87f0a} .callout.callout-info{border-color:#0097bc} .callout.callout-success{border-color:#00733e} .alert{border-radius:3px} .alert h4{font-weight:600} .alert .icon{margin-right:10px} .alert .close{color:#000;opacity:.2;filter:alpha(opacity=20)} .alert .close:hover{opacity:.5;filter:alpha(opacity=50)} .alert a{color:#fff;text-decoration:underline} .alert-success{border-color:#008d4c} .alert-danger,.alert-error{border-color:#d73925} .alert-warning{border-color:#e08e0b} .alert-info{border-color:#00acd6} .nav>li>a:hover,.nav>li>a:active,.nav>li>a:focus{color:#444;background:#f7f7f7} .nav-pills>li>a{border-radius:0;border-top:3px solid transparent;color:#444} .nav-pills>li>a>.fa,.nav-pills>li>a>.glyphicon,.nav-pills>li>a>.ion{margin-right:5px} .nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{border-top-color:#3c8dbc} .nav-pills>li.active>a{font-weight:600} .nav-stacked>li>a{border-radius:0;border-top:0;border-left:3px solid transparent;color:#444} .nav-stacked>li.active>a,.nav-stacked>li.active>a:hover{background:transparent;color:#444;border-top:0;border-left-color:#3c8dbc} .nav-stacked>li.header{border-bottom:1px solid #ddd;color:#777;margin-bottom:10px;padding:5px 10px;text-transform:uppercase} .nav-tabs-custom{margin-bottom:20px;background:#fff;box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px} .nav-tabs-custom>.nav-tabs{margin:0;border-bottom-color:#f4f4f4;border-top-right-radius:3px;border-top-left-radius:3px} .nav-tabs-custom>.nav-tabs>li{border-top:3px solid transparent;margin-bottom:-2px;margin-right:5px} .nav-tabs-custom>.nav-tabs>li>a{color:#444;border-radius:0} .nav-tabs-custom>.nav-tabs>li>a.text-muted{color:#999} .nav-tabs-custom>.nav-tabs>li>a,.nav-tabs-custom>.nav-tabs>li>a:hover{background:transparent;margin:0} .nav-tabs-custom>.nav-tabs>li>a:hover{color:#999} .nav-tabs-custom>.nav-tabs>li:not(.active)>a:hover,.nav-tabs-custom>.nav-tabs>li:not(.active)>a:focus,.nav-tabs-custom>.nav-tabs>li:not(.active)>a:active{border-color:transparent} .nav-tabs-custom>.nav-tabs>li.active{border-top-color:#3c8dbc} .nav-tabs-custom>.nav-tabs>li.active>a,.nav-tabs-custom>.nav-tabs>li.active:hover>a{background-color:#fff;color:#444} .nav-tabs-custom>.nav-tabs>li.active>a{border-top-color:transparent;border-left-color:#f4f4f4;border-right-color:#f4f4f4} .nav-tabs-custom>.nav-tabs>li:first-of-type{margin-left:0} .nav-tabs-custom>.nav-tabs>li:first-of-type.active>a{border-left-color:transparent} .nav-tabs-custom>.nav-tabs.pull-right{float:none !important} .nav-tabs-custom>.nav-tabs.pull-right>li{float:right} .nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type{margin-right:0} .nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type>a{border-left-width:1px} .nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type.active>a{border-left-color:#f4f4f4;border-right-color:transparent} .nav-tabs-custom>.nav-tabs>li.header{line-height:35px;padding:0 10px;font-size:20px;color:#444} .nav-tabs-custom>.nav-tabs>li.header>.fa,.nav-tabs-custom>.nav-tabs>li.header>.glyphicon,.nav-tabs-custom>.nav-tabs>li.header>.ion{margin-right:5px} .nav-tabs-custom>.tab-content{background:#fff;padding:10px;border-bottom-right-radius:3px;border-bottom-left-radius:3px} .nav-tabs-custom .dropdown.open>a:active,.nav-tabs-custom .dropdown.open>a:focus{background:transparent;color:#999} .nav-tabs-custom.tab-primary>.nav-tabs>li.active{border-top-color:#3c8dbc} .nav-tabs-custom.tab-info>.nav-tabs>li.active{border-top-color:#00c0ef} .nav-tabs-custom.tab-danger>.nav-tabs>li.active{border-top-color:#dd4b39} .nav-tabs-custom.tab-warning>.nav-tabs>li.active{border-top-color:#f39c12} .nav-tabs-custom.tab-success>.nav-tabs>li.active{border-top-color:#00a65a} .nav-tabs-custom.tab-default>.nav-tabs>li.active{border-top-color:#d2d6de} .pagination>li>a{background:#fafafa;color:#666} .pagination.pagination-flat>li>a{border-radius:0 !important} .products-list{list-style:none;margin:0;padding:0} .products-list>.item{border-radius:3px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);padding:10px 0;background:#fff} .products-list>.item:before,.products-list>.item:after{content:" ";display:table} .products-list>.item:after{clear:both} .products-list .product-img{float:left} .products-list .product-img img{width:50px;height:50px} .products-list .product-info{margin-left:60px} .products-list .product-title{font-weight:600} .products-list .product-description{display:block;color:#999;overflow:hidden;white-space:nowrap;text-overflow:ellipsis} .product-list-in-box>.item{-webkit-box-shadow:none;box-shadow:none;border-radius:0;border-bottom:1px solid #f4f4f4} .product-list-in-box>.item:last-of-type{border-bottom-width:0} .table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{border-top:1px solid #f4f4f4} .table>thead>tr>th{border-bottom:2px solid #f4f4f4} .table tr td .progress{margin-top:5px} .table-bordered{border:1px solid #f4f4f4} .table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #f4f4f4} .table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px} .table.no-border,.table.no-border td,.table.no-border th{border:0} table.text-center,table.text-center td,table.text-center th{text-align:center} .table.align th{text-align:left} .table.align td{text-align:right} .label-default{background-color:#d2d6de;color:#444} .direct-chat .box-body{border-bottom-right-radius:0;border-bottom-left-radius:0;position:relative;overflow-x:hidden;padding:0} .direct-chat.chat-pane-open .direct-chat-contacts{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)} .direct-chat-messages{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0);padding:10px;height:250px;overflow:auto} .direct-chat-msg,.direct-chat-text{display:block} .direct-chat-msg{margin-bottom:10px} .direct-chat-msg:before,.direct-chat-msg:after{content:" ";display:table} .direct-chat-msg:after{clear:both} .direct-chat-messages,.direct-chat-contacts{-webkit-transition:-webkit-transform .5s ease-in-out;-moz-transition:-moz-transform .5s ease-in-out;-o-transition:-o-transform .5s ease-in-out;transition:transform .5s ease-in-out} .direct-chat-text{border-radius:5px;position:relative;padding:5px 10px;background:#d2d6de;border:1px solid #d2d6de;margin:5px 0 0 50px;color:#444} .direct-chat-text:after,.direct-chat-text:before{position:absolute;right:100%;top:15px;border:solid transparent;border-right-color:#d2d6de;content:' ';height:0;width:0;pointer-events:none} .direct-chat-text:after{border-width:5px;margin-top:-5px} .direct-chat-text:before{border-width:6px;margin-top:-6px} .right .direct-chat-text{margin-right:50px;margin-left:0} .right .direct-chat-text:after,.right .direct-chat-text:before{right:auto;left:100%;border-right-color:transparent;border-left-color:#d2d6de} .direct-chat-img{border-radius:50%;float:left;width:40px;height:40px} .right .direct-chat-img{float:right} .direct-chat-info{display:block;margin-bottom:2px;font-size:12px} .direct-chat-name{font-weight:600} .direct-chat-timestamp{color:#999} .direct-chat-contacts-open .direct-chat-contacts{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)} .direct-chat-contacts{-webkit-transform:translate(101%, 0);-ms-transform:translate(101%, 0);-o-transform:translate(101%, 0);transform:translate(101%, 0);position:absolute;top:0;bottom:0;height:250px;width:100%;background:#222d32;color:#fff;overflow:auto} .contacts-list>li{border-bottom:1px solid rgba(0,0,0,0.2);padding:10px;margin:0} .contacts-list>li:before,.contacts-list>li:after{content:" ";display:table} .contacts-list>li:after{clear:both} .contacts-list>li:last-of-type{border-bottom:none} .contacts-list-img{border-radius:50%;width:40px;float:left} .contacts-list-info{margin-left:45px;color:#fff} .contacts-list-name,.contacts-list-status{display:block} .contacts-list-name{font-weight:600} .contacts-list-status{font-size:12px} .contacts-list-date{color:#aaa;font-weight:normal} .contacts-list-msg{color:#999} .direct-chat-danger .right>.direct-chat-text{background:#dd4b39;border-color:#dd4b39;color:#fff} .direct-chat-danger .right>.direct-chat-text:after,.direct-chat-danger .right>.direct-chat-text:before{border-left-color:#dd4b39} .direct-chat-primary .right>.direct-chat-text{background:#3c8dbc;border-color:#3c8dbc;color:#fff} .direct-chat-primary .right>.direct-chat-text:after,.direct-chat-primary .right>.direct-chat-text:before{border-left-color:#3c8dbc} .direct-chat-warning .right>.direct-chat-text{background:#f39c12;border-color:#f39c12;color:#fff} .direct-chat-warning .right>.direct-chat-text:after,.direct-chat-warning .right>.direct-chat-text:before{border-left-color:#f39c12} .direct-chat-info .right>.direct-chat-text{background:#00c0ef;border-color:#00c0ef;color:#fff} .direct-chat-info .right>.direct-chat-text:after,.direct-chat-info .right>.direct-chat-text:before{border-left-color:#00c0ef} .direct-chat-success .right>.direct-chat-text{background:#00a65a;border-color:#00a65a;color:#fff} .direct-chat-success .right>.direct-chat-text:after,.direct-chat-success .right>.direct-chat-text:before{border-left-color:#00a65a} .users-list>li{width:25%;float:left;padding:10px;text-align:center} .users-list>li img{border-radius:50%;max-width:100%;height:auto} .users-list>li>a:hover,.users-list>li>a:hover .users-list-name{color:#999} .users-list-name,.users-list-date{display:block} .users-list-name{font-weight:600;color:#444;overflow:hidden;white-space:nowrap;text-overflow:ellipsis} .users-list-date{color:#999;font-size:12px} .carousel-control.left,.carousel-control.right{background-image:none} .carousel-control>.fa{font-size:40px;position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-20px} .modal{background:rgba(0,0,0,0.3)} .modal-content{border-radius:0;-webkit-box-shadow:0 2px 3px rgba(0,0,0,0.125);box-shadow:0 2px 3px rgba(0,0,0,0.125);border:0}@media (min-width:768px){.modal-content{-webkit-box-shadow:0 2px 3px rgba(0,0,0,0.125);box-shadow:0 2px 3px rgba(0,0,0,0.125)}} .modal-header{border-bottom-color:#f4f4f4} .modal-footer{border-top-color:#f4f4f4} .modal-primary .modal-header,.modal-primary .modal-footer{border-color:#307095} .modal-warning .modal-header,.modal-warning .modal-footer{border-color:#c87f0a} .modal-info .modal-header,.modal-info .modal-footer{border-color:#0097bc} .modal-success .modal-header,.modal-success .modal-footer{border-color:#00733e} .modal-danger .modal-header,.modal-danger .modal-footer{border-color:#c23321} .box-widget{border:none;position:relative} .widget-user .widget-user-header{padding:20px;height:120px;border-top-right-radius:3px;border-top-left-radius:3px} .widget-user .widget-user-username{margin-top:0;margin-bottom:5px;font-size:25px;font-weight:300;text-shadow:0 1px 1px rgba(0,0,0,0.2)} .widget-user .widget-user-desc{margin-top:0} .widget-user .widget-user-image{position:absolute;top:65px;left:50%;margin-left:-45px} .widget-user .widget-user-image>img{width:90px;height:auto;border:3px solid #fff} .widget-user .box-footer{padding-top:30px} .widget-user-2 .widget-user-header{padding:20px;border-top-right-radius:3px;border-top-left-radius:3px} .widget-user-2 .widget-user-username{margin-top:5px;margin-bottom:5px;font-size:25px;font-weight:300} .widget-user-2 .widget-user-desc{margin-top:0} .widget-user-2 .widget-user-username,.widget-user-2 .widget-user-desc{margin-left:75px} .widget-user-2 .widget-user-image>img{width:65px;height:auto;float:left} .mailbox-messages>.table{margin:0} .mailbox-controls{padding:5px} .mailbox-controls.with-border{border-bottom:1px solid #f4f4f4} .mailbox-read-info{border-bottom:1px solid #f4f4f4;padding:10px} .mailbox-read-info h3{font-size:20px;margin:0} .mailbox-read-info h5{margin:0;padding:5px 0 0 0} .mailbox-read-time{color:#999;font-size:13px} .mailbox-read-message{padding:10px} .mailbox-attachments li{float:left;width:200px;border:1px solid #eee;margin-bottom:10px;margin-right:10px} .mailbox-attachment-name{font-weight:bold;color:#666} .mailbox-attachment-icon,.mailbox-attachment-info,.mailbox-attachment-size{display:block} .mailbox-attachment-info{padding:10px;background:#f4f4f4} .mailbox-attachment-size{color:#999;font-size:12px} .mailbox-attachment-icon{text-align:center;font-size:65px;color:#666;padding:20px 10px} .mailbox-attachment-icon.has-img{padding:0} .mailbox-attachment-icon.has-img>img{max-width:100%;height:auto} .lockscreen{background:#d2d6de} .lockscreen-logo{font-size:35px;text-align:center;margin-bottom:25px;font-weight:300} .lockscreen-logo a{color:#444} .lockscreen-wrapper{max-width:400px;margin:0 auto;margin-top:10%} .lockscreen .lockscreen-name{text-align:center;font-weight:600} .lockscreen-item{border-radius:4px;padding:0;background:#fff;position:relative;margin:10px auto 30px auto;width:290px} .lockscreen-image{border-radius:50%;position:absolute;left:-10px;top:-25px;background:#fff;padding:5px;z-index:10} .lockscreen-image>img{border-radius:50%;width:70px;height:70px} .lockscreen-credentials{margin-left:70px} .lockscreen-credentials .form-control{border:0} .lockscreen-credentials .btn{background-color:#fff;border:0;padding:0 10px} .lockscreen-footer{margin-top:10px} .login-logo,.register-logo{font-size:35px;text-align:center;margin-bottom:25px;font-weight:300} .login-logo a,.register-logo a{color:#444} .login-page,.register-page{background:#d2d6de} .login-box,.register-box{width:360px;margin:7% auto}@media (max-width:768px){.login-box,.register-box{width:90%;margin-top:20px}} .login-box-body,.register-box-body{background:#fff;padding:20px;border-top:0;color:#666} .login-box-body .form-control-feedback,.register-box-body .form-control-feedback{color:#777} .login-box-msg,.register-box-msg{margin:0;text-align:center;padding:0 20px 20px 20px} .social-auth-links{margin:10px 0} .error-page{width:600px;margin:20px auto 0 auto}@media (max-width:991px){.error-page{width:100%}} .error-page>.headline{float:left;font-size:100px;font-weight:300}@media (max-width:991px){.error-page>.headline{float:none;text-align:center}} .error-page>.error-content{margin-left:190px;display:block}@media (max-width:991px){.error-page>.error-content{margin-left:0}} .error-page>.error-content>h3{font-weight:300;font-size:25px}@media (max-width:991px){.error-page>.error-content>h3{text-align:center}} .invoice{position:relative;background:#fff;border:1px solid #f4f4f4;padding:20px;margin:10px 25px} .invoice-title{margin-top:0} .profile-user-img{margin:0 auto;width:100px;padding:3px;border:3px solid #d2d6de} .profile-username{font-size:21px;margin-top:5px} .post{border-bottom:1px solid #d2d6de;margin-bottom:15px;padding-bottom:15px;color:#666} .post:last-of-type{border-bottom:0;margin-bottom:0;padding-bottom:0} .post .user-block{margin-bottom:15px} .btn-social{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .btn-social>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)} .btn-social.btn-lg{padding-left:61px} .btn-social.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em} .btn-social.btn-sm{padding-left:38px} .btn-social.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em} .btn-social.btn-xs{padding-left:30px} .btn-social.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em} .btn-social-icon{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;height:34px;width:34px;padding:0} .btn-social-icon>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)} .btn-social-icon.btn-lg{padding-left:61px} .btn-social-icon.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em} .btn-social-icon.btn-sm{padding-left:38px} .btn-social-icon.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em} .btn-social-icon.btn-xs{padding-left:30px} .btn-social-icon.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em} .btn-social-icon>:first-child{border:none;text-align:center;width:100%} .btn-social-icon.btn-lg{height:45px;width:45px;padding-left:0;padding-right:0} .btn-social-icon.btn-sm{height:30px;width:30px;padding-left:0;padding-right:0} .btn-social-icon.btn-xs{height:22px;width:22px;padding-left:0;padding-right:0} .btn-adn{color:#fff;background-color:#d87a68;border-color:rgba(0,0,0,0.2)} .btn-adn:focus,.btn-adn.focus{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)} .btn-adn:hover{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)} .btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)} .btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{background-image:none} .btn-adn .badge{color:#d87a68;background-color:#fff} .btn-bitbucket{color:#fff;background-color:#205081;border-color:rgba(0,0,0,0.2)} .btn-bitbucket:focus,.btn-bitbucket.focus{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)} .btn-bitbucket:hover{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)} .btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)} .btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{background-image:none} .btn-bitbucket .badge{color:#205081;background-color:#fff} .btn-dropbox{color:#fff;background-color:#1087dd;border-color:rgba(0,0,0,0.2)} .btn-dropbox:focus,.btn-dropbox.focus{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)} .btn-dropbox:hover{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)} .btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)} .btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{background-image:none} .btn-dropbox .badge{color:#1087dd;background-color:#fff} .btn-facebook{color:#fff;background-color:#3b5998;border-color:rgba(0,0,0,0.2)} .btn-facebook:focus,.btn-facebook.focus{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)} .btn-facebook:hover{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)} .btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)} .btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{background-image:none} .btn-facebook .badge{color:#3b5998;background-color:#fff} .btn-flickr{color:#fff;background-color:#ff0084;border-color:rgba(0,0,0,0.2)} .btn-flickr:focus,.btn-flickr.focus{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)} .btn-flickr:hover{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)} .btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)} .btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{background-image:none} .btn-flickr .badge{color:#ff0084;background-color:#fff} .btn-foursquare{color:#fff;background-color:#f94877;border-color:rgba(0,0,0,0.2)} .btn-foursquare:focus,.btn-foursquare.focus{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)} .btn-foursquare:hover{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)} .btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)} .btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{background-image:none} .btn-foursquare .badge{color:#f94877;background-color:#fff} .btn-github{color:#fff;background-color:#444;border-color:rgba(0,0,0,0.2)} .btn-github:focus,.btn-github.focus{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)} .btn-github:hover{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)} .btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)} .btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{background-image:none} .btn-github .badge{color:#444;background-color:#fff} .btn-google{color:#fff;background-color:#dd4b39;border-color:rgba(0,0,0,0.2)} .btn-google:focus,.btn-google.focus{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)} .btn-google:hover{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)} .btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)} .btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{background-image:none} .btn-google .badge{color:#dd4b39;background-color:#fff} .btn-instagram{color:#fff;background-color:#3f729b;border-color:rgba(0,0,0,0.2)} .btn-instagram:focus,.btn-instagram.focus{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)} .btn-instagram:hover{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)} .btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)} .btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{background-image:none} .btn-instagram .badge{color:#3f729b;background-color:#fff} .btn-linkedin{color:#fff;background-color:#007bb6;border-color:rgba(0,0,0,0.2)} .btn-linkedin:focus,.btn-linkedin.focus{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)} .btn-linkedin:hover{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)} .btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)} .btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{background-image:none} .btn-linkedin .badge{color:#007bb6;background-color:#fff} .btn-microsoft{color:#fff;background-color:#2672ec;border-color:rgba(0,0,0,0.2)} .btn-microsoft:focus,.btn-microsoft.focus{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)} .btn-microsoft:hover{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)} .btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)} .btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{background-image:none} .btn-microsoft .badge{color:#2672ec;background-color:#fff} .btn-openid{color:#fff;background-color:#f7931e;border-color:rgba(0,0,0,0.2)} .btn-openid:focus,.btn-openid.focus{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)} .btn-openid:hover{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)} .btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)} .btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{background-image:none} .btn-openid .badge{color:#f7931e;background-color:#fff} .btn-pinterest{color:#fff;background-color:#cb2027;border-color:rgba(0,0,0,0.2)} .btn-pinterest:focus,.btn-pinterest.focus{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)} .btn-pinterest:hover{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)} .btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)} .btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{background-image:none} .btn-pinterest .badge{color:#cb2027;background-color:#fff} .btn-reddit{color:#000;background-color:#eff7ff;border-color:rgba(0,0,0,0.2)} .btn-reddit:focus,.btn-reddit.focus{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)} .btn-reddit:hover{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)} .btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)} .btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{background-image:none} .btn-reddit .badge{color:#eff7ff;background-color:#000} .btn-soundcloud{color:#fff;background-color:#f50;border-color:rgba(0,0,0,0.2)} .btn-soundcloud:focus,.btn-soundcloud.focus{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)} .btn-soundcloud:hover{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)} .btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)} .btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{background-image:none} .btn-soundcloud .badge{color:#f50;background-color:#fff} .btn-tumblr{color:#fff;background-color:#2c4762;border-color:rgba(0,0,0,0.2)} .btn-tumblr:focus,.btn-tumblr.focus{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)} .btn-tumblr:hover{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)} .btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)} .btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{background-image:none} .btn-tumblr .badge{color:#2c4762;background-color:#fff} .btn-twitter{color:#fff;background-color:#55acee;border-color:rgba(0,0,0,0.2)} .btn-twitter:focus,.btn-twitter.focus{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)} .btn-twitter:hover{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)} .btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)} .btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{background-image:none} .btn-twitter .badge{color:#55acee;background-color:#fff} .btn-vimeo{color:#fff;background-color:#1ab7ea;border-color:rgba(0,0,0,0.2)} .btn-vimeo:focus,.btn-vimeo.focus{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)} .btn-vimeo:hover{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)} .btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)} .btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{background-image:none} .btn-vimeo .badge{color:#1ab7ea;background-color:#fff} .btn-vk{color:#fff;background-color:#587ea3;border-color:rgba(0,0,0,0.2)} .btn-vk:focus,.btn-vk.focus{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)} .btn-vk:hover{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)} .btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)} .btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{background-image:none} .btn-vk .badge{color:#587ea3;background-color:#fff} .btn-yahoo{color:#fff;background-color:#720e9e;border-color:rgba(0,0,0,0.2)} .btn-yahoo:focus,.btn-yahoo.focus{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)} .btn-yahoo:hover{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)} .btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)} .btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{background-image:none} .btn-yahoo .badge{color:#720e9e;background-color:#fff} .fc-button{background:#f4f4f4;background-image:none;color:#444;border-color:#ddd;border-bottom-color:#ddd} .fc-button:hover,.fc-button:active,.fc-button.hover{background-color:#e9e9e9} .fc-header-title h2{font-size:15px;line-height:1.6em;color:#666;margin-left:10px} .fc-header-right{padding-right:10px} .fc-header-left{padding-left:10px} .fc-widget-header{background:#fafafa} .fc-grid{width:100%;border:0} .fc-widget-header:first-of-type,.fc-widget-content:first-of-type{border-left:0;border-right:0} .fc-widget-header:last-of-type,.fc-widget-content:last-of-type{border-right:0} .fc-toolbar{padding:10px;margin:0} .fc-day-number{font-size:20px;font-weight:300;padding-right:10px} .fc-color-picker{list-style:none;margin:0;padding:0} .fc-color-picker>li{float:left;font-size:30px;margin-right:5px;line-height:30px} .fc-color-picker>li .fa{-webkit-transition:-webkit-transform linear .3s;-moz-transition:-moz-transform linear .3s;-o-transition:-o-transform linear .3s;transition:transform linear .3s} .fc-color-picker>li .fa:hover{-webkit-transform:rotate(30deg);-ms-transform:rotate(30deg);-o-transform:rotate(30deg);transform:rotate(30deg)} #add-new-event{-webkit-transition:all linear .3s;-o-transition:all linear .3s;transition:all linear .3s} .external-event{padding:5px 10px;font-weight:bold;margin-bottom:4px;box-shadow:0 1px 1px rgba(0,0,0,0.1);text-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px;cursor:move} .external-event:hover{box-shadow:inset 0 0 90px rgba(0,0,0,0.2)} .select2-container--default.select2-container--focus,.select2-selection.select2-container--focus,.select2-container--default:focus,.select2-selection:focus,.select2-container--default:active,.select2-selection:active{outline:none} .select2-container--default .select2-selection--single,.select2-selection .select2-selection--single{border:1px solid #d2d6de;border-radius:0;padding:6px 12px;height:34px} .select2-container--default.select2-container--open{border-color:#3c8dbc} .select2-dropdown{border:1px solid #d2d6de;border-radius:0} .select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#3c8dbc;color:white} .select2-results__option{padding:6px 12px;user-select:none;-webkit-user-select:none} .select2-container .select2-selection--single .select2-selection__rendered{padding-left:0;padding-right:0;height:auto;margin-top:-4px} .select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:6px;padding-left:20px} .select2-container--default .select2-selection--single .select2-selection__arrow{height:28px;right:3px} .select2-container--default .select2-selection--single .select2-selection__arrow b{margin-top:0} .select2-dropdown .select2-search__field,.select2-search--inline .select2-search__field{border:1px solid #d2d6de} .select2-dropdown .select2-search__field:focus,.select2-search--inline .select2-search__field:focus{outline:none;border:1px solid #3c8dbc} .select2-container--default .select2-results__option[aria-disabled=true]{color:#999} .select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd} .select2-container--default .select2-results__option[aria-selected=true],.select2-container--default .select2-results__option[aria-selected=true]:hover{color:#444} .select2-container--default .select2-selection--multiple{border:1px solid #d2d6de;border-radius:0} .select2-container--default .select2-selection--multiple:focus{border-color:#3c8dbc} .select2-container--default.select2-container--focus .select2-selection--multiple{border-color:#d2d6de} .select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#3c8dbc;border-color:#367fa9;padding:1px 10px;color:#fff} .select2-container--default .select2-selection--multiple .select2-selection__choice__remove{margin-right:5px;color:rgba(255,255,255,0.7)} .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#fff} .select2-container .select2-selection--single .select2-selection__rendered{padding-right:10px} .pad{padding:10px} .margin{margin:10px} .margin-bottom{margin-bottom:20px} .margin-bottom-none{margin-bottom:0} .margin-r-5{margin-right:5px} .inline{display:inline} .description-block{display:block;margin:10px 0;text-align:center} .description-block.margin-bottom{margin-bottom:25px} .description-block>.description-header{margin:0;padding:0;font-weight:600;font-size:16px} .description-block>.description-text{text-transform:uppercase} .bg-red,.bg-yellow,.bg-aqua,.bg-blue,.bg-light-blue,.bg-green,.bg-navy,.bg-teal,.bg-olive,.bg-lime,.bg-orange,.bg-fuchsia,.bg-purple,.bg-maroon,.bg-black,.bg-red-active,.bg-yellow-active,.bg-aqua-active,.bg-blue-active,.bg-light-blue-active,.bg-green-active,.bg-navy-active,.bg-teal-active,.bg-olive-active,.bg-lime-active,.bg-orange-active,.bg-fuchsia-active,.bg-purple-active,.bg-maroon-active,.bg-black-active,.callout.callout-danger,.callout.callout-warning,.callout.callout-info,.callout.callout-success,.alert-success,.alert-danger,.alert-error,.alert-warning,.alert-info,.label-danger,.label-info,.label-warning,.label-primary,.label-success,.modal-primary .modal-body,.modal-primary .modal-header,.modal-primary .modal-footer,.modal-warning .modal-body,.modal-warning .modal-header,.modal-warning .modal-footer,.modal-info .modal-body,.modal-info .modal-header,.modal-info .modal-footer,.modal-success .modal-body,.modal-success .modal-header,.modal-success .modal-footer,.modal-danger .modal-body,.modal-danger .modal-header,.modal-danger .modal-footer{color:#fff !important} .bg-gray{color:#000;background-color:#d2d6de !important} .bg-gray-light{background-color:#f7f7f7} .bg-black{background-color:#111 !important} .bg-red,.callout.callout-danger,.alert-danger,.alert-error,.label-danger,.modal-danger .modal-body{background-color:#dd4b39 !important} .bg-yellow,.callout.callout-warning,.alert-warning,.label-warning,.modal-warning .modal-body{background-color:#f39c12 !important} .bg-aqua,.callout.callout-info,.alert-info,.label-info,.modal-info .modal-body{background-color:#00c0ef !important} .bg-blue{background-color:#0073b7 !important} .bg-light-blue,.label-primary,.modal-primary .modal-body{background-color:#3c8dbc !important} .bg-green,.callout.callout-success,.alert-success,.label-success,.modal-success .modal-body{background-color:#00a65a !important} .bg-navy{background-color:#001f3f !important} .bg-teal{background-color:#39cccc !important} .bg-olive{background-color:#3d9970 !important} .bg-lime{background-color:#01ff70 !important} .bg-orange{background-color:#ff851b !important} .bg-fuchsia{background-color:#f012be !important} .bg-purple{background-color:#605ca8 !important} .bg-maroon{background-color:#d81b60 !important} .bg-gray-active{color:#000;background-color:#b5bbc8 !important} .bg-black-active{background-color:#000 !important} .bg-red-active,.modal-danger .modal-header,.modal-danger .modal-footer{background-color:#d33724 !important} .bg-yellow-active,.modal-warning .modal-header,.modal-warning .modal-footer{background-color:#db8b0b !important} .bg-aqua-active,.modal-info .modal-header,.modal-info .modal-footer{background-color:#00a7d0 !important} .bg-blue-active{background-color:#005384 !important} .bg-light-blue-active,.modal-primary .modal-header,.modal-primary .modal-footer{background-color:#357ca5 !important} .bg-green-active,.modal-success .modal-header,.modal-success .modal-footer{background-color:#008d4c !important} .bg-navy-active{background-color:#001a35 !important} .bg-teal-active{background-color:#30bbbb !important} .bg-olive-active{background-color:#368763 !important} .bg-lime-active{background-color:#00e765 !important} .bg-orange-active{background-color:#ff7701 !important} .bg-fuchsia-active{background-color:#db0ead !important} .bg-purple-active{background-color:#555299 !important} .bg-maroon-active{background-color:#ca195a !important} [class^="bg-"].disabled{opacity:.65;filter:alpha(opacity=65)} .text-red{color:#dd4b39 !important} .text-yellow{color:#f39c12 !important} .text-aqua{color:#00c0ef !important} .text-blue{color:#0073b7 !important} .text-black{color:#111 !important} .text-light-blue{color:#3c8dbc !important} .text-green{color:#00a65a !important} .text-gray{color:#d2d6de !important} .text-navy{color:#001f3f !important} .text-teal{color:#39cccc !important} .text-olive{color:#3d9970 !important} .text-lime{color:#01ff70 !important} .text-orange{color:#ff851b !important} .text-fuchsia{color:#f012be !important} .text-purple{color:#605ca8 !important} .text-maroon{color:#d81b60 !important} .link-muted{color:#7a869d} .link-muted:hover,.link-muted:focus{color:#606c84} .link-black{color:#666} .link-black:hover,.link-black:focus{color:#999} .hide{display:none !important} .no-border{border:0 !important} .no-padding{padding:0 !important} .no-margin{margin:0 !important} .no-shadow{box-shadow:none !important} .list-unstyled,.chart-legend,.contacts-list,.users-list,.mailbox-attachments{list-style:none;margin:0;padding:0} .list-group-unbordered>.list-group-item{border-left:0;border-right:0;border-radius:0;padding-left:0;padding-right:0} .flat{border-radius:0 !important} .text-bold,.text-bold.table td,.text-bold.table th{font-weight:700} .text-sm{font-size:12px} .jqstooltip{padding:5px !important;width:auto !important;height:auto !important} .bg-teal-gradient{background:#39cccc !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #39cccc), color-stop(1, #7adddd)) !important;background:-ms-linear-gradient(bottom, #39cccc, #7adddd) !important;background:-moz-linear-gradient(center bottom, #39cccc 0, #7adddd 100%) !important;background:-o-linear-gradient(#7adddd, #39cccc) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#7adddd', endColorstr='#39cccc', GradientType=0) !important;color:#fff} .bg-light-blue-gradient{background:#3c8dbc !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #3c8dbc), color-stop(1, #67a8ce)) !important;background:-ms-linear-gradient(bottom, #3c8dbc, #67a8ce) !important;background:-moz-linear-gradient(center bottom, #3c8dbc 0, #67a8ce 100%) !important;background:-o-linear-gradient(#67a8ce, #3c8dbc) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#67a8ce', endColorstr='#3c8dbc', GradientType=0) !important;color:#fff} .bg-blue-gradient{background:#0073b7 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #0073b7), color-stop(1, #0089db)) !important;background:-ms-linear-gradient(bottom, #0073b7, #0089db) !important;background:-moz-linear-gradient(center bottom, #0073b7 0, #0089db 100%) !important;background:-o-linear-gradient(#0089db, #0073b7) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0089db', endColorstr='#0073b7', GradientType=0) !important;color:#fff} .bg-aqua-gradient{background:#00c0ef !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #00c0ef), color-stop(1, #14d1ff)) !important;background:-ms-linear-gradient(bottom, #00c0ef, #14d1ff) !important;background:-moz-linear-gradient(center bottom, #00c0ef 0, #14d1ff 100%) !important;background:-o-linear-gradient(#14d1ff, #00c0ef) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#14d1ff', endColorstr='#00c0ef', GradientType=0) !important;color:#fff} .bg-yellow-gradient{background:#f39c12 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #f39c12), color-stop(1, #f7bc60)) !important;background:-ms-linear-gradient(bottom, #f39c12, #f7bc60) !important;background:-moz-linear-gradient(center bottom, #f39c12 0, #f7bc60 100%) !important;background:-o-linear-gradient(#f7bc60, #f39c12) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f7bc60', endColorstr='#f39c12', GradientType=0) !important;color:#fff} .bg-purple-gradient{background:#605ca8 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #605ca8), color-stop(1, #9491c4)) !important;background:-ms-linear-gradient(bottom, #605ca8, #9491c4) !important;background:-moz-linear-gradient(center bottom, #605ca8 0, #9491c4 100%) !important;background:-o-linear-gradient(#9491c4, #605ca8) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#9491c4', endColorstr='#605ca8', GradientType=0) !important;color:#fff} .bg-green-gradient{background:#00a65a !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #00a65a), color-stop(1, #00ca6d)) !important;background:-ms-linear-gradient(bottom, #00a65a, #00ca6d) !important;background:-moz-linear-gradient(center bottom, #00a65a 0, #00ca6d 100%) !important;background:-o-linear-gradient(#00ca6d, #00a65a) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ca6d', endColorstr='#00a65a', GradientType=0) !important;color:#fff} .bg-red-gradient{background:#dd4b39 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #dd4b39), color-stop(1, #e47365)) !important;background:-ms-linear-gradient(bottom, #dd4b39, #e47365) !important;background:-moz-linear-gradient(center bottom, #dd4b39 0, #e47365 100%) !important;background:-o-linear-gradient(#e47365, #dd4b39) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e47365', endColorstr='#dd4b39', GradientType=0) !important;color:#fff} .bg-black-gradient{background:#111 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #111), color-stop(1, #2b2b2b)) !important;background:-ms-linear-gradient(bottom, #111, #2b2b2b) !important;background:-moz-linear-gradient(center bottom, #111 0, #2b2b2b 100%) !important;background:-o-linear-gradient(#2b2b2b, #111) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#2b2b2b', endColorstr='#111111', GradientType=0) !important;color:#fff} .bg-maroon-gradient{background:#d81b60 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #d81b60), color-stop(1, #e73f7c)) !important;background:-ms-linear-gradient(bottom, #d81b60, #e73f7c) !important;background:-moz-linear-gradient(center bottom, #d81b60 0, #e73f7c 100%) !important;background:-o-linear-gradient(#e73f7c, #d81b60) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e73f7c', endColorstr='#d81b60', GradientType=0) !important;color:#fff} .description-block .description-icon{font-size:16px} .no-pad-top{padding-top:0} .position-static{position:static !important} .list-header{font-size:15px;padding:10px 4px;font-weight:bold;color:#666} .list-seperator{height:1px;background:#f4f4f4;margin:15px 0 9px 0} .list-link>a{padding:4px;color:#777} .list-link>a:hover{color:#222} .font-light{font-weight:300} .user-block:before,.user-block:after{content:" ";display:table} .user-block:after{clear:both} .user-block img{width:40px;height:40px;float:left} .user-block .username,.user-block .description,.user-block .comment{display:block;margin-left:50px} .user-block .username{font-size:16px;font-weight:600} .user-block .description{color:#999;font-size:13px} .user-block.user-block-sm .username,.user-block.user-block-sm .description,.user-block.user-block-sm .comment{margin-left:40px} .user-block.user-block-sm .username{font-size:14px} .img-sm,.img-md,.img-lg,.box-comments .box-comment img,.user-block.user-block-sm img{float:left} .img-sm,.box-comments .box-comment img,.user-block.user-block-sm img{width:30px !important;height:30px !important} .img-sm+.img-push{margin-left:40px} .img-md{width:60px;height:60px} .img-md+.img-push{margin-left:70px} .img-lg{width:100px;height:100px} .img-lg+.img-push{margin-left:110px} .img-bordered{border:3px solid #d2d6de;padding:3px} .img-bordered-sm{border:2px solid #d2d6de;padding:2px} .attachment-block{border:1px solid #f4f4f4;padding:5px;margin-bottom:10px;background:#f7f7f7} .attachment-block .attachment-img{max-width:100px;max-height:100px;height:auto;float:left} .attachment-block .attachment-pushed{margin-left:110px} .attachment-block .attachment-heading{margin:0} .attachment-block .attachment-text{color:#555} .connectedSortable{min-height:100px} .ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px} .sort-highlight{background:#f4f4f4;border:1px dashed #ddd;margin-bottom:10px} .full-opacity-hover{opacity:.65;filter:alpha(opacity=65)} .full-opacity-hover:hover{opacity:1;filter:alpha(opacity=100)} .chart{position:relative;overflow:hidden;width:100%} .chart svg,.chart canvas{width:100% !important}@media print{.no-print,.main-sidebar,.left-side,.main-header,.content-header{display:none !important}.content-wrapper,.right-side,.main-footer{margin-left:0 !important;min-height:0 !important;-webkit-transform:translate(0, 0) !important;-ms-transform:translate(0, 0) !important;-o-transform:translate(0, 0) !important;transform:translate(0, 0) !important}.fixed .content-wrapper,.fixed .right-side{padding-top:0 !important}.invoice{width:100%;border:0;margin:0;padding:0}.invoice-col{float:left;width:33.3333333%}.table-responsive{overflow:auto}.table-responsive>.table tr th,.table-responsive>.table tr td{white-space:normal !important}} \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/dist/css/skins/_all-skins.min.css b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/_all-skins.min.css new file mode 100644 index 0000000..1710db5 --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/_all-skins.min.css @@ -0,0 +1 @@ +.skin-blue .main-header .navbar{background-color:#3c8dbc}.skin-blue .main-header .navbar .nav>li>a{color:#fff}.skin-blue .main-header .navbar .nav>li>a:hover,.skin-blue .main-header .navbar .nav>li>a:active,.skin-blue .main-header .navbar .nav>li>a:focus,.skin-blue .main-header .navbar .nav .open>a,.skin-blue .main-header .navbar .nav .open>a:hover,.skin-blue .main-header .navbar .nav .open>a:focus,.skin-blue .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-blue .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-blue .main-header .navbar .dropdown-menu li a{color:#fff}.skin-blue .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-blue .main-header .logo{background-color:#367fa9;color:#fff;border-bottom:0 solid transparent}.skin-blue .main-header .logo:hover{background-color:#357ca5}.skin-blue .main-header li.user-header{background-color:#3c8dbc}.skin-blue .content-header{background:transparent}.skin-blue .wrapper,.skin-blue .main-sidebar,.skin-blue .left-side{background-color:#222d32}.skin-blue .user-panel>.info,.skin-blue .user-panel>.info>a{color:#fff}.skin-blue .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-blue .sidebar-menu>li>a{border-left:3px solid transparent}.skin-blue .sidebar-menu>li:hover>a,.skin-blue .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#3c8dbc}.skin-blue .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-blue .sidebar a{color:#b8c7ce}.skin-blue .sidebar a:hover{text-decoration:none}.skin-blue .treeview-menu>li>a{color:#8aa4af}.skin-blue .treeview-menu>li.active>a,.skin-blue .treeview-menu>li>a:hover{color:#fff}.skin-blue .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-blue .sidebar-form input[type="text"],.skin-blue .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-blue .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-blue .sidebar-form input[type="text"]:focus,.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-blue .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8}.skin-blue-light .main-header .navbar{background-color:#3c8dbc}.skin-blue-light .main-header .navbar .nav>li>a{color:#fff}.skin-blue-light .main-header .navbar .nav>li>a:hover,.skin-blue-light .main-header .navbar .nav>li>a:active,.skin-blue-light .main-header .navbar .nav>li>a:focus,.skin-blue-light .main-header .navbar .nav .open>a,.skin-blue-light .main-header .navbar .nav .open>a:hover,.skin-blue-light .main-header .navbar .nav .open>a:focus,.skin-blue-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-blue-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-blue-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue-light .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-blue-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-blue-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-blue-light .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-blue-light .main-header .logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue-light .main-header .logo:hover{background-color:#3b8ab8}.skin-blue-light .main-header li.user-header{background-color:#3c8dbc}.skin-blue-light .content-header{background:transparent}.skin-blue-light .wrapper,.skin-blue-light .main-sidebar,.skin-blue-light .left-side{background-color:#f9fafc}.skin-blue-light .content-wrapper,.skin-blue-light .main-footer{border-left:1px solid #d2d6de}.skin-blue-light .user-panel>.info,.skin-blue-light .user-panel>.info>a{color:#444}.skin-blue-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-blue-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-blue-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-blue-light .sidebar-menu>li:hover>a,.skin-blue-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-blue-light .sidebar-menu>li.active{border-left-color:#3c8dbc}.skin-blue-light .sidebar-menu>li.active>a{font-weight:600}.skin-blue-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-blue-light .sidebar a{color:#444}.skin-blue-light .sidebar a:hover{text-decoration:none}.skin-blue-light .treeview-menu>li>a{color:#777}.skin-blue-light .treeview-menu>li.active>a,.skin-blue-light .treeview-menu>li>a:hover{color:#000}.skin-blue-light .treeview-menu>li.active>a{font-weight:600}.skin-blue-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-blue-light .sidebar-form input[type="text"],.skin-blue-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-blue-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-blue-light .sidebar-form input[type="text"]:focus,.skin-blue-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-blue-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-blue-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-blue-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-blue-light .main-footer{border-top-color:#d2d6de}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8}.skin-black .main-header{-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.skin-black .main-header .navbar-toggle{color:#333}.skin-black .main-header .navbar-brand{color:#333;border-right:1px solid #eee}.skin-black .main-header>.navbar{background-color:#fff}.skin-black .main-header>.navbar .nav>li>a{color:#333}.skin-black .main-header>.navbar .nav>li>a:hover,.skin-black .main-header>.navbar .nav>li>a:active,.skin-black .main-header>.navbar .nav>li>a:focus,.skin-black .main-header>.navbar .nav .open>a,.skin-black .main-header>.navbar .nav .open>a:hover,.skin-black .main-header>.navbar .nav .open>a:focus,.skin-black .main-header>.navbar .nav>.active>a{background:#fff;color:#999}.skin-black .main-header>.navbar .sidebar-toggle{color:#333}.skin-black .main-header>.navbar .sidebar-toggle:hover{color:#999;background:#fff}.skin-black .main-header>.navbar>.sidebar-toggle{color:#333;border-right:1px solid #eee}.skin-black .main-header>.navbar .navbar-nav>li>a{border-right:1px solid #eee}.skin-black .main-header>.navbar .navbar-custom-menu .navbar-nav>li>a,.skin-black .main-header>.navbar .navbar-right>li>a{border-left:1px solid #eee;border-right-width:0}.skin-black .main-header>.logo{background-color:#fff;color:#333;border-bottom:0 solid transparent;border-right:1px solid #eee}.skin-black .main-header>.logo:hover{background-color:#fcfcfc}@media (max-width:767px){.skin-black .main-header>.logo{background-color:#222;color:#fff;border-bottom:0 solid transparent;border-right:none}.skin-black .main-header>.logo:hover{background-color:#1f1f1f}}.skin-black .main-header li.user-header{background-color:#222}.skin-black .content-header{background:transparent;box-shadow:none}.skin-black .wrapper,.skin-black .main-sidebar,.skin-black .left-side{background-color:#222d32}.skin-black .user-panel>.info,.skin-black .user-panel>.info>a{color:#fff}.skin-black .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-black .sidebar-menu>li>a{border-left:3px solid transparent}.skin-black .sidebar-menu>li:hover>a,.skin-black .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#fff}.skin-black .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-black .sidebar a{color:#b8c7ce}.skin-black .sidebar a:hover{text-decoration:none}.skin-black .treeview-menu>li>a{color:#8aa4af}.skin-black .treeview-menu>li.active>a,.skin-black .treeview-menu>li>a:hover{color:#fff}.skin-black .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-black .sidebar-form input[type="text"],.skin-black .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-black .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-black .sidebar-form input[type="text"]:focus,.skin-black .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-black .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-black .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-black-light .main-header{-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.skin-black-light .main-header .navbar-toggle{color:#333}.skin-black-light .main-header .navbar-brand{color:#333;border-right:1px solid #eee}.skin-black-light .main-header>.navbar{background-color:#fff}.skin-black-light .main-header>.navbar .nav>li>a{color:#333}.skin-black-light .main-header>.navbar .nav>li>a:hover,.skin-black-light .main-header>.navbar .nav>li>a:active,.skin-black-light .main-header>.navbar .nav>li>a:focus,.skin-black-light .main-header>.navbar .nav .open>a,.skin-black-light .main-header>.navbar .nav .open>a:hover,.skin-black-light .main-header>.navbar .nav .open>a:focus,.skin-black-light .main-header>.navbar .nav>.active>a{background:#fff;color:#999}.skin-black-light .main-header>.navbar .sidebar-toggle{color:#333}.skin-black-light .main-header>.navbar .sidebar-toggle:hover{color:#999;background:#fff}.skin-black-light .main-header>.navbar>.sidebar-toggle{color:#333;border-right:1px solid #eee}.skin-black-light .main-header>.navbar .navbar-nav>li>a{border-right:1px solid #eee}.skin-black-light .main-header>.navbar .navbar-custom-menu .navbar-nav>li>a,.skin-black-light .main-header>.navbar .navbar-right>li>a{border-left:1px solid #eee;border-right-width:0}.skin-black-light .main-header>.logo{background-color:#fff;color:#333;border-bottom:0 solid transparent;border-right:1px solid #eee}.skin-black-light .main-header>.logo:hover{background-color:#fcfcfc}@media (max-width:767px){.skin-black-light .main-header>.logo{background-color:#222;color:#fff;border-bottom:0 solid transparent;border-right:none}.skin-black-light .main-header>.logo:hover{background-color:#1f1f1f}}.skin-black-light .main-header li.user-header{background-color:#222}.skin-black-light .content-header{background:transparent;box-shadow:none}.skin-black-light .wrapper,.skin-black-light .main-sidebar,.skin-black-light .left-side{background-color:#f9fafc}.skin-black-light .content-wrapper,.skin-black-light .main-footer{border-left:1px solid #d2d6de}.skin-black-light .user-panel>.info,.skin-black-light .user-panel>.info>a{color:#444}.skin-black-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-black-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-black-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-black-light .sidebar-menu>li:hover>a,.skin-black-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-black-light .sidebar-menu>li.active{border-left-color:#fff}.skin-black-light .sidebar-menu>li.active>a{font-weight:600}.skin-black-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-black-light .sidebar a{color:#444}.skin-black-light .sidebar a:hover{text-decoration:none}.skin-black-light .treeview-menu>li>a{color:#777}.skin-black-light .treeview-menu>li.active>a,.skin-black-light .treeview-menu>li>a:hover{color:#000}.skin-black-light .treeview-menu>li.active>a{font-weight:600}.skin-black-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-black-light .sidebar-form input[type="text"],.skin-black-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-black-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-black-light .sidebar-form input[type="text"]:focus,.skin-black-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-black-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-black-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-black-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-green .main-header .navbar{background-color:#00a65a}.skin-green .main-header .navbar .nav>li>a{color:#fff}.skin-green .main-header .navbar .nav>li>a:hover,.skin-green .main-header .navbar .nav>li>a:active,.skin-green .main-header .navbar .nav>li>a:focus,.skin-green .main-header .navbar .nav .open>a,.skin-green .main-header .navbar .nav .open>a:hover,.skin-green .main-header .navbar .nav .open>a:focus,.skin-green .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-green .main-header .navbar .sidebar-toggle{color:#fff}.skin-green .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-green .main-header .navbar .sidebar-toggle{color:#fff}.skin-green .main-header .navbar .sidebar-toggle:hover{background-color:#008d4c}@media (max-width:767px){.skin-green .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-green .main-header .navbar .dropdown-menu li a{color:#fff}.skin-green .main-header .navbar .dropdown-menu li a:hover{background:#008d4c}}.skin-green .main-header .logo{background-color:#008d4c;color:#fff;border-bottom:0 solid transparent}.skin-green .main-header .logo:hover{background-color:#008749}.skin-green .main-header li.user-header{background-color:#00a65a}.skin-green .content-header{background:transparent}.skin-green .wrapper,.skin-green .main-sidebar,.skin-green .left-side{background-color:#222d32}.skin-green .user-panel>.info,.skin-green .user-panel>.info>a{color:#fff}.skin-green .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-green .sidebar-menu>li>a{border-left:3px solid transparent}.skin-green .sidebar-menu>li:hover>a,.skin-green .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#00a65a}.skin-green .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-green .sidebar a{color:#b8c7ce}.skin-green .sidebar a:hover{text-decoration:none}.skin-green .treeview-menu>li>a{color:#8aa4af}.skin-green .treeview-menu>li.active>a,.skin-green .treeview-menu>li>a:hover{color:#fff}.skin-green .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-green .sidebar-form input[type="text"],.skin-green .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-green .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-green .sidebar-form input[type="text"]:focus,.skin-green .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-green .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-green .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-green-light .main-header .navbar{background-color:#00a65a}.skin-green-light .main-header .navbar .nav>li>a{color:#fff}.skin-green-light .main-header .navbar .nav>li>a:hover,.skin-green-light .main-header .navbar .nav>li>a:active,.skin-green-light .main-header .navbar .nav>li>a:focus,.skin-green-light .main-header .navbar .nav .open>a,.skin-green-light .main-header .navbar .nav .open>a:hover,.skin-green-light .main-header .navbar .nav .open>a:focus,.skin-green-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-green-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-green-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-green-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-green-light .main-header .navbar .sidebar-toggle:hover{background-color:#008d4c}@media (max-width:767px){.skin-green-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-green-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-green-light .main-header .navbar .dropdown-menu li a:hover{background:#008d4c}}.skin-green-light .main-header .logo{background-color:#00a65a;color:#fff;border-bottom:0 solid transparent}.skin-green-light .main-header .logo:hover{background-color:#00a157}.skin-green-light .main-header li.user-header{background-color:#00a65a}.skin-green-light .content-header{background:transparent}.skin-green-light .wrapper,.skin-green-light .main-sidebar,.skin-green-light .left-side{background-color:#f9fafc}.skin-green-light .content-wrapper,.skin-green-light .main-footer{border-left:1px solid #d2d6de}.skin-green-light .user-panel>.info,.skin-green-light .user-panel>.info>a{color:#444}.skin-green-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-green-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-green-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-green-light .sidebar-menu>li:hover>a,.skin-green-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-green-light .sidebar-menu>li.active{border-left-color:#00a65a}.skin-green-light .sidebar-menu>li.active>a{font-weight:600}.skin-green-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-green-light .sidebar a{color:#444}.skin-green-light .sidebar a:hover{text-decoration:none}.skin-green-light .treeview-menu>li>a{color:#777}.skin-green-light .treeview-menu>li.active>a,.skin-green-light .treeview-menu>li>a:hover{color:#000}.skin-green-light .treeview-menu>li.active>a{font-weight:600}.skin-green-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-green-light .sidebar-form input[type="text"],.skin-green-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-green-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-green-light .sidebar-form input[type="text"]:focus,.skin-green-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-green-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-green-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-green-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-red .main-header .navbar{background-color:#dd4b39}.skin-red .main-header .navbar .nav>li>a{color:#fff}.skin-red .main-header .navbar .nav>li>a:hover,.skin-red .main-header .navbar .nav>li>a:active,.skin-red .main-header .navbar .nav>li>a:focus,.skin-red .main-header .navbar .nav .open>a,.skin-red .main-header .navbar .nav .open>a:hover,.skin-red .main-header .navbar .nav .open>a:focus,.skin-red .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-red .main-header .navbar .sidebar-toggle{color:#fff}.skin-red .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-red .main-header .navbar .sidebar-toggle{color:#fff}.skin-red .main-header .navbar .sidebar-toggle:hover{background-color:#d73925}@media (max-width:767px){.skin-red .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-red .main-header .navbar .dropdown-menu li a{color:#fff}.skin-red .main-header .navbar .dropdown-menu li a:hover{background:#d73925}}.skin-red .main-header .logo{background-color:#d73925;color:#fff;border-bottom:0 solid transparent}.skin-red .main-header .logo:hover{background-color:#d33724}.skin-red .main-header li.user-header{background-color:#dd4b39}.skin-red .content-header{background:transparent}.skin-red .wrapper,.skin-red .main-sidebar,.skin-red .left-side{background-color:#222d32}.skin-red .user-panel>.info,.skin-red .user-panel>.info>a{color:#fff}.skin-red .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-red .sidebar-menu>li>a{border-left:3px solid transparent}.skin-red .sidebar-menu>li:hover>a,.skin-red .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#dd4b39}.skin-red .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-red .sidebar a{color:#b8c7ce}.skin-red .sidebar a:hover{text-decoration:none}.skin-red .treeview-menu>li>a{color:#8aa4af}.skin-red .treeview-menu>li.active>a,.skin-red .treeview-menu>li>a:hover{color:#fff}.skin-red .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-red .sidebar-form input[type="text"],.skin-red .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-red .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-red .sidebar-form input[type="text"]:focus,.skin-red .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-red .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-red .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-red-light .main-header .navbar{background-color:#dd4b39}.skin-red-light .main-header .navbar .nav>li>a{color:#fff}.skin-red-light .main-header .navbar .nav>li>a:hover,.skin-red-light .main-header .navbar .nav>li>a:active,.skin-red-light .main-header .navbar .nav>li>a:focus,.skin-red-light .main-header .navbar .nav .open>a,.skin-red-light .main-header .navbar .nav .open>a:hover,.skin-red-light .main-header .navbar .nav .open>a:focus,.skin-red-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-red-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-red-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-red-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-red-light .main-header .navbar .sidebar-toggle:hover{background-color:#d73925}@media (max-width:767px){.skin-red-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-red-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-red-light .main-header .navbar .dropdown-menu li a:hover{background:#d73925}}.skin-red-light .main-header .logo{background-color:#dd4b39;color:#fff;border-bottom:0 solid transparent}.skin-red-light .main-header .logo:hover{background-color:#dc4735}.skin-red-light .main-header li.user-header{background-color:#dd4b39}.skin-red-light .content-header{background:transparent}.skin-red-light .wrapper,.skin-red-light .main-sidebar,.skin-red-light .left-side{background-color:#f9fafc}.skin-red-light .content-wrapper,.skin-red-light .main-footer{border-left:1px solid #d2d6de}.skin-red-light .user-panel>.info,.skin-red-light .user-panel>.info>a{color:#444}.skin-red-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-red-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-red-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-red-light .sidebar-menu>li:hover>a,.skin-red-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-red-light .sidebar-menu>li.active{border-left-color:#dd4b39}.skin-red-light .sidebar-menu>li.active>a{font-weight:600}.skin-red-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-red-light .sidebar a{color:#444}.skin-red-light .sidebar a:hover{text-decoration:none}.skin-red-light .treeview-menu>li>a{color:#777}.skin-red-light .treeview-menu>li.active>a,.skin-red-light .treeview-menu>li>a:hover{color:#000}.skin-red-light .treeview-menu>li.active>a{font-weight:600}.skin-red-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-red-light .sidebar-form input[type="text"],.skin-red-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-red-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-red-light .sidebar-form input[type="text"]:focus,.skin-red-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-red-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-red-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-red-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-yellow .main-header .navbar{background-color:#f39c12}.skin-yellow .main-header .navbar .nav>li>a{color:#fff}.skin-yellow .main-header .navbar .nav>li>a:hover,.skin-yellow .main-header .navbar .nav>li>a:active,.skin-yellow .main-header .navbar .nav>li>a:focus,.skin-yellow .main-header .navbar .nav .open>a,.skin-yellow .main-header .navbar .nav .open>a:hover,.skin-yellow .main-header .navbar .nav .open>a:focus,.skin-yellow .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-yellow .main-header .navbar .sidebar-toggle{color:#fff}.skin-yellow .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-yellow .main-header .navbar .sidebar-toggle{color:#fff}.skin-yellow .main-header .navbar .sidebar-toggle:hover{background-color:#e08e0b}@media (max-width:767px){.skin-yellow .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-yellow .main-header .navbar .dropdown-menu li a{color:#fff}.skin-yellow .main-header .navbar .dropdown-menu li a:hover{background:#e08e0b}}.skin-yellow .main-header .logo{background-color:#e08e0b;color:#fff;border-bottom:0 solid transparent}.skin-yellow .main-header .logo:hover{background-color:#db8b0b}.skin-yellow .main-header li.user-header{background-color:#f39c12}.skin-yellow .content-header{background:transparent}.skin-yellow .wrapper,.skin-yellow .main-sidebar,.skin-yellow .left-side{background-color:#222d32}.skin-yellow .user-panel>.info,.skin-yellow .user-panel>.info>a{color:#fff}.skin-yellow .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-yellow .sidebar-menu>li>a{border-left:3px solid transparent}.skin-yellow .sidebar-menu>li:hover>a,.skin-yellow .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#f39c12}.skin-yellow .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-yellow .sidebar a{color:#b8c7ce}.skin-yellow .sidebar a:hover{text-decoration:none}.skin-yellow .treeview-menu>li>a{color:#8aa4af}.skin-yellow .treeview-menu>li.active>a,.skin-yellow .treeview-menu>li>a:hover{color:#fff}.skin-yellow .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-yellow .sidebar-form input[type="text"],.skin-yellow .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-yellow .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-yellow .sidebar-form input[type="text"]:focus,.skin-yellow .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-yellow .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-yellow .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-yellow-light .main-header .navbar{background-color:#f39c12}.skin-yellow-light .main-header .navbar .nav>li>a{color:#fff}.skin-yellow-light .main-header .navbar .nav>li>a:hover,.skin-yellow-light .main-header .navbar .nav>li>a:active,.skin-yellow-light .main-header .navbar .nav>li>a:focus,.skin-yellow-light .main-header .navbar .nav .open>a,.skin-yellow-light .main-header .navbar .nav .open>a:hover,.skin-yellow-light .main-header .navbar .nav .open>a:focus,.skin-yellow-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-yellow-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-yellow-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-yellow-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-yellow-light .main-header .navbar .sidebar-toggle:hover{background-color:#e08e0b}@media (max-width:767px){.skin-yellow-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-yellow-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-yellow-light .main-header .navbar .dropdown-menu li a:hover{background:#e08e0b}}.skin-yellow-light .main-header .logo{background-color:#f39c12;color:#fff;border-bottom:0 solid transparent}.skin-yellow-light .main-header .logo:hover{background-color:#f39a0d}.skin-yellow-light .main-header li.user-header{background-color:#f39c12}.skin-yellow-light .content-header{background:transparent}.skin-yellow-light .wrapper,.skin-yellow-light .main-sidebar,.skin-yellow-light .left-side{background-color:#f9fafc}.skin-yellow-light .content-wrapper,.skin-yellow-light .main-footer{border-left:1px solid #d2d6de}.skin-yellow-light .user-panel>.info,.skin-yellow-light .user-panel>.info>a{color:#444}.skin-yellow-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-yellow-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-yellow-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-yellow-light .sidebar-menu>li:hover>a,.skin-yellow-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-yellow-light .sidebar-menu>li.active{border-left-color:#f39c12}.skin-yellow-light .sidebar-menu>li.active>a{font-weight:600}.skin-yellow-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-yellow-light .sidebar a{color:#444}.skin-yellow-light .sidebar a:hover{text-decoration:none}.skin-yellow-light .treeview-menu>li>a{color:#777}.skin-yellow-light .treeview-menu>li.active>a,.skin-yellow-light .treeview-menu>li>a:hover{color:#000}.skin-yellow-light .treeview-menu>li.active>a{font-weight:600}.skin-yellow-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-yellow-light .sidebar-form input[type="text"],.skin-yellow-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-yellow-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-yellow-light .sidebar-form input[type="text"]:focus,.skin-yellow-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-yellow-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-yellow-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-yellow-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-purple .main-header .navbar{background-color:#605ca8}.skin-purple .main-header .navbar .nav>li>a{color:#fff}.skin-purple .main-header .navbar .nav>li>a:hover,.skin-purple .main-header .navbar .nav>li>a:active,.skin-purple .main-header .navbar .nav>li>a:focus,.skin-purple .main-header .navbar .nav .open>a,.skin-purple .main-header .navbar .nav .open>a:hover,.skin-purple .main-header .navbar .nav .open>a:focus,.skin-purple .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-purple .main-header .navbar .sidebar-toggle{color:#fff}.skin-purple .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-purple .main-header .navbar .sidebar-toggle{color:#fff}.skin-purple .main-header .navbar .sidebar-toggle:hover{background-color:#555299}@media (max-width:767px){.skin-purple .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-purple .main-header .navbar .dropdown-menu li a{color:#fff}.skin-purple .main-header .navbar .dropdown-menu li a:hover{background:#555299}}.skin-purple .main-header .logo{background-color:#555299;color:#fff;border-bottom:0 solid transparent}.skin-purple .main-header .logo:hover{background-color:#545096}.skin-purple .main-header li.user-header{background-color:#605ca8}.skin-purple .content-header{background:transparent}.skin-purple .wrapper,.skin-purple .main-sidebar,.skin-purple .left-side{background-color:#222d32}.skin-purple .user-panel>.info,.skin-purple .user-panel>.info>a{color:#fff}.skin-purple .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-purple .sidebar-menu>li>a{border-left:3px solid transparent}.skin-purple .sidebar-menu>li:hover>a,.skin-purple .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#605ca8}.skin-purple .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-purple .sidebar a{color:#b8c7ce}.skin-purple .sidebar a:hover{text-decoration:none}.skin-purple .treeview-menu>li>a{color:#8aa4af}.skin-purple .treeview-menu>li.active>a,.skin-purple .treeview-menu>li>a:hover{color:#fff}.skin-purple .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-purple .sidebar-form input[type="text"],.skin-purple .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-purple .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-purple .sidebar-form input[type="text"]:focus,.skin-purple .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-purple .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-purple .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-purple-light .main-header .navbar{background-color:#605ca8}.skin-purple-light .main-header .navbar .nav>li>a{color:#fff}.skin-purple-light .main-header .navbar .nav>li>a:hover,.skin-purple-light .main-header .navbar .nav>li>a:active,.skin-purple-light .main-header .navbar .nav>li>a:focus,.skin-purple-light .main-header .navbar .nav .open>a,.skin-purple-light .main-header .navbar .nav .open>a:hover,.skin-purple-light .main-header .navbar .nav .open>a:focus,.skin-purple-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-purple-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-purple-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-purple-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-purple-light .main-header .navbar .sidebar-toggle:hover{background-color:#555299}@media (max-width:767px){.skin-purple-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-purple-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-purple-light .main-header .navbar .dropdown-menu li a:hover{background:#555299}}.skin-purple-light .main-header .logo{background-color:#605ca8;color:#fff;border-bottom:0 solid transparent}.skin-purple-light .main-header .logo:hover{background-color:#5d59a6}.skin-purple-light .main-header li.user-header{background-color:#605ca8}.skin-purple-light .content-header{background:transparent}.skin-purple-light .wrapper,.skin-purple-light .main-sidebar,.skin-purple-light .left-side{background-color:#f9fafc}.skin-purple-light .content-wrapper,.skin-purple-light .main-footer{border-left:1px solid #d2d6de}.skin-purple-light .user-panel>.info,.skin-purple-light .user-panel>.info>a{color:#444}.skin-purple-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-purple-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-purple-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-purple-light .sidebar-menu>li:hover>a,.skin-purple-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-purple-light .sidebar-menu>li.active{border-left-color:#605ca8}.skin-purple-light .sidebar-menu>li.active>a{font-weight:600}.skin-purple-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-purple-light .sidebar a{color:#444}.skin-purple-light .sidebar a:hover{text-decoration:none}.skin-purple-light .treeview-menu>li>a{color:#777}.skin-purple-light .treeview-menu>li.active>a,.skin-purple-light .treeview-menu>li>a:hover{color:#000}.skin-purple-light .treeview-menu>li.active>a{font-weight:600}.skin-purple-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-purple-light .sidebar-form input[type="text"],.skin-purple-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-purple-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-purple-light .sidebar-form input[type="text"]:focus,.skin-purple-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-purple-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-purple-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-purple-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}} \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-black-light.min.css b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-black-light.min.css new file mode 100644 index 0000000..c631ec5 --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-black-light.min.css @@ -0,0 +1 @@ +.skin-black-light .main-header{-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.skin-black-light .main-header .navbar-toggle{color:#333}.skin-black-light .main-header .navbar-brand{color:#333;border-right:1px solid #eee}.skin-black-light .main-header>.navbar{background-color:#fff}.skin-black-light .main-header>.navbar .nav>li>a{color:#333}.skin-black-light .main-header>.navbar .nav>li>a:hover,.skin-black-light .main-header>.navbar .nav>li>a:active,.skin-black-light .main-header>.navbar .nav>li>a:focus,.skin-black-light .main-header>.navbar .nav .open>a,.skin-black-light .main-header>.navbar .nav .open>a:hover,.skin-black-light .main-header>.navbar .nav .open>a:focus,.skin-black-light .main-header>.navbar .nav>.active>a{background:#fff;color:#999}.skin-black-light .main-header>.navbar .sidebar-toggle{color:#333}.skin-black-light .main-header>.navbar .sidebar-toggle:hover{color:#999;background:#fff}.skin-black-light .main-header>.navbar>.sidebar-toggle{color:#333;border-right:1px solid #eee}.skin-black-light .main-header>.navbar .navbar-nav>li>a{border-right:1px solid #eee}.skin-black-light .main-header>.navbar .navbar-custom-menu .navbar-nav>li>a,.skin-black-light .main-header>.navbar .navbar-right>li>a{border-left:1px solid #eee;border-right-width:0}.skin-black-light .main-header>.logo{background-color:#fff;color:#333;border-bottom:0 solid transparent;border-right:1px solid #eee}.skin-black-light .main-header>.logo:hover{background-color:#fcfcfc}@media (max-width:767px){.skin-black-light .main-header>.logo{background-color:#222;color:#fff;border-bottom:0 solid transparent;border-right:none}.skin-black-light .main-header>.logo:hover{background-color:#1f1f1f}}.skin-black-light .main-header li.user-header{background-color:#222}.skin-black-light .content-header{background:transparent;box-shadow:none}.skin-black-light .wrapper,.skin-black-light .main-sidebar,.skin-black-light .left-side{background-color:#f9fafc}.skin-black-light .content-wrapper,.skin-black-light .main-footer{border-left:1px solid #d2d6de}.skin-black-light .user-panel>.info,.skin-black-light .user-panel>.info>a{color:#444}.skin-black-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-black-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-black-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-black-light .sidebar-menu>li:hover>a,.skin-black-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-black-light .sidebar-menu>li.active{border-left-color:#fff}.skin-black-light .sidebar-menu>li.active>a{font-weight:600}.skin-black-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-black-light .sidebar a{color:#444}.skin-black-light .sidebar a:hover{text-decoration:none}.skin-black-light .treeview-menu>li>a{color:#777}.skin-black-light .treeview-menu>li.active>a,.skin-black-light .treeview-menu>li>a:hover{color:#000}.skin-black-light .treeview-menu>li.active>a{font-weight:600}.skin-black-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-black-light .sidebar-form input[type="text"],.skin-black-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-black-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-black-light .sidebar-form input[type="text"]:focus,.skin-black-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-black-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-black-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-black-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}} \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-black.min.css b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-black.min.css new file mode 100644 index 0000000..a7d1888 --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-black.min.css @@ -0,0 +1 @@ +.skin-black .main-header{-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.skin-black .main-header .navbar-toggle{color:#333}.skin-black .main-header .navbar-brand{color:#333;border-right:1px solid #eee}.skin-black .main-header>.navbar{background-color:#fff}.skin-black .main-header>.navbar .nav>li>a{color:#333}.skin-black .main-header>.navbar .nav>li>a:hover,.skin-black .main-header>.navbar .nav>li>a:active,.skin-black .main-header>.navbar .nav>li>a:focus,.skin-black .main-header>.navbar .nav .open>a,.skin-black .main-header>.navbar .nav .open>a:hover,.skin-black .main-header>.navbar .nav .open>a:focus,.skin-black .main-header>.navbar .nav>.active>a{background:#fff;color:#999}.skin-black .main-header>.navbar .sidebar-toggle{color:#333}.skin-black .main-header>.navbar .sidebar-toggle:hover{color:#999;background:#fff}.skin-black .main-header>.navbar>.sidebar-toggle{color:#333;border-right:1px solid #eee}.skin-black .main-header>.navbar .navbar-nav>li>a{border-right:1px solid #eee}.skin-black .main-header>.navbar .navbar-custom-menu .navbar-nav>li>a,.skin-black .main-header>.navbar .navbar-right>li>a{border-left:1px solid #eee;border-right-width:0}.skin-black .main-header>.logo{background-color:#fff;color:#333;border-bottom:0 solid transparent;border-right:1px solid #eee}.skin-black .main-header>.logo:hover{background-color:#fcfcfc}@media (max-width:767px){.skin-black .main-header>.logo{background-color:#222;color:#fff;border-bottom:0 solid transparent;border-right:none}.skin-black .main-header>.logo:hover{background-color:#1f1f1f}}.skin-black .main-header li.user-header{background-color:#222}.skin-black .content-header{background:transparent;box-shadow:none}.skin-black .wrapper,.skin-black .main-sidebar,.skin-black .left-side{background-color:#222d32}.skin-black .user-panel>.info,.skin-black .user-panel>.info>a{color:#fff}.skin-black .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-black .sidebar-menu>li>a{border-left:3px solid transparent}.skin-black .sidebar-menu>li:hover>a,.skin-black .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#fff}.skin-black .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-black .sidebar a{color:#b8c7ce}.skin-black .sidebar a:hover{text-decoration:none}.skin-black .treeview-menu>li>a{color:#8aa4af}.skin-black .treeview-menu>li.active>a,.skin-black .treeview-menu>li>a:hover{color:#fff}.skin-black .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-black .sidebar-form input[type="text"],.skin-black .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-black .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-black .sidebar-form input[type="text"]:focus,.skin-black .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-black .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-black .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0} \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-blue-light.min.css b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-blue-light.min.css new file mode 100644 index 0000000..4fab22a --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-blue-light.min.css @@ -0,0 +1 @@ +.skin-blue-light .main-header .navbar{background-color:#3c8dbc}.skin-blue-light .main-header .navbar .nav>li>a{color:#fff}.skin-blue-light .main-header .navbar .nav>li>a:hover,.skin-blue-light .main-header .navbar .nav>li>a:active,.skin-blue-light .main-header .navbar .nav>li>a:focus,.skin-blue-light .main-header .navbar .nav .open>a,.skin-blue-light .main-header .navbar .nav .open>a:hover,.skin-blue-light .main-header .navbar .nav .open>a:focus,.skin-blue-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-blue-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-blue-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue-light .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-blue-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-blue-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-blue-light .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-blue-light .main-header .logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue-light .main-header .logo:hover{background-color:#3b8ab8}.skin-blue-light .main-header li.user-header{background-color:#3c8dbc}.skin-blue-light .content-header{background:transparent}.skin-blue-light .wrapper,.skin-blue-light .main-sidebar,.skin-blue-light .left-side{background-color:#f9fafc}.skin-blue-light .content-wrapper,.skin-blue-light .main-footer{border-left:1px solid #d2d6de}.skin-blue-light .user-panel>.info,.skin-blue-light .user-panel>.info>a{color:#444}.skin-blue-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-blue-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-blue-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-blue-light .sidebar-menu>li:hover>a,.skin-blue-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-blue-light .sidebar-menu>li.active{border-left-color:#3c8dbc}.skin-blue-light .sidebar-menu>li.active>a{font-weight:600}.skin-blue-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-blue-light .sidebar a{color:#444}.skin-blue-light .sidebar a:hover{text-decoration:none}.skin-blue-light .treeview-menu>li>a{color:#777}.skin-blue-light .treeview-menu>li.active>a,.skin-blue-light .treeview-menu>li>a:hover{color:#000}.skin-blue-light .treeview-menu>li.active>a{font-weight:600}.skin-blue-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-blue-light .sidebar-form input[type="text"],.skin-blue-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-blue-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-blue-light .sidebar-form input[type="text"]:focus,.skin-blue-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-blue-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-blue-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-blue-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-blue-light .main-footer{border-top-color:#d2d6de}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8} \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-blue.min.css b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-blue.min.css new file mode 100644 index 0000000..123c04f --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-blue.min.css @@ -0,0 +1 @@ +.skin-blue .main-header .navbar{background-color:#3c8dbc}.skin-blue .main-header .navbar .nav>li>a{color:#fff}.skin-blue .main-header .navbar .nav>li>a:hover,.skin-blue .main-header .navbar .nav>li>a:active,.skin-blue .main-header .navbar .nav>li>a:focus,.skin-blue .main-header .navbar .nav .open>a,.skin-blue .main-header .navbar .nav .open>a:hover,.skin-blue .main-header .navbar .nav .open>a:focus,.skin-blue .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-blue .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-blue .main-header .navbar .dropdown-menu li a{color:#fff}.skin-blue .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-blue .main-header .logo{background-color:#367fa9;color:#fff;border-bottom:0 solid transparent}.skin-blue .main-header .logo:hover{background-color:#357ca5}.skin-blue .main-header li.user-header{background-color:#3c8dbc}.skin-blue .content-header{background:transparent}.skin-blue .wrapper,.skin-blue .main-sidebar,.skin-blue .left-side{background-color:#222d32}.skin-blue .user-panel>.info,.skin-blue .user-panel>.info>a{color:#fff}.skin-blue .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-blue .sidebar-menu>li>a{border-left:3px solid transparent}.skin-blue .sidebar-menu>li:hover>a,.skin-blue .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#3c8dbc}.skin-blue .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-blue .sidebar a{color:#b8c7ce}.skin-blue .sidebar a:hover{text-decoration:none}.skin-blue .treeview-menu>li>a{color:#8aa4af}.skin-blue .treeview-menu>li.active>a,.skin-blue .treeview-menu>li>a:hover{color:#fff}.skin-blue .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-blue .sidebar-form input[type="text"],.skin-blue .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-blue .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-blue .sidebar-form input[type="text"]:focus,.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-blue .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8} \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-green-light.min.css b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-green-light.min.css new file mode 100644 index 0000000..43ceea4 --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-green-light.min.css @@ -0,0 +1 @@ +.skin-green-light .main-header .navbar{background-color:#00a65a}.skin-green-light .main-header .navbar .nav>li>a{color:#fff}.skin-green-light .main-header .navbar .nav>li>a:hover,.skin-green-light .main-header .navbar .nav>li>a:active,.skin-green-light .main-header .navbar .nav>li>a:focus,.skin-green-light .main-header .navbar .nav .open>a,.skin-green-light .main-header .navbar .nav .open>a:hover,.skin-green-light .main-header .navbar .nav .open>a:focus,.skin-green-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-green-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-green-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-green-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-green-light .main-header .navbar .sidebar-toggle:hover{background-color:#008d4c}@media (max-width:767px){.skin-green-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-green-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-green-light .main-header .navbar .dropdown-menu li a:hover{background:#008d4c}}.skin-green-light .main-header .logo{background-color:#00a65a;color:#fff;border-bottom:0 solid transparent}.skin-green-light .main-header .logo:hover{background-color:#00a157}.skin-green-light .main-header li.user-header{background-color:#00a65a}.skin-green-light .content-header{background:transparent}.skin-green-light .wrapper,.skin-green-light .main-sidebar,.skin-green-light .left-side{background-color:#f9fafc}.skin-green-light .content-wrapper,.skin-green-light .main-footer{border-left:1px solid #d2d6de}.skin-green-light .user-panel>.info,.skin-green-light .user-panel>.info>a{color:#444}.skin-green-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-green-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-green-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-green-light .sidebar-menu>li:hover>a,.skin-green-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-green-light .sidebar-menu>li.active{border-left-color:#00a65a}.skin-green-light .sidebar-menu>li.active>a{font-weight:600}.skin-green-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-green-light .sidebar a{color:#444}.skin-green-light .sidebar a:hover{text-decoration:none}.skin-green-light .treeview-menu>li>a{color:#777}.skin-green-light .treeview-menu>li.active>a,.skin-green-light .treeview-menu>li>a:hover{color:#000}.skin-green-light .treeview-menu>li.active>a{font-weight:600}.skin-green-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-green-light .sidebar-form input[type="text"],.skin-green-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-green-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-green-light .sidebar-form input[type="text"]:focus,.skin-green-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-green-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-green-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-green-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}} \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-green.min.css b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-green.min.css new file mode 100644 index 0000000..8f885ed --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-green.min.css @@ -0,0 +1 @@ +.skin-green .main-header .navbar{background-color:#00a65a}.skin-green .main-header .navbar .nav>li>a{color:#fff}.skin-green .main-header .navbar .nav>li>a:hover,.skin-green .main-header .navbar .nav>li>a:active,.skin-green .main-header .navbar .nav>li>a:focus,.skin-green .main-header .navbar .nav .open>a,.skin-green .main-header .navbar .nav .open>a:hover,.skin-green .main-header .navbar .nav .open>a:focus,.skin-green .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-green .main-header .navbar .sidebar-toggle{color:#fff}.skin-green .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-green .main-header .navbar .sidebar-toggle{color:#fff}.skin-green .main-header .navbar .sidebar-toggle:hover{background-color:#008d4c}@media (max-width:767px){.skin-green .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-green .main-header .navbar .dropdown-menu li a{color:#fff}.skin-green .main-header .navbar .dropdown-menu li a:hover{background:#008d4c}}.skin-green .main-header .logo{background-color:#008d4c;color:#fff;border-bottom:0 solid transparent}.skin-green .main-header .logo:hover{background-color:#008749}.skin-green .main-header li.user-header{background-color:#00a65a}.skin-green .content-header{background:transparent}.skin-green .wrapper,.skin-green .main-sidebar,.skin-green .left-side{background-color:#222d32}.skin-green .user-panel>.info,.skin-green .user-panel>.info>a{color:#fff}.skin-green .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-green .sidebar-menu>li>a{border-left:3px solid transparent}.skin-green .sidebar-menu>li:hover>a,.skin-green .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#00a65a}.skin-green .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-green .sidebar a{color:#b8c7ce}.skin-green .sidebar a:hover{text-decoration:none}.skin-green .treeview-menu>li>a{color:#8aa4af}.skin-green .treeview-menu>li.active>a,.skin-green .treeview-menu>li>a:hover{color:#fff}.skin-green .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-green .sidebar-form input[type="text"],.skin-green .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-green .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-green .sidebar-form input[type="text"]:focus,.skin-green .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-green .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-green .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0} \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-purple-light.min.css b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-purple-light.min.css new file mode 100644 index 0000000..53333c4 --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-purple-light.min.css @@ -0,0 +1 @@ +.skin-purple-light .main-header .navbar{background-color:#605ca8}.skin-purple-light .main-header .navbar .nav>li>a{color:#fff}.skin-purple-light .main-header .navbar .nav>li>a:hover,.skin-purple-light .main-header .navbar .nav>li>a:active,.skin-purple-light .main-header .navbar .nav>li>a:focus,.skin-purple-light .main-header .navbar .nav .open>a,.skin-purple-light .main-header .navbar .nav .open>a:hover,.skin-purple-light .main-header .navbar .nav .open>a:focus,.skin-purple-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-purple-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-purple-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-purple-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-purple-light .main-header .navbar .sidebar-toggle:hover{background-color:#555299}@media (max-width:767px){.skin-purple-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-purple-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-purple-light .main-header .navbar .dropdown-menu li a:hover{background:#555299}}.skin-purple-light .main-header .logo{background-color:#605ca8;color:#fff;border-bottom:0 solid transparent}.skin-purple-light .main-header .logo:hover{background-color:#5d59a6}.skin-purple-light .main-header li.user-header{background-color:#605ca8}.skin-purple-light .content-header{background:transparent}.skin-purple-light .wrapper,.skin-purple-light .main-sidebar,.skin-purple-light .left-side{background-color:#f9fafc}.skin-purple-light .content-wrapper,.skin-purple-light .main-footer{border-left:1px solid #d2d6de}.skin-purple-light .user-panel>.info,.skin-purple-light .user-panel>.info>a{color:#444}.skin-purple-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-purple-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-purple-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-purple-light .sidebar-menu>li:hover>a,.skin-purple-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-purple-light .sidebar-menu>li.active{border-left-color:#605ca8}.skin-purple-light .sidebar-menu>li.active>a{font-weight:600}.skin-purple-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-purple-light .sidebar a{color:#444}.skin-purple-light .sidebar a:hover{text-decoration:none}.skin-purple-light .treeview-menu>li>a{color:#777}.skin-purple-light .treeview-menu>li.active>a,.skin-purple-light .treeview-menu>li>a:hover{color:#000}.skin-purple-light .treeview-menu>li.active>a{font-weight:600}.skin-purple-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-purple-light .sidebar-form input[type="text"],.skin-purple-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-purple-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-purple-light .sidebar-form input[type="text"]:focus,.skin-purple-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-purple-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-purple-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-purple-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}} \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-purple.min.css b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-purple.min.css new file mode 100644 index 0000000..1eff3d9 --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-purple.min.css @@ -0,0 +1 @@ +.skin-purple .main-header .navbar{background-color:#605ca8}.skin-purple .main-header .navbar .nav>li>a{color:#fff}.skin-purple .main-header .navbar .nav>li>a:hover,.skin-purple .main-header .navbar .nav>li>a:active,.skin-purple .main-header .navbar .nav>li>a:focus,.skin-purple .main-header .navbar .nav .open>a,.skin-purple .main-header .navbar .nav .open>a:hover,.skin-purple .main-header .navbar .nav .open>a:focus,.skin-purple .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-purple .main-header .navbar .sidebar-toggle{color:#fff}.skin-purple .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-purple .main-header .navbar .sidebar-toggle{color:#fff}.skin-purple .main-header .navbar .sidebar-toggle:hover{background-color:#555299}@media (max-width:767px){.skin-purple .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-purple .main-header .navbar .dropdown-menu li a{color:#fff}.skin-purple .main-header .navbar .dropdown-menu li a:hover{background:#555299}}.skin-purple .main-header .logo{background-color:#555299;color:#fff;border-bottom:0 solid transparent}.skin-purple .main-header .logo:hover{background-color:#545096}.skin-purple .main-header li.user-header{background-color:#605ca8}.skin-purple .content-header{background:transparent}.skin-purple .wrapper,.skin-purple .main-sidebar,.skin-purple .left-side{background-color:#222d32}.skin-purple .user-panel>.info,.skin-purple .user-panel>.info>a{color:#fff}.skin-purple .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-purple .sidebar-menu>li>a{border-left:3px solid transparent}.skin-purple .sidebar-menu>li:hover>a,.skin-purple .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#605ca8}.skin-purple .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-purple .sidebar a{color:#b8c7ce}.skin-purple .sidebar a:hover{text-decoration:none}.skin-purple .treeview-menu>li>a{color:#8aa4af}.skin-purple .treeview-menu>li.active>a,.skin-purple .treeview-menu>li>a:hover{color:#fff}.skin-purple .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-purple .sidebar-form input[type="text"],.skin-purple .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-purple .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-purple .sidebar-form input[type="text"]:focus,.skin-purple .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-purple .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-purple .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0} \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-red-light.min.css b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-red-light.min.css new file mode 100644 index 0000000..7ab4c1f --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-red-light.min.css @@ -0,0 +1 @@ +.skin-red-light .main-header .navbar{background-color:#dd4b39}.skin-red-light .main-header .navbar .nav>li>a{color:#fff}.skin-red-light .main-header .navbar .nav>li>a:hover,.skin-red-light .main-header .navbar .nav>li>a:active,.skin-red-light .main-header .navbar .nav>li>a:focus,.skin-red-light .main-header .navbar .nav .open>a,.skin-red-light .main-header .navbar .nav .open>a:hover,.skin-red-light .main-header .navbar .nav .open>a:focus,.skin-red-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-red-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-red-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-red-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-red-light .main-header .navbar .sidebar-toggle:hover{background-color:#d73925}@media (max-width:767px){.skin-red-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-red-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-red-light .main-header .navbar .dropdown-menu li a:hover{background:#d73925}}.skin-red-light .main-header .logo{background-color:#dd4b39;color:#fff;border-bottom:0 solid transparent}.skin-red-light .main-header .logo:hover{background-color:#dc4735}.skin-red-light .main-header li.user-header{background-color:#dd4b39}.skin-red-light .content-header{background:transparent}.skin-red-light .wrapper,.skin-red-light .main-sidebar,.skin-red-light .left-side{background-color:#f9fafc}.skin-red-light .content-wrapper,.skin-red-light .main-footer{border-left:1px solid #d2d6de}.skin-red-light .user-panel>.info,.skin-red-light .user-panel>.info>a{color:#444}.skin-red-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-red-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-red-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-red-light .sidebar-menu>li:hover>a,.skin-red-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-red-light .sidebar-menu>li.active{border-left-color:#dd4b39}.skin-red-light .sidebar-menu>li.active>a{font-weight:600}.skin-red-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-red-light .sidebar a{color:#444}.skin-red-light .sidebar a:hover{text-decoration:none}.skin-red-light .treeview-menu>li>a{color:#777}.skin-red-light .treeview-menu>li.active>a,.skin-red-light .treeview-menu>li>a:hover{color:#000}.skin-red-light .treeview-menu>li.active>a{font-weight:600}.skin-red-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-red-light .sidebar-form input[type="text"],.skin-red-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-red-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-red-light .sidebar-form input[type="text"]:focus,.skin-red-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-red-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-red-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-red-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}} \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-red.min.css b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-red.min.css new file mode 100644 index 0000000..3252b27 --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-red.min.css @@ -0,0 +1 @@ +.skin-red .main-header .navbar{background-color:#dd4b39}.skin-red .main-header .navbar .nav>li>a{color:#fff}.skin-red .main-header .navbar .nav>li>a:hover,.skin-red .main-header .navbar .nav>li>a:active,.skin-red .main-header .navbar .nav>li>a:focus,.skin-red .main-header .navbar .nav .open>a,.skin-red .main-header .navbar .nav .open>a:hover,.skin-red .main-header .navbar .nav .open>a:focus,.skin-red .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-red .main-header .navbar .sidebar-toggle{color:#fff}.skin-red .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-red .main-header .navbar .sidebar-toggle{color:#fff}.skin-red .main-header .navbar .sidebar-toggle:hover{background-color:#d73925}@media (max-width:767px){.skin-red .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-red .main-header .navbar .dropdown-menu li a{color:#fff}.skin-red .main-header .navbar .dropdown-menu li a:hover{background:#d73925}}.skin-red .main-header .logo{background-color:#d73925;color:#fff;border-bottom:0 solid transparent}.skin-red .main-header .logo:hover{background-color:#d33724}.skin-red .main-header li.user-header{background-color:#dd4b39}.skin-red .content-header{background:transparent}.skin-red .wrapper,.skin-red .main-sidebar,.skin-red .left-side{background-color:#222d32}.skin-red .user-panel>.info,.skin-red .user-panel>.info>a{color:#fff}.skin-red .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-red .sidebar-menu>li>a{border-left:3px solid transparent}.skin-red .sidebar-menu>li:hover>a,.skin-red .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#dd4b39}.skin-red .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-red .sidebar a{color:#b8c7ce}.skin-red .sidebar a:hover{text-decoration:none}.skin-red .treeview-menu>li>a{color:#8aa4af}.skin-red .treeview-menu>li.active>a,.skin-red .treeview-menu>li>a:hover{color:#fff}.skin-red .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-red .sidebar-form input[type="text"],.skin-red .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-red .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-red .sidebar-form input[type="text"]:focus,.skin-red .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-red .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-red .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0} \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-yellow-light.min.css b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-yellow-light.min.css new file mode 100644 index 0000000..773b254 --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-yellow-light.min.css @@ -0,0 +1 @@ +.skin-yellow-light .main-header .navbar{background-color:#f39c12}.skin-yellow-light .main-header .navbar .nav>li>a{color:#fff}.skin-yellow-light .main-header .navbar .nav>li>a:hover,.skin-yellow-light .main-header .navbar .nav>li>a:active,.skin-yellow-light .main-header .navbar .nav>li>a:focus,.skin-yellow-light .main-header .navbar .nav .open>a,.skin-yellow-light .main-header .navbar .nav .open>a:hover,.skin-yellow-light .main-header .navbar .nav .open>a:focus,.skin-yellow-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-yellow-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-yellow-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-yellow-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-yellow-light .main-header .navbar .sidebar-toggle:hover{background-color:#e08e0b}@media (max-width:767px){.skin-yellow-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-yellow-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-yellow-light .main-header .navbar .dropdown-menu li a:hover{background:#e08e0b}}.skin-yellow-light .main-header .logo{background-color:#f39c12;color:#fff;border-bottom:0 solid transparent}.skin-yellow-light .main-header .logo:hover{background-color:#f39a0d}.skin-yellow-light .main-header li.user-header{background-color:#f39c12}.skin-yellow-light .content-header{background:transparent}.skin-yellow-light .wrapper,.skin-yellow-light .main-sidebar,.skin-yellow-light .left-side{background-color:#f9fafc}.skin-yellow-light .content-wrapper,.skin-yellow-light .main-footer{border-left:1px solid #d2d6de}.skin-yellow-light .user-panel>.info,.skin-yellow-light .user-panel>.info>a{color:#444}.skin-yellow-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-yellow-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-yellow-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-yellow-light .sidebar-menu>li:hover>a,.skin-yellow-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-yellow-light .sidebar-menu>li.active{border-left-color:#f39c12}.skin-yellow-light .sidebar-menu>li.active>a{font-weight:600}.skin-yellow-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-yellow-light .sidebar a{color:#444}.skin-yellow-light .sidebar a:hover{text-decoration:none}.skin-yellow-light .treeview-menu>li>a{color:#777}.skin-yellow-light .treeview-menu>li.active>a,.skin-yellow-light .treeview-menu>li>a:hover{color:#000}.skin-yellow-light .treeview-menu>li.active>a{font-weight:600}.skin-yellow-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-yellow-light .sidebar-form input[type="text"],.skin-yellow-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-yellow-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-yellow-light .sidebar-form input[type="text"]:focus,.skin-yellow-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-yellow-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-yellow-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-yellow-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}} \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-yellow.min.css b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-yellow.min.css new file mode 100644 index 0000000..67fc2e2 --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-yellow.min.css @@ -0,0 +1 @@ +.skin-yellow .main-header .navbar{background-color:#f39c12}.skin-yellow .main-header .navbar .nav>li>a{color:#fff}.skin-yellow .main-header .navbar .nav>li>a:hover,.skin-yellow .main-header .navbar .nav>li>a:active,.skin-yellow .main-header .navbar .nav>li>a:focus,.skin-yellow .main-header .navbar .nav .open>a,.skin-yellow .main-header .navbar .nav .open>a:hover,.skin-yellow .main-header .navbar .nav .open>a:focus,.skin-yellow .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-yellow .main-header .navbar .sidebar-toggle{color:#fff}.skin-yellow .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-yellow .main-header .navbar .sidebar-toggle{color:#fff}.skin-yellow .main-header .navbar .sidebar-toggle:hover{background-color:#e08e0b}@media (max-width:767px){.skin-yellow .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-yellow .main-header .navbar .dropdown-menu li a{color:#fff}.skin-yellow .main-header .navbar .dropdown-menu li a:hover{background:#e08e0b}}.skin-yellow .main-header .logo{background-color:#e08e0b;color:#fff;border-bottom:0 solid transparent}.skin-yellow .main-header .logo:hover{background-color:#db8b0b}.skin-yellow .main-header li.user-header{background-color:#f39c12}.skin-yellow .content-header{background:transparent}.skin-yellow .wrapper,.skin-yellow .main-sidebar,.skin-yellow .left-side{background-color:#222d32}.skin-yellow .user-panel>.info,.skin-yellow .user-panel>.info>a{color:#fff}.skin-yellow .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-yellow .sidebar-menu>li>a{border-left:3px solid transparent}.skin-yellow .sidebar-menu>li:hover>a,.skin-yellow .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#f39c12}.skin-yellow .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-yellow .sidebar a{color:#b8c7ce}.skin-yellow .sidebar a:hover{text-decoration:none}.skin-yellow .treeview-menu>li>a{color:#8aa4af}.skin-yellow .treeview-menu>li.active>a,.skin-yellow .treeview-menu>li>a:hover{color:#fff}.skin-yellow .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-yellow .sidebar-form input[type="text"],.skin-yellow .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-yellow .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-yellow .sidebar-form input[type="text"]:focus,.skin-yellow .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-yellow .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-yellow .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0} \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/dist/img/boxed-bg.jpg b/public/vendor/laravel-admin/AdminLTE/dist/img/boxed-bg.jpg new file mode 100644 index 0000000..e47586a Binary files /dev/null and b/public/vendor/laravel-admin/AdminLTE/dist/img/boxed-bg.jpg differ diff --git a/public/vendor/laravel-admin/AdminLTE/dist/img/boxed-bg.png b/public/vendor/laravel-admin/AdminLTE/dist/img/boxed-bg.png new file mode 100644 index 0000000..2f9f1ad Binary files /dev/null and b/public/vendor/laravel-admin/AdminLTE/dist/img/boxed-bg.png differ diff --git a/public/vendor/laravel-admin/AdminLTE/dist/img/default-50x50.gif b/public/vendor/laravel-admin/AdminLTE/dist/img/default-50x50.gif new file mode 100644 index 0000000..2f5a14a Binary files /dev/null and b/public/vendor/laravel-admin/AdminLTE/dist/img/default-50x50.gif differ diff --git a/public/vendor/laravel-admin/AdminLTE/dist/img/icons.png b/public/vendor/laravel-admin/AdminLTE/dist/img/icons.png new file mode 100644 index 0000000..048c8d2 Binary files /dev/null and b/public/vendor/laravel-admin/AdminLTE/dist/img/icons.png differ diff --git a/public/vendor/laravel-admin/AdminLTE/dist/img/user2-160x160.jpg b/public/vendor/laravel-admin/AdminLTE/dist/img/user2-160x160.jpg new file mode 100644 index 0000000..aec74cb Binary files /dev/null and b/public/vendor/laravel-admin/AdminLTE/dist/img/user2-160x160.jpg differ diff --git a/public/vendor/laravel-admin/AdminLTE/dist/js/app.min.js b/public/vendor/laravel-admin/AdminLTE/dist/js/app.min.js new file mode 100644 index 0000000..8ec5fe1 --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/dist/js/app.min.js @@ -0,0 +1,13 @@ +/*! AdminLTE app.js + * ================ + * Main JS application file for AdminLTE v2. This file + * should be included in all pages. It controls some layout + * options and implements exclusive AdminLTE plugins. + * + * @Author Almsaeed Studio + * @Support + * @Email + * @version 2.3.2 + * @license MIT + */ +function _init(){"use strict";$.AdminLTE.layout={activate:function(){var a=this;a.fix(),a.fixSidebar(),$(window,".wrapper").resize(function(){a.fix(),a.fixSidebar()})},fix:function(){var a=$(".main-header").outerHeight()+$(".main-footer").outerHeight(),b=$(window).height(),c=$(".sidebar").height();if($("body").hasClass("fixed"))$(".content-wrapper, .right-side").css("min-height",b-$(".main-footer").outerHeight());else{var d;b>=c?($(".content-wrapper, .right-side").css("min-height",b-a),d=b-a):($(".content-wrapper, .right-side").css("min-height",c),d=c);var e=$($.AdminLTE.options.controlSidebarOptions.selector);"undefined"!=typeof e&&e.height()>d&&$(".content-wrapper, .right-side").css("min-height",e.height())}},fixSidebar:function(){return $("body").hasClass("fixed")?("undefined"==typeof $.fn.slimScroll&&window.console&&window.console.error("Error: the fixed layout requires the slimscroll plugin!"),void($.AdminLTE.options.sidebarSlimScroll&&"undefined"!=typeof $.fn.slimScroll&&($(".sidebar").slimScroll({destroy:!0}).height("auto"),$(".sidebar").slimscroll({height:$(window).height()-$(".main-header").height()+"px",color:"rgba(0,0,0,0.2)",size:"3px"})))):void("undefined"!=typeof $.fn.slimScroll&&$(".sidebar").slimScroll({destroy:!0}).height("auto"))}},$.AdminLTE.pushMenu={activate:function(a){var b=$.AdminLTE.options.screenSizes;$(document).on("click",a,function(a){a.preventDefault(),$(window).width()>b.sm-1?$("body").hasClass("sidebar-collapse")?$("body").removeClass("sidebar-collapse").trigger("expanded.pushMenu"):$("body").addClass("sidebar-collapse").trigger("collapsed.pushMenu"):$("body").hasClass("sidebar-open")?$("body").removeClass("sidebar-open").removeClass("sidebar-collapse").trigger("collapsed.pushMenu"):$("body").addClass("sidebar-open").trigger("expanded.pushMenu")}),$(".content-wrapper").click(function(){$(window).width()<=b.sm-1&&$("body").hasClass("sidebar-open")&&$("body").removeClass("sidebar-open")}),($.AdminLTE.options.sidebarExpandOnHover||$("body").hasClass("fixed")&&$("body").hasClass("sidebar-mini"))&&this.expandOnHover()},expandOnHover:function(){var a=this,b=$.AdminLTE.options.screenSizes.sm-1;$(".main-sidebar").hover(function(){$("body").hasClass("sidebar-mini")&&$("body").hasClass("sidebar-collapse")&&$(window).width()>b&&a.expand()},function(){$("body").hasClass("sidebar-mini")&&$("body").hasClass("sidebar-expanded-on-hover")&&$(window).width()>b&&a.collapse()})},expand:function(){$("body").removeClass("sidebar-collapse").addClass("sidebar-expanded-on-hover")},collapse:function(){$("body").hasClass("sidebar-expanded-on-hover")&&$("body").removeClass("sidebar-expanded-on-hover").addClass("sidebar-collapse")}},$.AdminLTE.tree=function(a){var b=this,c=$.AdminLTE.options.animationSpeed;$(a).on("click","li a",function(a){var d=$(this),e=d.next();if(e.is(".treeview-menu")&&e.is(":visible")&&!$("body").hasClass("sidebar-collapse"))e.slideUp(c,function(){e.removeClass("menu-open")}),e.parent("li").removeClass("active");else if(e.is(".treeview-menu")&&!e.is(":visible")){var f=d.parents("ul").first(),g=f.find("ul:visible").slideUp(c);g.removeClass("menu-open");var h=d.parent("li");e.slideDown(c,function(){e.addClass("menu-open"),f.find("li.active").removeClass("active"),h.addClass("active"),b.layout.fix()})}e.is(".treeview-menu")&&a.preventDefault()})},$.AdminLTE.controlSidebar={activate:function(){var a=this,b=$.AdminLTE.options.controlSidebarOptions,c=$(b.selector),d=$(b.toggleBtnSelector);d.on("click",function(d){d.preventDefault(),c.hasClass("control-sidebar-open")||$("body").hasClass("control-sidebar-open")?a.close(c,b.slide):a.open(c,b.slide)});var e=$(".control-sidebar-bg");a._fix(e),$("body").hasClass("fixed")?a._fixForFixed(c):$(".content-wrapper, .right-side").height() .box-body, > .box-footer, > form >.box-body, > form > .box-footer");c.hasClass("collapsed-box")?(a.children(":first").removeClass(b.icons.open).addClass(b.icons.collapse),d.slideDown(b.animationSpeed,function(){c.removeClass("collapsed-box")})):(a.children(":first").removeClass(b.icons.collapse).addClass(b.icons.open),d.slideUp(b.animationSpeed,function(){c.addClass("collapsed-box")}))},remove:function(a){var b=a.parents(".box").first();b.slideUp(this.animationSpeed)}}}if("undefined"==typeof jQuery)throw new Error("AdminLTE requires jQuery");$.AdminLTE={},$.AdminLTE.options={navbarMenuSlimscroll:!0,navbarMenuSlimscrollWidth:"3px",navbarMenuHeight:"200px",animationSpeed:500,sidebarToggleSelector:"[data-toggle='offcanvas']",sidebarPushMenu:!0,sidebarSlimScroll:!0,sidebarExpandOnHover:!1,enableBoxRefresh:!0,enableBSToppltip:!0,BSTooltipSelector:"[data-toggle='tooltip']",enableFastclick:!0,enableControlSidebar:!0,controlSidebarOptions:{toggleBtnSelector:"[data-toggle='control-sidebar']",selector:".control-sidebar",slide:!0},enableBoxWidget:!0,boxWidgetOptions:{boxWidgetIcons:{collapse:"fa-minus",open:"fa-plus",remove:"fa-times"},boxWidgetSelectors:{remove:'[data-widget="remove"]',collapse:'[data-widget="collapse"]'}},directChat:{enable:!0,contactToggleSelector:'[data-widget="chat-pane-toggle"]'},colors:{lightBlue:"#3c8dbc",red:"#f56954",green:"#00a65a",aqua:"#00c0ef",yellow:"#f39c12",blue:"#0073b7",navy:"#001F3F",teal:"#39CCCC",olive:"#3D9970",lime:"#01FF70",orange:"#FF851B",fuchsia:"#F012BE",purple:"#8E24AA",maroon:"#D81B60",black:"#222222",gray:"#d2d6de"},screenSizes:{xs:480,sm:768,md:992,lg:1200}},$(function(){"use strict";$("body").removeClass("hold-transition"),"undefined"!=typeof AdminLTEOptions&&$.extend(!0,$.AdminLTE.options,AdminLTEOptions);var a=$.AdminLTE.options;_init(),$.AdminLTE.layout.activate(),$.AdminLTE.tree(".sidebar"),a.enableControlSidebar&&$.AdminLTE.controlSidebar.activate(),a.navbarMenuSlimscroll&&"undefined"!=typeof $.fn.slimscroll&&$(".navbar .menu").slimscroll({height:a.navbarMenuHeight,alwaysVisible:!1,size:a.navbarMenuSlimscrollWidth}).css("width","100%"),a.sidebarPushMenu&&$.AdminLTE.pushMenu.activate(a.sidebarToggleSelector),a.enableBSToppltip&&$("body").tooltip({selector:a.BSTooltipSelector}),a.enableBoxWidget&&$.AdminLTE.boxWidget.activate(),a.enableFastclick&&"undefined"!=typeof FastClick&&FastClick.attach(document.body),a.directChat.enable&&$(document).on("click",a.directChat.contactToggleSelector,function(){var a=$(this).parents(".direct-chat").first();a.toggleClass("direct-chat-contacts-open")}),$('.btn-group[data-toggle="btn-toggle"]').each(function(){var a=$(this);$(this).find(".btn").on("click",function(b){a.find(".btn.active").removeClass("active"),$(this).addClass("active"),b.preventDefault()})})}),function(a){"use strict";a.fn.boxRefresh=function(b){function c(a){a.append(f),e.onLoadStart.call(a)}function d(a){a.find(f).remove(),e.onLoadDone.call(a)}var e=a.extend({trigger:".refresh-btn",source:"",onLoadStart:function(a){return a},onLoadDone:function(a){return a}},b),f=a('
');return this.each(function(){if(""===e.source)return void(window.console&&window.console.log("Please specify a source first - boxRefresh()"));var b=a(this),f=b.find(e.trigger).first();f.on("click",function(a){a.preventDefault(),c(b),b.find(".box-body").load(e.source,function(){d(b)})})})}}(jQuery),function(a){"use strict";a.fn.activateBox=function(){a.AdminLTE.boxWidget.activate(this)},a.fn.toggleBox=function(){var b=a(a.AdminLTE.boxWidget.selectors.collapse,this);a.AdminLTE.boxWidget.collapse(b)},a.fn.removeBox=function(){var b=a(a.AdminLTE.boxWidget.selectors.remove,this);a.AdminLTE.boxWidget.remove(b)}}(jQuery),function(a){"use strict";a.fn.todolist=function(b){var c=a.extend({onCheck:function(a){return a},onUncheck:function(a){return a}},b);return this.each(function(){"undefined"!=typeof a.fn.iCheck?(a("input",this).on("ifChecked",function(){var b=a(this).parents("li").first();b.toggleClass("done"),c.onCheck.call(b)}),a("input",this).on("ifUnchecked",function(){var b=a(this).parents("li").first();b.toggleClass("done"),c.onUncheck.call(b)})):a("input",this).on("change",function(){var b=a(this).parents("li").first();b.toggleClass("done"),a("input",b).is(":checked")?c.onCheck.call(b):c.onUncheck.call(b)})})}}(jQuery); \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/plugins/bootstrap-slider/bootstrap-slider.js b/public/vendor/laravel-admin/AdminLTE/plugins/bootstrap-slider/bootstrap-slider.js new file mode 100644 index 0000000..2e072fe --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/plugins/bootstrap-slider/bootstrap-slider.js @@ -0,0 +1,1167 @@ +/*! ========================================================= + * bootstrap-slider.js + * + * Maintainers: + * Kyle Kemp + * - Twitter: @seiyria + * - Github: seiyria + * Rohit Kalkur + * - Twitter: @Rovolutionary + * - Github: rovolution + * + * ========================================================= + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= */ + + +/** + * Bridget makes jQuery widgets + * v1.0.1 + * MIT license + */ +( function( $ ) { + + ( function( $ ) { + + 'use strict'; + + // -------------------------- utils -------------------------- // + + var slice = Array.prototype.slice; + + function noop() {} + + // -------------------------- definition -------------------------- // + + function defineBridget( $ ) { + + // bail if no jQuery + if ( !$ ) { + return; + } + + // -------------------------- addOptionMethod -------------------------- // + + /** + * adds option method -> $().plugin('option', {...}) + * @param {Function} PluginClass - constructor class + */ + function addOptionMethod( PluginClass ) { + // don't overwrite original option method + if ( PluginClass.prototype.option ) { + return; + } + + // option setter + PluginClass.prototype.option = function( opts ) { + // bail out if not an object + if ( !$.isPlainObject( opts ) ){ + return; + } + this.options = $.extend( true, this.options, opts ); + }; + } + + + // -------------------------- plugin bridge -------------------------- // + + // helper function for logging errors + // $.error breaks jQuery chaining + var logError = typeof console === 'undefined' ? noop : + function( message ) { + console.error( message ); + }; + + /** + * jQuery plugin bridge, access methods like $elem.plugin('method') + * @param {String} namespace - plugin name + * @param {Function} PluginClass - constructor class + */ + function bridge( namespace, PluginClass ) { + // add to jQuery fn namespace + $.fn[ namespace ] = function( options ) { + if ( typeof options === 'string' ) { + // call plugin method when first argument is a string + // get arguments for method + var args = slice.call( arguments, 1 ); + + for ( var i=0, len = this.length; i < len; i++ ) { + var elem = this[i]; + var instance = $.data( elem, namespace ); + if ( !instance ) { + logError( "cannot call methods on " + namespace + " prior to initialization; " + + "attempted to call '" + options + "'" ); + continue; + } + if ( !$.isFunction( instance[options] ) || options.charAt(0) === '_' ) { + logError( "no such method '" + options + "' for " + namespace + " instance" ); + continue; + } + + // trigger method with arguments + var returnValue = instance[ options ].apply( instance, args); + + // break look and return first value if provided + if ( returnValue !== undefined && returnValue !== instance) { + return returnValue; + } + } + // return this if no return value + return this; + } else { + var objects = this.map( function() { + var instance = $.data( this, namespace ); + if ( instance ) { + // apply options & init + instance.option( options ); + instance._init(); + } else { + // initialize new instance + instance = new PluginClass( this, options ); + $.data( this, namespace, instance ); + } + return $(this); + }); + + if(!objects || objects.length > 1) { + return objects; + } else { + return objects[0]; + } + } + }; + + } + + // -------------------------- bridget -------------------------- // + + /** + * converts a Prototypical class into a proper jQuery plugin + * the class must have a ._init method + * @param {String} namespace - plugin name, used in $().pluginName + * @param {Function} PluginClass - constructor class + */ + $.bridget = function( namespace, PluginClass ) { + addOptionMethod( PluginClass ); + bridge( namespace, PluginClass ); + }; + + return $.bridget; + + } + + // get jquery from browser global + defineBridget( $ ); + + })( $ ); + + + /************************************************* + + BOOTSTRAP-SLIDER SOURCE CODE + + **************************************************/ + + (function( $ ) { + + var ErrorMsgs = { + formatInvalidInputErrorMsg : function(input) { + return "Invalid input value '" + input + "' passed in"; + }, + callingContextNotSliderInstance : "Calling context element does not have instance of Slider bound to it. Check your code to make sure the JQuery object returned from the call to the slider() initializer is calling the method" + }; + + + + /************************************************* + + CONSTRUCTOR + + **************************************************/ + var Slider = function(element, options) { + createNewSlider.call(this, element, options); + return this; + }; + + function createNewSlider(element, options) { + /************************************************* + + Create Markup + + **************************************************/ + if(typeof element === "string") { + this.element = document.querySelector(element); + } else if(element instanceof HTMLElement) { + this.element = element; + } + + var origWidth = this.element.style.width; + var updateSlider = false; + var parent = this.element.parentNode; + var sliderTrackSelection; + var sliderMinHandle; + var sliderMaxHandle; + + if (this.sliderElem) { + updateSlider = true; + } else { + /* Create elements needed for slider */ + this.sliderElem = document.createElement("div"); + this.sliderElem.className = "slider"; + + /* Create slider track elements */ + var sliderTrack = document.createElement("div"); + sliderTrack.className = "slider-track"; + + sliderTrackSelection = document.createElement("div"); + sliderTrackSelection.className = "slider-selection"; + + sliderMinHandle = document.createElement("div"); + sliderMinHandle.className = "slider-handle min-slider-handle"; + + sliderMaxHandle = document.createElement("div"); + sliderMaxHandle.className = "slider-handle max-slider-handle"; + + sliderTrack.appendChild(sliderTrackSelection); + sliderTrack.appendChild(sliderMinHandle); + sliderTrack.appendChild(sliderMaxHandle); + + var createAndAppendTooltipSubElements = function(tooltipElem) { + var arrow = document.createElement("div"); + arrow.className = "tooltip-arrow"; + + var inner = document.createElement("div"); + inner.className = "tooltip-inner"; + + tooltipElem.appendChild(arrow); + tooltipElem.appendChild(inner); + }; + + /* Create tooltip elements */ + var sliderTooltip = document.createElement("div"); + sliderTooltip.className = "tooltip tooltip-main"; + createAndAppendTooltipSubElements(sliderTooltip); + + var sliderTooltipMin = document.createElement("div"); + sliderTooltipMin.className = "tooltip tooltip-min"; + createAndAppendTooltipSubElements(sliderTooltipMin); + + var sliderTooltipMax = document.createElement("div"); + sliderTooltipMax.className = "tooltip tooltip-max"; + createAndAppendTooltipSubElements(sliderTooltipMax); + + + /* Append components to sliderElem */ + this.sliderElem.appendChild(sliderTrack); + this.sliderElem.appendChild(sliderTooltip); + this.sliderElem.appendChild(sliderTooltipMin); + this.sliderElem.appendChild(sliderTooltipMax); + + /* Append slider element to parent container, right before the original element */ + parent.insertBefore(this.sliderElem, this.element); + + /* Hide original element */ + this.element.style.display = "none"; + } + /* If JQuery exists, cache JQ references */ + if($) { + this.$element = $(this.element); + this.$sliderElem = $(this.sliderElem); + } + + /************************************************* + + Process Options + + **************************************************/ + options = options ? options : {}; + var optionTypes = Object.keys(this.defaultOptions); + + for(var i = 0; i < optionTypes.length; i++) { + var optName = optionTypes[i]; + + // First check if an option was passed in via the constructor + var val = options[optName]; + // If no data attrib, then check data atrributes + val = (typeof val !== 'undefined') ? val : getDataAttrib(this.element, optName); + // Finally, if nothing was specified, use the defaults + val = (val !== null) ? val : this.defaultOptions[optName]; + + // Set all options on the instance of the Slider + if(!this.options) { + this.options = {}; + } + this.options[optName] = val; + } + + function getDataAttrib(element, optName) { + var dataName = "data-slider-" + optName; + var dataValString = element.getAttribute(dataName); + + try { + return JSON.parse(dataValString); + } + catch(err) { + return dataValString; + } + } + + /************************************************* + + Setup + + **************************************************/ + this.eventToCallbackMap = {}; + this.sliderElem.id = this.options.id; + + this.touchCapable = 'ontouchstart' in window || (window.DocumentTouch && document instanceof window.DocumentTouch); + + this.tooltip = this.sliderElem.querySelector('.tooltip-main'); + this.tooltipInner = this.tooltip.querySelector('.tooltip-inner'); + + this.tooltip_min = this.sliderElem.querySelector('.tooltip-min'); + this.tooltipInner_min = this.tooltip_min.querySelector('.tooltip-inner'); + + this.tooltip_max = this.sliderElem.querySelector('.tooltip-max'); + this.tooltipInner_max= this.tooltip_max.querySelector('.tooltip-inner'); + + if (updateSlider === true) { + // Reset classes + this._removeClass(this.sliderElem, 'slider-horizontal'); + this._removeClass(this.sliderElem, 'slider-vertical'); + this._removeClass(this.tooltip, 'hide'); + this._removeClass(this.tooltip_min, 'hide'); + this._removeClass(this.tooltip_max, 'hide'); + + // Undo existing inline styles for track + ["left", "top", "width", "height"].forEach(function(prop) { + this._removeProperty(this.trackSelection, prop); + }, this); + + // Undo inline styles on handles + [this.handle1, this.handle2].forEach(function(handle) { + this._removeProperty(handle, 'left'); + this._removeProperty(handle, 'top'); + }, this); + + // Undo inline styles and classes on tooltips + [this.tooltip, this.tooltip_min, this.tooltip_max].forEach(function(tooltip) { + this._removeProperty(tooltip, 'left'); + this._removeProperty(tooltip, 'top'); + this._removeProperty(tooltip, 'margin-left'); + this._removeProperty(tooltip, 'margin-top'); + + this._removeClass(tooltip, 'right'); + this._removeClass(tooltip, 'top'); + }, this); + } + + if(this.options.orientation === 'vertical') { + this._addClass(this.sliderElem,'slider-vertical'); + + this.stylePos = 'top'; + this.mousePos = 'pageY'; + this.sizePos = 'offsetHeight'; + + this._addClass(this.tooltip, 'right'); + this.tooltip.style.left = '100%'; + + this._addClass(this.tooltip_min, 'right'); + this.tooltip_min.style.left = '100%'; + + this._addClass(this.tooltip_max, 'right'); + this.tooltip_max.style.left = '100%'; + } else { + this._addClass(this.sliderElem, 'slider-horizontal'); + this.sliderElem.style.width = origWidth; + + this.options.orientation = 'horizontal'; + this.stylePos = 'left'; + this.mousePos = 'pageX'; + this.sizePos = 'offsetWidth'; + + this._addClass(this.tooltip, 'top'); + this.tooltip.style.top = -this.tooltip.outerHeight - 14 + 'px'; + + this._addClass(this.tooltip_min, 'top'); + this.tooltip_min.style.top = -this.tooltip_min.outerHeight - 14 + 'px'; + + this._addClass(this.tooltip_max, 'top'); + this.tooltip_max.style.top = -this.tooltip_max.outerHeight - 14 + 'px'; + } + + if (this.options.value instanceof Array) { + this.options.range = true; + } else if (this.options.range) { + // User wants a range, but value is not an array + this.options.value = [this.options.value, this.options.max]; + } + + this.trackSelection = sliderTrackSelection || this.trackSelection; + if (this.options.selection === 'none') { + this._addClass(this.trackSelection, 'hide'); + } + + this.handle1 = sliderMinHandle || this.handle1; + this.handle2 = sliderMaxHandle || this.handle2; + + if (updateSlider === true) { + // Reset classes + this._removeClass(this.handle1, 'round triangle'); + this._removeClass(this.handle2, 'round triangle hide'); + } + + var availableHandleModifiers = ['round', 'triangle', 'custom']; + var isValidHandleType = availableHandleModifiers.indexOf(this.options.handle) !== -1; + if (isValidHandleType) { + this._addClass(this.handle1, this.options.handle); + this._addClass(this.handle2, this.options.handle); + } + + this.offset = this._offset(this.sliderElem); + this.size = this.sliderElem[this.sizePos]; + this.setValue(this.options.value); + + /****************************************** + + Bind Event Listeners + + ******************************************/ + + // Bind keyboard handlers + this.handle1Keydown = this._keydown.bind(this, 0); + this.handle1.addEventListener("keydown", this.handle1Keydown, false); + + this.handle2Keydown = this._keydown.bind(this, 0); + this.handle2.addEventListener("keydown", this.handle2Keydown, false); + + if (this.touchCapable) { + // Bind touch handlers + this.mousedown = this._mousedown.bind(this); + this.sliderElem.addEventListener("touchstart", this.mousedown, false); + } else { + // Bind mouse handlers + this.mousedown = this._mousedown.bind(this); + this.sliderElem.addEventListener("mousedown", this.mousedown, false); + } + + // Bind tooltip-related handlers + if(this.options.tooltip === 'hide') { + this._addClass(this.tooltip, 'hide'); + this._addClass(this.tooltip_min, 'hide'); + this._addClass(this.tooltip_max, 'hide'); + } else if(this.options.tooltip === 'always') { + this._showTooltip(); + this._alwaysShowTooltip = true; + } else { + this.showTooltip = this._showTooltip.bind(this); + this.hideTooltip = this._hideTooltip.bind(this); + + this.sliderElem.addEventListener("mouseenter", this.showTooltip, false); + this.sliderElem.addEventListener("mouseleave", this.hideTooltip, false); + + this.handle1.addEventListener("focus", this.showTooltip, false); + this.handle1.addEventListener("blur", this.hideTooltip, false); + + this.handle2.addEventListener("focus", this.showTooltip, false); + this.handle2.addEventListener("blur", this.hideTooltip, false); + } + + if(this.options.enabled) { + this.enable(); + } else { + this.disable(); + } + } + + /************************************************* + + INSTANCE PROPERTIES/METHODS + + - Any methods bound to the prototype are considered + part of the plugin's `public` interface + + **************************************************/ + Slider.prototype = { + _init: function() {}, // NOTE: Must exist to support bridget + + constructor: Slider, + + defaultOptions: { + id: "", + min: 0, + max: 10, + step: 1, + precision: 0, + orientation: 'horizontal', + value: 5, + range: false, + selection: 'before', + tooltip: 'show', + tooltip_split: false, + handle: 'round', + reversed: false, + enabled: true, + formatter: function(val) { + if(val instanceof Array) { + return val[0] + " : " + val[1]; + } else { + return val; + } + }, + natural_arrow_keys: false + }, + + over: false, + + inDrag: false, + + getValue: function() { + if (this.options.range) { + return this.options.value; + } + return this.options.value[0]; + }, + + setValue: function(val, triggerSlideEvent) { + if (!val) { + val = 0; + } + this.options.value = this._validateInputValue(val); + var applyPrecision = this._applyPrecision.bind(this); + + if (this.options.range) { + this.options.value[0] = applyPrecision(this.options.value[0]); + this.options.value[1] = applyPrecision(this.options.value[1]); + + this.options.value[0] = Math.max(this.options.min, Math.min(this.options.max, this.options.value[0])); + this.options.value[1] = Math.max(this.options.min, Math.min(this.options.max, this.options.value[1])); + } else { + this.options.value = applyPrecision(this.options.value); + this.options.value = [ Math.max(this.options.min, Math.min(this.options.max, this.options.value))]; + this._addClass(this.handle2, 'hide'); + if (this.options.selection === 'after') { + this.options.value[1] = this.options.max; + } else { + this.options.value[1] = this.options.min; + } + } + + this.diff = this.options.max - this.options.min; + if (this.diff > 0) { + this.percentage = [ + (this.options.value[0] - this.options.min) * 100 / this.diff, + (this.options.value[1] - this.options.min) * 100 / this.diff, + this.options.step * 100 / this.diff + ]; + } else { + this.percentage = [0, 0, 100]; + } + + this._layout(); + + var sliderValue = this.options.range ? this.options.value : this.options.value[0]; + this._setDataVal(sliderValue); + + if(triggerSlideEvent === true) { + this._trigger('slide', sliderValue); + } + + return this; + }, + + destroy: function(){ + // Remove event handlers on slider elements + this._removeSliderEventHandlers(); + + // Remove the slider from the DOM + this.sliderElem.parentNode.removeChild(this.sliderElem); + /* Show original element */ + this.element.style.display = ""; + + // Clear out custom event bindings + this._cleanUpEventCallbacksMap(); + + // Remove data values + this.element.removeAttribute("data"); + + // Remove JQuery handlers/data + if($) { + this._unbindJQueryEventHandlers(); + this.$element.removeData('slider'); + } + }, + + disable: function() { + this.options.enabled = false; + this.handle1.removeAttribute("tabindex"); + this.handle2.removeAttribute("tabindex"); + this._addClass(this.sliderElem, 'slider-disabled'); + this._trigger('slideDisabled'); + + return this; + }, + + enable: function() { + this.options.enabled = true; + this.handle1.setAttribute("tabindex", 0); + this.handle2.setAttribute("tabindex", 0); + this._removeClass(this.sliderElem, 'slider-disabled'); + this._trigger('slideEnabled'); + + return this; + }, + + toggle: function() { + if(this.options.enabled) { + this.disable(); + } else { + this.enable(); + } + + return this; + }, + + isEnabled: function() { + return this.options.enabled; + }, + + on: function(evt, callback) { + if($) { + this.$element.on(evt, callback); + this.$sliderElem.on(evt, callback); + } else { + this._bindNonQueryEventHandler(evt, callback); + } + return this; + }, + + getAttribute: function(attribute) { + if(attribute) { + return this.options[attribute]; + } else { + return this.options; + } + }, + + setAttribute: function(attribute, value) { + this.options[attribute] = value; + return this; + }, + + refresh: function() { + this._removeSliderEventHandlers(); + createNewSlider.call(this, this.element, this.options); + if($) { + // Bind new instance of slider to the element + $.data(this.element, 'slider', this); + } + return this; + }, + + /******************************+ + + HELPERS + + - Any method that is not part of the public interface. + - Place it underneath this comment block and write its signature like so: + + _fnName : function() {...} + + ********************************/ + _removeSliderEventHandlers: function() { + // Remove event listeners from handle1 + this.handle1.removeEventListener("keydown", this.handle1Keydown, false); + this.handle1.removeEventListener("focus", this.showTooltip, false); + this.handle1.removeEventListener("blur", this.hideTooltip, false); + + // Remove event listeners from handle2 + this.handle2.removeEventListener("keydown", this.handle2Keydown, false); + this.handle2.removeEventListener("focus", this.handle2Keydown, false); + this.handle2.removeEventListener("blur", this.handle2Keydown, false); + + // Remove event listeners from sliderElem + this.sliderElem.removeEventListener("mouseenter", this.showTooltip, false); + this.sliderElem.removeEventListener("mouseleave", this.hideTooltip, false); + this.sliderElem.removeEventListener("touchstart", this.mousedown, false); + this.sliderElem.removeEventListener("mousedown", this.mousedown, false); + }, + _bindNonQueryEventHandler: function(evt, callback) { + if(this.eventToCallbackMap[evt]===undefined) { + this.eventToCallbackMap[evt] = []; + } + this.eventToCallbackMap[evt].push(callback); + }, + _cleanUpEventCallbacksMap: function() { + var eventNames = Object.keys(this.eventToCallbackMap); + for(var i = 0; i < eventNames.length; i++) { + var eventName = eventNames[i]; + this.eventToCallbackMap[eventName] = null; + } + }, + _showTooltip: function() { + if (this.options.tooltip_split === false ){ + this._addClass(this.tooltip, 'in'); + } else { + this._addClass(this.tooltip_min, 'in'); + this._addClass(this.tooltip_max, 'in'); + } + this.over = true; + }, + _hideTooltip: function() { + if (this.inDrag === false && this.alwaysShowTooltip !== true) { + this._removeClass(this.tooltip, 'in'); + this._removeClass(this.tooltip_min, 'in'); + this._removeClass(this.tooltip_max, 'in'); + } + this.over = false; + }, + _layout: function() { + var positionPercentages; + + if(this.options.reversed) { + positionPercentages = [ 100 - this.percentage[0], this.percentage[1] ]; + } else { + positionPercentages = [ this.percentage[0], this.percentage[1] ]; + } + + this.handle1.style[this.stylePos] = positionPercentages[0]+'%'; + this.handle2.style[this.stylePos] = positionPercentages[1]+'%'; + + if (this.options.orientation === 'vertical') { + this.trackSelection.style.top = Math.min(positionPercentages[0], positionPercentages[1]) +'%'; + this.trackSelection.style.height = Math.abs(positionPercentages[0] - positionPercentages[1]) +'%'; + } else { + this.trackSelection.style.left = Math.min(positionPercentages[0], positionPercentages[1]) +'%'; + this.trackSelection.style.width = Math.abs(positionPercentages[0] - positionPercentages[1]) +'%'; + + var offset_min = this.tooltip_min.getBoundingClientRect(); + var offset_max = this.tooltip_max.getBoundingClientRect(); + + if (offset_min.right > offset_max.left) { + this._removeClass(this.tooltip_max, 'top'); + this._addClass(this.tooltip_max, 'bottom'); + this.tooltip_max.style.top = 18 + 'px'; + } else { + this._removeClass(this.tooltip_max, 'bottom'); + this._addClass(this.tooltip_max, 'top'); + this.tooltip_max.style.top = -30 + 'px'; + } + } + + + var formattedTooltipVal; + + if (this.options.range) { + formattedTooltipVal = this.options.formatter(this.options.value); + this._setText(this.tooltipInner, formattedTooltipVal); + this.tooltip.style[this.stylePos] = (positionPercentages[1] + positionPercentages[0])/2 + '%'; + + if (this.options.orientation === 'vertical') { + this._css(this.tooltip, 'margin-top', -this.tooltip.offsetHeight / 2 + 'px'); + } else { + this._css(this.tooltip, 'margin-left', -this.tooltip.offsetWidth / 2 + 'px'); + } + + if (this.options.orientation === 'vertical') { + this._css(this.tooltip, 'margin-top', -this.tooltip.offsetHeight / 2 + 'px'); + } else { + this._css(this.tooltip, 'margin-left', -this.tooltip.offsetWidth / 2 + 'px'); + } + + var innerTooltipMinText = this.options.formatter(this.options.value[0]); + this._setText(this.tooltipInner_min, innerTooltipMinText); + + var innerTooltipMaxText = this.options.formatter(this.options.value[1]); + this._setText(this.tooltipInner_max, innerTooltipMaxText); + + this.tooltip_min.style[this.stylePos] = positionPercentages[0] + '%'; + + if (this.options.orientation === 'vertical') { + this._css(this.tooltip_min, 'margin-top', -this.tooltip_min.offsetHeight / 2 + 'px'); + } else { + this._css(this.tooltip_min, 'margin-left', -this.tooltip_min.offsetWidth / 2 + 'px'); + } + + this.tooltip_max.style[this.stylePos] = positionPercentages[1] + '%'; + + if (this.options.orientation === 'vertical') { + this._css(this.tooltip_max, 'margin-top', -this.tooltip_max.offsetHeight / 2 + 'px'); + } else { + this._css(this.tooltip_max, 'margin-left', -this.tooltip_max.offsetWidth / 2 + 'px'); + } + } else { + formattedTooltipVal = this.options.formatter(this.options.value[0]); + this._setText(this.tooltipInner, formattedTooltipVal); + + this.tooltip.style[this.stylePos] = positionPercentages[0] + '%'; + if (this.options.orientation === 'vertical') { + this._css(this.tooltip, 'margin-top', -this.tooltip.offsetHeight / 2 + 'px'); + } else { + this._css(this.tooltip, 'margin-left', -this.tooltip.offsetWidth / 2 + 'px'); + } + } + }, + _removeProperty: function(element, prop) { + if (element.style.removeProperty) { + element.style.removeProperty(prop); + } else { + element.style.removeAttribute(prop); + } + }, + _mousedown: function(ev) { + if(!this.options.enabled) { + return false; + } + + this._triggerFocusOnHandle(); + + this.offset = this._offset(this.sliderElem); + this.size = this.sliderElem[this.sizePos]; + + var percentage = this._getPercentage(ev); + + if (this.options.range) { + var diff1 = Math.abs(this.percentage[0] - percentage); + var diff2 = Math.abs(this.percentage[1] - percentage); + this.dragged = (diff1 < diff2) ? 0 : 1; + } else { + this.dragged = 0; + } + + this.percentage[this.dragged] = this.options.reversed ? 100 - percentage : percentage; + this._layout(); + + this.mousemove = this._mousemove.bind(this); + this.mouseup = this._mouseup.bind(this); + + if (this.touchCapable) { + // Touch: Bind touch events: + document.addEventListener("touchmove", this.mousemove, false); + document.addEventListener("touchend", this.mouseup, false); + } else { + // Bind mouse events: + document.addEventListener("mousemove", this.mousemove, false); + document.addEventListener("mouseup", this.mouseup, false); + } + + this.inDrag = true; + + var val = this._calculateValue(); + this._trigger('slideStart', val); + this._setDataVal(val); + this.setValue(val); + + this._pauseEvent(ev); + + return true; + }, + _triggerFocusOnHandle: function(handleIdx) { + if(handleIdx === 0) { + this.handle1.focus(); + } + if(handleIdx === 1) { + this.handle2.focus(); + } + }, + _keydown: function(handleIdx, ev) { + if(!this.options.enabled) { + return false; + } + + var dir; + switch (ev.keyCode) { + case 37: // left + case 40: // down + dir = -1; + break; + case 39: // right + case 38: // up + dir = 1; + break; + } + if (!dir) { + return; + } + + // use natural arrow keys instead of from min to max + if (this.options.natural_arrow_keys) { + var ifVerticalAndNotReversed = (this.options.orientation === 'vertical' && !this.options.reversed); + var ifHorizontalAndReversed = (this.options.orientation === 'horizontal' && this.options.reversed); + + if (ifVerticalAndNotReversed || ifHorizontalAndReversed) { + dir = dir * -1; + } + } + + var oneStepValuePercentageChange = dir * this.percentage[2]; + var percentage = this.percentage[handleIdx] + oneStepValuePercentageChange; + + if (percentage > 100) { + percentage = 100; + } else if (percentage < 0) { + percentage = 0; + } + + this.dragged = handleIdx; + this._adjustPercentageForRangeSliders(percentage); + this.percentage[this.dragged] = percentage; + this._layout(); + + var val = this._calculateValue(); + + this._trigger('slideStart', val); + this._setDataVal(val); + this.setValue(val, true); + + this._trigger('slideStop', val); + this._setDataVal(val); + + this._pauseEvent(ev); + + return false; + }, + _pauseEvent: function(ev) { + if(ev.stopPropagation) { + ev.stopPropagation(); + } + if(ev.preventDefault) { + ev.preventDefault(); + } + ev.cancelBubble=true; + ev.returnValue=false; + }, + _mousemove: function(ev) { + if(!this.options.enabled) { + return false; + } + + var percentage = this._getPercentage(ev); + this._adjustPercentageForRangeSliders(percentage); + this.percentage[this.dragged] = this.options.reversed ? 100 - percentage : percentage; + this._layout(); + + var val = this._calculateValue(); + this.setValue(val, true); + + return false; + }, + _adjustPercentageForRangeSliders: function(percentage) { + if (this.options.range) { + if (this.dragged === 0 && this.percentage[1] < percentage) { + this.percentage[0] = this.percentage[1]; + this.dragged = 1; + } else if (this.dragged === 1 && this.percentage[0] > percentage) { + this.percentage[1] = this.percentage[0]; + this.dragged = 0; + } + } + }, + _mouseup: function() { + if(!this.options.enabled) { + return false; + } + if (this.touchCapable) { + // Touch: Unbind touch event handlers: + document.removeEventListener("touchmove", this.mousemove, false); + document.removeEventListener("touchend", this.mouseup, false); + } else { + // Unbind mouse event handlers: + document.removeEventListener("mousemove", this.mousemove, false); + document.removeEventListener("mouseup", this.mouseup, false); + } + + this.inDrag = false; + if (this.over === false) { + this._hideTooltip(); + } + var val = this._calculateValue(); + + this._layout(); + this._setDataVal(val); + this._trigger('slideStop', val); + + return false; + }, + _calculateValue: function() { + var val; + if (this.options.range) { + val = [this.options.min,this.options.max]; + if (this.percentage[0] !== 0){ + val[0] = (Math.max(this.options.min, this.options.min + Math.round((this.diff * this.percentage[0]/100)/this.options.step)*this.options.step)); + val[0] = this._applyPrecision(val[0]); + } + if (this.percentage[1] !== 100){ + val[1] = (Math.min(this.options.max, this.options.min + Math.round((this.diff * this.percentage[1]/100)/this.options.step)*this.options.step)); + val[1] = this._applyPrecision(val[1]); + } + this.options.value = val; + } else { + val = (this.options.min + Math.round((this.diff * this.percentage[0]/100)/this.options.step)*this.options.step); + if (val < this.options.min) { + val = this.options.min; + } + else if (val > this.options.max) { + val = this.options.max; + } + val = parseFloat(val); + val = this._applyPrecision(val); + this.options.value = [val, this.options.value[1]]; + } + return val; + }, + _applyPrecision: function(val) { + var precision = this.options.precision || this._getNumDigitsAfterDecimalPlace(this.step); + return this._applyToFixedAndParseFloat(val, precision); + }, + _getNumDigitsAfterDecimalPlace: function(num) { + var match = (''+num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); + if (!match) { return 0; } + return Math.max(0, (match[1] ? match[1].length : 0) - (match[2] ? +match[2] : 0)); + }, + _applyToFixedAndParseFloat: function(num, toFixedInput) { + var truncatedNum = num.toFixed(toFixedInput); + return parseFloat(truncatedNum); + }, + /* + Credits to Mike Samuel for the following method! + Source: http://stackoverflow.com/questions/10454518/javascript-how-to-retrieve-the-number-of-decimals-of-a-string-number + */ + _getPercentage: function(ev) { + if (this.touchCapable && (ev.type === 'touchstart' || ev.type === 'touchmove')) { + ev = ev.touches[0]; + } + var percentage = (ev[this.mousePos] - this.offset[this.stylePos])*100/this.size; + percentage = Math.round(percentage/this.percentage[2])*this.percentage[2]; + return Math.max(0, Math.min(100, percentage)); + }, + _validateInputValue: function(val) { + if(typeof val === 'number') { + return val; + } else if(val instanceof Array) { + this._validateArray(val); + return val; + } else { + throw new Error( ErrorMsgs.formatInvalidInputErrorMsg(val) ); + } + }, + _validateArray: function(val) { + for(var i = 0; i < val.length; i++) { + var input = val[i]; + if (typeof input !== 'number') { throw new Error( ErrorMsgs.formatInvalidInputErrorMsg(input) ); } + } + }, + _setDataVal: function(val) { + var value = "value: '" + val + "'"; + this.element.setAttribute('data', value); + this.element.setAttribute('value', val); + }, + _trigger: function(evt, val) { + val = val || undefined; + + var callbackFnArray = this.eventToCallbackMap[evt]; + if(callbackFnArray && callbackFnArray.length) { + for(var i = 0; i < callbackFnArray.length; i++) { + var callbackFn = callbackFnArray[i]; + callbackFn(val); + } + } + + /* If JQuery exists, trigger JQuery events */ + if($) { + this._triggerJQueryEvent(evt, val); + } + }, + _triggerJQueryEvent: function(evt, val) { + var eventData = { + type: evt, + value: val + }; + this.$element.trigger(eventData); + this.$sliderElem.trigger(eventData); + }, + _unbindJQueryEventHandlers: function() { + this.$element.off(); + this.$sliderElem.off(); + }, + _setText: function(element, text) { + if(typeof element.innerText !== "undefined") { + element.innerText = text; + } else if(typeof element.textContent !== "undefined") { + element.textContent = text; + } + }, + _removeClass: function(element, classString) { + var classes = classString.split(" "); + var newClasses = element.className; + + for(var i = 0; i < classes.length; i++) { + var classTag = classes[i]; + var regex = new RegExp("(?:\\s|^)" + classTag + "(?:\\s|$)"); + newClasses = newClasses.replace(regex, " "); + } + + element.className = newClasses.trim(); + }, + _addClass: function(element, classString) { + var classes = classString.split(" "); + var newClasses = element.className; + + for(var i = 0; i < classes.length; i++) { + var classTag = classes[i]; + var regex = new RegExp("(?:\\s|^)" + classTag + "(?:\\s|$)"); + var ifClassExists = regex.test(newClasses); + + if(!ifClassExists) { + newClasses += " " + classTag; + } + } + + element.className = newClasses.trim(); + }, + _offset: function (obj) { + var ol = 0; + var ot = 0; + if (obj.offsetParent) { + do { + ol += obj.offsetLeft; + ot += obj.offsetTop; + } while (obj = obj.offsetParent); + } + return { + left: ol, + top: ot + }; + }, + _css: function(elementRef, styleName, value) { + elementRef.style[styleName] = value; + } + }; + + /********************************* + + Attach to global namespace + + *********************************/ + if($) { + var namespace = $.fn.slider ? 'bootstrapSlider' : 'slider'; + $.bridget(namespace, Slider); + } else { + window.Slider = Slider; + } + + + })( $ ); + +})( window.jQuery ); \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/plugins/bootstrap-slider/slider.css b/public/vendor/laravel-admin/AdminLTE/plugins/bootstrap-slider/slider.css new file mode 100644 index 0000000..a96db7f --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/plugins/bootstrap-slider/slider.css @@ -0,0 +1,169 @@ +/*! + * Slider for Bootstrap + * + * Copyright 2012 Stefan Petre + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + */ +.slider { + display: block; + vertical-align: middle; + position: relative; + +} +.slider.slider-horizontal { + width: 100%; + height: 20px; + margin-bottom: 20px; +} +.slider.slider-horizontal:last-of-type { + margin-bottom: 0; +} +.slider.slider-horizontal .slider-track { + height: 10px; + width: 100%; + margin-top: -5px; + top: 50%; + left: 0; +} +.slider.slider-horizontal .slider-selection { + height: 100%; + top: 0; + bottom: 0; +} +.slider.slider-horizontal .slider-handle { + margin-left: -10px; + margin-top: -5px; +} +.slider.slider-horizontal .slider-handle.triangle { + border-width: 0 10px 10px 10px; + width: 0; + height: 0; + border-bottom-color: #0480be; + margin-top: 0; +} +.slider.slider-vertical { + height: 230px; + width: 20px; + margin-right: 20px; + display: inline-block; +} +.slider.slider-vertical:last-of-type { + margin-right: 0; +} +.slider.slider-vertical .slider-track { + width: 10px; + height: 100%; + margin-left: -5px; + left: 50%; + top: 0; +} +.slider.slider-vertical .slider-selection { + width: 100%; + left: 0; + top: 0; + bottom: 0; +} +.slider.slider-vertical .slider-handle { + margin-left: -5px; + margin-top: -10px; +} +.slider.slider-vertical .slider-handle.triangle { + border-width: 10px 0 10px 10px; + width: 1px; + height: 1px; + border-left-color: #0480be; + margin-left: 0; +} +.slider input { + display: none; +} +.slider .tooltip-inner { + white-space: nowrap; +} +.slider-track { + position: absolute; + cursor: pointer; + background-color: #f7f7f7; + background-image: -moz-linear-gradient(top, #f0f0f0, #f9f9f9); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f0f0f0), to(#f9f9f9)); + background-image: -webkit-linear-gradient(top, #f0f0f0, #f9f9f9); + background-image: -o-linear-gradient(top, #f0f0f0, #f9f9f9); + background-image: linear-gradient(to bottom, #f0f0f0, #f9f9f9); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0f0f0', endColorstr='#fff9f9f9', GradientType=0); + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.slider-selection { + position: absolute; + background-color: #f7f7f7; + background-image: -moz-linear-gradient(top, #f9f9f9, #f5f5f5); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f9f9f9), to(#f5f5f5)); + background-image: -webkit-linear-gradient(top, #f9f9f9, #f5f5f5); + background-image: -o-linear-gradient(top, #f9f9f9, #f5f5f5); + background-image: linear-gradient(to bottom, #f9f9f9, #f5f5f5); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9', endColorstr='#fff5f5f5', GradientType=0); + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.slider-handle { + position: absolute; + width: 20px; + height: 20px; + background-color: #444; + -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); + -moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); + box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); + opacity: 1; + border: 0px solid transparent; +} +.slider-handle.round { + -webkit-border-radius: 20px; + -moz-border-radius: 20px; + border-radius: 20px; +} +.slider-handle.triangle { + background: transparent none; +} + +.slider-disabled .slider-selection { + opacity: 0.5; +} + +#red .slider-selection { + background: #f56954; +} + +#blue .slider-selection { + background: #3c8dbc; +} + +#green .slider-selection { + background: #00a65a; +} + +#yellow .slider-selection { + background: #f39c12; +} + +#aqua .slider-selection { + background: #00c0ef; +} + +#purple .slider-selection { + background: #932ab6; +} \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/plugins/colorpicker/bootstrap-colorpicker.min.css b/public/vendor/laravel-admin/AdminLTE/plugins/colorpicker/bootstrap-colorpicker.min.css new file mode 100644 index 0000000..01894d5 --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/plugins/colorpicker/bootstrap-colorpicker.min.css @@ -0,0 +1,9 @@ +/*! + * Bootstrap Colorpicker + * http://mjolnic.github.io/bootstrap-colorpicker/ + * + * Originally written by (c) 2012 Stefan Petre + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0.txt + * + */.colorpicker-saturation{float:left;width:100px;height:100px;cursor:crosshair;background-image:url("img/saturation.png")} .colorpicker-saturation i{position:absolute;top:0;left:0;display:block;width:5px;height:5px;margin:-4px 0 0 -4px;border:1px solid #000;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px} .colorpicker-saturation i b{display:block;width:5px;height:5px;border:1px solid #fff;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px} .colorpicker-hue,.colorpicker-alpha{float:left;width:15px;height:100px;margin-bottom:4px;margin-left:4px;cursor:row-resize} .colorpicker-hue i,.colorpicker-alpha i{position:absolute;top:0;left:0;display:block;width:100%;height:1px;margin-top:-1px;background:#000;border-top:1px solid #fff} .colorpicker-hue{background-image:url("img/hue.png")} .colorpicker-alpha{display:none;background-image:url("img/alpha.png")} .colorpicker{top:0;left:0;z-index:2500;min-width:130px;padding:4px;margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1} .colorpicker:before,.colorpicker:after{display:table;line-height:0;content:""} .colorpicker:after{clear:both} .colorpicker:before{position:absolute;top:-7px;left:6px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''} .colorpicker:after{position:absolute;top:-6px;left:7px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''} .colorpicker div{position:relative} .colorpicker.colorpicker-with-alpha{min-width:140px} .colorpicker.colorpicker-with-alpha .colorpicker-alpha{display:block} .colorpicker-color{height:10px;margin-top:5px;clear:both;background-image:url("img/alpha.png");background-position:0 100%} .colorpicker-color div{height:10px} .colorpicker-element .input-group-addon i{display:block;width:16px;height:16px;cursor:pointer} .colorpicker.colorpicker-inline{position:relative;display:inline-block;float:none} .colorpicker.colorpicker-horizontal{width:110px;height:auto;min-width:110px} .colorpicker.colorpicker-horizontal .colorpicker-saturation{margin-bottom:4px} .colorpicker.colorpicker-horizontal .colorpicker-color{width:100px} .colorpicker.colorpicker-horizontal .colorpicker-hue,.colorpicker.colorpicker-horizontal .colorpicker-alpha{float:left;width:100px;height:15px;margin-bottom:4px;margin-left:0;cursor:col-resize} .colorpicker.colorpicker-horizontal .colorpicker-hue i,.colorpicker.colorpicker-horizontal .colorpicker-alpha i{position:absolute;top:0;left:0;display:block;width:1px;height:15px;margin-top:0;background:#fff;border:0} .colorpicker.colorpicker-horizontal .colorpicker-hue{background-image:url("img/hue-horizontal.png")} .colorpicker.colorpicker-horizontal .colorpicker-alpha{background-image:url("img/alpha-horizontal.png")} .colorpicker.colorpicker-hidden{display:none} .colorpicker.colorpicker-visible{display:block} .colorpicker-inline.colorpicker-visible{display:inline-block} \ No newline at end of file diff --git a/public/vendor/laravel-admin/AdminLTE/plugins/colorpicker/bootstrap-colorpicker.min.js b/public/vendor/laravel-admin/AdminLTE/plugins/colorpicker/bootstrap-colorpicker.min.js new file mode 100644 index 0000000..bc4fba9 --- /dev/null +++ b/public/vendor/laravel-admin/AdminLTE/plugins/colorpicker/bootstrap-colorpicker.min.js @@ -0,0 +1 @@ +!function(a){"use strict";var b=function(a){this.value={h:0,s:0,b:0,a:1},this.origFormat=null,a&&(void 0!==a.toLowerCase?this.setColor(a):void 0!==a.h&&(this.value=a))};b.prototype={constructor:b,_sanitizeNumber:function(a){return"number"==typeof a?a:isNaN(a)||null===a||""===a||void 0===a?1:void 0!==a.toLowerCase?parseFloat(a):1},setColor:function(a){a=a.toLowerCase(),this.value=this.stringToHSB(a)||{h:0,s:0,b:0,a:1}},stringToHSB:function(b){b=b.toLowerCase();var c=this,d=!1;return a.each(this.stringParsers,function(a,e){var f=e.re.exec(b),g=f&&e.parse.apply(c,[f]),h=e.format||"rgba";return g?(d=h.match(/hsla?/)?c.RGBtoHSB.apply(c,c.HSLtoRGB.apply(c,g)):c.RGBtoHSB.apply(c,g),c.origFormat=h,!1):!0}),d},setHue:function(a){this.value.h=1-a},setSaturation:function(a){this.value.s=a},setBrightness:function(a){this.value.b=1-a},setAlpha:function(a){this.value.a=parseInt(100*(1-a),10)/100},toRGB:function(a,b,c,d){a=a||this.value.h,b=b||this.value.s,c=c||this.value.b,d=d||this.value.a;var e,f,g,h,i,j,k,l;switch(a&&void 0===b&&void 0===c&&(b=a.s,c=a.v,a=a.h),h=Math.floor(6*a),i=6*a-h,j=c*(1-b),k=c*(1-i*b),l=c*(1-(1-i)*b),h%6){case 0:e=c,f=l,g=j;break;case 1:e=k,f=c,g=j;break;case 2:e=j,f=c,g=l;break;case 3:e=j,f=k,g=c;break;case 4:e=l,f=j,g=c;break;case 5:e=c,f=j,g=k}return{r:Math.floor(255*e),g:Math.floor(255*f),b:Math.floor(255*g),a:d}},toHex:function(a,b,c,d){var e=this.toRGB(a,b,c,d);return"#"+(1<<24|parseInt(e.r)<<16|parseInt(e.g)<<8|parseInt(e.b)).toString(16).substr(1)},toHSL:function(a,b,c,d){a=a||this.value.h,b=b||this.value.s,c=c||this.value.b,d=d||this.value.a;var e=a,f=(2-b)*c,g=b*c;return g/=f>0&&1>=f?f:2-f,f/=2,g>1&&(g=1),{h:e,s:g,l:f,a:d}},RGBtoHSB:function(a,b,c,d){a/=255,b/=255,c/=255;var e,f,g,h;return g=Math.max(a,b,c),h=g-Math.min(a,b,c),e=0===h?null:g===a?(b-c)/h:g===b?(c-a)/h+2:(a-b)/h+4,e=(e+360)%6*60/360,f=0===h?0:h/g,{h:this._sanitizeNumber(e),s:f,b:g,a:this._sanitizeNumber(d)}},HueToRGB:function(a,b,c){return 0>c?c+=1:c>1&&(c-=1),1>6*c?a+(b-a)*c*6:1>2*c?b:2>3*c?a+(b-a)*(2/3-c)*6:a},HSLtoRGB:function(a,b,c,d){0>b&&(b=0);var e;e=.5>=c?c*(1+b):c+b-c*b;var f=2*c-e,g=a+1/3,h=a,i=a-1/3,j=Math.round(255*this.HueToRGB(f,e,g)),k=Math.round(255*this.HueToRGB(f,e,h)),l=Math.round(255*this.HueToRGB(f,e,i));return[j,k,l,this._sanitizeNumber(d)]},toString:function(a){switch(a=a||"rgba"){case"rgb":var b=this.toRGB();return"rgb("+b.r+","+b.g+","+b.b+")";case"rgba":var b=this.toRGB();return"rgba("+b.r+","+b.g+","+b.b+","+b.a+")";case"hsl":var c=this.toHSL();return"hsl("+Math.round(360*c.h)+","+Math.round(100*c.s)+"%,"+Math.round(100*c.l)+"%)";case"hsla":var c=this.toHSL();return"hsla("+Math.round(360*c.h)+","+Math.round(100*c.s)+"%,"+Math.round(100*c.l)+"%,"+c.a+")";case"hex":return this.toHex();default:return!1}},stringParsers:[{re:/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,format:"hex",parse:function(a){return[parseInt(a[1],16),parseInt(a[2],16),parseInt(a[3],16),1]}},{re:/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/,format:"hex",parse:function(a){return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16),1]}},{re:/rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*?\)/,format:"rgb",parse:function(a){return[a[1],a[2],a[3],1]}},{re:/rgb\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*?\)/,format:"rgb",parse:function(a){return[2.55*a[1],2.55*a[2],2.55*a[3],1]}},{re:/rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,format:"rgba",parse:function(a){return[a[1],a[2],a[3],a[4]]}},{re:/rgba\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,format:"rgba",parse:function(a){return[2.55*a[1],2.55*a[2],2.55*a[3],a[4]]}},{re:/hsl\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*?\)/,format:"hsl",parse:function(a){return[a[1]/360,a[2]/100,a[3]/100,a[4]]}},{re:/hsla\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,format:"hsla",parse:function(a){return[a[1]/360,a[2]/100,a[3]/100,a[4]]}},{re:/^([a-z]{3,})$/,format:"alias",parse:function(a){var b=this.colorNameToHex(a[0])||"#000000",c=this.stringParsers[0].re.exec(b),d=c&&this.stringParsers[0].parse.apply(this,[c]);return d}}],colorNameToHex:function(a){var b={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4","indianred ":"#cd5c5c","indigo ":"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370d8",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#d87093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};return"undefined"!=typeof b[a.toLowerCase()]?b[a.toLowerCase()]:!1}};var c={horizontal:!1,inline:!1,color:!1,format:!1,input:"input",container:!1,component:".add-on, .input-group-addon",sliders:{saturation:{maxLeft:100,maxTop:100,callLeft:"setSaturation",callTop:"setBrightness"},hue:{maxLeft:0,maxTop:100,callLeft:!1,callTop:"setHue"},alpha:{maxLeft:0,maxTop:100,callLeft:!1,callTop:"setAlpha"}},slidersHorz:{saturation:{maxLeft:100,maxTop:100,callLeft:"setSaturation",callTop:"setBrightness"},hue:{maxLeft:100,maxTop:0,callLeft:"setHue",callTop:!1},alpha:{maxLeft:100,maxTop:0,callLeft:"setAlpha",callTop:!1}},template:'