Browse Source

feature:000.000.003:前端完成数据分析和图表生成接口的对接
实现上传数据和结果图表的渲染展示;
完成用户管理的基本架构;
完成图表管理的基本架构;

yang yi 1 month ago
parent
commit
230859f94e
35 changed files with 1689 additions and 72 deletions
  1. 7 0
      BI_front/index.html
  2. 29 0
      BI_front/package-lock.json
  3. 2 0
      BI_front/package.json
  4. 66 0
      BI_front/src/common/api/chart/index.js
  5. 51 0
      BI_front/src/common/api/user/index.js
  6. 6 1
      BI_front/src/common/utils/auth.js
  7. 3 3
      BI_front/src/common/utils/request.js
  8. 90 0
      BI_front/src/components/CommonAside.vue
  9. 81 0
      BI_front/src/components/CommonHeader.vue
  10. 2 1
      BI_front/src/components/siginIn.vue
  11. 44 0
      BI_front/src/components/userTable.vue
  12. 8 1
      BI_front/src/main.js
  13. 51 20
      BI_front/src/router/index.js
  14. 1 0
      BI_front/src/stores/index.js
  15. 17 0
      BI_front/src/stores/tab.js
  16. 246 0
      BI_front/src/views/chartManage.vue
  17. 236 0
      BI_front/src/views/generateChart.vue
  18. 34 4
      BI_front/src/views/home.vue
  19. 13 0
      BI_front/src/views/myChart.vue
  20. 343 0
      BI_front/src/views/userManage.vue
  21. 2 2
      BI_front/vite.config.js
  22. 1 1
      serve/sql/init.sql
  23. 5 1
      serve/src/main/java/space/anyi/BI/config/MybatisplusConfig.java
  24. 19 7
      serve/src/main/java/space/anyi/BI/controller/ChartController.java
  25. 46 0
      serve/src/main/java/space/anyi/BI/controller/UserController.java
  26. 9 1
      serve/src/main/java/space/anyi/BI/entity/Chart.java
  27. 92 0
      serve/src/main/java/space/anyi/BI/entity/dto/UserDTO.java
  28. 8 8
      serve/src/main/java/space/anyi/BI/entity/vo/ChartVO.java
  29. 36 0
      serve/src/main/java/space/anyi/BI/entity/vo/PageVO.java
  30. 79 0
      serve/src/main/java/space/anyi/BI/entity/vo/UserVO.java
  31. 10 10
      serve/src/main/java/space/anyi/BI/entity/xinghuo/HttpRequestMessage.java
  32. 6 0
      serve/src/main/java/space/anyi/BI/service/UserService.java
  33. 4 3
      serve/src/main/java/space/anyi/BI/service/impl/ChartServiceImpl.java
  34. 34 3
      serve/src/main/java/space/anyi/BI/service/impl/UserServiceImpl.java
  35. 8 6
      serve/src/main/resources/application.yaml

+ 7 - 0
BI_front/index.html

@@ -5,6 +5,13 @@
     <link rel="icon" type="image/svg+xml" href="/icon.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>BI</title>
+      <!--边距和内边距清零-->
+      <style>
+          * {
+              padding: 0;
+              margin: 0;
+          }
+      </style>
   </head>
   <body>
     <div id="app"></div>

+ 29 - 0
BI_front/package-lock.json

@@ -8,7 +8,9 @@
       "name": "bi-front",
       "version": "0.0.0",
       "dependencies": {
+        "@element-plus/icons-vue": "^2.3.1",
         "axios": "^1.7.8",
+        "echarts": "^5.5.1",
         "element-plus": "^2.9.0",
         "js-cookie": "^3.0.5",
         "pinia": "^2.2.8",
@@ -1487,6 +1489,20 @@
         "node": ">=0.10"
       }
     },
+    "node_modules/echarts": {
+      "version": "5.5.1",
+      "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.5.1.tgz",
+      "integrity": "sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==",
+      "dependencies": {
+        "tslib": "2.3.0",
+        "zrender": "5.6.0"
+      }
+    },
+    "node_modules/echarts/node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
+    },
     "node_modules/element-plus": {
       "version": "2.9.0",
       "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.0.tgz",
@@ -2681,6 +2697,19 @@
       "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
       "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
       "dev": true
+    },
+    "node_modules/zrender": {
+      "version": "5.6.0",
+      "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.0.tgz",
+      "integrity": "sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==",
+      "dependencies": {
+        "tslib": "2.3.0"
+      }
+    },
+    "node_modules/zrender/node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
     }
   }
 }

+ 2 - 0
BI_front/package.json

@@ -9,7 +9,9 @@
     "preview": "vite preview"
   },
   "dependencies": {
+    "@element-plus/icons-vue": "^2.3.1",
     "axios": "^1.7.8",
+    "echarts": "^5.5.1",
     "element-plus": "^2.9.0",
     "js-cookie": "^3.0.5",
     "pinia": "^2.2.8",

+ 66 - 0
BI_front/src/common/api/chart/index.js

@@ -0,0 +1,66 @@
+import request from '@/common/utils/request'
+
+// 查询图表管理列表
+export function listChart(query) {
+    return request({
+        url: '/chart/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 查询图表管理详细
+export function getChart(id) {
+    return request({
+        url: '/chart/getChartById/' + id,
+        method: 'get'
+    })
+}
+
+// 新增图表管理
+export function addChart(data) {
+    return request({
+        url: '/chart',
+        method: 'post',
+        data: data
+    })
+}
+
+// 修改图表管理
+export function updateChart(data) {
+    return request({
+        url: '/chart',
+        method: 'put',
+        data: data
+    })
+}
+
+// 删除图表管理
+export function delChart(id) {
+    return request({
+        url: '/chart/' + id,
+        method: 'delete'
+    })
+}
+// 生成图表
+export function generateChartByAI(data) {
+    const formData = new FormData();
+    formData.set('chartType',data.chartType);
+    formData.set('analysisTarget',data.analysisTarget);
+    formData.set('name',data.name);
+    formData.append('file',data.file);
+    return request.postForm(
+        '/chart/generateChartByAI',
+        formData,
+        {
+            headers: {
+                'Content-Type': 'multipart/form-data'
+            }
+        }
+    );
+    // return request({
+    //     url: '/chart/generateChartByAI',
+    //     method: 'post',
+    //     data: data
+    // })
+}

+ 51 - 0
BI_front/src/common/api/user/index.js

@@ -27,3 +27,54 @@ export function userRegister(data) {
 export function getUserInfo() {
     return request.get('/user/getUserInfo');
 }
+
+/**
+ * 获取用户
+ * @returns {*}
+ */
+export function userPage(currentPage,pageSize,userName,userAccount) {
+    return request.get(`/user/page?currentPage=${currentPage}&pageSize=${pageSize}&userName=${userName}&userAccount=${userAccount}`);
+}
+
+// 查询用户列表
+export function listUser(query) {
+    return request({
+        url: '/user/page',
+        method: 'get',
+        params: query
+    })
+}
+
+// 查询用户详细
+export function getUser(id) {
+    return request({
+        url: '/user/' + id,
+        method: 'get'
+    })
+}
+
+// 新增用户
+export function addUser(data) {
+    return request({
+        url: '/user',
+        method: 'post',
+        data: data
+    })
+}
+
+// 修改用户
+export function updateUser(data) {
+    return request({
+        url: '/user',
+        method: 'put',
+        data: data
+    })
+}
+
+// 删除用户
+export function delUser(id) {
+    return request({
+        url: '/user/' + id,
+        method: 'delete'
+    })
+}

+ 6 - 1
BI_front/src/common/utils/auth.js

@@ -57,5 +57,10 @@ export  function clearTokenAndUserInfo(){
 }
 export  function saveUserInfo(userInfo){
   //保存用户信息
-  localStorage.setItem('userInfo',userInfo);
+  localStorage.setItem('userInfo',JSON.stringify(userInfo));
+}
+
+export function isAdmin(){
+  let role = getUserRole();
+  return role != undefined &&role != null && role == '管理员';
 }

+ 3 - 3
BI_front/src/common/utils/request.js

@@ -22,7 +22,7 @@ axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
 
 const request = axios.create({
     baseURL: '/api',
-    timeout: 10000
+    timeout: 1000*30
 });
 //请求拦截器
 request.interceptors.request.use(config =>{
@@ -37,9 +37,9 @@ request.interceptors.request.use(config =>{
 request.interceptors.response.use(response =>{
     endLoading();
     let res = response.data;
-    if (res.code === 401){
+    if (res.code === 401 || res.code === 403) {
         // location.reload();
-        router.push('/index');
+        router.push('/login');
     }
     //如果是文件就直接返回
     if(response.config.responseType === 'blob')return res;

+ 90 - 0
BI_front/src/components/CommonAside.vue

@@ -0,0 +1,90 @@
+<template>
+  <el-menu default-active="1-4-1" class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose"
+           :collapse="isCollapse" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b" :router="true">
+    <!-- 要放到导航栏里面 -->
+    <h3>{{isCollapse?"BI":"BI生成系统"}}</h3>
+    <!-- 观察数据,我们发现name是唯一标识 -->
+    <!-- 查看文档,index是唯一标识 -->
+    <el-menu-item  :key="'/index'" :index="'/index'">
+        <el-icon><location /></el-icon>
+      <template #title>
+        <span>图表生成</span>
+      </template>
+    </el-menu-item>
+    <el-menu-item  :key="'/index/chart'" :index="'/index/chart'">
+      <el-icon><CirclePlus /></el-icon>
+      <template #title>
+        <span>我的图表</span>
+      </template>
+    </el-menu-item>
+    <el-sub-menu  :key="1" :index="1" v-if="isAdmin">
+      <template #title>
+      <el-icon><location /></el-icon>
+        <span>后台管理</span>
+      </template>
+      <el-menu-item-group :key="1" :index="1">
+        <el-menu-item :index="'/index/userManage'" :key="'/index/userManage'">
+            <el-icon><CirclePlus /></el-icon>
+          <template #title>
+            <span>用户管理</span>
+          </template>
+        </el-menu-item>
+        <el-menu-item :index="'/index/chartManage'" :key="'/index/chartManage'">
+            <el-icon><CirclePlus /></el-icon>
+          <template #title>
+            <span>图表管理</span>
+          </template>
+        </el-menu-item>
+      </el-menu-item-group>
+    </el-sub-menu>
+  </el-menu>
+</template>
+
+<style lang="less" scoped>
+.el-menu-vertical-demo:not(.el-menu--collapse) {
+  width: 200px;
+  min-height: 400px;
+}
+
+.el-menu{
+  height:100vh;
+
+  h3{
+    text-align: center;
+    line-height: 48px;
+    color: #fff;
+    font-size: 16px;
+    font-weight: 400;
+  }
+}
+</style>
+
+<script>
+import {useTabStore} from '@/stores/index'
+import {isAdmin} from "../common/utils/auth.js";
+
+const tabStore = useTabStore();
+export default {
+  data() {
+    return {
+      isCollapse: false,
+    };
+  },
+  methods: {
+    handleOpen(key, keyPath) {
+      // console.log(key, keyPath);
+    },
+    handleClose(key, keyPath) {
+      // console.log(key, keyPath);
+    },
+  },
+  computed: {
+    isCollapse() {
+      return tabStore.isCollapse;
+    },
+    isAdmin(){
+      return isAdmin();
+    }
+  }
+}
+</script>

+ 81 - 0
BI_front/src/components/CommonHeader.vue

@@ -0,0 +1,81 @@
+<template>
+  <div class="header-container">
+    <div class="l-content">
+      <el-button @click="handleMenu" size="mini"><el-icon><Menu /></el-icon></el-button>
+    </div>
+    <div class="r-content">
+      <el-dropdown v-if="isLogin">
+                <span class="el-dropdown-link">
+                    <img class="user" src="@/assets/images/user.png" alt="用户">
+                </span>
+          <template #dropdown>
+          <el-dropdown-item>个人信息</el-dropdown-item>
+          <el-dropdown-item @click="logoutHandler()">退出</el-dropdown-item>
+            </template>
+      </el-dropdown>
+      <el-button v-else @click="toLogin">登陆</el-button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import {useTabStore} from '@/stores/index'
+import {clearTokenAndUserInfo, getToken, getUserRole, isAdmin} from "../common/utils/auth.js";
+import router from "../router/index.js";
+import {ElMessage} from "element-plus";
+
+
+const tabStore = useTabStore();
+function handleMenu(){
+  // 相当于调用这个方法
+  // this.$store.commit('CollapseMenu')
+  tabStore.CollapseMenu();
+}
+function logoutHandler(){
+  clearTokenAndUserInfo();
+  router.push({
+    name:'login'
+  });
+  ElMessage({
+    message:'退出成功',
+    type:'success'
+  })
+}
+function isLogin(){
+  let  token = getToken();
+  return token != null;
+}
+function toLogin(){
+  router.push({
+    name:'login',
+    path:'/login'
+    });
+}
+</script>
+
+<style lang="less" scoped>
+.header-container {
+  background-color: #333;
+  height: 60px;
+
+  // 让按钮和头像居中
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  // 不要紧贴边框
+  padding: 0 20px;
+  margin: 0;
+
+  .el-dropdown-link {
+    cursor: pointer;
+    color: #409EFF;
+
+    .user {
+      width: 40px;
+      height: 40px;
+      // 50%变圆形
+      border-radius: 50%;
+    }
+  }
+}
+</style>

+ 2 - 1
BI_front/src/components/siginIn.vue

@@ -54,7 +54,8 @@ let loginHandler = () =>{
       saveUserInfo(res.data.userInfo)
 
       router.push({
-        name:'index'
+        name:'index',
+        path:'/index'
       });
       ElMessage({
         message: '登陆成功',

+ 44 - 0
BI_front/src/components/userTable.vue

@@ -0,0 +1,44 @@
+<template>
+  <el-table :data="tableData" style="width: 100%">
+    <el-table-column prop="id" label="用户ID" width="180"></el-table-column>
+    <el-table-column prop="userAccount" label="用户账号" width="180"></el-table-column>
+    <el-table-column prop="userName" label="用户名称"></el-table-column>
+    <el-table-column prop="userAvatar" label="用户头像"></el-table-column>
+    <el-table-column prop="userRole" label="用户角色"></el-table-column>
+    <el-table-column prop="createTime" label="创建时间"></el-table-column>
+    <el-table-column prop="updateTime" label="更新时间"></el-table-column>
+    <el-table-column label="操作">
+      <template #default="scope">
+        <el-button size="small" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
+        <el-button size="small" type="danger" @click="handleDelete(scope.$index, scope.row)">删除</el-button>
+      </template>
+    </el-table-column>
+  </el-table>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      // tableData: []
+    };
+  },
+  props:["tableData"],
+  methods: {
+    handleEdit(index, row) {
+      this.$emit('edit', row);
+    },
+    handleDelete(index, row) {
+      this.$emit('delete', row);
+    }
+  },
+  computed:{
+    tableData() {
+      return this.tableData;
+    }
+  },
+  updated() {
+    console.log(this.tableData);
+  }
+};
+</script>

+ 8 - 1
BI_front/src/main.js

@@ -4,10 +4,17 @@ import App from './App.vue'
 import router from './router'
 import ElementPlus from 'element-plus'
 import 'element-plus/dist/index.css'
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
 //全局样式
 // import 'virtual:uno.css'
 import {createPinia} from 'pinia'
 // 创建 Pinia 实例
 const pinia = createPinia()
 
-createApp(App).use(router).use(ElementPlus).use(pinia).mount('#app')
+let  app = createApp(App);
+app.use(router).use(ElementPlus).use(pinia).mount('#app');
+// 全局注册图标
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+    app.component(key, component)
+}
+

+ 51 - 20
BI_front/src/router/index.js

@@ -1,7 +1,7 @@
 // 引入需要的模块
 import { createRouter,  createWebHistory } from 'vue-router'
 import {getUserInfo, login} from "../common/api/user/index.js";
-import {getToken} from "../common/utils/auth.js";
+import {getToken, isAdmin} from "../common/utils/auth.js";
 import {ElMessage} from "element-plus";
 
 // 下面使用了es6的对象增强写法,命名必须是routes
@@ -29,6 +29,36 @@ const routes = [
             // 自定义一个字段,标识需要进行鉴权
             logined:false
         },
+        children:[
+            {
+                path: '/index/chart',
+                name:'chart',
+                component: () => import('@/views/myChart'),
+                beforeEnter:(to, from, next) =>{
+                    next();
+                }
+            },
+            {
+                path: '/index/userManage',
+                name:'userManage',
+                component: () => import('@/views/userManage'),
+                beforeEnter:(to, from, next) =>{
+                    if (isAdmin()){
+                        next();
+                    }
+                }
+            },
+            {
+                path: '/index/chartManage',
+                name:'chartManage',
+                component: () => import('@/views/chartManage'),
+                beforeEnter:(to, from, next) =>{
+                    if (isAdmin()){
+                        next();
+                    }
+                }
+            }
+        ]
     }
 ]
 
@@ -41,25 +71,26 @@ const router = createRouter({
 // 全局前置路由守卫
 router.beforeEach( (to,from,next)=>{
     // to是目标路由对象,from当前路由对象
-    // getUserInfo().then(res => {
-    //    console.log(res)
-    //     if (res.code == 200 || to.fullPath == '/login'){
-    //         next();
-    //     }else if(res.code == 200 && to.fullPath == '/login'){
-    //         router.replace({
-    //             path: '/',
-    //         })
-    //     }else {
-    //         ElMessage({
-    //             message: '请先登录',
-    //             type:'warning'
-    //         })
-    //             router.replace({
-    //                 path: '/login',
-    //             })
-    //         }
-    // })
-    next()
+        next();
+        // getUserInfo().then(res => {
+        //     console.log(res)
+        //     // if (res.code == 200 && to.fullPath == '/login'){
+        //     //     router.replace({
+        //     //         path: '/',
+        //     //     });
+        //     // }else if(res.code == 200){
+        //     //     next();
+        //     // }else {
+        //     //     ElMessage({
+        //     //         message: '请先登录',
+        //     //         type:'warning'
+        //     //     })
+        //     //     router.replace({
+        //     //         path: '/login',
+        //     //     })
+        //     // }
+        // });
+    // next()
 });
 
 //全局后置路由守卫

+ 1 - 0
BI_front/src/stores/index.js

@@ -1 +1,2 @@
 export * from './message'
+export * from './tab'

+ 17 - 0
BI_front/src/stores/tab.js

@@ -0,0 +1,17 @@
+import { defineStore } from 'pinia'
+
+export const useTabStore = defineStore('tab', {
+    state: () => ({
+        isCollapse:false,//导航栏是否折叠
+    }),
+    getters: {
+
+    },
+    actions: {
+        // 修改导航栏展开和收起的方法
+        CollapseMenu(){
+            this.isCollapse=!this.isCollapse
+        }
+    },
+})
+

+ 246 - 0
BI_front/src/views/chartManage.vue

@@ -0,0 +1,246 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="图表名称" prop="name">
+        <el-input
+            v-model="queryParams.name"
+            placeholder="请输入图表名称"
+            clearable
+            @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="创建用户ID" prop="userId">
+        <el-input
+            v-model="queryParams.userId"
+            placeholder="请输入创建用户ID"
+            clearable
+            @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-table v-loading="loading" :data="chartList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="图表ID" align="center" prop="id" />
+      <el-table-column label="图表名称" align="center" prop="name" />
+      <el-table-column label="分析目标" align="center" prop="analysisTarget" />
+      <el-table-column label="图标类型" align="center" prop="chartType" />
+      <!--<el-table-column label="生成的分析结论" align="center" prop="analysisConclusion" />-->
+      <el-table-column label="创建用户ID" align="center" prop="userId" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template #default="scope">
+          <el-button
+              size="mini"
+              @click="handleUpdate(scope.row)"
+          >
+            <template #icon>
+              <el-icon-edit/>
+            </template>
+            修改
+          </el-button>
+          <el-popconfirm
+              confirm-button-text="Yes"
+              cancel-button-text="No"
+              :icon="InfoFilled"
+              icon-color="#626AEF"
+              title="Are you sure to delete this?"
+              @confirm="handleDelete(scope.row)"
+              @cancel="cancelEvent"
+          >
+            <template #reference>
+              <el-button>Delete</el-button>
+            </template>
+          </el-popconfirm>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <el-pagination
+        v-show="total>0"
+        v-model:current-page="queryParams.pageNum"
+        v-model:page-size="queryParams.pageSize"
+        :page-sizes="[10, 15, 30]"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="total"
+        @size-change="getList"
+        @current-change="getList"
+    />
+
+    <!-- 添加或修改图表管理对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listChart, getChart, delChart, addChart, updateChart } from "@/common/api/chart";
+import {ElMessage} from "element-plus";
+
+export default {
+  name: "Chart",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 图表管理表格数据
+      chartList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 5,
+        name: null,
+        analysisTarget: null,
+        chartType: null,
+        userId: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询图表管理列表 */
+    getList() {
+      this.loading = true;
+      listChart(this.queryParams).then(response => {
+        this.chartList = response.data.records;
+        this.total = response.data.total;
+        console.log(this.total)
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        analysisTarget: null,
+        chartData: null,
+        chartType: null,
+        generatedChartData: null,
+        analysisConclusion: null,
+        userId: null,
+        createdTime: null,
+        updatedTime: null,
+        deleteFlag: null
+      };
+      // this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      // this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加图表管理";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getChart(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改图表管理";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateChart(this.form).then(response => {
+              this.$modal.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addChart(this.form).then(response => {
+              this.$modal.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      delChart(ids).then(res => {
+        if (res.code === 200) {
+          ElMessage({
+            message: '删除成功',
+            type: 'success'
+          })
+        }else {
+          ElMessage({
+            message: res.msg,
+            type: 'error'
+          })
+        }
+        this.getList();
+      }).error(err => {
+        ElMessage({
+          message: 'err',
+          type: 'error'
+        })
+      });
+      // this.$modal.confirm('是否确认删除图表管理编号为"' + ids + '"的数据项?').then(function() {
+      //   return delChart(ids);
+      // }).then(() => {
+      //   this.getList();
+      //   this.$modal.msgSuccess("删除成功");
+      // }).catch(() => {});
+    },
+  }
+};
+</script>

+ 236 - 0
BI_front/src/views/generateChart.vue

@@ -0,0 +1,236 @@
+<template>
+  <el-row>
+    <el-col :span="8">
+      <el-card>
+        <template #header>11</template>
+        <template #default>
+          <el-form ref="chartForm" :model="formData" :rules="rules" size="medium" label-width="100px"
+                   label-position="top">
+            <el-form-item label="图表名称" prop="name">
+              <el-input v-model="formData.name" placeholder="请输入图表名称" clearable :style="{width: '100%'}"></el-input>
+            </el-form-item>
+            <el-form-item label="分析目标" prop="analysisTarget">
+              <el-input v-model="formData.analysisTarget" type="textarea" placeholder="请输入分析目标;比如:分析网站的增长趋势"
+                        :autosize="{minRows: 4, maxRows: 4}" :style="{width: '100%'}"></el-input>
+            </el-form-item>
+            <el-form-item label="图表类型" prop="chartType">
+              <el-select v-model="formData.chartType" placeholder="请选择图表类型" clearable :style="{width: '100%'}">
+                <el-option v-for="(item, index) in chartTypeOptions" :key="index" :label="item.label"
+                           :value="item.value" :disabled="item.disabled"></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="上传原始数据" prop="file" required>
+              <el-upload ref="file" :file-list="filefileList" :action="fileAction" :auto-upload="false" limit="1"
+                         :before-upload="fileBeforeUpload" @change="changeFile" accept=".xls,.xlsx">
+                <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
+                <div slot="tip" class="el-upload__tip">只能上传不超过 2MB 的.xls,.xlsx文件</div>
+              </el-upload>
+            </el-form-item>
+            <el-form-item size="large">
+              <el-button type="primary" @click="submitForm">提交</el-button>
+              <el-button @click="resetForm">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </template>
+      </el-card>
+
+    </el-col>
+    <el-col :span="1">
+      <!--<el-divider direction="vertical" style="height: 100vh"/>-->
+    </el-col>
+    <el-col :span="15">
+      <el-row>
+        <el-col :span="24">
+          <el-card>
+            <template #header>数据图表</template>
+            <template #default>
+              <div ref="chart" style="height:50vh"/>
+            </template>
+          </el-card>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="24">
+          <el-card>
+            <template #header>分析结论</template>
+            <template #default>
+              {{analysisConclusion}}
+            </template>
+          </el-card>
+        </el-col>
+      </el-row>
+    </el-col>
+  </el-row>
+
+</template>
+
+<script>
+import {generateChartByAI} from "../common/api/chart/index.js";
+import {ElMessage} from "element-plus";
+import {ref} from "vue";
+import * as echarts from 'echarts';
+
+export default {
+  name: 'generateChart',
+  components: {},
+  props: [],
+  data() {
+    return {
+      formData: {
+        name: undefined,
+        analysisTarget: undefined,
+        chartType: undefined,
+        file: null,
+      },
+      option:{
+        xAxis: {
+          type: 'category',
+          data: ['2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19'],
+          boundaryGap: true,
+          axisLabel:{
+            interval:0,
+            rotate : 50
+          },
+        },
+        yAxis: {
+          type: 'value'
+        },
+        series: [{
+          name:'增长人数',
+          type:'line',
+          data:[34.0, 34.0, 3.0, 3.0, 43.0, 34.0, 43.0, 34.0, 34.0, 12.0, 12.0, 12.0, 12.0, 32.0, 12.0, 32.0, 32.0, 32.0, 32.0],
+          markPoint: {
+            data: [
+              {type: 'max', name: '最大值'},
+              {type: 'min', name: '最小值'}
+            ]
+          },
+          markLine: {data: [
+              {type: 'average', name: '平均值'}
+            ]},
+          smooth: true
+        }]
+      },
+      analysisConclusion:'',
+      rules: {
+        name: [{
+          required: true,
+          message: '请输入图表名称',
+          trigger: 'blur'
+        }],
+        analysisTarget: [{
+          required: true,
+          message: '请输入分析目标;比如:分析网站的增长趋势',
+          trigger: 'blur'
+        }],
+        chartType: [{
+          required: true,
+          message: '请选择图表类型',
+          trigger: 'change'
+        }],
+      },
+      fileAction: 'https://jsonplaceholder.typicode.com/posts/',
+      filefileList: [],
+      chartTypeOptions: [{
+        "label": "折线图",
+        "value": "折线图"
+      }, {
+        "label": "柱状图",
+        "value": "柱状图"
+      }, {
+        "label": "饼图",
+        "value": "饼图"
+      }, {
+        "label": "热力图",
+        "value": "热力图"
+      },{
+        "label": "散点图",
+        "value": "散点图"
+      },{
+        "label": "雷达图",
+        "value": "雷达图"
+      }],
+    }
+  },
+  computed: {},
+  watch: {},
+  created() {
+
+  },
+  mounted() {
+    this.chartRefresh();
+  },
+  methods: {
+    submitForm() {
+      this.$refs['chartForm'].validate(valid => {
+        if (!valid) return
+        generateChartByAI(this.formData).then(res => {
+          if (res.code === 200) {
+            this.analysisConclusion = res.data.analysisConclusion;
+            //取出配置对象的代码
+            let generatedChartData = res.data.generatedChartData;
+            let option = generatedChartData;
+            //将配置对象的代码转换为JSON对象,不是不是标准的JSON对象,需要使用eval函数进行转换
+            // let optionJson = JSON.parse(option);
+            let optionJson = eval("(" +generatedChartData+ ")")
+            console.log(optionJson)
+            //返回格式不稳定,有可能会多封装一层option,需要判断一下
+            if (optionJson.option === undefined || optionJson.option === null){
+              this.option = optionJson;
+            }else{
+              this.option = optionJson.option;
+            }
+            this.chartRefresh();
+            ElMessage({
+              message: res.msg,
+              type: 'success',
+            })
+          }else {
+            ElMessage({
+              message: res.msg,
+              type: 'error',
+            });
+          }
+        }).catch(err => {
+          ElMessage({
+            message: err,
+            type: 'error',
+          });
+        })
+      })
+    },
+    changeFile(file){
+      this.formData.file = file.raw;
+    },
+    resetForm() {
+      this.$refs['chartForm'].resetFields()
+    },
+    fileBeforeUpload(file) {
+      let isRightSize = file.size / 1024 / 1024 < 2
+      if (!isRightSize) {
+        this.$message.error('文件大小超过 2MB')
+      }
+      let isAccept = new RegExp('.xls,.xlsx').test(file.type)
+      if (!isAccept) {
+        this.$message.error('应该选择.xls,.xlsx类型的文件')
+      }
+      return isRightSize && isAccept
+    },
+    submitUpload() {
+      this.$refs['file'].submit()
+    },
+    chartRefresh(){
+      let chart = echarts.init(this.$refs.chart);
+      chart.clear();
+      chart.setOption(this.option);
+    }
+  }
+}
+</script>
+
+<style scoped>
+.el-upload__tip {
+  line-height: 1.2;
+}
+
+</style>

+ 34 - 4
BI_front/src/views/home.vue

@@ -1,17 +1,47 @@
 <template>
-  <HelloWorld/>
+  <el-container>
+    <el-aside width="auto">
+      <common-aside/>
+    </el-aside>
+    <el-container>
+      <el-header>
+        <common-header/>
+      </el-header>
+      <el-main>
+        <generate-chart v-if="isIndex"/>
+        <router-view v-else/>
+      </el-main>
+    </el-container>
+  </el-container>
 </template>
 
 <script>
-import HelloWorld from '@/components/HelloWorld.vue'
+import CommonAside from '@/components/CommonAside.vue'
+import CommonHeader from '@/components/CommonHeader.vue'
+import GenerateChart from "./generateChart.vue";
+import router from "../router/index.js";
 export default {
   components:{
-    HelloWorld
+    GenerateChart,
+    CommonAside
+  },
+  created() {
+    // console.log('router',router.currentRoute.value.fullPath);
+  },
+  computed:{
+    isIndex(){
+      return router.currentRoute.value.fullPath == '/index';
+    }
   },
   name: "home"
 }
 </script>
 
 <style scoped>
-
+/*el-header自动宽度*/
+el-header{
+  width: 100%;
+  padding: 0;
+  margin: 0;
+}
 </style>

+ 13 - 0
BI_front/src/views/myChart.vue

@@ -0,0 +1,13 @@
+<template>
+我的图表
+</template>
+
+<script>
+export default {
+  name: "myChart"
+}
+</script>
+
+<style scoped>
+
+</style>

+ 343 - 0
BI_front/src/views/userManage.vue

@@ -0,0 +1,343 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="用户账号" prop="userAccount">
+        <el-input
+            v-model="queryParams.userAccount"
+            placeholder="请输入用户账号"
+            clearable
+            @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="用户名称" prop="userName">
+        <el-input
+            v-model="queryParams.userName"
+            placeholder="请输入用户名称"
+            clearable
+            @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="用户角色" prop="userRole" style="width: 200px">
+        <!--<el-input-->
+        <!--    v-model="queryParams.userRole"-->
+        <!--    placeholder="请输入用户角色"-->
+        <!--    clearable-->
+        <!--    @keyup.enter.native="handleQuery"-->
+        <!--/>-->
+        <el-select v-model="queryParams.userRole" placeholder="请选择用户角色" clearable>
+          <el-option :value="'用户'" :label="'用户'" :key="'用户'"/>
+          <el-option :value="'管理员'" :label="'管理员' " :key="'管理员'"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+            type="primary"
+            plain
+            icon="el-icon-plus"
+            size="mini"
+            @click="handleAdd"
+            v-hasPermi="['system:user:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+            type="success"
+            plain
+            icon="el-icon-edit"
+            size="mini"
+            :disabled="single"
+            @click="handleUpdate"
+            v-hasPermi="['system:user:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+            type="danger"
+            plain
+            icon="el-icon-delete"
+            size="mini"
+            :disabled="multiple"
+            @click="handleDelete"
+            v-hasPermi="['system:user:remove']"
+        >删除</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="用户ID" align="center" prop="id" />
+      <el-table-column label="用户账号" align="center" prop="userAccount" />
+      <el-table-column label="用户名称" align="center" prop="userName" />
+      <el-table-column label="用户头像" align="center" prop="userAvatar" />
+      <el-table-column label="用户角色" align="center" prop="userRole" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template #default="scope">
+          <el-button
+              size="mini"
+              @click="handleUpdate(scope.row)"
+          >
+            <template #icon>
+              <el-icon-edit/>
+            </template>
+            修改</el-button>
+          <!--<el-button-->
+          <!--    size="mini"-->
+          <!--    type="text"-->
+          <!--    icon="el-icon-delete"-->
+          <!--    @click="handleDelete(scope.row)"-->
+          <!--&gt;删除</el-button>-->
+
+          <el-popconfirm
+              confirm-button-text="Yes"
+              cancel-button-text="No"
+              icon-color="#626AEF"
+              title="Are you sure to delete this?"
+              @confirm="handleDelete(scope.row)"
+              @cancel="cancelEvent"
+          >
+            <InfoFilled/>
+            <template #reference>
+              <el-button>Delete</el-button>
+            </template>
+          </el-popconfirm>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <el-pagination
+        v-show="total>0"
+        v-model:current-page="queryParams.currentPage"
+        v-model:page-size="queryParams.pageSize"
+        :page-sizes="[10, 15, 30]"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="total"
+        @size-change="getList"
+        @current-change="getList"
+    />
+
+    <!-- 添加或修改用户对话框 -->
+    <el-dialog :title="title" :model-value="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="用户账号" prop="userAccount">
+          <el-input v-model="form.userAccount" placeholder="请输入用户账号" />
+        </el-form-item>
+        <el-form-item label="用户密码" prop="userPassword">
+          <el-input v-model="form.userPassword" placeholder="请输入用户密码" />
+        </el-form-item>
+        <el-form-item label="用户名称" prop="userName">
+          <el-input v-model="form.userName" placeholder="请输入用户名称" />
+        </el-form-item>
+        <el-form-item label="用户头像" prop="userAvatar">
+          <el-input v-model="form.userAvatar" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+        <el-form-item label="用户角色" prop="userRole">
+          <!--<el-input v-model="form.userRole" placeholder="请输入用户角色" />-->
+          <el-select v-model="form.userRole" placeholder="请选择用户角色">
+            <el-option :value="'用户'" :label="'用户'"/>
+            <el-option :value="'管理员'" :label="'管理员'"/>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listUser, getUser, delUser, addUser, updateUser } from "@/common/api/user";
+import {ElMessage} from "element-plus";
+
+export default {
+  name: "User",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 用户表格数据
+      userList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        currentPage: 1,
+        pageSize: 10,
+        userAccount: null,
+        userName: null,
+        userRole: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        userAccount: [
+          { required: true, message: "用户账号不能为空", trigger: "blur" }
+        ],
+        userPassword: [
+          { required: true, message: "用户密码不能为空", trigger: "blur" }
+        ],
+        userName: [
+          { required: true, message: "用户名称不能为空", trigger: "blur" }
+        ],
+        userRole: [
+          { required: true, message: "用户角色不能为空", trigger: "blur" }
+        ],
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询用户列表 */
+    getList() {
+      this.loading = true;
+      listUser(this.queryParams).then(response => {
+        this.userList = response.data.records;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        userAccount: null,
+        userPassword: null,
+        userName: null,
+        userAvatar: null,
+        userRole: null,
+        createTime: null,
+        updateTime: null,
+        deleteFlag: null
+      };
+      // this.resetForm("form");
+      // this.$refs.form.resetFields();
+      // this.$refs['form'].resetFields();
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.currentPage = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      // this.resetForm("queryForm");
+      // this.$refs['queryForm'].resetFields();
+      this.queryParams = {
+        currentPage: 1,
+        pageSize: 10,
+        userAccount: null,
+        userName: null,
+        userRole: null,
+      }
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加用户";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getUser(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改用户";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateUser(this.form).then(response => {
+              // this.$modal.msgSuccess("修改成功");
+              ElMessage({
+                message: '修改成功',
+                type: 'success',
+              });
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addUser(this.form).then(response => {
+              // this.$modal.msgSuccess("新增成功");
+              ElMessage({
+                message: '新增成功',
+                type: 'success',
+              });
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      delUser(ids).then(res => {
+        if (res.code == 200){
+          ElMessage({
+            message: '删除成功',
+            type: 'success',
+          });
+        }else {
+          ElMessage({
+            message: res.msg,
+            type: 'error',
+          });
+        }
+      }).error(err => {
+        ElMessage({
+            message: '删除失败',
+            type: 'error',
+        });
+      })
+      this.$modal.confirm('是否确认删除用户编号为"' + ids + '"的数据项?').then(function() {
+        return delUser(ids);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+  }
+};
+</script>

+ 2 - 2
BI_front/vite.config.js

@@ -34,10 +34,10 @@ export default defineConfig({
   // 代理配置
   server: {
     // https: true,
-    port:80,
+    port:3000,
     proxy: {
       '/api': { // 配置需要代理的路径 --> 这里的意思是代理http://localhost:80/api/后的所有路由
-        target: 'http://localhost:8080', // 目标地址 --> 服务器地址
+        target: 'http://localhost:8888', // 目标地址 --> 服务器地址
         changeOrigin: true, // 允许跨域
         ws: true,  // 允许websocket代理
         // 重写路径 --> 作用与vue配置pathRewrite作用相同

+ 1 - 1
serve/sql/init.sql

@@ -29,4 +29,4 @@ CREATE TABLE if NOT EXISTS `chart` (
                           `created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
                           `updated_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
                           `delete_flag` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '删除标志,0:未删除,1:已删除'
-) COMMENT='图表';
+) COMMENT='图表';

+ 5 - 1
serve/src/main/java/space/anyi/BI/config/MybatisplusConfig.java

@@ -1,5 +1,6 @@
 package space.anyi.BI.config;
 
+import com.baomidou.mybatisplus.annotation.DbType;
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
 import org.mybatis.spring.annotation.MapperScan;
@@ -21,7 +22,10 @@ public class MybatisplusConfig {
         //拦截器
         MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
         //分页拦截器
-        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
+        PaginationInnerInterceptor innerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
+        //设置请求的页面大于最大页后操作,true调回到首页,false 继续请求 默认false
+        innerInterceptor.setOverflow(true);
+        mybatisPlusInterceptor.addInnerInterceptor(innerInterceptor);
         return mybatisPlusInterceptor;
     }
 }

+ 19 - 7
serve/src/main/java/space/anyi/BI/controller/ChartController.java

@@ -13,11 +13,15 @@ import space.anyi.BI.entity.Chart;
 import space.anyi.BI.entity.ResponseResult;
 import space.anyi.BI.entity.dto.ChartDTO;
 import space.anyi.BI.entity.vo.ChartVO;
+import space.anyi.BI.entity.vo.PageVO;
 import space.anyi.BI.service.ChartService;
+import space.anyi.BI.util.BeanCopyUtil;
 import space.anyi.BI.util.SecurityUtils;
 
 import javax.annotation.Resource;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * @ProjectName: BI
@@ -48,10 +52,10 @@ public class ChartController {
         chartService.save(chart);
         return ResponseResult.okResult();
     }
-    @DeleteMapping("/delete/{id}")
+    @DeleteMapping("/{ids}")
     @ResponseBody
-    public ResponseResult deleteChartById(@PathVariable Long id){
-        chartService.removeById(id);
+    public ResponseResult deleteChartById(@PathVariable List<Long> ids){
+        chartService.removeByIds(ids);
         return ResponseResult.okResult();
     }
     @PutMapping("/update")
@@ -62,15 +66,23 @@ public class ChartController {
     }
     @GetMapping("/list")
     @ResponseBody
-    public ResponseResult page(Integer size,Integer current){
-        Page<Chart> page = chartService.page(new Page<>(current, size));
-        return ResponseResult.okResult(page);
+    public ResponseResult page(Integer pageNum,Integer pageSize){
+        Page<Chart> page = chartService.page(new Page<Chart>(pageNum, pageSize));
+        List<ChartVO> voList = new ArrayList<>();
+        for (Chart chart : page.getRecords()) {
+            ChartVO chartVO = BeanCopyUtil.copyBean(chart, ChartVO.class);
+            chartVO.setId(chart.getId().toString());
+            chartVO.setUserId(chart.getUserId().toString());
+            voList.add(chartVO);
+        }
+        PageVO pageVO = new PageVO(voList, page.getTotal());
+        return ResponseResult.okResult(pageVO);
     }
 
     @PostMapping("/generateChartByAI")
     @ResponseBody
     public ResponseResult generateChartByAI(ChartDTO chartDTO, MultipartFile file) throws IOException {
-        log.info("分析目标:{},图标名称:{}",chartDTO.getAnalysisTarget(),chartDTO.getName());
+        log.info("分析目标:{}图标名称:{}",chartDTO.getAnalysisTarget(),chartDTO.getName());
         if (file == null || file.isEmpty()) {
             return ResponseResult.errorResult(ResponseResult.AppHttpCodeEnum.FILE_NOT_NULL);
         }

+ 46 - 0
serve/src/main/java/space/anyi/BI/controller/UserController.java

@@ -1,13 +1,20 @@
 package space.anyi.BI.controller;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.web.bind.annotation.*;
 import space.anyi.BI.entity.LoginUserDetails;
 import space.anyi.BI.entity.ResponseResult;
 import space.anyi.BI.entity.User;
+import space.anyi.BI.entity.dto.UserDTO;
+import space.anyi.BI.entity.vo.PageVO;
+import space.anyi.BI.entity.vo.UserVO;
 import space.anyi.BI.service.UserService;
+import space.anyi.BI.util.BeanCopyUtil;
 import space.anyi.BI.util.SecurityUtils;
 
 import javax.annotation.Resource;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -20,6 +27,7 @@ import java.util.Objects;
 @RequestMapping("/user")
 @RestController
 public class UserController {
+    private final static Logger log = LoggerFactory.getLogger(UserController.class);
     @Resource
     private UserService userService;
 
@@ -44,4 +52,42 @@ public class UserController {
         System.out.println("user = " + user);
         return ResponseResult.okResult(user);
     }
+    @GetMapping("/page")
+    @ResponseBody
+    public ResponseResult page(Integer currentPage,Integer pageSize,String userName,String userAccount,String userRole){
+        log.info("currentPage:{},pageSize:{},userName:{},userAccount:{}",currentPage,pageSize,userName,userAccount);
+        PageVO page = userService.pageByUsernameAndUseraccount(currentPage, pageSize, userName, userAccount,userRole);
+        return ResponseResult.okResult(page);
+    }
+    @GetMapping("/{id}")
+    @ResponseBody
+    public ResponseResult getUserById(@PathVariable Long id){
+        User user = userService.getById(id);
+        UserVO userVO = BeanCopyUtil.copyBean(user, UserVO.class);
+        userVO.setId(user.getId().toString());
+        return ResponseResult.okResult(userVO);
+    }
+
+    @DeleteMapping("/{ids}")
+    @ResponseBody
+    public ResponseResult deleteUserById(@PathVariable List<Long> ids){
+        userService.removeByIds(ids);
+        return ResponseResult.okResult();
+    }
+
+    @PutMapping
+    @ResponseBody
+    public ResponseResult updateUser(@RequestBody UserDTO userDTO){
+        User user = BeanCopyUtil.copyBean(userDTO, User.class);
+        userService.updateById(user);
+        return ResponseResult.okResult();
+    }
+
+    @PostMapping
+    @ResponseBody
+    public ResponseResult addUser(@RequestBody UserDTO userDTO){
+        userService.addUser(userDTO);
+        return ResponseResult.okResult();
+    }
+
 }

+ 9 - 1
serve/src/main/java/space/anyi/BI/entity/Chart.java

@@ -16,7 +16,7 @@ public class Chart implements Serializable {
     /**
      * 图表ID
      */
-    @TableId(type = IdType.AUTO)
+    @TableId()
     private Long id;
 
     /**
@@ -212,6 +212,14 @@ public class Chart implements Serializable {
         this.deleteFlag = deleteFlag;
     }
 
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
     @Override
     public boolean equals(Object that) {
         if (this == that) {

+ 92 - 0
serve/src/main/java/space/anyi/BI/entity/dto/UserDTO.java

@@ -0,0 +1,92 @@
+package space.anyi.BI.entity.dto;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+
+
+/**
+ * @ProjectName: serve
+ * @FileName: UserDTO
+ * @Author: 杨逸
+ * @Data:2024/12/5 21:29
+ * @Description:
+ */
+public class UserDTO {
+    /**
+     * 用户ID
+     */
+    private Long id;
+
+    /**
+     * 用户账号
+     */
+    private String userAccount;
+
+    /**
+     * 用户密码
+     */
+    private String userPassword;
+
+    /**
+     * 用户名称
+     */
+    private String userName;
+
+    /**
+     * 用户头像
+     */
+    private String userAvatar;
+
+    /**
+     * 用户角色
+     */
+    private String userRole = "用户";
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getUserAccount() {
+        return userAccount;
+    }
+
+    public void setUserAccount(String userAccount) {
+        this.userAccount = userAccount;
+    }
+
+    public String getUserPassword() {
+        return userPassword;
+    }
+
+    public void setUserPassword(String userPassword) {
+        this.userPassword = userPassword;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public String getUserAvatar() {
+        return userAvatar;
+    }
+
+    public void setUserAvatar(String userAvatar) {
+        this.userAvatar = userAvatar;
+    }
+
+    public String getUserRole() {
+        return userRole;
+    }
+
+    public void setUserRole(String userRole) {
+        this.userRole = userRole;
+    }
+}

+ 8 - 8
serve/src/main/java/space/anyi/BI/entity/vo/ChartVO.java

@@ -11,7 +11,7 @@ public class ChartVO {
     /**
      * 图表ID
      */
-    private Long id;
+    private String id;
 
     /**
      * 图标名称
@@ -46,13 +46,13 @@ public class ChartVO {
     /**
      * 创建用户ID
      */
-    private Long userId;
+    private String userId;
 
-    public Long getId() {
+    public String getId() {
         return id;
     }
 
-    public void setId(Long id) {
+    public void setId(String id) {
         this.id = id;
     }
 
@@ -104,25 +104,25 @@ public class ChartVO {
         this.analysisConclusion = analysisConclusion;
     }
 
-    public Long getUserId() {
+    public String getUserId() {
         return userId;
     }
 
-    public void setUserId(Long userId) {
+    public void setUserId(String userId) {
         this.userId = userId;
     }
 
     @Override
     public String toString() {
         return "ChartVO{" +
-                "id=" + id +
+                "id='" + id + '\'' +
                 ", name='" + name + '\'' +
                 ", analysisTarget='" + analysisTarget + '\'' +
                 ", chartData='" + chartData + '\'' +
                 ", chartType='" + chartType + '\'' +
                 ", generatedChartData='" + generatedChartData + '\'' +
                 ", analysisConclusion='" + analysisConclusion + '\'' +
-                ", userId=" + userId +
+                ", userId='" + userId + '\'' +
                 '}';
     }
 }

+ 36 - 0
serve/src/main/java/space/anyi/BI/entity/vo/PageVO.java

@@ -0,0 +1,36 @@
+package space.anyi.BI.entity.vo;
+
+import java.util.List;
+
+/**
+ * @ProjectName: serve
+ * @FileName: PageVO
+ * @Author: 杨逸
+ * @Data:2024/12/5 10:23
+ * @Description:
+ */
+public class PageVO {
+    private Integer total;
+    private List<Object> records;
+
+    public PageVO(List records, long total) {
+        this.records = records;
+        this.total = (int) total;
+    }
+
+    public Integer getTotal() {
+        return total;
+    }
+
+    public void setTotal(Integer total) {
+        this.total = total;
+    }
+
+    public List<Object> getRecords() {
+        return records;
+    }
+
+    public void setRecords(List<Object> records) {
+        this.records = records;
+    }
+}

+ 79 - 0
serve/src/main/java/space/anyi/BI/entity/vo/UserVO.java

@@ -0,0 +1,79 @@
+package space.anyi.BI.entity.vo;
+
+import java.util.Date;
+
+/**
+ * @ProjectName: serve
+ * @FileName: UserVO
+ * @Author: 杨逸
+ * @Data:2024/12/5 21:09
+ * @Description:
+ */
+public class UserVO {
+    /**
+     * 用户ID
+     */
+    private String id;
+
+    /**
+     * 用户账号
+     */
+    private String userAccount;
+
+
+    /**
+     * 用户名称
+     */
+    private String userName;
+
+    /**
+     * 用户头像
+     */
+    private String userAvatar;
+
+    /**
+     * 用户角色
+     */
+    private String userRole = "用户";
+
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getUserAccount() {
+        return userAccount;
+    }
+
+    public void setUserAccount(String userAccount) {
+        this.userAccount = userAccount;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public String getUserAvatar() {
+        return userAvatar;
+    }
+
+    public void setUserAvatar(String userAvatar) {
+        this.userAvatar = userAvatar;
+    }
+
+    public String getUserRole() {
+        return userRole;
+    }
+
+    public void setUserRole(String userRole) {
+        this.userRole = userRole;
+    }
+}

+ 10 - 10
serve/src/main/java/space/anyi/BI/entity/xinghuo/HttpRequestMessage.java

@@ -58,20 +58,20 @@ public class HttpRequestMessage implements Serializable {
     public static HttpRequestMessage getPromptMessage() {
         String prompt = "你是一位专业的数据分析师和前端工程师,擅长对数据进行分析并得出明确的结论。你精通使用Echarts进行数据可视化,并能结合可视化的数据图表进行合理的分析。\n" +
                 "\n" +
-                "要求:\n" +
-                "\n" +
-                "- 直接输出两个部分:Echarts图表的配置代码和数据分析结论,不包含其他提示内容。\n" +
-                "- 我将提供原始CSV数据和分析目标,你需根据这些信息生成Echarts图表的配置代码。\n" +
-                "\t- CSV数据以','字符分割,\n" +
-                "- 输出格式为:先输出Echarts的option配置对象的JavaScript代码,然后输出分析结论。\n" +
-                "- 请按照指定的输出格式提供我需要的内容。\n" +
-                "输入格式:\n" +
+                "**要求:**\n" +
+                "1. 直接输出两个部分:Echarts图表的配置代码和数据分析结论,不包含其他提示内容。\n" +
+                "2. 我将提供原始数据和分析目标,你需要根据这些信息生成Echarts图表的配置对象标准JSON代码。代码中不能使用不存在的变量和函数。\n" +
+                "3. 输出格式为:\n" +
+                "   - 首先输出Echarts的option配置对象的标准格式JSON代码。\n" +
+                "   - 然后输出基于原始数据和图表分析得出的结论。\n" +
+                "4. 请严格按照指定的输出格式提供所需内容。\n" +
                 "\n" +
+                "**输入格式:**\n" +
                 "- 原始数据\n" +
                 "- 分析目标\n" +
-                "输出格式:\n" +
                 "\n" +
-                "- Echarts的option配置对象的JavaScript代码\n" +
+                "**输出格式:**\n" +
+                "- Echarts的option配置对象的JSON代码\n" +
                 "- 数据分析结论";
         HttpRequestMessage message = new HttpRequestMessage(HttpRequestMessage.SYSTEM_ROLE,prompt);
         return message;

+ 6 - 0
serve/src/main/java/space/anyi/BI/service/UserService.java

@@ -3,6 +3,8 @@ package space.anyi.BI.service;
 import space.anyi.BI.entity.ResponseResult;
 import space.anyi.BI.entity.User;
 import com.baomidou.mybatisplus.extension.service.IService;
+import space.anyi.BI.entity.dto.UserDTO;
+import space.anyi.BI.entity.vo.PageVO;
 
 /**
 * @author 杨逸
@@ -14,4 +16,8 @@ public interface UserService extends IService<User> {
     ResponseResult login(User user);
 
     ResponseResult register(User user);
+
+    PageVO pageByUsernameAndUseraccount(Integer currentPage, Integer pageSize, String userName, String userAccount, String userRole);
+
+    void addUser(UserDTO userDTO);
 }

+ 4 - 3
serve/src/main/java/space/anyi/BI/service/impl/ChartServiceImpl.java

@@ -48,6 +48,7 @@ public class ChartServiceImpl extends ServiceImpl<ChartMapper, Chart>
         message.append(csvData);
         message.append("分析目标:\n");
         message.append(chartDTO.getAnalysisTarget());
+        message.append("\n.使用").append(chartDTO.getChartType()).append("进行可视化分析.\n");
         //配置prompt向AI发送请求
         HttpRequestData requestData = AiUtil.createDefaultRequestData(message.toString());
         HttpResponseData responseData = AiUtil.doChat(requestData);
@@ -57,14 +58,14 @@ public class ChartServiceImpl extends ServiceImpl<ChartMapper, Chart>
         log.info("AI返回的数据为:{}", content);
         int index = content.indexOf("```");
         int endIndex = content.lastIndexOf("```");
-        if (index == -1){
+        if (index == -1 || endIndex == -1){
             throw new SystemException(500, "AI生成图表失败");
         }
         //数据可视化,Echarts的option代码
-        chartVO.setGeneratedChartData(content.substring(index+14, endIndex));
+        chartVO.setGeneratedChartData(content.substring(index+7, endIndex).trim());
         index = endIndex;
         //分析结论
-        chartVO.setAnalysisConclusion(content.substring(index+3));
+        chartVO.setAnalysisConclusion(content.substring(index+3).trim());
 
         Chart chart = BeanCopyUtil.copyBean(chartVO, Chart.class);
         chart.setUserId(SecurityUtils.getUserId());

+ 34 - 3
serve/src/main/java/space/anyi/BI/service/impl/UserServiceImpl.java

@@ -2,27 +2,31 @@ package space.anyi.BI.service.impl;
 
 import cn.hutool.core.util.IdUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.util.StringUtils;
 import space.anyi.BI.constant.SystemConstants;
 import space.anyi.BI.entity.LoginUserDetails;
 import space.anyi.BI.entity.ResponseResult;
 import space.anyi.BI.entity.User;
+import space.anyi.BI.entity.dto.UserDTO;
+import space.anyi.BI.entity.vo.PageVO;
 import space.anyi.BI.entity.vo.UserInfo;
+import space.anyi.BI.entity.vo.UserVO;
 import space.anyi.BI.exception.SystemException;
 import space.anyi.BI.service.UserService;
 import space.anyi.BI.mapper.UserMapper;
 import org.springframework.stereotype.Service;
+import space.anyi.BI.util.BeanCopyUtil;
 import space.anyi.BI.util.JwtUtil;
 import space.anyi.BI.util.RedisCache;
 
 import javax.annotation.Resource;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
 
 /**
 * @author 杨逸
@@ -60,6 +64,33 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
         return ResponseResult.okResult(map);
     }
 
+    @Override
+    public void addUser(UserDTO userDTO) {
+        User user = BeanCopyUtil.copyBean(userDTO, User.class);
+        //密码加密
+        user.setUserPassword(passwordEncoder.encode(user.getUserPassword()));
+        //生成ID
+        long id = IdUtil.getSnowflake(1, 1).nextId();
+        user.setId(id);
+        save(user);
+    }
+
+    @Override
+    public PageVO pageByUsernameAndUseraccount(Integer currentPage, Integer pageSize, String userName, String userAccount, String userRole) {
+        List<UserVO> userVOList = new ArrayList<>();
+        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<User>()
+                .like(StringUtils.hasText(userName), User::getUserName, userName)
+                .eq(StringUtils.hasText(userAccount), User::getUserAccount, userAccount)
+                .eq(StringUtils.hasText(userRole), User::getUserRole, userRole);
+        Page<User> page = page(new Page<User>(currentPage,pageSize), lambdaQueryWrapper);
+        for (User user : page.getRecords()) {
+            UserVO userVO = BeanCopyUtil.copyBean(user, UserVO.class);
+            userVO.setId(user.getId().toString());
+            userVOList.add(userVO);
+        }
+        return new PageVO(userVOList,page.getTotal());
+    }
+
     @Override
     public ResponseResult register(User user) {
         //用户账号是否存在

+ 8 - 6
serve/src/main/resources/application.yaml

@@ -3,19 +3,21 @@ spring:
     name: BI
   datasource:
     driver-class-name: com.mysql.jdbc.Driver
-    url: jdbc:mysql://localhost:3306/bi?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
+    url: jdbc:mysql://localhost:3306/bi?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
     username: root
     password: hsp
   mvc:
     pathmatch:
       matching-strategy: ant_path_matcher
   redis:
-    database: 12
-    host: anyi.space
-    port: 7000
-    password: yangyi
+    database: 0
+    host: localhost
+#    host: anyi.space
+#    port: 7000
+    port: 6379
+#    password: yangyi
 server:
-  port: 8080
+  port: 8888
   servlet:
     context-path: /
 mybatis-plus: