mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	feat: the dashboard page is ready for admin console
This commit is contained in:
		
							
								
								
									
										55
									
								
								api/handler/admin/dashboard_handler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								api/handler/admin/dashboard_handler.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					package admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"chatplus/core"
 | 
				
			||||||
 | 
						"chatplus/handler"
 | 
				
			||||||
 | 
						"chatplus/store/model"
 | 
				
			||||||
 | 
						"chatplus/utils/resp"
 | 
				
			||||||
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						"gorm.io/gorm"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DashboardHandler struct {
 | 
				
			||||||
 | 
						handler.BaseHandler
 | 
				
			||||||
 | 
						db *gorm.DB
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewDashboardHandler(app *core.AppServer, db *gorm.DB) *DashboardHandler {
 | 
				
			||||||
 | 
						h := DashboardHandler{db: db}
 | 
				
			||||||
 | 
						h.App = app
 | 
				
			||||||
 | 
						return &h
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type statsVo struct {
 | 
				
			||||||
 | 
						Users  int64 `json:"users"`
 | 
				
			||||||
 | 
						Chats  int64 `json:"chats"`
 | 
				
			||||||
 | 
						Tokens int64 `json:"tokens"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *DashboardHandler) Stats(c *gin.Context) {
 | 
				
			||||||
 | 
						stats := statsVo{}
 | 
				
			||||||
 | 
						// new users statistic
 | 
				
			||||||
 | 
						var userCount int64
 | 
				
			||||||
 | 
						now := time.Now()
 | 
				
			||||||
 | 
						zeroTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
 | 
				
			||||||
 | 
						res := h.db.Model(&model.User{}).Where("created_at > ?", zeroTime).Count(&userCount)
 | 
				
			||||||
 | 
						if res.Error == nil {
 | 
				
			||||||
 | 
							stats.Users = userCount
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// new chats statistic
 | 
				
			||||||
 | 
						var chatCount int64
 | 
				
			||||||
 | 
						res = h.db.Model(&model.ChatItem{}).Where("created_at > ?", zeroTime).Count(&chatCount)
 | 
				
			||||||
 | 
						if res.Error == nil {
 | 
				
			||||||
 | 
							stats.Chats = chatCount
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// tokens took stats
 | 
				
			||||||
 | 
						var tokenCount int64
 | 
				
			||||||
 | 
						res = h.db.Model(&model.HistoryMessage{}).Select("sum(tokens) as tokens_total").Where("created_at > ?", zeroTime).Scan(&tokenCount)
 | 
				
			||||||
 | 
						if res.Error == nil {
 | 
				
			||||||
 | 
							stats.Tokens = tokenCount
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						resp.SUCCESS(c, stats)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -141,6 +141,7 @@ func main() {
 | 
				
			|||||||
		fx.Provide(admin.NewUserHandler),
 | 
							fx.Provide(admin.NewUserHandler),
 | 
				
			||||||
		fx.Provide(admin.NewChatRoleHandler),
 | 
							fx.Provide(admin.NewChatRoleHandler),
 | 
				
			||||||
		fx.Provide(admin.NewRewardHandler),
 | 
							fx.Provide(admin.NewRewardHandler),
 | 
				
			||||||
 | 
							fx.Provide(admin.NewDashboardHandler),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 创建服务
 | 
							// 创建服务
 | 
				
			||||||
		fx.Provide(service.NewAliYunSmsService),
 | 
							fx.Provide(service.NewAliYunSmsService),
 | 
				
			||||||
@@ -231,6 +232,10 @@ func main() {
 | 
				
			|||||||
			group := s.Engine.Group("/api/admin/reward/")
 | 
								group := s.Engine.Group("/api/admin/reward/")
 | 
				
			||||||
			group.GET("list", h.List)
 | 
								group.GET("list", h.List)
 | 
				
			||||||
		}),
 | 
							}),
 | 
				
			||||||
 | 
							fx.Invoke(func(s *core.AppServer, h *admin.DashboardHandler) {
 | 
				
			||||||
 | 
								group := s.Engine.Group("/api/admin/dashboard/")
 | 
				
			||||||
 | 
								group.GET("stats", h.Stats)
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
 | 
							fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
 | 
				
			||||||
			err := s.Run(db)
 | 
								err := s.Run(db)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										29
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										29
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -25,7 +25,8 @@
 | 
				
			|||||||
        "sortablejs": "^1.15.0",
 | 
					        "sortablejs": "^1.15.0",
 | 
				
			||||||
        "vant": "^4.5.0",
 | 
					        "vant": "^4.5.0",
 | 
				
			||||||
        "vue": "^3.2.13",
 | 
					        "vue": "^3.2.13",
 | 
				
			||||||
        "vue-router": "^4.0.15"
 | 
					        "vue-router": "^4.0.15",
 | 
				
			||||||
 | 
					        "vue-schart": "^2.0.0"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "devDependencies": {
 | 
					      "devDependencies": {
 | 
				
			||||||
        "@babel/core": "7.18.6",
 | 
					        "@babel/core": "7.18.6",
 | 
				
			||||||
@@ -9366,6 +9367,11 @@
 | 
				
			|||||||
      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
 | 
					      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/schart.js": {
 | 
				
			||||||
 | 
					      "version": "3.0.4",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/schart.js/-/schart.js-3.0.4.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-uylb2u9rrHX1jyAuSAJUQON8XTfyDKI9kWj1J3fUlCQCkLVZ4HG4+IiV8qm//Z71dqvLI78QZ/fCBw0reB22Zw=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/schema-utils": {
 | 
					    "node_modules/schema-utils": {
 | 
				
			||||||
      "version": "2.7.1",
 | 
					      "version": "2.7.1",
 | 
				
			||||||
      "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-2.7.1.tgz",
 | 
					      "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-2.7.1.tgz",
 | 
				
			||||||
@@ -10694,6 +10700,14 @@
 | 
				
			|||||||
        "vue": "^3.2.0"
 | 
					        "vue": "^3.2.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/vue-schart": {
 | 
				
			||||||
 | 
					      "version": "2.0.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/vue-schart/-/vue-schart-2.0.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-qAu3e5wfMcq26wK1xeHExEWfGpnjfoN1R/9QXblNi+AsU/p52X7tTwhi+Fw7H/otfEufhEY2X7z7emaoF4QO+g==",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "schart.js": "^3.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/vue-style-loader": {
 | 
					    "node_modules/vue-style-loader": {
 | 
				
			||||||
      "version": "4.1.3",
 | 
					      "version": "4.1.3",
 | 
				
			||||||
      "resolved": "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
 | 
					      "resolved": "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
 | 
				
			||||||
@@ -18621,6 +18635,11 @@
 | 
				
			|||||||
      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
 | 
					      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "schart.js": {
 | 
				
			||||||
 | 
					      "version": "3.0.4",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/schart.js/-/schart.js-3.0.4.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-uylb2u9rrHX1jyAuSAJUQON8XTfyDKI9kWj1J3fUlCQCkLVZ4HG4+IiV8qm//Z71dqvLI78QZ/fCBw0reB22Zw=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "schema-utils": {
 | 
					    "schema-utils": {
 | 
				
			||||||
      "version": "2.7.1",
 | 
					      "version": "2.7.1",
 | 
				
			||||||
      "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-2.7.1.tgz",
 | 
					      "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-2.7.1.tgz",
 | 
				
			||||||
@@ -19686,6 +19705,14 @@
 | 
				
			|||||||
        "@vue/devtools-api": "^6.0.0"
 | 
					        "@vue/devtools-api": "^6.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "vue-schart": {
 | 
				
			||||||
 | 
					      "version": "2.0.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/vue-schart/-/vue-schart-2.0.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-qAu3e5wfMcq26wK1xeHExEWfGpnjfoN1R/9QXblNi+AsU/p52X7tTwhi+Fw7H/otfEufhEY2X7z7emaoF4QO+g==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "schart.js": "^3.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "vue-style-loader": {
 | 
					    "vue-style-loader": {
 | 
				
			||||||
      "version": "4.1.3",
 | 
					      "version": "4.1.3",
 | 
				
			||||||
      "resolved": "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
 | 
					      "resolved": "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -71,7 +71,7 @@ httpGet('/api/admin/config/get?key=system').then(res => {
 | 
				
			|||||||
const items = [
 | 
					const items = [
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    icon: 'home',
 | 
					    icon: 'home',
 | 
				
			||||||
    index: '/admin/welcome',
 | 
					    index: '/admin/dashboard',
 | 
				
			||||||
    title: '仪表盘',
 | 
					    title: '仪表盘',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,15 +35,15 @@ const routes = [
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        name: 'admin',
 | 
					        name: 'admin',
 | 
				
			||||||
        path: '/admin',
 | 
					        path: '/admin',
 | 
				
			||||||
        redirect: '/admin/welcome',
 | 
					        redirect: '/admin/dashboard',
 | 
				
			||||||
        component: () => import("@/views/admin/Home.vue"),
 | 
					        component: () => import("@/views/admin/Home.vue"),
 | 
				
			||||||
        meta: {title: 'ChatGPT-Plus 管理后台'},
 | 
					        meta: {title: 'ChatGPT-Plus 管理后台'},
 | 
				
			||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                path: '/admin/welcome',
 | 
					                path: '/admin/dashboard',
 | 
				
			||||||
                name: 'admin-home',
 | 
					                name: 'admin-dashboard',
 | 
				
			||||||
                meta: {title: '系统首页'},
 | 
					                meta: {title: '仪表盘'},
 | 
				
			||||||
                component: () => import('@/views/admin/Welcome.vue'),
 | 
					                component: () => import('@/views/admin/Dashboard.vue'),
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                path: '/admin/system',
 | 
					                path: '/admin/system',
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										120
									
								
								web/src/views/admin/Dashboard.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								web/src/views/admin/Dashboard.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,120 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div class="dashboard">
 | 
				
			||||||
 | 
					    <el-row class="mgb20" :gutter="20">
 | 
				
			||||||
 | 
					      <el-col :span="8">
 | 
				
			||||||
 | 
					        <el-card shadow="hover" :body-style="{ padding: '0px' }">
 | 
				
			||||||
 | 
					          <div class="grid-content grid-con-1">
 | 
				
			||||||
 | 
					            <el-icon class="grid-con-icon">
 | 
				
			||||||
 | 
					              <User/>
 | 
				
			||||||
 | 
					            </el-icon>
 | 
				
			||||||
 | 
					            <div class="grid-cont-right">
 | 
				
			||||||
 | 
					              <div class="grid-num">{{ stats.users }}</div>
 | 
				
			||||||
 | 
					              <div>今日新增用户</div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </el-card>
 | 
				
			||||||
 | 
					      </el-col>
 | 
				
			||||||
 | 
					      <el-col :span="8">
 | 
				
			||||||
 | 
					        <el-card shadow="hover" :body-style="{ padding: '0px' }">
 | 
				
			||||||
 | 
					          <div class="grid-content grid-con-2">
 | 
				
			||||||
 | 
					            <el-icon class="grid-con-icon">
 | 
				
			||||||
 | 
					              <ChatDotRound/>
 | 
				
			||||||
 | 
					            </el-icon>
 | 
				
			||||||
 | 
					            <div class="grid-cont-right">
 | 
				
			||||||
 | 
					              <div class="grid-num">{{ stats.chats }}</div>
 | 
				
			||||||
 | 
					              <div>今日新增对话</div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </el-card>
 | 
				
			||||||
 | 
					      </el-col>
 | 
				
			||||||
 | 
					      <el-col :span="8">
 | 
				
			||||||
 | 
					        <el-card shadow="hover" :body-style="{ padding: '0px' }">
 | 
				
			||||||
 | 
					          <div class="grid-content grid-con-3">
 | 
				
			||||||
 | 
					            <el-icon class="grid-con-icon">
 | 
				
			||||||
 | 
					              <TrendCharts/>
 | 
				
			||||||
 | 
					            </el-icon>
 | 
				
			||||||
 | 
					            <div class="grid-cont-right">
 | 
				
			||||||
 | 
					              <div class="grid-num">{{ stats.tokens }}</div>
 | 
				
			||||||
 | 
					              <div>今日消耗 Tokens</div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </el-card>
 | 
				
			||||||
 | 
					      </el-col>
 | 
				
			||||||
 | 
					    </el-row>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup>
 | 
				
			||||||
 | 
					import {ref} from 'vue';
 | 
				
			||||||
 | 
					import {ChatDotRound, TrendCharts, User} from "@element-plus/icons-vue";
 | 
				
			||||||
 | 
					import {httpGet} from "@/utils/http";
 | 
				
			||||||
 | 
					import {ElMessage} from "element-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const stats = ref({users: 0, chats: 0, tokens: 0})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					httpGet('/api/admin/dashboard/stats').then((res) => {
 | 
				
			||||||
 | 
					  stats.value.users = res.data.users
 | 
				
			||||||
 | 
					  stats.value.chats = res.data.chats
 | 
				
			||||||
 | 
					  stats.value.tokens = res.data.tokens
 | 
				
			||||||
 | 
					}).catch((e) => {
 | 
				
			||||||
 | 
					  ElMessage.error("获取统计数据失败:" + e.message)
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped lang="stylus">
 | 
				
			||||||
 | 
					.dashboard {
 | 
				
			||||||
 | 
					  .grid-content {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    height: 100px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .grid-cont-right {
 | 
				
			||||||
 | 
					    flex: 1;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    font-size: 14px;
 | 
				
			||||||
 | 
					    color: #999;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .grid-num {
 | 
				
			||||||
 | 
					    font-size: 30px;
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .grid-con-icon {
 | 
				
			||||||
 | 
					    font-size: 50px;
 | 
				
			||||||
 | 
					    width: 100px;
 | 
				
			||||||
 | 
					    height: 100px;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    line-height: 100px;
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .grid-con-1 .grid-con-icon {
 | 
				
			||||||
 | 
					    background: rgb(45, 140, 240);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .grid-con-1 .grid-num {
 | 
				
			||||||
 | 
					    color: rgb(45, 140, 240);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .grid-con-2 .grid-con-icon {
 | 
				
			||||||
 | 
					    background: rgb(100, 213, 114);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .grid-con-2 .grid-num {
 | 
				
			||||||
 | 
					    color: rgb(100, 213, 114);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .grid-con-3 .grid-con-icon {
 | 
				
			||||||
 | 
					    background: rgb(242, 94, 67);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .grid-con-3 .grid-num {
 | 
				
			||||||
 | 
					    color: rgb(242, 94, 67);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@@ -1,25 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div class="welcome">
 | 
					 | 
				
			||||||
    <h1>ChatGPT-PLUS 控制台</h1>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup></script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					 | 
				
			||||||
.welcome {
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
  justify-content: center;
 | 
					 | 
				
			||||||
  align-items: center;
 | 
					 | 
				
			||||||
  color: #202020;
 | 
					 | 
				
			||||||
  background-color: #282c34;
 | 
					 | 
				
			||||||
  height 100%;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  h1 {
 | 
					 | 
				
			||||||
    font-size: 300%;
 | 
					 | 
				
			||||||
    font-weight: bold;
 | 
					 | 
				
			||||||
    letter-spacing: 0.1em;
 | 
					 | 
				
			||||||
    text-shadow: -1px -1px 1px #111111, 2px 2px 1px #363636;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
		Reference in New Issue
	
	Block a user