mirror of
https://gitee.com/koogua/course-tencent-cloud.git
synced 2025-06-24 20:06:09 +08:00
设计后台首页
This commit is contained in:
parent
33b24e65e7
commit
1ec9c6f612
65
app/Caches/SaleTrend.php
Normal file
65
app/Caches/SaleTrend.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Caches;
|
||||
|
||||
use App\Models\Order as OrderModel;
|
||||
use Phalcon\Mvc\Model\Resultset;
|
||||
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||
|
||||
class SaleTrend extends Cache
|
||||
{
|
||||
|
||||
protected $lifetime = 2 * 3600;
|
||||
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
public function getKey($id = null)
|
||||
{
|
||||
return 'sale_trend';
|
||||
}
|
||||
|
||||
public function getContent($id = null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OrderModel[] $sales
|
||||
* @param int $days
|
||||
* @return array
|
||||
*/
|
||||
protected function handleSales($sales, $days = 7)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach (array_reverse(range(1, $days)) as $num) {
|
||||
$date = date('Y-m-d', strtotime("-{$num} days"));
|
||||
$result[$date] = 0;
|
||||
}
|
||||
|
||||
foreach ($sales as $sale) {
|
||||
$date = date('Y-m-d', $sale->create_time);
|
||||
$result[$date] += $sale->amount;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $days
|
||||
* @return ResultsetInterface|Resultset|OrderModel[]
|
||||
*/
|
||||
protected function findSales($days = 7)
|
||||
{
|
||||
$time = strtotime("-{$days} days");
|
||||
|
||||
return OrderModel::query()
|
||||
->where('status = :status:', ['status' => OrderModel::STATUS_FINISHED])
|
||||
->andWhere('create_time > :time:', ['time' => $time])
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
52
app/Caches/SiteStat.php
Normal file
52
app/Caches/SiteStat.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Caches;
|
||||
|
||||
use App\Repos\Consult as ConsultRepo;
|
||||
use App\Repos\Course as CourseRepo;
|
||||
use App\Repos\ImGroup as GroupRepo;
|
||||
use App\Repos\Order as OrderRepo;
|
||||
use App\Repos\Package as PackageRepo;
|
||||
use App\Repos\Review as ReviewRepo;
|
||||
use App\Repos\Topic as TopicRepo;
|
||||
use App\Repos\User as UserRepo;
|
||||
|
||||
class SiteStat extends Cache
|
||||
{
|
||||
|
||||
protected $lifetime = 2 * 3600;
|
||||
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
public function getKey($id = null)
|
||||
{
|
||||
return 'site_stat';
|
||||
}
|
||||
|
||||
public function getContent($id = null)
|
||||
{
|
||||
$courseRepo = new CourseRepo();
|
||||
$consultRepo = new ConsultRepo();
|
||||
$groupRepo = new GroupRepo();
|
||||
$orderRepo = new OrderRepo();
|
||||
$packageRepo = new PackageRepo();
|
||||
$reviewRepo = new ReviewRepo();
|
||||
$topicRepo = new TopicRepo();
|
||||
$userRepo = new UserRepo();
|
||||
|
||||
return [
|
||||
'course_count' => $courseRepo->countCourses(),
|
||||
'consult_count' => $consultRepo->countConsults(),
|
||||
'group_count' => $groupRepo->countGroups(),
|
||||
'order_count' => $orderRepo->countOrders(),
|
||||
'package_count' => $packageRepo->countPackages(),
|
||||
'review_count' => $reviewRepo->countReviews(),
|
||||
'topic_count' => $topicRepo->countTopics(),
|
||||
'user_count' => $userRepo->countUsers(),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
@ -3,7 +3,6 @@
|
||||
namespace App\Http\Admin\Controllers;
|
||||
|
||||
use App\Http\Admin\Services\Index as IndexService;
|
||||
use App\Library\AppInfo;
|
||||
use Phalcon\Mvc\View;
|
||||
|
||||
/**
|
||||
@ -21,8 +20,7 @@ class IndexController extends Controller
|
||||
|
||||
$topMenus = $indexService->getTopMenus();
|
||||
$leftMenus = $indexService->getLeftMenus();
|
||||
|
||||
$appInfo = new AppInfo();
|
||||
$appInfo = $indexService->getAppInfo();
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->setVar('app_info', $appInfo);
|
||||
@ -35,7 +33,15 @@ class IndexController extends Controller
|
||||
*/
|
||||
public function mainAction()
|
||||
{
|
||||
$indexService = new IndexService();
|
||||
|
||||
$statInfo = $indexService->getStatInfo();
|
||||
$appInfo = $indexService->getAppInfo();
|
||||
$serverInfo = $indexService->getServerInfo();
|
||||
|
||||
$this->view->setVar('stat_info', $statInfo);
|
||||
$this->view->setVar('app_info', $appInfo);
|
||||
$this->view->setVar('server_info', $serverInfo);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,6 +2,10 @@
|
||||
|
||||
namespace App\Http\Admin\Services;
|
||||
|
||||
use App\Caches\SiteStat;
|
||||
use App\Library\AppInfo;
|
||||
use App\Library\Utils\ServerInfo;
|
||||
|
||||
class Index extends Service
|
||||
{
|
||||
|
||||
@ -19,4 +23,25 @@ class Index extends Service
|
||||
return $authMenu->getLeftMenus();
|
||||
}
|
||||
|
||||
public function getAppInfo()
|
||||
{
|
||||
return new AppInfo();
|
||||
}
|
||||
|
||||
public function getServerInfo()
|
||||
{
|
||||
return [
|
||||
'cpu' => ServerInfo::cpu(),
|
||||
'memory' => ServerInfo::memory(),
|
||||
'disk' => ServerInfo::disk(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getStatInfo()
|
||||
{
|
||||
$cache = new SiteStat();
|
||||
|
||||
return $cache->get();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -59,8 +59,8 @@
|
||||
<div class="layui-body">
|
||||
<iframe name="content" style="width:100%;height:100%;border:0;" src="{{ url({'for':'admin.main'}) }}"></iframe>
|
||||
</div>
|
||||
<div class="layui-footer">
|
||||
<span>Powered by <a href="{{ app_info.link }}" title="{{ app_info.name }}">{{ app_info.alias }} {{ app_info.version }}</a></span>
|
||||
<div class="layui-copyright">
|
||||
Powered by <a href="{{ app_info.link }}" title="{{ app_info.name }}">{{ app_info.alias }} {{ app_info.version }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
@ -2,4 +2,28 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="layui-fluid">
|
||||
<div class="layui-row layui-col-space15">
|
||||
<div class="layui-col-md8">
|
||||
{{ partial('index/main_stat_info') }}
|
||||
</div>
|
||||
<div class="layui-col-md4">
|
||||
{{ partial('index/main_app_info') }}
|
||||
{{ partial('index/main_server_info') }}
|
||||
{{ partial('index/main_team_info') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block inline_css %}
|
||||
|
||||
<style>
|
||||
.kg-body {
|
||||
padding: 15px 0;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% endblock %}
|
28
app/Http/Admin/Views/index/main_app_info.volt
Normal file
28
app/Http/Admin/Views/index/main_app_info.volt
Normal file
@ -0,0 +1,28 @@
|
||||
<div class="layui-card layui-text">
|
||||
<div class="layui-card-header">应用信息</div>
|
||||
<div class="layui-card-body">
|
||||
<table class="layui-table">
|
||||
<colgroup>
|
||||
<col width="100">
|
||||
<col>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>当前版本</td>
|
||||
<td>{{ app_info.alias }} {{ app_info.version }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>系统框架</td>
|
||||
<td><a href="https://gitee.com/koogua/cphalcon">Phalcon 3.4.5</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>获取渠道</td>
|
||||
<td>
|
||||
<a href="https://gitee.com/koogua/course-tencent-cloud" target="_blank">Gitee</a>
|
||||
<a href="https://github.com/koogua/course-tencent-cloud" target="_blank">Github</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
25
app/Http/Admin/Views/index/main_server_info.volt
Normal file
25
app/Http/Admin/Views/index/main_server_info.volt
Normal file
@ -0,0 +1,25 @@
|
||||
<div class="layui-card layui-text" xmlns="http://www.w3.org/1999/html">
|
||||
<div class="layui-card-header">服务器信息</div>
|
||||
<div class="layui-card-body">
|
||||
<table class="layui-table">
|
||||
<colgroup>
|
||||
<col width="100">
|
||||
<col>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>磁盘空间</td>
|
||||
<td>{{ server_info.disk.total }} 已用 {{ server_info.disk.usage }} 使用率 {{ server_info.disk.percent }}%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>内存空间</td>
|
||||
<td>{{ server_info.memory.total }} 已用 {{ server_info.memory.usage }} 使用率 {{ server_info.memory.percent }}%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>系统负载</td>
|
||||
<td>{{ server_info.cpu[0] }} {{ server_info.cpu[1] }} {{ server_info.cpu[2] }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
55
app/Http/Admin/Views/index/main_stat_info.volt
Normal file
55
app/Http/Admin/Views/index/main_stat_info.volt
Normal file
@ -0,0 +1,55 @@
|
||||
<div class="layui-card kg-stats">
|
||||
<div class="layui-card-header">数据统计</div>
|
||||
<div class="layui-card-body">
|
||||
<div class="layui-row layui-col-space10">
|
||||
<div class="layui-col-md3">
|
||||
<div class="kg-stat-card">
|
||||
<div class="name">课程数</div>
|
||||
<div class="count">{{ stat_info.course_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-md3">
|
||||
<div class="kg-stat-card">
|
||||
<div class="name">用户数</div>
|
||||
<div class="count">{{ stat_info.user_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-md3">
|
||||
<div class="kg-stat-card">
|
||||
<div class="name">群组数</div>
|
||||
<div class="count">{{ stat_info.group_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-md3">
|
||||
<div class="kg-stat-card">
|
||||
<div class="name">订单数</div>
|
||||
<div class="count">{{ stat_info.order_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-md3">
|
||||
<div class="kg-stat-card">
|
||||
<div class="name">评价数</div>
|
||||
<div class="count">{{ stat_info.review_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-md3">
|
||||
<div class="kg-stat-card">
|
||||
<div class="name">咨询数</div>
|
||||
<div class="count">{{ stat_info.consult_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-md3">
|
||||
<div class="kg-stat-card">
|
||||
<div class="name">套餐数</div>
|
||||
<div class="count">{{ stat_info.package_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-md3">
|
||||
<div class="kg-stat-card">
|
||||
<div class="name">专题数</div>
|
||||
<div class="count">{{ stat_info.topic_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
25
app/Http/Admin/Views/index/main_team_info.volt
Normal file
25
app/Http/Admin/Views/index/main_team_info.volt
Normal file
@ -0,0 +1,25 @@
|
||||
<div class="layui-card layui-text">
|
||||
<div class="layui-card-header">开发团队</div>
|
||||
<div class="layui-card-body">
|
||||
<table class="layui-table">
|
||||
<colgroup>
|
||||
<col width="100">
|
||||
<col>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>版权所有</td>
|
||||
<td><a href="http://koogua.com">深圳市酷瓜软件有限公司</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>项目经理</td>
|
||||
<td>小虫哥哥</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>开发人员</td>
|
||||
<td>小虫哥哥</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
73
app/Library/Utils/ServerInfo.php
Normal file
73
app/Library/Utils/ServerInfo.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library\Utils;
|
||||
|
||||
class ServerInfo
|
||||
{
|
||||
|
||||
static function disk($dir = '/')
|
||||
{
|
||||
$free = disk_free_space($dir);
|
||||
$total = disk_total_space($dir);
|
||||
$usage = $total - $free;
|
||||
$percent = 100 * $usage / $total;
|
||||
|
||||
return [
|
||||
'total' => self::size($total),
|
||||
'free' => self::size($free),
|
||||
'usage' => self::size($usage),
|
||||
'percent' => round($percent),
|
||||
];
|
||||
}
|
||||
|
||||
static function memory()
|
||||
{
|
||||
$mem = file_get_contents('/proc/meminfo');
|
||||
|
||||
$total = 0;
|
||||
|
||||
if (preg_match('/MemTotal\:\s+(\d+) kB/', $mem, $matches)) {
|
||||
$total = $matches[1];
|
||||
}
|
||||
|
||||
unset($matches);
|
||||
|
||||
$free = 0;
|
||||
|
||||
if (preg_match('/MemFree\:\s+(\d+) kB/', $mem, $matches)) {
|
||||
$free = $matches[1];
|
||||
}
|
||||
|
||||
$usage = $total - $free;
|
||||
|
||||
$percent = 100 * $usage / $total;
|
||||
|
||||
return array(
|
||||
'total' => self::size($total * 1024),
|
||||
'free' => self::size($free * 1024),
|
||||
'usage' => self::size($usage * 1024),
|
||||
'percent' => round($percent),
|
||||
);
|
||||
}
|
||||
|
||||
static function cpu()
|
||||
{
|
||||
$load = sys_getloadavg();
|
||||
|
||||
return array_map(function ($value) {
|
||||
return sprintf('%.2f', $value);
|
||||
}, $load);
|
||||
}
|
||||
|
||||
static function size($bytes)
|
||||
{
|
||||
if (!$bytes) return 0;
|
||||
|
||||
$symbols = array('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
|
||||
|
||||
$exp = floor(log($bytes) / log(1024));
|
||||
|
||||
return sprintf('%.2f ' . $symbols[$exp], ($bytes / pow(1024, floor($exp))));
|
||||
}
|
||||
|
||||
}
|
@ -92,6 +92,11 @@ class Package extends Repository
|
||||
->execute();
|
||||
}
|
||||
|
||||
public function countPackages()
|
||||
{
|
||||
return (int)PackageModel::count(['conditions' => 'deleted = 0']);
|
||||
}
|
||||
|
||||
public function countCourses($packageId)
|
||||
{
|
||||
return (int)CoursePackageModel::count([
|
||||
|
@ -125,7 +125,7 @@ class User extends Repository
|
||||
|
||||
public function countUsers()
|
||||
{
|
||||
return (int)UserModel::count();
|
||||
return (int)UserModel::count(['conditions' => 'deleted = 0']);
|
||||
}
|
||||
|
||||
public function countCourses($userId)
|
||||
|
@ -10,7 +10,6 @@
|
||||
"swiftmailer/swiftmailer": "^6.0",
|
||||
"peppeocchi/php-cron-scheduler": "^2.4",
|
||||
"yansongda/pay": "^2.8",
|
||||
"yansongda/supports": "^2.0",
|
||||
"tencentcloud/tencentcloud-sdk-php": "3.*",
|
||||
"qcloudsms/qcloudsms_php": "0.1.*",
|
||||
"qcloud/cos-sdk-v5": "2.*",
|
||||
@ -19,12 +18,13 @@
|
||||
"whichbrowser/parser": "^2.0",
|
||||
"hightman/xunsearch": "^1.4.14",
|
||||
"aferrandini/phpqrcode": "1.0.1",
|
||||
"xiaochong0302/ip2region": "^1.0"
|
||||
"xiaochong0302/ip2region": "^1.0",
|
||||
"robmorgan/phinx": "^0.12"
|
||||
},
|
||||
"require-dev": {
|
||||
"odan/phinx-migrations-generator": "^4.6",
|
||||
"phalcon/ide-stubs": "^3.4.3",
|
||||
"jaeger/querylist": "^4.1"
|
||||
"jaeger/querylist": "^4.1",
|
||||
"odan/phinx-migrations-generator": "^5.1"
|
||||
},
|
||||
"repositories": {
|
||||
"packagist": {
|
||||
|
2808
composer.lock
generated
2808
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,25 @@
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.layui-layout-admin .layui-body {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.layui-copyright {
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 180px;
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, .7);
|
||||
}
|
||||
|
||||
.layui-copyright a {
|
||||
color: rgba(255, 255, 255, .7);
|
||||
}
|
||||
|
||||
.kg-body {
|
||||
padding: 15px;
|
||||
}
|
||||
@ -206,3 +225,19 @@ img.kg-qrcode {
|
||||
padding-top: 0;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.kg-stat-card {
|
||||
padding: 10px 0;
|
||||
text-align: center;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
|
||||
.kg-stat-card .name {
|
||||
margin-bottom: 5px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.kg-stat-card .count {
|
||||
font-size: 20px;
|
||||
color: rgb(0, 150, 136);
|
||||
}
|
||||
|
17552
public/static/lib/layui/extends/echarts.js
Normal file
17552
public/static/lib/layui/extends/echarts.js
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user