Jelajahi Sumber

change system page

tycoding 1 tahun lalu
induk
melakukan
b04d18af1e

+ 5 - 7
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcMessageController.java

@@ -2,7 +2,6 @@ package cn.tycoding.langchat.aigc.controller;
 
 import cn.hutool.core.util.StrUtil;
 import cn.tycoding.langchat.aigc.entity.AigcMessage;
-import cn.tycoding.langchat.aigc.mapper.AigcMessageMapper;
 import cn.tycoding.langchat.aigc.service.AigcMessageService;
 import cn.tycoding.langchat.common.utils.MybatisUtil;
 import cn.tycoding.langchat.common.utils.QueryPage;
@@ -11,7 +10,11 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.AllArgsConstructor;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
 
 /**
  * @author tycoding
@@ -23,7 +26,6 @@ import org.springframework.web.bind.annotation.*;
 public class AigcMessageController {
 
     private final AigcMessageService aigcMessageService;
-    private final AigcMessageMapper aigcMessageMapper;
 
     @GetMapping("/page")
     public R list(AigcMessage data, QueryPage queryPage) {
@@ -46,8 +48,4 @@ public class AigcMessageController {
         return R.ok(aigcMessageService.removeById(id));
     }
 
-    @GetMapping("/charts")
-    public R charts() {
-        return R.ok(aigcMessageMapper.charts());
-    }
 }

+ 58 - 0
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/StatisticsController.java

@@ -0,0 +1,58 @@
+package cn.tycoding.langchat.aigc.controller;
+
+import cn.hutool.core.lang.Dict;
+import cn.tycoding.langchat.aigc.mapper.AigcKnowledgeMapper;
+import cn.tycoding.langchat.aigc.mapper.AigcMessageMapper;
+import cn.tycoding.langchat.aigc.mapper.AigcPromptMapper;
+import cn.tycoding.langchat.aigc.mapper.AigcUserMapper;
+import cn.tycoding.langchat.common.utils.R;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author tycoding
+ * @since 2024/6/8
+ */
+@RequestMapping("/aigc/statistic")
+@RestController
+@AllArgsConstructor
+public class StatisticsController {
+
+    private final AigcMessageMapper aigcMessageMapper;
+    private final AigcUserMapper aigcUserMapper;
+    private final AigcKnowledgeMapper aigcKnowledgeMapper;
+    private final AigcPromptMapper aigcPromptMapper;
+
+    @GetMapping("/requestBy30")
+    public R request30Chart() {
+        return R.ok(aigcMessageMapper.getReqChartBy30());
+    }
+
+    @GetMapping("/token")
+    public R tokenChart() {
+        return R.ok(aigcMessageMapper.getTokenChart());
+    }
+
+    @GetMapping("/request")
+    public R requestChart() {
+        return R.ok(aigcMessageMapper.getReqChart());
+    }
+
+    @GetMapping("/home")
+    public R home() {
+        Dict reqData = aigcMessageMapper.getCount();
+        Dict totalData = aigcMessageMapper.getTotalSum();
+        Dict userData = aigcUserMapper.getCount();
+        Long totalKnowledge = aigcKnowledgeMapper.selectCount(Wrappers.query());
+        Long totalPrompt = aigcPromptMapper.selectCount(Wrappers.query());
+        Dict result = Dict.create();
+        result.putAll(reqData);
+        result.putAll(totalData);
+        result.putAll(userData);
+        result.set("totalKnowledge", totalKnowledge).set("totalPrompt", totalPrompt);
+        return R.ok(result);
+    }
+}

+ 54 - 1
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/mapper/AigcMessageMapper.java

@@ -23,11 +23,64 @@ public interface AigcMessageMapper extends BaseMapper<AigcMessage> {
             aigc_message
         WHERE
             create_time >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
+            AND role = 'assistant'
         GROUP BY
             date
         ORDER BY
             date DESC;
     """)
-    List<Dict> charts();
+    List<Dict> getReqChartBy30();
+
+    @Select("""
+        SELECT
+            DATE_FORMAT(create_time, '%Y-%m') as month,
+            COUNT(*) as count
+        FROM
+            aigc_message
+        WHERE
+            create_time >= DATE_SUB(CURDATE(), INTERVAL 1 YEAR)
+            AND role = 'assistant'
+        GROUP BY
+            month
+        ORDER BY
+            month DESC;
+    """)
+    List<Dict> getReqChart();
+
+    @Select("""
+        SELECT
+            DATE_FORMAT(create_time, '%Y-%m') as month,
+            SUM(tokens) as count
+        FROM
+            aigc_message
+        WHERE
+            create_time >= DATE_SUB(CURDATE(), INTERVAL 1 YEAR)
+            AND role = 'assistant'
+        GROUP BY
+            month
+        ORDER BY
+            month DESC;
+    """)
+    List<Dict> getTokenChart();
+
+    @Select("""
+        SELECT
+            COUNT(*) AS totalReq,
+            SUM( CASE WHEN DATE ( create_time ) = CURDATE() THEN 1 ELSE 0 END ) AS curReq
+        FROM
+            aigc_message
+        WHERE
+            role = 'assistant'
+    """)
+    Dict getCount();
+
+    @Select("""
+        SELECT
+            SUM( tokens ) AS totalToken,
+            SUM( CASE WHEN DATE ( create_time ) = CURDATE() THEN tokens ELSE 0 END ) AS curToken
+        FROM
+            aigc_message
+    """)
+    Dict getTotalSum();
 }
 

+ 10 - 0
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/mapper/AigcUserMapper.java

@@ -1,8 +1,10 @@
 package cn.tycoding.langchat.aigc.mapper;
 
+import cn.hutool.core.lang.Dict;
 import cn.tycoding.langchat.aigc.entity.AigcUser;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
 
 /**
  * @author tycoding
@@ -11,4 +13,12 @@ import org.apache.ibatis.annotations.Mapper;
 @Mapper
 public interface AigcUserMapper extends BaseMapper<AigcUser> {
 
+    @Select("""
+        SELECT
+            COUNT(*) AS totalUser,
+            SUM( CASE WHEN YEAR ( create_time ) = YEAR ( CURDATE()) AND MONTH ( create_time ) = MONTH ( CURDATE()) THEN 1 ELSE 0 END ) AS curUser
+        FROM
+            aigc_user;
+    """)
+    Dict getCount();
 }

+ 43 - 6
langchat-auth/src/main/java/cn/tycoding/langchat/auth/endpoint/AuthEndpoint.java

@@ -1,24 +1,40 @@
 package cn.tycoding.langchat.auth.endpoint;
 
+import static cn.tycoding.langchat.common.constant.CacheConst.AUTH_SESSION_PREFIX;
+
 import cn.dev33.satoken.stp.SaTokenInfo;
 import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.lang.Dict;
 import cn.hutool.core.util.StrUtil;
 import cn.tycoding.langchat.auth.service.TokenInfo;
 import cn.tycoding.langchat.common.constant.CacheConst;
 import cn.tycoding.langchat.common.exception.ServiceException;
 import cn.tycoding.langchat.common.properties.AuthProps;
+import cn.tycoding.langchat.common.utils.MybatisUtil;
 import cn.tycoding.langchat.common.utils.QueryPage;
 import cn.tycoding.langchat.common.utils.R;
 import cn.tycoding.langchat.upms.dto.UserInfo;
 import cn.tycoding.langchat.upms.service.SysUserService;
 import cn.tycoding.langchat.upms.utils.AuthUtil;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.data.redis.core.StringRedisTemplate;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-import java.util.Map;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
 
 /**
  * @author tycoding
@@ -72,15 +88,36 @@ public class AuthEndpoint {
 
     @GetMapping("/token/page")
     public R tokenPage(QueryPage queryPage) {
-        List<String> list = StpUtil.searchTokenValue("", queryPage.getPage(), queryPage.getLimit(), true);
+        List<String> list = StpUtil.searchTokenValue("", queryPage.getPage() - 1, queryPage.getLimit(), true);
         List ids = redisTemplate.opsForValue().multiGet(list);
+        Set<String> keys = redisTemplate.keys(AUTH_SESSION_PREFIX + "*");
+
+        List<Object> result = new ArrayList<>();
         ids.forEach(id -> {
+            Dict data = Dict.create();
             Map<String, Object> dataMap = StpUtil.getSessionByLoginId(id).getDataMap();
             UserInfo userInfo = (UserInfo)dataMap.get(CacheConst.AUTH_USER_INFO_KEY);
             SaTokenInfo tokenInfo = (SaTokenInfo)dataMap.get(CacheConst.AUTH_TOKEN_INFO_KEY);
+            data.set("token", tokenInfo.tokenValue);
+            data.set("perms", userInfo.getPerms());
+            data.set("roles", userInfo.getRoles());
+            data.set("email", userInfo.getEmail());
+            data.set("id", userInfo.getId());
+            data.set("username", userInfo.getUsername());
+            data.set("realName", userInfo.getRealName());
+
+            long expiration = StpUtil.getTokenTimeout();
+            Date targetDate = new Date(System.currentTimeMillis() + expiration);
+            String formattedDate = DateUtil.format(targetDate, DatePattern.NORM_DATETIME_PATTERN);
+            data.set("expiration", formattedDate);
+
+            result.add(data);
         });
 
-        return R.ok(list);
+        IPage page = new Page(queryPage.getPage(), queryPage.getLimit());
+        page.setRecords(result);
+        page.setTotal(keys == null ? 0 : keys.size());
+        return R.ok(MybatisUtil.getData(page));
     }
 
 }

+ 1 - 1
langchat-common/src/main/java/cn/tycoding/langchat/common/constant/CacheConst.java

@@ -19,7 +19,7 @@ public interface CacheConst {
     /**
      * Auth Session缓存前缀
      */
-    String AUTH_SESSION_PREFIX = REDIS_KEY_PREFIX + "auth:session:";
+    String AUTH_SESSION_PREFIX = AUTH_PREFIX + "session:";
 
     /**
      * Auth Session缓存变量前缀

+ 0 - 7
langchat-ui/src/api/aigc/message.ts

@@ -38,10 +38,3 @@ export function del(id: string) {
     method: 'delete',
   });
 }
-
-export function charts() {
-  return http.request({
-    url: `/aigc/message/charts`,
-    method: 'get',
-  });
-}

+ 29 - 0
langchat-ui/src/api/aigc/statictic.ts

@@ -0,0 +1,29 @@
+import { http } from '@/utils/http/axios';
+
+export function getReqChartBy30() {
+  return http.request({
+    url: `/aigc/statistic/requestBy30`,
+    method: 'get',
+  });
+}
+
+export function getReqChart() {
+  return http.request({
+    url: `/aigc/statistic/request`,
+    method: 'get',
+  });
+}
+
+export function getTokenChart() {
+  return http.request({
+    url: `/aigc/statistic/token`,
+    method: 'get',
+  });
+}
+
+export function getHomeData() {
+  return http.request({
+    url: `/aigc/statistic/home`,
+    method: 'get',
+  });
+}

+ 0 - 3
langchat-ui/src/components/CountTo/CountTo.vue

@@ -84,9 +84,6 @@
       }
 
       function formatNumber(num: number | string) {
-        if (!num) {
-          return '';
-        }
         const { decimals, decimal, separator, suffix, prefix } = props;
         num = Number(num).toFixed(decimals);
         num += '';

+ 1 - 1
langchat-ui/src/views/aigc/knowledge/index.vue

@@ -127,7 +127,7 @@
           <div class="py-3 px-4 border-t flex justify-between items-center">
             <div class="flex gap-1 items-center">
               <n-icon size="20"> <CloudOutlined /> </n-icon>
-              <span>文件大小:{{ (Number(item.totalSize) / 1000000).toFixed(2) }} MB</span>
+              <span class="text-sm"> {{ (Number(item.totalSize) / 1000000).toFixed(2) }} MB </span>
             </div>
             <div class="flex gap-3">
               <n-button @click="handleEdit(item)" text type="primary">

+ 2 - 2
langchat-ui/src/views/aigc/statistics/components/Chart.vue

@@ -1,13 +1,13 @@
 <script lang="ts" setup>
   import { onMounted, ref, Ref } from 'vue';
   import { useECharts } from '@/hooks/web/useECharts';
-  import { charts } from '@/api/aigc/message';
+  import { getReqChartBy30 } from '@/api/aigc/statictic';
 
   const chartRef = ref<HTMLDivElement | null>(null);
   const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
 
   onMounted(async () => {
-    const data = await charts();
+    const data = await getReqChartBy30();
     const xData: any = [];
     const yData: any = [];
     data.forEach((i: any) => {

+ 71 - 0
langchat-ui/src/views/dashboard/components/AiReqChart.vue

@@ -0,0 +1,71 @@
+<template>
+  <div ref="chartRef" class="w-full" style="height: calc(100vh - 400px)"></div>
+</template>
+<script lang="ts" setup>
+  import { onMounted, ref, Ref } from 'vue';
+  import { useECharts } from '@/hooks/web/useECharts';
+  import { getReqChart } from '@/api/aigc/statictic';
+
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+
+  onMounted(async () => {
+    const list = await getReqChart();
+    const xData = list.map((i) => i.month);
+    const yData = list.map((i) => Number(i.count));
+
+    setOptions({
+      tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+          lineStyle: {
+            width: 1,
+            color: '#019680',
+          },
+        },
+      },
+      xAxis: {
+        type: 'category',
+        boundaryGap: false,
+        data: xData,
+        splitLine: {
+          show: true,
+          lineStyle: {
+            width: 1,
+            type: 'solid',
+            color: 'rgba(226,226,226,0.5)',
+          },
+        },
+        axisTick: {
+          show: false,
+        },
+      },
+      yAxis: [
+        {
+          type: 'value',
+          axisTick: {
+            show: false,
+          },
+          splitArea: {
+            show: true,
+            areaStyle: {
+              color: ['rgba(255,255,255,0.2)', 'rgba(226,226,226,0.2)'],
+            },
+          },
+        },
+      ],
+      grid: { left: '1%', right: '1%', top: '2  %', bottom: 0, containLabel: true },
+      series: [
+        {
+          smooth: true,
+          data: yData,
+          type: 'line',
+          areaStyle: {},
+          itemStyle: {
+            color: '#5ab1ef',
+          },
+        },
+      ],
+    });
+  });
+</script>

+ 71 - 0
langchat-ui/src/views/dashboard/components/AiTokenChart.vue

@@ -0,0 +1,71 @@
+<template>
+  <div ref="chartRef" class="w-full" style="height: calc(100vh - 400px)"></div>
+</template>
+<script lang="ts" setup>
+  import { onMounted, ref, Ref } from 'vue';
+  import { useECharts } from '@/hooks/web/useECharts';
+  import { getTokenChart } from '@/api/aigc/statictic';
+
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+
+  onMounted(async () => {
+    const list = await getTokenChart();
+    const xData = list.map((i) => i.month);
+    const yData = list.map((i) => Number(i.count));
+
+    setOptions({
+      tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+          lineStyle: {
+            width: 1,
+            color: '#019680',
+          },
+        },
+      },
+      xAxis: {
+        type: 'category',
+        boundaryGap: false,
+        data: xData,
+        splitLine: {
+          show: true,
+          lineStyle: {
+            width: 1,
+            type: 'solid',
+            color: 'rgba(226,226,226,0.5)',
+          },
+        },
+        axisTick: {
+          show: false,
+        },
+      },
+      yAxis: [
+        {
+          type: 'value',
+          axisTick: {
+            show: false,
+          },
+          splitArea: {
+            show: true,
+            areaStyle: {
+              color: ['rgba(255,255,255,0.2)', 'rgba(226,226,226,0.2)'],
+            },
+          },
+        },
+      ],
+      grid: { left: '1%', right: '1%', top: '2  %', bottom: 0, containLabel: true },
+      series: [
+        {
+          smooth: true,
+          data: yData,
+          type: 'line',
+          areaStyle: {},
+          itemStyle: {
+            color: '#5ab1ef',
+          },
+        },
+      ],
+    });
+  });
+</script>

+ 25 - 0
langchat-ui/src/views/dashboard/components/Icons.ts

@@ -0,0 +1,25 @@
+import {
+  CaretUpOutlined,
+  CaretDownOutlined,
+  UsergroupAddOutlined,
+  BarChartOutlined,
+  ShoppingCartOutlined,
+  AccountBookOutlined,
+  CreditCardOutlined,
+  MailOutlined,
+  TagsOutlined,
+  SettingOutlined,
+} from '@vicons/antd';
+
+export default {
+  CaretUpOutlined,
+  CaretDownOutlined,
+  UsergroupAddOutlined,
+  BarChartOutlined,
+  ShoppingCartOutlined,
+  AccountBookOutlined,
+  CreditCardOutlined,
+  MailOutlined,
+  TagsOutlined,
+  SettingOutlined,
+};

+ 22 - 0
langchat-ui/src/views/dashboard/components/VisiTab.vue

@@ -0,0 +1,22 @@
+<template>
+  <div class="mt-4">
+    <NRow :gutter="24">
+      <NCol :span="24">
+        <n-card content-style="padding: 0;" :bordered="false">
+          <n-tabs type="line" size="large" :tabs-padding="20" pane-style="padding: 20px;">
+            <n-tab-pane name="AI请求量统计">
+              <AiReqChart />
+            </n-tab-pane>
+            <n-tab-pane name="Token消耗量统计">
+              <AiTokenChart />
+            </n-tab-pane>
+          </n-tabs>
+        </n-card>
+      </NCol>
+    </NRow>
+  </div>
+</template>
+<script lang="ts" setup>
+  import AiReqChart from './AiReqChart.vue';
+  import AiTokenChart from './AiTokenChart.vue';
+</script>

+ 110 - 35
langchat-ui/src/views/dashboard/index.vue

@@ -1,43 +1,118 @@
 <script lang="ts" setup>
-  import avatar from '@/assets/avatar.jpg';
+  import { ref, onMounted } from 'vue';
+  import VisiTab from './components/VisiTab.vue';
+  import { CountTo } from '@/components/CountTo';
+  import { CaretUpOutlined } from '@vicons/antd';
+  import { getHomeData, getReqChart } from '@/api/aigc/statictic';
+
+  const loading = ref(true);
+  const list = ref([
+    {
+      key: 'req',
+      label: 'AI请求量',
+      value: 0,
+      totalLabel: 'AI总请求量',
+      totalValue: 0,
+      category: '日',
+      type: 'success',
+    },
+    {
+      key: 'token',
+      label: 'Token消耗量',
+      value: 0,
+      totalLabel: 'AI总消耗Token量',
+      totalValue: 0,
+      category: '日',
+      type: 'primary',
+    },
+    {
+      key: 'user',
+      label: '用户增长量',
+      value: 0,
+      totalLabel: '平台用户总量',
+      totalValue: 0,
+      category: '月',
+      type: 'warning',
+    },
+    {
+      key: 'knowledge',
+      label: 'AI知识库数量',
+      value: 0,
+      totalLabel: 'Prompt提示词数量',
+      totalValue: 0,
+      category: '合',
+      type: 'error',
+    },
+  ]);
+
+  onMounted(async () => {
+    const {
+      totalReq,
+      curReq,
+      totalToken,
+      curToken,
+      totalUser,
+      curUser,
+      totalKnowledge,
+      totalPrompt,
+    } = await getHomeData();
+    list.value.forEach((i) => {
+      if (i.key === 'req') {
+        i.value = Number(curReq);
+        i.totalValue = Number(totalReq);
+      }
+      if (i.key === 'token') {
+        i.value = Number(curToken);
+        i.totalValue = Number(totalToken);
+      }
+      if (i.key === 'user') {
+        i.value = Number(curUser);
+        i.totalValue = Number(totalUser);
+      }
+      if (i.key === 'knowledge') {
+        i.value = Number(totalKnowledge);
+        i.totalValue = Number(totalPrompt);
+      }
+    });
+    loading.value = false;
+  });
 </script>
 
 <template>
-  <div>
-    <div class="n-layout-page-header">
-      <n-card :bordered="false" title="工作台">
-        <n-grid cols="2 s:1 m:1 l:2 xl:2 2xl:2" responsive="screen">
-          <n-gi>
-            <div class="flex items-center">
-              <div>
-                <n-avatar circle :size="64" :src="avatar" />
-              </div>
-              <div>
-                <p class="px-4 text-xl">早安,Ah jung,开始您一天的工作吧!</p>
-                <p class="px-4 text-gray-400">今日阴转大雨,15℃ - 25℃,出门记得带伞哦。</p>
-              </div>
+  <div class="h-full overflow-y-auto">
+    <!--数据卡片-->
+    <n-grid cols="1 s:2 m:3 l:4 xl:4 2xl:4" responsive="screen" :x-gap="12" :y-gap="8">
+      <n-grid-item v-for="item in list" :key="item.key">
+        <NCard
+          :title="item.label"
+          :segmented="{ content: true, footer: true }"
+          size="small"
+          :bordered="false"
+        >
+          <template #header-extra>
+            <n-tag :type="item.type" :bordered="false">{{ item.category }}</n-tag>
+          </template>
+          <div class="flex justify-between px-1 py-1">
+            <n-skeleton v-if="loading" :width="100" size="medium" />
+            <CountTo v-else :startVal="0" :endVal="item.value" class="text-2xl" />
+          </div>
+          <div class="flex justify-between px-1 py-1">
+            <div class="text-gray-600">
+              <n-skeleton v-if="loading" :width="100" size="medium" />
+              <template v-else>
+                {{ item.totalLabel }}
+                <CountTo :startVal="0" suffix=" " :endVal="item.totalValue" />
+                <n-icon size="12" color="#00ff6f">
+                  <CaretUpOutlined />
+                </n-icon>
+              </template>
             </div>
-          </n-gi>
-          <n-gi>
-            <div class="flex justify-end w-full">
-              <div class="flex flex-col justify-center flex-1 text-right">
-                <span class="text-secondary">项目数</span>
-                <span class="text-2xl">16</span>
-              </div>
-              <div class="flex flex-col justify-center flex-1 text-right">
-                <span class="text-secondary">待办</span>
-                <span class="text-2xl">3/15</span>
-              </div>
-              <div class="flex flex-col justify-center flex-1 text-right">
-                <span class="text-secondary">消息</span>
-                <span class="text-2xl">35</span>
-              </div>
-            </div>
-          </n-gi>
-        </n-grid>
-      </n-card>
-    </div>
-    <div style="width: 200px"> </div>
+          </div>
+        </NCard>
+      </n-grid-item>
+    </n-grid>
+
+    <VisiTab />
   </div>
 </template>
 

+ 7 - 7
langchat-ui/src/views/system/token/columns.ts

@@ -1,20 +1,20 @@
 import { BasicColumn } from '@/components/Table';
-import { Token } from '@/api/models/auth';
-import { h } from 'vue';
 
-export const columns: BasicColumn<Token>[] = [
+export const columns: BasicColumn<any>[] = [
   {
     title: '令牌',
-    key: 'value',
+    key: 'token',
     align: 'center',
   },
   {
     title: '账户',
     key: 'username',
     align: 'center',
-    render: (row) => {
-      return h('span', row.principal.username);
-    },
+  },
+  {
+    title: '姓名',
+    key: 'realName',
+    align: 'center',
   },
   {
     title: '过期时间',

+ 16 - 12
langchat-ui/src/views/system/token/edit.vue

@@ -1,6 +1,6 @@
 <template>
   <n-drawer v-model:show="isShow" width="40%" placement="right">
-    <n-drawer-content :title="info.principal.username" closable>
+    <n-drawer-content :title="info.username" closable>
       <n-descriptions
         label-placement="left"
         size="small"
@@ -8,15 +8,22 @@
         bordered
         label-style="width: 110px"
       >
-        <n-descriptions-item label="账户ID">{{ info.principal.id }}</n-descriptions-item>
-        <n-descriptions-item label="账户名">{{ info.principal.username }}</n-descriptions-item>
-        <n-descriptions-item label="令牌">{{ info.value }}</n-descriptions-item>
-        <n-descriptions-item label="刷新令牌">{{ info.refreshToken }}</n-descriptions-item>
+        <n-descriptions-item label="账户ID">{{ info.id }}</n-descriptions-item>
+        <n-descriptions-item label="账户名">{{ info.username }}</n-descriptions-item>
+        <n-descriptions-item label="用户名">{{ info.realName }}</n-descriptions-item>
+        <n-descriptions-item label="令牌">{{ info.token }}</n-descriptions-item>
         <n-descriptions-item label="失效时间">{{ info.expiration }}</n-descriptions-item>
+        <n-descriptions-item label="角色列表">
+          <n-space>
+            <n-tag v-for="item in info.roles" :key="item" size="small" type="success">
+              {{ item.name }}
+            </n-tag>
+          </n-space>
+        </n-descriptions-item>
         <n-descriptions-item label="权限列表">
           <n-space>
-            <n-tag v-for="item in info.principal.authorities" :key="item">
-              {{ item.authority }}
+            <n-tag v-for="item in info.perms" :key="item" size="small" type="info">
+              {{ item }}
             </n-tag>
           </n-space>
         </n-descriptions-item>
@@ -26,13 +33,10 @@
 </template>
 <script lang="ts" setup>
   import { ref } from 'vue';
-  import { useMessage } from 'naive-ui';
-  import { Token } from '@/api/models/auth';
 
   const emit = defineEmits(['reload']);
-  const message = useMessage();
   const isShow = ref(false);
-  let info: Token = {
+  let info: any = {
     value: '',
     tokenType: '',
     expiration: '',
@@ -47,7 +51,7 @@
     },
   };
 
-  async function show(token: Token) {
+  async function show(token: any) {
     info = token;
     isShow.value = true;
   }

+ 4 - 5
langchat-ui/src/views/system/token/index.vue

@@ -26,7 +26,6 @@
   import { DeleteOutlined, EyeOutlined } from '@vicons/antd';
   import Edit from './edit.vue';
   import { useDialog, useMessage } from 'naive-ui';
-  import { Token } from '@/api/models/auth';
   const message = useMessage();
   const dialog = useDialog();
 
@@ -70,18 +69,18 @@
     actionRef.value.reload();
   }
 
-  function handleEdit(record: Token) {
+  function handleEdit(record: any) {
     editRef.value.show(record);
   }
 
-  function handleDelete(record: Token) {
+  function handleDelete(record: any) {
     dialog.info({
       title: '提示',
-      content: `您确定强制下线 ${record.principal.username} 账户?`,
+      content: `您确定强制下线 ${record.username} 账户?`,
       positiveText: '确定',
       negativeText: '取消',
       onPositiveClick: async () => {
-        await del(record.value);
+        await del(record.token);
         message.success('下线成功');
         reloadTable();
       },