300 lines
10 KiB
PHP
300 lines
10 KiB
PHP
<?php
|
||
|
||
namespace App\Admin\Controllers;
|
||
|
||
use Illuminate\Http\JsonResponse;
|
||
use Slowlyo\OwlAdmin\Renderers\Card;
|
||
use Slowlyo\OwlAdmin\Renderers\Flex;
|
||
use Slowlyo\OwlAdmin\Renderers\Html;
|
||
use Slowlyo\OwlAdmin\Renderers\Grid;
|
||
use Slowlyo\OwlAdmin\Renderers\Chart;
|
||
use Slowlyo\OwlAdmin\Renderers\Image;
|
||
use Slowlyo\OwlAdmin\Renderers\Action;
|
||
use Slowlyo\OwlAdmin\Renderers\Custom;
|
||
use Slowlyo\OwlAdmin\Renderers\Wrapper;
|
||
use Illuminate\Http\Resources\Json\JsonResource;
|
||
use Slowlyo\OwlAdmin\Controllers\AdminController;
|
||
use App\Admin\Components;
|
||
use App\Models\Oldmen;
|
||
use App\Models\ConstFlow;
|
||
use App\Models\Keyword;
|
||
use Carbon\Carbon;
|
||
use DB;
|
||
|
||
class HomeController extends AdminController
|
||
{
|
||
public function index(): JsonResponse|JsonResource
|
||
{
|
||
$page = $this->basePage()->css($this->css())->body([
|
||
Grid::make()->columns([
|
||
$this->frameworkInfo()->md(8),
|
||
Flex::make()->items([
|
||
$this->clock(),
|
||
$this->hitokoto(),
|
||
])->direction('column'),
|
||
]),
|
||
Grid::make()->columns([
|
||
$this->lineChart()->md(6),
|
||
$this->pieChart()->md(6),
|
||
]),
|
||
]);
|
||
|
||
return $this->response()->success($page);
|
||
}
|
||
|
||
/**
|
||
* 一言
|
||
*/
|
||
public function hitokoto()
|
||
{
|
||
return Card::make()
|
||
->className('h-full clear-card-mb')
|
||
->body(
|
||
Custom::make()->html(<<<HTML
|
||
<div class="h-full flex flex-col mt-5 py-5 px-7">
|
||
<div>『</div>
|
||
<div class="flex flex-1 items-center w-full justify-center" id="hitokoto">
|
||
<a class="text-dark" href="#" id="hitokoto_text" target="_blank"></a>
|
||
</div>
|
||
<div class="flex justify-end">』</div>
|
||
</div>
|
||
<div class="flex justify-end mt-3">
|
||
——
|
||
<span id="hitokoto_from_who"></span>
|
||
<span>「</span>
|
||
<span id="hitokoto_from"></span>
|
||
<span>」</span>
|
||
</div>
|
||
HTML
|
||
|
||
)->onMount(<<<JS
|
||
fetch('https://v1.hitokoto.cn?c=i')
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
const hitokoto = document.querySelector('#hitokoto_text')
|
||
hitokoto.href = `https://hitokoto.cn/?uuid=\${data.uuid}`
|
||
hitokoto.innerText = data.hitokoto
|
||
document.querySelector('#hitokoto_from_who').innerText = data.from_who
|
||
document.querySelector('#hitokoto_from').innerText = data.from
|
||
})
|
||
.catch(console.error)
|
||
JS
|
||
)
|
||
);
|
||
}
|
||
|
||
public function clock(): Card
|
||
{
|
||
return Card::make()->className('h-full bg-blingbling')->header([
|
||
'title' => '时钟',
|
||
])->body([
|
||
Custom::make()
|
||
->name('clock')
|
||
->html('<div id="clock" class="text-5xl"></div><div id="clock-date" class="mt-5"></div>')
|
||
->onMount(<<<JS
|
||
const clock = document.getElementById('clock');
|
||
const tick = () => {
|
||
clock.innerHTML = (new Date()).toLocaleTimeString();
|
||
requestAnimationFrame(tick);
|
||
};
|
||
tick();
|
||
|
||
const clockDate = document.getElementById('clock-date');
|
||
clockDate.innerHTML = (new Date()).toLocaleDateString();
|
||
JS
|
||
|
||
),
|
||
]);
|
||
}
|
||
|
||
public function frameworkInfo(): Card
|
||
{
|
||
return Card::make()->className('h-96')->body(
|
||
Wrapper::make()->className('h-full')->body([
|
||
Flex::make()->className('h-full')->direction('column')->justify('center')->items([
|
||
Image::make()->src(url(config('admin.logo'))),
|
||
Wrapper::make()->className('text-3xl mt-9')->body(config('admin.name')),
|
||
Flex::make()->className('w-64 mt-5')->justify('space-around')->items([
|
||
Action::make()
|
||
->level('link')
|
||
->label('客人入住')
|
||
->actionType('url')
|
||
->link('live-in-do'),
|
||
Action::make()
|
||
->level('link')
|
||
->label('客人续住')
|
||
->actionType('url')
|
||
->link('live-continue-do'),
|
||
Action::make()
|
||
->level('link')
|
||
->label('退住结算')
|
||
->actionType('url')
|
||
->link('live-exit-do'),
|
||
]),
|
||
]),
|
||
])
|
||
);
|
||
}
|
||
|
||
public function pieChart(): Card
|
||
{
|
||
$lvList = Keyword::getByParentKey('nurse_lv')->pluck('name', 'value');
|
||
$listData = Oldmen::select(DB::raw('`nurse_lv`, count(`id`) as num'))->where('live_in', Oldmen::STATUS_LIVE)->groupBy('nurse_lv')->get()->keyBy('nurse_lv')->toArray();
|
||
$data = [];
|
||
foreach($lvList as $lv=>$name){
|
||
$data[] = [
|
||
"name"=>$name, "value"=>$listData[$lv]['num'] ?? 0
|
||
];
|
||
}
|
||
return Card::make()->className('h-96')->body(
|
||
Chart::make()->height(350)->config(Components::make()->chartPieConfig('当前护理占比', $data))
|
||
);
|
||
}
|
||
|
||
public function lineChart(): Card
|
||
{
|
||
$endDay = now()->format('Y-m-d');
|
||
$startDay = now()->subDays(7)->format('Y-m-d');
|
||
|
||
$data = [];
|
||
list($day, $diffDays, $xKeys) = $this->makeChartXkeys($startDay, $endDay);
|
||
|
||
$listData = ConstFlow::select(DB::raw('DATE_FORMAT(`start_at`, "%Y-%m-%d") as ymd, count(`oldman_id`) as num'))
|
||
->where('const_type', ConstFlow::TYPE_IN)->whereBetween('start_at', [$startDay.' 00:00:00', $endDay.' 23:59:59'])->groupBy('ymd')->get()->keyBy('ymd')->toArray();
|
||
|
||
foreach($xKeys as $key){
|
||
$data[] = $listData[$key]['num'] ?? 0;
|
||
}
|
||
|
||
$chart = amisMake()->Chart()->config(Components::make()->chartLineBarConfig('入住情况', $xKeys, [
|
||
[
|
||
'name'=> '入住情况',
|
||
'type'=>'line',
|
||
'data' => $data,
|
||
'color' => '#3491fa',
|
||
'unit' => '人'
|
||
]
|
||
]));
|
||
|
||
return Card::make()->height(380)->className('h-96')->body($chart);
|
||
}
|
||
|
||
public function cube(): Card
|
||
{
|
||
return Card::make()->className('h-96 ml-4 w-8/12')->body(
|
||
Html::make()->html(<<<HTML
|
||
<style>
|
||
.cube-box{ height: 300px; display: flex; align-items: center; justify-content: center; }
|
||
.cube { width: 100px; height: 100px; position: relative; transform-style: preserve-3d; animation: rotate 10s linear infinite; }
|
||
.cube:after {
|
||
content: '';
|
||
width: 100%;
|
||
height: 100%;
|
||
box-shadow: 0 0 50px rgba(0, 0, 0, 0.2);
|
||
position: absolute;
|
||
transform-origin: bottom;
|
||
transform-style: preserve-3d;
|
||
transform: rotateX(90deg) translateY(50px) translateZ(-50px);
|
||
background-color: rgba(0, 0, 0, 0.1);
|
||
}
|
||
.cube div {
|
||
background-color: rgba(64, 158, 255, 0.7);
|
||
position: absolute;
|
||
width: 100%;
|
||
height: 100%;
|
||
border: 1px solid rgb(27, 99, 170);
|
||
box-shadow: 0 0 60px rgba(64, 158, 255, 0.7);
|
||
}
|
||
.cube div:nth-child(1) { transform: translateZ(-50px); animation: shade 10s -5s linear infinite; }
|
||
.cube div:nth-child(2) { transform: translateZ(50px) rotateY(180deg); animation: shade 10s linear infinite; }
|
||
.cube div:nth-child(3) { transform-origin: right; transform: translateZ(50px) rotateY(270deg); animation: shade 10s -2.5s linear infinite; }
|
||
.cube div:nth-child(4) { transform-origin: left; transform: translateZ(50px) rotateY(90deg); animation: shade 10s -7.5s linear infinite; }
|
||
.cube div:nth-child(5) { transform-origin: bottom; transform: translateZ(50px) rotateX(90deg); background-color: rgba(0, 0, 0, 0.7); }
|
||
.cube div:nth-child(6) { transform-origin: top; transform: translateZ(50px) rotateX(270deg); }
|
||
|
||
@keyframes rotate {
|
||
0% { transform: rotateX(-15deg) rotateY(0deg); }
|
||
100% { transform: rotateX(-15deg) rotateY(360deg); }
|
||
}
|
||
@keyframes shade { 50% { background-color: rgba(0, 0, 0, 0.7); } }
|
||
</style>
|
||
<div class="cube-box">
|
||
<div class="cube">
|
||
<div></div>
|
||
<div></div>
|
||
<div></div>
|
||
<div></div>
|
||
<div></div>
|
||
<div></div>
|
||
</div>
|
||
</div>
|
||
HTML
|
||
|
||
)
|
||
);
|
||
}
|
||
|
||
private function css(): array
|
||
{
|
||
return [
|
||
'.clear-card-mb' => [
|
||
'margin-bottom' => '0 !important',
|
||
],
|
||
'.cxd-Image' => [
|
||
'border' => '0',
|
||
],
|
||
'.bg-blingbling' => [
|
||
'color' => '#fff',
|
||
'background' => 'linear-gradient(to bottom right, #2C3E50, #FD746C, #FF8235, #ffff1c, #92FE9D, #00C9FF, #a044ff, #e73827)',
|
||
'background-repeat' => 'no-repeat',
|
||
'background-size' => '1000% 1000%',
|
||
'animation' => 'gradient 60s ease infinite',
|
||
],
|
||
'@keyframes gradient' => [
|
||
'0%{background-position:0% 0%}
|
||
50%{background-position:100% 100%}
|
||
100%{background-position:0% 0%}',
|
||
],
|
||
'.bg-blingbling .cxd-Card-title' => [
|
||
'color' => '#fff',
|
||
],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 根据时间,处理X轴横坐标
|
||
*/
|
||
private function makeChartXkeys($startTime = null, $endTime = null){
|
||
$diffDays = 0;
|
||
$day = date('Y-m-d');
|
||
$xKeys = [];
|
||
if($startTime && $endTime){
|
||
if($startTime == $endTime){//查询某一天
|
||
$day = $startTime;
|
||
}else{
|
||
$startDay = Carbon::parse($startTime);
|
||
$endDay = Carbon::parse($endTime);
|
||
$diffDays = $startDay->diffInDays($endDay, false);
|
||
}
|
||
}
|
||
|
||
$xKeys = [];
|
||
if($diffDays){
|
||
for ($i = 0; $i<=$diffDays; $i++) {
|
||
$xKeys[] =(clone $startDay)->addDays($i)->startOfDay()->format('Y-m-d');
|
||
}
|
||
}else{
|
||
//调整截至到当前小时
|
||
$h = 23;
|
||
if($day == date('Y-m-d')){
|
||
$h = date('H');
|
||
}
|
||
for ($i = 0; $i < ($h+1); $i++) {
|
||
$xKeys[] = str_pad($i, 2, '0', STR_PAD_LEFT).':00';
|
||
}
|
||
}
|
||
|
||
return array($day, $diffDays, $xKeys);
|
||
}
|
||
}
|