mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	feat: add chart for admin dashbord
This commit is contained in:
		@@ -7,11 +7,13 @@
 | 
				
			|||||||
* 功能优化:SD 绘画页面采用 websocket 替换 http 轮询机制,节省带宽
 | 
					* 功能优化:SD 绘画页面采用 websocket 替换 http 轮询机制,节省带宽
 | 
				
			||||||
* 功能优化:移动端聊天页面图片支持预览和放大功能
 | 
					* 功能优化:移动端聊天页面图片支持预览和放大功能
 | 
				
			||||||
* 功能优化:MJ 和 SD 页面数据分页加载,解决一次性加载太多数据导致页面卡顿的问题
 | 
					* 功能优化:MJ 和 SD 页面数据分页加载,解决一次性加载太多数据导致页面卡顿的问题
 | 
				
			||||||
* 功能优化:**前端不登录也可以预览功能,只有在发起操作的时候才需要登录**
 | 
					* 功能优化:**PC端不登录也可以预览功能,只有在发起操作的时候才需要登录**
 | 
				
			||||||
* 功能优化:控制台订单管理页面显示未支付订单,并提供订单删除功能
 | 
					* 功能优化:控制台订单管理页面显示未支付订单,并提供订单删除功能
 | 
				
			||||||
* 功能新增:支持H5支付
 | 
					* 功能新增:支持H5支付
 | 
				
			||||||
* 功能优化:支持数学公式的识别和美化输出
 | 
					* 功能优化:支持数学公式的识别和美化输出
 | 
				
			||||||
* 功能新增:新增算力消费日志功能
 | 
					* 功能新增:新增算力消费日志功能
 | 
				
			||||||
 | 
					* 功能优化:整合 XXL-JOB 实现订单清理,每日算力派发,VIP 算力重置等任务
 | 
				
			||||||
 | 
					* 功能新增:管理后台新增7日内新增用户和新增订单统计
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## v3.2.7
 | 
					## v3.2.7
 | 
				
			||||||
* 功能重构:采用 Vant 重构移动页面,新增 MidJourney 功能
 | 
					* 功能重构:采用 Vant 重构移动页面,新增 MidJourney 功能
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -163,7 +163,7 @@ func (e *XXLJobExecutor) ResetUserPower(cxt context.Context, param *xxl.RunReq)
 | 
				
			|||||||
				Mark:      types.PowerAdd,
 | 
									Mark:      types.PowerAdd,
 | 
				
			||||||
				Balance:   user.Power,
 | 
									Balance:   user.Power,
 | 
				
			||||||
				Model:     "系统赠送",
 | 
									Model:     "系统赠送",
 | 
				
			||||||
				Remark:    fmt.Sprintf("系统每日算力派发,今日额度:%d", config.VipMonthPower),
 | 
									Remark:    fmt.Sprintf("系统每日算力派发,今日额度:%d", config.DailyPower),
 | 
				
			||||||
				CreatedAt: time.Now(),
 | 
									CreatedAt: time.Now(),
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,7 @@
 | 
				
			|||||||
    "clipboard": "^2.0.11",
 | 
					    "clipboard": "^2.0.11",
 | 
				
			||||||
    "compressorjs": "^1.2.1",
 | 
					    "compressorjs": "^1.2.1",
 | 
				
			||||||
    "core-js": "^3.8.3",
 | 
					    "core-js": "^3.8.3",
 | 
				
			||||||
 | 
					    "echarts": "^5.5.0",
 | 
				
			||||||
    "element-plus": "^2.3.0",
 | 
					    "element-plus": "^2.3.0",
 | 
				
			||||||
    "good-storage": "^1.1.1",
 | 
					    "good-storage": "^1.1.1",
 | 
				
			||||||
    "highlight.js": "^11.7.0",
 | 
					    "highlight.js": "^11.7.0",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -171,27 +171,6 @@
 | 
				
			|||||||
                <el-tab-pane label="文生图(可选)" name="image">
 | 
					                <el-tab-pane label="文生图(可选)" name="image">
 | 
				
			||||||
                  <div class="text">图生图:以某张图片为底稿参考来创作绘画,生成类似风格或类型图像,支持 PNG 和 JPG 格式图片;
 | 
					                  <div class="text">图生图:以某张图片为底稿参考来创作绘画,生成类似风格或类型图像,支持 PNG 和 JPG 格式图片;
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                  <div class="param-line pt">
 | 
					 | 
				
			||||||
                    <el-form-item label="">
 | 
					 | 
				
			||||||
                      <template #default>
 | 
					 | 
				
			||||||
                        <div class="form-item-inner flex-row items-center">
 | 
					 | 
				
			||||||
                          <el-input v-model="params.img" size="small" placeholder="请输入图片地址或者上传图片"
 | 
					 | 
				
			||||||
                                    style="width: 300px;"/>
 | 
					 | 
				
			||||||
                          <el-icon @click="params.img = ''" title="清空图片">
 | 
					 | 
				
			||||||
                            <DeleteFilled/>
 | 
					 | 
				
			||||||
                          </el-icon>
 | 
					 | 
				
			||||||
                          <el-tooltip effect="light"
 | 
					 | 
				
			||||||
                                      content="垫图:以某张图片为底稿参考来创作绘画 <br/> 支持 PNG 和 JPG 格式图片"
 | 
					 | 
				
			||||||
                                      raw-content placement="right">
 | 
					 | 
				
			||||||
                            <el-icon>
 | 
					 | 
				
			||||||
                              <InfoFilled/>
 | 
					 | 
				
			||||||
                            </el-icon>
 | 
					 | 
				
			||||||
                          </el-tooltip>
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                      </template>
 | 
					 | 
				
			||||||
                    </el-form-item>
 | 
					 | 
				
			||||||
                  </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  <div class="param-line">
 | 
					                  <div class="param-line">
 | 
				
			||||||
                    <div class="img-inline">
 | 
					                    <div class="img-inline">
 | 
				
			||||||
                      <div class="img-list-box">
 | 
					                      <div class="img-list-box">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -124,7 +124,33 @@ const fetchData = () => {
 | 
				
			|||||||
  .inner {
 | 
					  .inner {
 | 
				
			||||||
    padding 0 20px 20px 20px
 | 
					    padding 0 20px 20px 20px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::-webkit-scrollbar {
 | 
				
			||||||
 | 
					      width: 8px; /* 滚动条宽度 */
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* 修改滚动条轨道的背景颜色 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::-webkit-scrollbar-track {
 | 
				
			||||||
 | 
					      background-color: #ffffff;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* 修改滚动条的滑块颜色 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::-webkit-scrollbar-thumb {
 | 
				
			||||||
 | 
					      background-color: #cccccc;
 | 
				
			||||||
 | 
					      border-radius 8px
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* 修改滚动条的滑块的悬停颜色 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::-webkit-scrollbar-thumb:hover {
 | 
				
			||||||
 | 
					      background-color: #999999;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .list-box {
 | 
					    .list-box {
 | 
				
			||||||
 | 
					      overflow-x hidden
 | 
				
			||||||
 | 
					      //overflow-y auto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      .handle-box {
 | 
					      .handle-box {
 | 
				
			||||||
        padding 20px 0
 | 
					        padding 20px 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,26 +54,136 @@
 | 
				
			|||||||
        </el-card>
 | 
					        </el-card>
 | 
				
			||||||
      </el-col>
 | 
					      </el-col>
 | 
				
			||||||
    </el-row>
 | 
					    </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <el-row class="mgb20" :gutter="20">
 | 
				
			||||||
 | 
					      <el-col :span="8">
 | 
				
			||||||
 | 
					        <div class="e-chart">
 | 
				
			||||||
 | 
					          <div id="chart-users" style="height: 400px"></div>
 | 
				
			||||||
 | 
					          <div class="title">最近7日用户注册</div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </el-col>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <el-col :span="8">
 | 
				
			||||||
 | 
					        <div class="e-chart">
 | 
				
			||||||
 | 
					          <div id="chart-tokens" style="height: 400px"></div>
 | 
				
			||||||
 | 
					          <div class="title">最近7日Token消耗</div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </el-col>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <el-col :span="8">
 | 
				
			||||||
 | 
					        <div class="e-chart">
 | 
				
			||||||
 | 
					          <div id="chart-income" style="height: 400px"></div>
 | 
				
			||||||
 | 
					          <div class="title">最近7日收入</div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </el-col>
 | 
				
			||||||
 | 
					    </el-row>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup>
 | 
					<script setup>
 | 
				
			||||||
import {ref} from 'vue';
 | 
					import {onMounted, ref} from 'vue';
 | 
				
			||||||
import {ChatDotRound, TrendCharts, User} from "@element-plus/icons-vue";
 | 
					import {ChatDotRound, TrendCharts, User} from "@element-plus/icons-vue";
 | 
				
			||||||
import {httpGet} from "@/utils/http";
 | 
					import {httpGet} from "@/utils/http";
 | 
				
			||||||
import {ElMessage} from "element-plus";
 | 
					import {ElMessage} from "element-plus";
 | 
				
			||||||
 | 
					import * as echarts from 'echarts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const stats = ref({users: 0, chats: 0, tokens: 0, rewards: 0})
 | 
					const stats = ref({users: 0, chats: 0, tokens: 0, rewards: 0})
 | 
				
			||||||
const loading = ref(true)
 | 
					const loading = ref(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
httpGet('/api/admin/dashboard/stats').then((res) => {
 | 
					onMounted(() => {
 | 
				
			||||||
  stats.value.users = res.data.users
 | 
					  const chartUsers = echarts.init(document.getElementById("chart-users"))
 | 
				
			||||||
  stats.value.chats = res.data.chats
 | 
					  const chartTokens = echarts.init(document.getElementById("chart-tokens"))
 | 
				
			||||||
  stats.value.tokens = res.data.tokens
 | 
					  const chartIncome = echarts.init(document.getElementById("chart-income"))
 | 
				
			||||||
  stats.value.income = res.data.income
 | 
					  httpGet('/api/admin/dashboard/stats').then((res) => {
 | 
				
			||||||
  loading.value = false
 | 
					    stats.value.users = res.data.users
 | 
				
			||||||
}).catch((e) => {
 | 
					    stats.value.chats = res.data.chats
 | 
				
			||||||
  ElMessage.error("获取统计数据失败:" + e.message)
 | 
					    stats.value.tokens = res.data.tokens
 | 
				
			||||||
 | 
					    stats.value.income = res.data.income
 | 
				
			||||||
 | 
					    const chartData = res.data.chart
 | 
				
			||||||
 | 
					    loading.value = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const x = []
 | 
				
			||||||
 | 
					    const dataUsers = []
 | 
				
			||||||
 | 
					    for (let k in chartData.users) {
 | 
				
			||||||
 | 
					      x.push(k)
 | 
				
			||||||
 | 
					      dataUsers.push(chartData.users[k])
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    chartUsers.setOption({
 | 
				
			||||||
 | 
					      xAxis: {
 | 
				
			||||||
 | 
					        data: x
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      yAxis: {},
 | 
				
			||||||
 | 
					      series: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          data: dataUsers,
 | 
				
			||||||
 | 
					          type: 'line',
 | 
				
			||||||
 | 
					          label: {
 | 
				
			||||||
 | 
					            show: true,
 | 
				
			||||||
 | 
					            position: 'bottom',
 | 
				
			||||||
 | 
					            textStyle: {
 | 
				
			||||||
 | 
					              fontSize: 18
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    const dataTokens = []
 | 
				
			||||||
 | 
					    for (let k in chartData.historyMessage) {
 | 
				
			||||||
 | 
					      dataTokens.push(chartData.historyMessage[k])
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    chartTokens.setOption({
 | 
				
			||||||
 | 
					      xAxis: {
 | 
				
			||||||
 | 
					        data: x
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      yAxis: {},
 | 
				
			||||||
 | 
					      series: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          data: dataTokens,
 | 
				
			||||||
 | 
					          type: 'line',
 | 
				
			||||||
 | 
					          label: {
 | 
				
			||||||
 | 
					            show: true,
 | 
				
			||||||
 | 
					            position: 'bottom',
 | 
				
			||||||
 | 
					            textStyle: {
 | 
				
			||||||
 | 
					              fontSize: 18
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const dataIncome = []
 | 
				
			||||||
 | 
					    for (let k in chartData.orders) {
 | 
				
			||||||
 | 
					      dataIncome.push(chartData.orders[k])
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    chartIncome.setOption({
 | 
				
			||||||
 | 
					      xAxis: {
 | 
				
			||||||
 | 
					        data: x
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      yAxis: {},
 | 
				
			||||||
 | 
					      series: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          data: dataIncome,
 | 
				
			||||||
 | 
					          type: 'line',
 | 
				
			||||||
 | 
					          label: {
 | 
				
			||||||
 | 
					            show: true,
 | 
				
			||||||
 | 
					            position: 'bottom',
 | 
				
			||||||
 | 
					            textStyle: {
 | 
				
			||||||
 | 
					              fontSize: 18
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }).catch((e) => {
 | 
				
			||||||
 | 
					    ElMessage.error("获取统计数据失败:" + e.message)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  window.onresize = function () { // 自适应大小
 | 
				
			||||||
 | 
					    chartUsers.resize()
 | 
				
			||||||
 | 
					    chartTokens.resize()
 | 
				
			||||||
 | 
					    chartIncome.resize()
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
@@ -113,6 +223,14 @@ httpGet('/api/admin/dashboard/stats').then((res) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .e-chart {
 | 
				
			||||||
 | 
					    .title {
 | 
				
			||||||
 | 
					      text-align center
 | 
				
			||||||
 | 
					      font-size 16px
 | 
				
			||||||
 | 
					      color #444444
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .grid-con-1 .grid-con-icon {
 | 
					  .grid-con-1 .grid-con-icon {
 | 
				
			||||||
    background: rgb(45, 140, 240);
 | 
					    background: rgb(45, 140, 240);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user