| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031 |
- <template>
- <div class="dashboard">
- <div class="admin-dashboard">
- <!-- 欢迎区域 -->
- <div class="welcome-banner">
- <div class="welcome-content">
- <h1 class="welcome-title">欢迎回来!👋</h1>
- <p class="welcome-desc">今天是 {{ todayDate }},来看看今天的运营数据吧</p>
- </div>
- <div class="welcome-illustration">📊</div>
- </div>
- <!-- 统计卡片 -->
- <el-row :gutter="20" class="stats-row">
- <el-col :xs="12" :sm="12" :md="6">
- <el-card class="stat-card" shadow="hover">
- <div class="stat-icon" style="background: linear-gradient(135deg, #FF8C42, #FFA66B);">
- <i class="el-icon-s-home"></i>
- </div>
- <div class="stat-info">
- <div class="stat-value">{{ stats.totalFamilies || 0 }}</div>
- <div class="stat-label">家庭总数</div>
- </div>
- </el-card>
- </el-col>
- <el-col :xs="12" :sm="12" :md="6">
- <el-card class="stat-card" shadow="hover">
- <div class="stat-icon" style="background: linear-gradient(135deg, #6366F1, #818CF8);">
- <i class="el-icon-user"></i>
- </div>
- <div class="stat-info">
- <div class="stat-value">{{ stats.totalParents || 0 }}</div>
- <div class="stat-label">家长总数</div>
- </div>
- </el-card>
- </el-col>
- <el-col :xs="12" :sm="12" :md="6">
- <el-card class="stat-card" shadow="hover">
- <div class="stat-icon" style="background: linear-gradient(135deg, #F59E0B, #FBBF24);">
- <i class="el-icon-s-custom"></i>
- </div>
- <div class="stat-info">
- <div class="stat-value">{{ stats.totalChildren || 0 }}</div>
- <div class="stat-label">孩子总数</div>
- </div>
- </el-card>
- </el-col>
- <el-col :xs="12" :sm="12" :md="6">
- <el-card class="stat-card" shadow="hover">
- <div class="stat-icon" style="background: linear-gradient(135deg, #10B981, #34D399);">
- <i class="el-icon-s-cooperation"></i>
- </div>
- <div class="stat-info">
- <div class="stat-value">{{ stats.totalTeachers || 0 }}</div>
- <div class="stat-label">规划师总数</div>
- </div>
- </el-card>
- </el-col>
- </el-row>
- <!-- 第二行统计卡片 -->
- <el-row :gutter="20" class="stats-row">
- <el-col :xs="12" :sm="12" :md="6">
- <el-card class="stat-card todo-stat-card" shadow="hover" @click.native="stats.pendingGuideCount > 0 && $router.push('/review-center?tab=guide')">
- <div class="stat-icon" :class="stats.pendingGuideCount > 0 ? 'todo-heart' : 'todo-disabled'">
- <i class="el-icon-s-check"></i>
- </div>
- <div class="stat-info">
- <div class="stat-value">{{ stats.pendingGuideCount }}</div>
- <div class="stat-label">待审核规划师</div>
- <div class="stat-trend" :class="stats.pendingGuideCount > 0 ? '' : 'disabled'">
- {{ stats.pendingGuideCount > 0 ? '点击前往审核' : '暂无待审核' }}
- </div>
- </div>
- </el-card>
- </el-col>
- <el-col :xs="12" :sm="12" :md="6">
- <el-card class="stat-card todo-stat-card" shadow="hover" @click.native="stats.pendingPackageCount > 0 && $router.push('/review-center?tab=package')">
- <div class="stat-icon" :class="stats.pendingPackageCount > 0 ? 'todo-danger' : 'todo-disabled'">
- <i class="el-icon-s-goods"></i>
- </div>
- <div class="stat-info">
- <div class="stat-value">{{ stats.pendingPackageCount }}</div>
- <div class="stat-label">待审核任务模板</div>
- <div class="stat-trend" :class="stats.pendingPackageCount > 0 ? '' : 'disabled'">
- {{ stats.pendingPackageCount > 0 ? '点击前往审核' : '暂无待审核' }}
- </div>
- </div>
- </el-card>
- </el-col>
- <el-col :xs="12" :sm="12" :md="6">
- <el-card class="stat-card" shadow="hover">
- <div class="stat-icon" style="background: linear-gradient(135deg, #0EA5E9, #38BDF8);">
- <i class="el-icon-user-solid"></i>
- </div>
- <div class="stat-info">
- <div class="stat-value">{{ stats.activeUsers || 0 }}</div>
- <div class="stat-label">活跃用户</div>
- </div>
- </el-card>
- </el-col>
- <el-col :xs="12" :sm="12" :md="6">
- <el-card class="stat-card" shadow="hover">
- <div class="stat-icon" style="background: linear-gradient(135deg, #8B5CF6, #A78BFA);">
- <i class="el-icon-s-home"></i>
- </div>
- <div class="stat-info">
- <div class="stat-value">{{ stats.monthFamilies || 0 }}</div>
- <div class="stat-label">本月新增家庭</div>
- </div>
- </el-card>
- </el-col>
- </el-row>
- <!-- 图表区域 -->
- <el-row :gutter="20" class="chart-row">
- <el-col :span="16">
- <el-card class="chart-card" v-loading="trendLoading">
- <div slot="header" class="card-header">
- <span class="card-title">📈 用户与家庭增长趋势(近30天)</span>
- </div>
- <div ref="trendChart" class="echarts-container"></div>
- </el-card>
- </el-col>
- <el-col :span="8">
- <el-card class="chart-card" v-loading="memberLoading">
- <div slot="header" class="card-header">
- <span class="card-title">🍩 会员等级分布</span>
- </div>
- <div ref="memberPieChart" class="echarts-container"></div>
- </el-card>
- </el-col>
- </el-row>
- <!-- 内容区域 -->
- <el-row :gutter="20" class="content-row">
- <el-col :span="14">
- <el-card class="content-card" shadow="hover">
- <div slot="header" class="card-header">
- <span class="card-title">📋 最近任务</span>
- <el-button type="text" size="small" @click="$router.push('/tasks')">
- 查看全部 →
- </el-button>
- </div>
- <el-table :data="recentTasks" size="small" class="custom-table">
- <el-table-column prop="title" label="任务名称" min-width="180"></el-table-column>
- <el-table-column prop="childName" label="孩子" width="100"></el-table-column>
- <el-table-column prop="status" label="状态" width="100">
- <template slot-scope="scope">
- <el-tag
- :type="scope.row.status === 'completed' ? 'success' : 'warning'"
- size="small"
- effect="plain"
- >
- {{ scope.row.status === 'completed' ? '已完成' : '进行中' }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="points" label="积分" width="80">
- <template slot-scope="scope">
- <span class="points-text">+{{ scope.row.points }}</span>
- </template>
- </el-table-column>
- </el-table>
- </el-card>
- </el-col>
- <el-col :span="10">
- <el-card class="content-card system-health-card" shadow="hover">
- <div slot="header" class="card-header">
- <span class="card-title">⚙️ 系统状态</span>
- <el-tag size="mini" type="success">正常运行</el-tag>
- </div>
- <div class="health-grid">
- <div class="health-item">
- <div class="health-icon health-icon--status">
- <i class="el-icon-success"></i>
- </div>
- <div class="health-info">
- <div class="health-label">系统状态</div>
- <div class="health-value health-value--online">● 在线</div>
- </div>
- </div>
- <div class="health-item">
- <div class="health-icon health-icon--version">
- <i class="el-icon-info"></i>
- </div>
- <div class="health-info">
- <div class="health-label">系统版本</div>
- <div class="health-value">
- {{ version }}
- <button type="button" style="font-size:12px;border:none;background:none;color:#409EFF;cursor:pointer;padding:0;margin-left:6px" @click="showChangelog()">查看变更日志</button>
- </div>
- </div>
- </div>
- <div class="health-item">
- <div class="health-icon health-icon--time">
- <i class="el-icon-time"></i>
- </div>
- <div class="health-info">
- <div class="health-label">最后刷新</div>
- <div class="health-value">{{ todayDate }}</div>
- </div>
- </div>
- <div class="health-item">
- <div class="health-icon health-icon--api">
- <i class="el-icon-link"></i>
- </div>
- <div class="health-info">
- <div class="health-label">API 接口</div>
- <div class="health-value">~45 个</div>
- </div>
- </div>
- <div class="health-item">
- <div class="health-icon health-icon--uptime">
- <i class="el-icon-time"></i>
- </div>
- <div class="health-info">
- <div class="health-label">服务器运行时长</div>
- <div class="health-value">{{ serverHealth.uptime }}</div>
- </div>
- </div>
- <div class="health-item">
- <div class="health-icon health-icon--cpu">
- <i class="el-icon-cpu"></i>
- </div>
- <div class="health-info">
- <div class="health-label">CPU 使用率</div>
- <div class="health-value">{{ serverHealth.cpuUsagePercent }}% ({{ serverHealth.cpuCores }}核)</div>
- </div>
- </div>
- <div class="health-item">
- <div class="health-icon health-icon--memory">
- <i class="el-icon-coffee"></i>
- </div>
- <div class="health-info">
- <div class="health-label">JVM 内存</div>
- <div class="health-value">{{ serverHealth.memUsed }} / {{ serverHealth.memMax }} ({{ serverHealth.memUsagePercent }}%)</div>
- </div>
- </div>
- </div>
- <div class="health-links">
- <el-button size="mini" icon="el-icon-s-order" @click="$router.push('/tasks')">任务管理</el-button>
- <el-button size="mini" icon="el-icon-s-custom" @click="$router.push('/families')">家庭管理</el-button>
- <el-button size="mini" icon="el-icon-user" @click="$router.push('/users')">用户管理</el-button>
- </div>
- </el-card>
- </el-col>
- </el-row>
- <!-- 快捷操作 -->
- <el-row :gutter="20">
- <el-col :span="24">
- <div class="quick-actions-section">
- <div class="section-header">
- <span class="section-title">快捷操作</span>
- </div>
- <div class="quick-actions-grid">
- <div class="action-card" @click="$router.push('/tasks')">
- <div class="action-icon action-icon--primary">
- <i class="el-icon-s-order"></i>
- </div>
- <div class="action-label">任务管理</div>
- </div>
- <div class="action-card" @click="$router.push('/families')">
- <div class="action-icon action-icon--family">
- <i class="el-icon-s-custom"></i>
- </div>
- <div class="action-label">家庭管理</div>
- </div>
- <div class="action-card" @click="$router.push('/guide-pending')">
- <div class="action-icon action-icon--guide">
- <i class="el-icon-user"></i>
- </div>
- <div class="action-label">规划师审核</div>
- </div>
- <div class="action-card" @click="$router.push('/package-pending')">
- <div class="action-icon action-icon--package">
- <i class="el-icon-goods"></i>
- </div>
- <div class="action-label">任务模板审核</div>
- </div>
- <div class="action-card" @click="$router.push('/rewards')">
- <div class="action-icon action-icon--reward">
- <i class="el-icon-medal"></i>
- </div>
- <div class="action-label">奖励管理</div>
- </div>
- <div class="action-card" @click="$router.push('/users')">
- <div class="action-icon action-icon--user">
- <i class="el-icon-user-solid"></i>
- </div>
- <div class="action-label">用户管理</div>
- </div>
- <div class="action-card" @click="$router.push('/audit')">
- <div class="action-icon action-icon--audit">
- <i class="el-icon-document-checked"></i>
- </div>
- <div class="action-label">审核中心</div>
- </div>
- <div class="action-card" @click="$router.push('/settings')">
- <div class="action-icon action-icon--settings">
- <i class="el-icon-setting"></i>
- </div>
- <div class="action-label">系统设置</div>
- </div>
- </div>
- </div>
- </el-col>
- </el-row>
- <!-- 变更日志弹窗 -->
- <el-dialog
- title="变更日志"
- :visible.sync="changelogVisible"
- width="900px"
- custom-class="changelog-dialog"
- append-to-body
- >
- <div v-loading="changelogLoading" class="changelog-body" v-html="changelogContent"></div>
- </el-dialog>
- </div>
- </div>
- </template>
- <script>
- import * as echarts from 'echarts'
- import { getDashboardStats, getMembershipStats, getTrendStats, getDashboardOverview, getSystemHealth } from '@/api/admin'
- export default {
- name: 'Dashboard',
- data() {
- return {
- loadingTasks: false,
- stats: {
- totalFamilies: 0,
- totalParents: 0,
- totalChildren: 0,
- totalTeachers: 0,
- pendingGuideCount: 0,
- pendingPackageCount: 0,
- activeUsers: 0,
- monthFamilies: 0
- },
- recentTasks: [],
- trendLoading: false,
- memberLoading: false,
- membershipData: {},
- trendData: { familyTrend: {}, userTrend: {} },
- trendChart: null,
- memberChart: null,
- resizeHandler: null,
- changelogVisible: false,
- changelogContent: '',
- changelogLoading: false,
- serverHealth: {
- uptime: '加载中...',
- memUsed: '--',
- memMax: '--',
- memUsagePercent: '--',
- cpuCores: '--',
- cpuLoad: '--',
- cpuUsagePercent: '--'
- }
- }
- },
- computed: {
- version() {
- return process.env.VUE_APP_VERSION || '1.0.0'
- },
- todayDate() {
- const now = new Date()
- const months = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二']
- return `${months[now.getMonth()]}月${now.getDate()}日 星期${['日', '一', '二', '三', '四', '五', '六'][now.getDay()]}`
- }
- },
- mounted() {
- this.loadDashboardData()
- this.loadTrend()
- this.loadMembership()
- this.loadServerHealth()
- this.resizeHandler = () => {
- this.trendChart && this.trendChart.resize()
- this.memberChart && this.memberChart.resize()
- }
- window.addEventListener('resize', this.resizeHandler)
- },
- beforeDestroy() {
- this.trendChart && this.trendChart.dispose()
- this.memberChart && this.memberChart.dispose()
- this.resizeHandler && window.removeEventListener('resize', this.resizeHandler)
- },
- methods: {
- async loadDashboardData() {
- try {
- const res = await getDashboardStats()
- const d = res.data || {}
- this.stats.totalFamilies = d.totalFamilies || 0
- this.stats.totalParents = d.totalParents || 0
- this.stats.totalChildren = d.totalChildren || 0
- this.stats.totalTeachers = d.totalTeachers || 0
- this.stats.pendingGuideCount = d.pendingGuideCount || 0
- this.stats.pendingPackageCount = d.pendingPackageCount || 0
- this.recentTasks = (d.recentTasks || []).map(t => ({
- title: t.title,
- childName: t.childName || '',
- status: t.status,
- points: t.points || 0
- }))
- const overviewRes = await getDashboardOverview()
- const overview = overviewRes.data || {}
- this.stats.activeUsers = overview.activeUsers || 0
- this.stats.monthFamilies = overview.monthFamilies || 0
- } catch (error) {
- console.error('加载仪表盘数据失败:', error)
- }
- },
- async loadServerHealth() {
- try {
- const res = await getSystemHealth()
- if (res.code === 200 && res.data) {
- const d = res.data
- this.serverHealth = {
- uptime: d.uptime || '--',
- memUsed: d.memUsed || '--',
- memMax: d.memMax || '--',
- memUsagePercent: d.memUsagePercent || '--',
- cpuCores: d.cpuCores || '--',
- cpuLoad: d.cpuLoad || '--',
- cpuUsagePercent: d.cpuUsagePercent || '--'
- }
- }
- } catch (error) {
- console.error('加载服务器状态失败:', error)
- }
- },
- showChangelog() {
- this.changelogVisible = true
- if (this.changelogContent) return
- this.changelogLoading = true
- fetch(process.env.BASE_URL + 'CHANGELOG.md?t=' + Date.now()).then(function(res) {
- if (!res.ok) throw new Error('Not found')
- return res.text()
- }).then((function(text) {
- var html = ''
- var lines = text.split('\n')
- var inCodeBlock = false
- var listBuffer = ''
- var inList = false
- function flushList() {
- if (inList) {
- html += '<ul>' + listBuffer + '</ul>'
- listBuffer = ''
- inList = false
- }
- }
- for (var i = 0; i < lines.length; i++) {
- var line = lines[i]
- if (line.match(/^```/)) {
- flushList()
- if (inCodeBlock) {
- html += '</code></pre>'
- inCodeBlock = false
- } else {
- html += '<pre><code>'
- inCodeBlock = true
- }
- continue
- }
- if (inCodeBlock) {
- html += this._escapeHtml(line) + '\n'
- continue
- }
- if (line.match(/^### /)) {
- flushList()
- html += '<h3>' + this._parseInline(line.replace(/^### /, '')) + '</h3>'
- continue
- }
- if (line.match(/^## /)) {
- flushList()
- html += '<h2>' + this._parseInline(line.replace(/^## /, '')) + '</h2>'
- continue
- }
- if (line.match(/^# /)) {
- flushList()
- html += '<h1>' + this._parseInline(line.replace(/^# /, '')) + '</h1>'
- continue
- }
- if (line.match(/^\- /)) {
- inList = true
- listBuffer += '<li>' + this._parseInline(line.replace(/^\- /, '')) + '</li>\n'
- continue
- }
- if (line.trim() === '') {
- flushList()
- continue
- }
- flushList()
- html += '<p class="cl-item">' + this._parseInline(line) + '</p>\n'
- }
- flushList()
- this.changelogContent = html || '<p class="cl-empty">暂无变更日志</p>'
- this.changelogLoading = false
- }).bind(this)).catch((function() {
- this.changelogContent = '<p class="cl-empty">暂无变更日志</p>'
- this.changelogLoading = false
- }).bind(this))
- },
- _parseInline(text) {
- text = text.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
- text = text.replace(/\*(.+?)\*/g, '<em>$1</em>')
- text = text.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2" target="_blank">$1</a>')
- text = text.replace(/`(.+?)`/g, '<code style="background:#F1F5F9;padding:2px 4px;border-radius:3px;font-size:13px">$1</code>')
- text = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
- return text
- },
- _escapeHtml(text) {
- return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
- },
- async loadTrend() {
- this.trendLoading = true
- try {
- const res = await getTrendStats()
- if (res.code === 200 && res.data) {
- this.trendData = res.data
- this.$nextTick(() => this.initTrendChart())
- }
- } catch (e) {
- console.error('加载趋势数据失败:', e)
- } finally {
- this.trendLoading = false
- }
- },
- async loadMembership() {
- this.memberLoading = true
- try {
- const res = await getMembershipStats()
- if (res.code === 200 && res.data) {
- this.membershipData = res.data
- this.$nextTick(() => this.initMemberPieChart())
- }
- } catch (e) {
- console.error('加载会员数据失败:', e)
- } finally {
- this.memberLoading = false
- }
- },
- initTrendChart() {
- if (this.trendChart) this.trendChart.dispose()
- this.trendChart = echarts.init(this.$refs.trendChart)
- const familyData = Object.values(this.trendData.familyTrend || {})
- const userData = Object.values(this.trendData.userTrend || {})
- const xAxis = Object.keys(this.trendData.familyTrend || {})
- this.trendChart.setOption({
- tooltip: { trigger: 'axis' },
- legend: { data: ['新增家庭', '新增用户'] },
- grid: { left: 50, right: 20, bottom: 30, top: 40 },
- xAxis: { type: 'category', data: xAxis, axisLabel: { rotate: xAxis.length > 15 ? 45 : 0 } },
- yAxis: { type: 'value', minInterval: 1 },
- series: [
- { name: '新增家庭', type: 'bar', barWidth: '40%', data: familyData, itemStyle: { color: '#F97316', borderRadius: [4, 4, 0, 0] } },
- { name: '新增用户', type: 'line', smooth: true, data: userData, lineStyle: { color: '#0EA5E9', width: 2 }, itemStyle: { color: '#0EA5E9' } }
- ]
- })
- },
- initMemberPieChart() {
- if (this.memberChart) this.memberChart.dispose()
- this.memberChart = echarts.init(this.$refs.memberPieChart)
- this.memberChart.setOption({
- tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
- legend: { orient: 'vertical', left: 'left', top: 'center' },
- series: [{
- type: 'pie',
- radius: ['40%', '65%'],
- center: ['55%', '50%'],
- avoidLabelOverlap: true,
- label: { show: false },
- emphasis: { label: { show: true, fontSize: 14, fontWeight: 'bold' } },
- data: [
- { value: this.membershipData.freeCount || 0, name: '免费用户', itemStyle: { color: '#94A3B8' } },
- { value: this.membershipData.familyCount || 0, name: '家庭会员', itemStyle: { color: '#F97316' } },
- { value: this.membershipData.providerCount || 0, name: '服务商', itemStyle: { color: '#10B981' } }
- ]
- }]
- })
- }
- }
- }
- </script>
- <style scoped>
- .dashboard {
- padding: 0;
- }
- /* 欢迎横幅 */
- .welcome-banner {
- background: linear-gradient(135deg, #6366F1, #8B5CF6);
- border-radius: 16px;
- padding: 32px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 24px;
- color: #FFFFFF;
- }
- .welcome-title {
- font-size: 28px;
- font-weight: 700;
- margin: 0 0 8px 0;
- }
- .welcome-desc {
- font-size: 16px;
- opacity: 0.9;
- margin: 0;
- }
- .welcome-illustration {
- font-size: 64px;
- }
- /* 统计卡片 */
- .stats-row {
- margin-bottom: 24px;
- }
- .stat-card {
- border-radius: 12px;
- border: none;
- }
- .stat-card ::v-deep .el-card__body {
- display: flex;
- align-items: center;
- padding: 24px;
- }
- .todo-stat-card {
- cursor: pointer;
- transition: transform 0.2s, box-shadow 0.2s;
- }
- .todo-stat-card:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(0,0,0,0.1);
- }
- .stat-icon {
- width: 56px;
- height: 56px;
- border-radius: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 24px;
- color: #fff;
- margin-right: 16px;
- flex-shrink: 0;
- }
- .stat-icon.blue { background: linear-gradient(135deg, #6366F1, #818CF8); }
- .stat-icon.green { background: linear-gradient(135deg, #10B981, #34D399); }
- .stat-icon.orange { background: linear-gradient(135deg, #F59E0B, #FBBF24); }
- .stat-icon.purple { background: linear-gradient(135deg, #8B5CF6, #A78BFA); }
- .stat-icon.todo-warning { background: linear-gradient(135deg, #F59E0B, #FBBF24); }
- .stat-icon.todo-heart { background: linear-gradient(135deg, #FF6B9D, #FF8FB3); }
- .stat-icon.todo-danger { background: linear-gradient(135deg, #EF4444, #F87171); }
- .stat-icon.todo-disabled { background: linear-gradient(135deg, #CBD5E1, #94A3B8); }
- .stat-value {
- font-size: 28px;
- font-weight: 700;
- color: #1E293B;
- line-height: 1.2;
- }
- .stat-label {
- color: #64748B;
- font-size: 14px;
- margin-top: 4px;
- }
- .stat-trend {
- font-size: 12px;
- color: #6366F1;
- margin-top: 8px;
- }
- .stat-trend.disabled {
- color: #94A3B8;
- }
- /* 内容卡片 */
- .content-row {
- margin-bottom: 24px;
- }
- .content-card {
- border-radius: 12px;
- border: none;
- margin-bottom: 20px;
- }
- .card-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .card-title {
- font-size: 16px;
- font-weight: 600;
- color: #1E293B;
- }
- /* 表格样式 */
- .custom-table ::v-deep .el-table__header th {
- background: #F8FAFC;
- color: #64748B;
- font-weight: 600;
- }
- .points-text {
- color: #F59E0B;
- font-weight: 600;
- }
- /* 系统健康面板 */
- .system-health-card {
- border-radius: 12px;
- border: none;
- }
- .health-grid {
- display: flex;
- gap: 16px;
- flex-wrap: wrap;
- }
- .health-item {
- display: flex;
- align-items: center;
- gap: 12px;
- flex: 1;
- min-width: 140px;
- }
- .health-icon {
- width: 40px;
- height: 40px;
- border-radius: 10px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 18px;
- color: white;
- }
- .health-icon--status {
- background: linear-gradient(135deg, #10B981, #059669);
- }
- .health-icon--version {
- background: linear-gradient(135deg, #6366F1, #4F46E5);
- }
- .health-icon--time {
- background: linear-gradient(135deg, #F59E0B, #D97706);
- }
- .health-icon--api {
- background: linear-gradient(135deg, #0EA5E9, #0284C7);
- }
- .health-icon--uptime {
- background: linear-gradient(135deg, #8B5CF6, #7C3AED);
- }
- .health-icon--cpu {
- background: linear-gradient(135deg, #EF4444, #F87171);
- }
- .health-icon--memory {
- background: linear-gradient(135deg, #10B981, #059669);
- }
- .health-info {
- flex: 1;
- }
- .health-label {
- font-size: 12px;
- color: #9ca3af;
- margin-bottom: 2px;
- }
- .health-value {
- font-size: 14px;
- font-weight: 600;
- color: #1f2937;
- }
- .health-value--online {
- color: #10B981;
- }
- .health-links {
- margin-top: 16px;
- padding-top: 16px;
- border-top: 1px solid #f3f4f6;
- display: flex;
- gap: 8px;
- }
- /* 快捷操作 */
- .quick-card .el-card__body {
- padding: 12px;
- }
- .quick-actions {
- display: flex;
- justify-content: space-around;
- }
- .quick-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- cursor: pointer;
- padding: 12px;
- border-radius: 8px;
- transition: background 0.2s;
- }
- .quick-item:hover {
- background: #F8FAFC;
- }
- .quick-item i {
- font-size: 24px;
- color: #6366F1;
- margin-bottom: 8px;
- }
- .quick-item span {
- font-size: 12px;
- color: #64748B;
- }
- /* 快捷操作 4x2 网格 */
- .quick-actions-section {
- margin-bottom: 24px;
- }
- .section-header {
- margin-bottom: 12px;
- }
- .section-title {
- font-size: 16px;
- font-weight: 600;
- color: #1f2937;
- }
- .quick-actions-grid {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 12px;
- }
- .action-card {
- background: white;
- border-radius: 12px;
- padding: 20px 16px;
- text-align: center;
- cursor: pointer;
- transition: all 0.2s ease;
- border: 1px solid #f3f4f6;
- }
- .action-card:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(0,0,0,0.08);
- border-color: #e5e7eb;
- }
- .action-icon {
- width: 48px;
- height: 48px;
- border-radius: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- margin: 0 auto 8px;
- font-size: 22px;
- color: white;
- }
- .action-icon--primary {
- background: linear-gradient(135deg, #FF8C42, #FF6B35);
- }
- .action-icon--family {
- background: linear-gradient(135deg, #6366F1, #4F46E5);
- }
- .action-icon--guide {
- background: linear-gradient(135deg, #F59E0B, #D97706);
- }
- .action-icon--package {
- background: linear-gradient(135deg, #10B981, #059669);
- }
- .action-icon--reward {
- background: linear-gradient(135deg, #FF6B9D, #E11D48);
- }
- .action-icon--user {
- background: linear-gradient(135deg, #0EA5E9, #0284C7);
- }
- .action-icon--audit {
- background: linear-gradient(135deg, #8B5CF6, #7C3AED);
- }
- .action-icon--settings {
- background: linear-gradient(135deg, #6B7280, #4B5563);
- }
- .action-label {
- font-size: 13px;
- color: #374151;
- font-weight: 500;
- }
- /* 图表区域 */
- .chart-row {
- margin-bottom: 24px;
- }
- .chart-card {
- border-radius: 12px;
- border: none;
- min-height: 320px;
- }
- .echarts-container {
- height: 260px;
- width: 100%;
- }
- </style>
- <style>
- .changelog-dialog .el-dialog__body {
- max-height: 700px;
- overflow-y: auto;
- }
- .changelog-body {
- padding: 8px 4px;
- line-height: 1.8;
- font-size: 14px;
- color: #374151;
- }
- .changelog-body h1 {
- font-size: 20px;
- font-weight: 700;
- margin: 16px 0 8px;
- color: #1E293B;
- }
- .changelog-body h2 {
- font-size: 17px;
- font-weight: 600;
- margin: 14px 0 6px;
- color: #1E293B;
- border-left: 3px solid #6366F1;
- padding-left: 8px;
- }
- .changelog-body h3 {
- font-size: 15px;
- font-weight: 600;
- margin: 10px 0 4px;
- color: #374151;
- }
- .changelog-body ul {
- margin: 4px 0 8px;
- padding-left: 20px;
- }
- .changelog-body li {
- margin: 2px 0;
- }
- .changelog-body .cl-item {
- margin: 4px 0;
- }
- .changelog-body .cl-empty {
- text-align: center;
- color: #94A3B8;
- padding: 32px 0;
- }
- .changelog-body code {
- background: #F1F5F9;
- padding: 2px 6px;
- border-radius: 3px;
- font-size: 13px;
- }
- .changelog-body pre {
- background: #F8FAFC;
- border: 1px solid #E2E8F0;
- border-radius: 8px;
- padding: 12px;
- overflow-x: auto;
- }
- .changelog-body pre code {
- background: none;
- padding: 0;
- }
- .changelog-body a {
- color: #6366F1;
- text-decoration: none;
- }
- .changelog-body a:hover {
- text-decoration: underline;
- }
- </style>
|