Sfoglia il codice sorgente

feature:000.000.001:实现登陆注册前后端

yang yi 9 mesi fa
parent
commit
07f1574a29

+ 2 - 2
BI_front/index.html

@@ -2,9 +2,9 @@
 <html lang="en">
   <head>
     <meta charset="UTF-8" />
-    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <link rel="icon" type="image/svg+xml" href="/icon.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Vite + Vue</title>
+    <title>BI</title>
   </head>
   <body>
     <div id="app"></div>

File diff suppressed because it is too large
+ 1130 - 34
BI_front/package-lock.json


+ 10 - 1
BI_front/package.json

@@ -9,10 +9,19 @@
     "preview": "vite preview"
   },
   "dependencies": {
-    "vue": "^3.5.13"
+    "axios": "^1.7.8",
+    "element-plus": "^2.9.0",
+    "js-cookie": "^3.0.5",
+    "pinia": "^2.2.8",
+    "sass": "^1.81.0",
+    "vue": "^3.5.13",
+    "vue-router": "^4.5.0"
   },
   "devDependencies": {
     "@vitejs/plugin-vue": "^5.2.1",
+    "less": "^4.2.1",
+    "unplugin-auto-import": "^0.18.6",
+    "unplugin-vue-components": "^0.27.5",
     "vite": "^6.0.1"
   }
 }

File diff suppressed because it is too large
+ 0 - 0
BI_front/public/icon.svg


+ 0 - 1
BI_front/public/vite.svg

@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

+ 3 - 22
BI_front/src/App.vue

@@ -1,30 +1,11 @@
 <script setup>
-import HelloWorld from './components/HelloWorld.vue'
+
 </script>
 
 <template>
-  <div>
-    <a href="https://vite.dev" target="_blank">
-      <img src="/vite.svg" class="logo" alt="Vite logo" />
-    </a>
-    <a href="https://vuejs.org/" target="_blank">
-      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
-    </a>
-  </div>
-  <HelloWorld msg="Vite + Vue" />
+  <router-view/>
 </template>
 
 <style scoped>
-.logo {
-  height: 6em;
-  padding: 1.5em;
-  will-change: filter;
-  transition: filter 300ms;
-}
-.logo:hover {
-  filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.vue:hover {
-  filter: drop-shadow(0 0 2em #42b883aa);
-}
+
 </style>

+ 30 - 0
BI_front/src/assets/styles/global.less

@@ -0,0 +1,30 @@
+html {
+  width: 100%;
+  overflow-x: hidden;
+}
+
+body {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
+  'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
+  'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
+  font-variant: tabular-nums;
+  background-color: var(--c-bg);
+  margin: 0;
+}
+
+// 滚动条美化
+::-webkit-scrollbar {
+  width: 6px;
+  height: 6px;
+}
+::-webkit-scrollbar-thumb {
+  background-color: #374151;
+  border-radius: 4px;
+  &:hover {
+    background-color: #1f2937;
+  }
+}
+::-webkit-scrollbar-track {
+  background-color: #6b7280;
+  border-radius: 4px;
+}

+ 283 - 0
BI_front/src/assets/styles/login.scss

@@ -0,0 +1,283 @@
+$bg: #edf2f0;
+
+$neu-1: #ecf0f3;
+$neu-2: #d1d9e6;
+
+$white: #f9f9f9;
+$gray: #a0a5a8;
+$black: #181818;
+
+$purple: #4b70e2;
+
+$transition: 1.25s;
+
+*,
+*::after,
+*::before {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  user-select: none;
+}
+/* Generic */
+body {
+  width: 100%;
+  height: 100vh;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-family: 'Montserrat', sans-serif;
+  font-size: 12px;
+  background-color: $neu-1;
+  color: $gray;
+}
+/**/
+.main {
+  position: relative;
+  width: 1000px;
+  min-width: 1000px;
+  min-height: 600px;
+  height: 600px;
+  padding: 25px;
+  background-color: $neu-1;
+  box-shadow:
+          10px 10px 10px $neu-2,
+          -10px -10px 10px $white;
+  border-radius: 12px;
+  overflow: hidden;
+  @media (max-width: 1200px) {
+    transform: scale(0.7);
+  }
+  @media (max-width: 1000px) {
+    transform: scale(0.6);
+  }
+  @media (max-width: 800px) {
+    transform: scale(0.5);
+  }
+  @media (max-width: 600px) {
+    transform: scale(0.4);
+  }
+}
+.container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  position: absolute;
+
+  top: 0;
+  width: 600px;
+  height: 100%;
+  padding: 25px;
+
+  background-color: $neu-1;
+  transition: $transition;
+}
+.form {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+  width: 100%;
+  height: 100%;
+
+  &__icon {
+    object-fit: contain;
+    width: 30px;
+    margin: 0 5px;
+    opacity: 0.5;
+    transition: 0.15s;
+
+    &:hover {
+      opacity: 1;
+      transition: 0.15s;
+      cursor: pointer;
+    }
+  }
+  &__input {
+    width: 350px;
+    height: 40px;
+    margin: 4px 0;
+
+    padding-left: 25px;
+    font-size: 13px;
+    letter-spacing: 0.15px;
+    border: none;
+    outline: none;
+
+    font-family: 'Montserrat', sans-serif;
+    background-color: $neu-1;
+    transition: 0.25s ease;
+    border-radius: 8px;
+
+    box-shadow:
+            inset 2px 2px 4px $neu-2,
+            inset -2px -2px 4px $white;
+
+    &:focus {
+      box-shadow:
+              inset 4px 4px 4px $neu-2,
+              inset -4px -4px 4px $white;
+    }
+  }
+  &__span {
+    margin-top: 30px;
+    margin-bottom: 12px;
+  }
+  &__link {
+    color: $black;
+    font-size: 15px;
+    margin-top: 25px;
+    border-bottom: 1px solid $gray;
+    line-height: 2;
+  }
+}
+.title {
+  font-size: 34px;
+  font-weight: 700;
+  line-height: 3;
+  color: $black;
+}
+.description {
+  font-size: 14px;
+  letter-spacing: 0.25px;
+  text-align: center;
+  line-height: 1.6;
+}
+.button {
+  width: 180px;
+  height: 50px;
+  border-radius: 25px;
+  margin-top: 50px;
+  font-weight: 700;
+  font-size: 14px;
+  letter-spacing: 1.15px;
+
+  background-color: $purple;
+  color: $white;
+  box-shadow:
+          8px 8px 16px $neu-2,
+          -8px -8px 16px $white;
+
+  border: none;
+  outline: none;
+}
+/**/
+
+.a-container {
+  z-index: 100;
+  left: calc(100% - 600px);
+}
+.b-container {
+  left: calc(100% - 600px);
+  z-index: 0;
+}
+
+.switch {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 100%;
+  width: 400px;
+
+  padding: 50px;
+  z-index: 200;
+  transition: $transition;
+
+  background-color: $neu-1;
+  overflow: hidden;
+
+  box-shadow:
+          4px 4px 10px $neu-2,
+          -4px -4px 10px $white;
+
+  &__circle {
+    position: absolute;
+    width: 500px;
+    height: 500px;
+    border-radius: 50%;
+    background-color: $neu-1;
+    box-shadow:
+            inset 8px 8px 12px $neu-2,
+            inset -8px -8px 12px $white;
+
+    bottom: -60%;
+    left: -60%;
+    transition: $transition;
+
+    &--t {
+      top: -30%;
+      left: 60%;
+      width: 300px;
+      height: 300px;
+    }
+  }
+
+  &__container {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex-direction: column;
+    position: absolute;
+    width: 400px;
+    padding: 50px 55px;
+
+    transition: $transition;
+  }
+
+  &__button {
+    cursor: pointer;
+    &:hover {
+      box-shadow:
+              6px 6px 10px $neu-2,
+              -6px -6px 10px $white;
+      transform: scale(0.985);
+      transition: 0.25s;
+    }
+    &:active,
+    &:focus {
+      box-shadow:
+              2px 2px 6px $neu-2,
+              -2px -2px 6px $white;
+      transform: scale(0.97);
+      transition: 0.25s;
+    }
+  }
+}
+/**/
+.is-txr {
+  left: calc(100% - 400px);
+  transition: $transition;
+  transform-origin: left;
+}
+.is-txl {
+  left: 0;
+  transition: $transition;
+  transform-origin: right;
+}
+.is-z200 {
+  z-index: 200;
+  transition: $transition;
+}
+.is-hidden {
+  visibility: hidden;
+  opacity: 0;
+  position: absolute;
+  transition: $transition;
+}
+.is-gx {
+  animation: is-gx $transition;
+}
+@keyframes is-gx {
+  0%,
+  10%,
+  100% {
+    width: 400px;
+  }
+  30%,
+  50% {
+    width: 500px;
+  }
+}

+ 27 - 0
BI_front/src/assets/styles/mixin.less

@@ -0,0 +1,27 @@
+// 满背景效果
+.mixin-bg() {
+  display: flex;
+  flex-shrink: 0;
+  background-size: 100% 100%;
+  background-repeat: no-repeat;
+  background-position: center;
+}
+
+// 图片自适应避免拉伸
+.mixin-img() {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+// 多行省略
+.mixin-line-clamp(@count) {
+  display: -webkit-box;
+  -webkit-line-clamp: @count;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  word-break: break-all;
+  max-width: 100%;
+  margin: 0 auto;
+}

+ 37 - 0
BI_front/src/assets/styles/var.less

@@ -0,0 +1,37 @@
+// https://cn.windicss.org/utilities/general/colors.html
+:root {
+  // 常用尺寸
+  --w-space-xs: 8px;
+  --w-space-sm: 16px;
+  --w-space: 24px;
+  --w-space-lg: 32px;
+
+  --w-radius-xs: 4px;
+  --w-radius: 8px;
+  --w-radius-lg: 12px;
+  --w-radius-xl: 16px;
+
+  // 常用配色
+  --c-white: #fff;
+  --c-black: #000;
+  --c-blue: #3b82f6;
+  --c-red: #ef4444;
+  --c-yellow: #f59e0b;
+  --c-green: #42b983;
+  --c-gray: #9ca3af;
+  --c-disable: rgba(0, 0, 0, 0.25);
+
+  // 常用元素色
+  --c-brand: var(--c-green);
+  --c-bg: #f8f8f8;
+  --c-text: #374151;
+  --c-tips: var(--c-gray);
+  --c-border: var(--c-gray);
+  --c-mask: rgba(0, 0, 0, 0.7);
+  --c-hover: var(--c-brand);
+
+  &.dark {
+    --c-bg: #181818;
+    --c-text: #e5e7eb;
+  }
+}

+ 0 - 1
BI_front/src/assets/vue.svg

@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

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

@@ -0,0 +1,29 @@
+import request from "@/common/utils/request";
+
+/**
+ * 登录
+ */
+export function login(data) {
+    return request.post(
+        '/user/login',
+        data
+    );
+}
+
+/**
+ * 注册
+ */
+export function userRegister(data) {
+    return request.post(
+        '/user/register',
+        data
+    );
+}
+
+/**
+ * 获取用户信息
+ * @returns {*}
+ */
+export function getUserInfo() {
+    return request.get('/user/getUserInfo');
+}

+ 61 - 0
BI_front/src/common/utils/auth.js

@@ -0,0 +1,61 @@
+import Cookies from 'js-cookie'
+
+const TokenKey = 'Admin-Token'
+
+export function getToken() {
+  return Cookies.get(TokenKey)
+}
+
+export function setToken(token) {
+  return Cookies.set(TokenKey, token)
+}
+
+export function removeToken() {
+  return Cookies.remove(TokenKey)
+}
+
+export function getUserId(){
+  //获取token
+  let token = getToken();
+  if (!token){
+    return null;
+  }
+  //截取token,获取载体
+  let strings = token.split(".");
+  //解析,需要吧‘_’,'-'进行转换否则会无法解析
+  let jwt = JSON.parse(decodeURIComponent(escape(window.atob(strings[1].replace(/-/g, "+").replace(/_/g, "/")))));
+  return jwt.sub;
+}
+
+export function getUserRole(){
+  let userInfo = JSON.parse(localStorage.getItem('userInfo'));
+  if (userInfo)return userInfo.userRole;
+  return '';
+}
+
+export function getUserName(){
+  let userInfo = JSON.parse(localStorage.getItem('userInfo'));
+  if (userInfo)return userInfo.userName;
+  return '';
+}
+export function getUserPhoto(){
+  let userInfo = JSON.parse(localStorage.getItem('userInfo'));
+  if (userInfo)return userInfo.userPhoto;
+  return '';
+}
+
+export function getUser(){
+  let userInfo = JSON.parse(localStorage.getItem('userInfo'));
+  return userInfo?userInfo:'';
+}
+
+export  function clearTokenAndUserInfo(){
+  //删除用户信息
+  localStorage.removeItem('userInfo');
+  //清除token
+  removeToken();
+}
+export  function saveUserInfo(userInfo){
+  //保存用户信息
+  localStorage.setItem('userInfo',userInfo);
+}

+ 55 - 0
BI_front/src/common/utils/request.js

@@ -0,0 +1,55 @@
+import axios from "axios";
+import router from '@/router'
+import {getToken} from "@/common/utils/auth.js";
+import {ElLoading} from "element-plus";
+
+
+let loading;
+const startLoading = ()=>{
+    const options = {
+        lock: true,
+        text: "加载中...",
+        background: "rgba(0,0,0,0.7)"
+    }
+    loading = ElLoading.service(options)
+}
+const endLoading = ()=>{
+    loading.close()
+}
+
+
+axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
+
+const request = axios.create({
+    baseURL: '/api',
+    timeout: 10000
+});
+//请求拦截器
+request.interceptors.request.use(config =>{
+    startLoading();
+    //添加token
+    let token = getToken();
+    config.headers['token'] = token?token+'':'';
+    return config;
+});
+
+//响应拦截器
+request.interceptors.response.use(response =>{
+    endLoading();
+    let res = response.data;
+    if (res.code === 401){
+        // location.reload();
+        router.push('/index');
+    }
+    //如果是文件就直接返回
+    if(response.config.responseType === 'blob')return res;
+    //如果是字符串就转换为json对象
+    if(typeof res === 'string'){
+        res = res?JSON.parse(res):res;
+    }
+    return res;
+});
+
+
+
+export default request;

+ 1 - 0
BI_front/src/components/HelloWorld.vue

@@ -34,6 +34,7 @@ const count = ref(0)
     >.
   </p>
   <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
+  <router-link to="/login" >to</router-link>
 </template>
 
 <style scoped>

File diff suppressed because it is too large
+ 7 - 0
BI_front/src/components/siginIn.vue


+ 342 - 0
BI_front/src/components/siginUp.vue

@@ -0,0 +1,342 @@
+<template>
+  <div id="a-container" class="container a-container">
+    <form id="a-form" class="form" method="POST" action="">
+      <h2 class="form_title title">Create Account</h2>
+      <span class="form__span">or use email for registration</span>
+      <!--<input class="form__input" v-model="user.userName" type="text" placeholder="Name" />-->
+      <input class="form__input" v-model="user.userAccount" type="text" placeholder="Account" />
+      <input class="form__input" v-model.lazy="user.userPassword" type="password" placeholder="Password" />
+      <button class="form__button button submit" @click.prevent="registerHandler()">SIGN UP</button>
+    </form>
+  </div>
+</template>
+
+<script setup >
+import { useMainStore } from '@/stores/message'
+import {ref, watch} from "vue";
+import {storeToRefs} from "pinia";
+import {ElMessage} from "element-plus";
+import {userRegister} from '@/common/api/user/index'
+const mainStore = useMainStore()
+const { showSignup } = storeToRefs(mainStore)
+watch(showSignup, () => {
+  const aContainer = document.querySelector('#a-container')
+  aContainer.classList.toggle('is-txl')
+})
+const user = ref({
+  userName:'用户'+Date.now(),
+  userAccount: '',
+  userPassword: '',
+})
+function registerHandler(){
+  userRegister(user.value).then(res =>{
+    if (res.code == 200){
+      ElMessage({
+        message: '注册成功',
+        type:'success'
+      })
+    }else {
+      ElMessage({
+        message: res.msg,
+        type:'error'
+      })
+    }
+
+  }).catch(err =>{
+    console.log(err)
+    ElMessage({
+      message: err,
+      type:'error'
+    })
+  })
+}
+</script>
+
+<style scoped lang="scss">
+//@import '../../styles/login.scss';
+/* 将源码中的css样式单独存放,在各组件中导入就可以。*/
+$bg: #edf2f0;
+
+$neu-1: #ecf0f3;
+$neu-2: #d1d9e6;
+
+$white: #f9f9f9;
+$gray: #a0a5a8;
+$black: #181818;
+
+$purple: #4b70e2;
+
+$transition: 1.25s;
+
+*,
+*::after,
+*::before {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  user-select: none;
+}
+/* Generic */
+body {
+  width: 100%;
+  height: 100vh;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-family: 'Montserrat', sans-serif;
+  font-size: 12px;
+  background-color: $neu-1;
+  color: $gray;
+}
+/**/
+.main {
+  position: relative;
+  width: 1000px;
+  min-width: 1000px;
+  min-height: 600px;
+  height: 600px;
+  padding: 25px;
+  background-color: $neu-1;
+  box-shadow:
+      10px 10px 10px $neu-2,
+      -10px -10px 10px $white;
+  border-radius: 12px;
+  overflow: hidden;
+  @media (max-width: 1200px) {
+    transform: scale(0.7);
+  }
+  @media (max-width: 1000px) {
+    transform: scale(0.6);
+  }
+  @media (max-width: 800px) {
+    transform: scale(0.5);
+  }
+  @media (max-width: 600px) {
+    transform: scale(0.4);
+  }
+}
+.container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  position: absolute;
+
+  top: 0;
+  width: 600px;
+  height: 100%;
+  padding: 25px;
+
+  background-color: $neu-1;
+  transition: $transition;
+}
+.form {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+  width: 100%;
+  height: 100%;
+
+  &__icon {
+    object-fit: contain;
+    width: 30px;
+    margin: 0 5px;
+    opacity: 0.5;
+    transition: 0.15s;
+
+    &:hover {
+      opacity: 1;
+      transition: 0.15s;
+      cursor: pointer;
+    }
+  }
+  &__input {
+    width: 350px;
+    height: 40px;
+    margin: 4px 0;
+
+    padding-left: 25px;
+    font-size: 13px;
+    letter-spacing: 0.15px;
+    border: none;
+    outline: none;
+
+    font-family: 'Montserrat', sans-serif;
+    background-color: $neu-1;
+    transition: 0.25s ease;
+    border-radius: 8px;
+
+    box-shadow:
+        inset 2px 2px 4px $neu-2,
+        inset -2px -2px 4px $white;
+
+    &:focus {
+      box-shadow:
+          inset 4px 4px 4px $neu-2,
+          inset -4px -4px 4px $white;
+    }
+  }
+  &__span {
+    margin-top: 30px;
+    margin-bottom: 12px;
+  }
+  &__link {
+    color: $black;
+    font-size: 15px;
+    margin-top: 25px;
+    border-bottom: 1px solid $gray;
+    line-height: 2;
+  }
+}
+.title {
+  font-size: 34px;
+  font-weight: 700;
+  line-height: 3;
+  color: $black;
+}
+.description {
+  font-size: 14px;
+  letter-spacing: 0.25px;
+  text-align: center;
+  line-height: 1.6;
+}
+.button {
+  width: 180px;
+  height: 50px;
+  border-radius: 25px;
+  margin-top: 50px;
+  font-weight: 700;
+  font-size: 14px;
+  letter-spacing: 1.15px;
+
+  background-color: $purple;
+  color: $white;
+  box-shadow:
+      8px 8px 16px $neu-2,
+      -8px -8px 16px $white;
+
+  border: none;
+  outline: none;
+}
+/**/
+
+.a-container {
+  z-index: 100;
+  left: calc(100% - 600px);
+}
+.b-container {
+  left: calc(100% - 600px);
+  z-index: 0;
+}
+
+.switch {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 100%;
+  width: 400px;
+
+  padding: 50px;
+  z-index: 200;
+  transition: $transition;
+
+  background-color: $neu-1;
+  overflow: hidden;
+
+  box-shadow:
+      4px 4px 10px $neu-2,
+      -4px -4px 10px $white;
+
+  &__circle {
+    position: absolute;
+    width: 500px;
+    height: 500px;
+    border-radius: 50%;
+    background-color: $neu-1;
+    box-shadow:
+        inset 8px 8px 12px $neu-2,
+        inset -8px -8px 12px $white;
+
+    bottom: -60%;
+    left: -60%;
+    transition: $transition;
+
+    &--t {
+      top: -30%;
+      left: 60%;
+      width: 300px;
+      height: 300px;
+    }
+  }
+
+  &__container {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex-direction: column;
+    position: absolute;
+    width: 400px;
+    padding: 50px 55px;
+
+    transition: $transition;
+  }
+
+  &__button {
+    cursor: pointer;
+    &:hover {
+      box-shadow:
+          6px 6px 10px $neu-2,
+          -6px -6px 10px $white;
+      transform: scale(0.985);
+      transition: 0.25s;
+    }
+    &:active,
+    &:focus {
+      box-shadow:
+          2px 2px 6px $neu-2,
+          -2px -2px 6px $white;
+      transform: scale(0.97);
+      transition: 0.25s;
+    }
+  }
+}
+/**/
+.is-txr {
+  left: calc(100% - 400px);
+  transition: $transition;
+  transform-origin: left;
+}
+.is-txl {
+  left: 0;
+  transition: $transition;
+  transform-origin: right;
+}
+.is-z200 {
+  z-index: 200;
+  transition: $transition;
+}
+.is-hidden {
+  visibility: hidden;
+  opacity: 0;
+  position: absolute;
+  transition: $transition;
+}
+.is-gx {
+  animation: is-gx $transition;
+}
+@keyframes is-gx {
+  0%,
+  10%,
+  100% {
+    width: 400px;
+  }
+  30%,
+  50% {
+    width: 500px;
+  }
+}
+
+</style>

+ 344 - 0
BI_front/src/components/switch.vue

@@ -0,0 +1,344 @@
+<template>
+  <div id="switch-cnt" class="switch">
+    <div class="switch__circle"></div>
+    <div class="switch__circle switch__circle--t"></div>
+    <!-- 状态1 -->
+    <div id="switch-c1" class="switch__container">
+      <h2 class="switch__title title">Welcome Back !</h2>
+      <p class="switch__description description">
+        To keep connected with us please login with your personal info
+      </p>
+      <button class="switch__button button switch-btn" @click="change">
+        SIGN IN
+      </button>
+    </div>
+    <!-- 状态2 -->
+    <div id="switch-c2" class="switch__container is-hidden">
+      <h2 class="switch__title title">Hello Friend !</h2>
+      <p class="switch__description description">
+        Enter your personal details and start journey with us
+      </p>
+      <button class="switch__button button switch-btn" @click="change">
+        SIGN UP
+      </button>
+    </div>
+  </div>
+</template>
+
+<script setup >
+import { useMainStore } from '@/stores/message'
+const mainStore = useMainStore()
+
+const change = () => {
+  // 加上as any防止ts类型检查报错
+  const switchC1 = document.querySelector('#switch-c1')
+  const switchC2 = document.querySelector('#switch-c2')
+  const switchCircle = document.querySelectorAll('.switch__circle')
+  const switchCtn = document.querySelector('#switch-cnt')
+
+  switchCtn.classList.add('is-gx')
+  setTimeout(function () {
+    switchCtn.classList.remove('is-gx')
+  }, 1500)
+  switchCtn.classList.toggle('is-txr')
+  switchCircle[0].classList.toggle('is-txr')
+  switchCircle[1].classList.toggle('is-txr')
+
+  switchC1.classList.toggle('is-hidden')
+  switchC2.classList.toggle('is-hidden')
+
+  mainStore.showSignup = !mainStore.showSignup
+}
+</script>
+
+<style scoped lang="scss">
+//@import '../../styles/login.scss';
+/*
+  将源码中的css样式单独存放,在各组件中导入就可以。
+  后续整理代码时建议,将全局样式和局部样式分开。
+  */
+$bg: #edf2f0;
+
+$neu-1: #ecf0f3;
+$neu-2: #d1d9e6;
+
+$white: #f9f9f9;
+$gray: #a0a5a8;
+$black: #181818;
+
+$purple: #4b70e2;
+
+$transition: 1.25s;
+
+*,
+*::after,
+*::before {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  user-select: none;
+}
+/* Generic */
+body {
+  width: 100%;
+  height: 100vh;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-family: 'Montserrat', sans-serif;
+  font-size: 12px;
+  background-color: $neu-1;
+  color: $gray;
+}
+/**/
+.main {
+  position: relative;
+  width: 1000px;
+  min-width: 1000px;
+  min-height: 600px;
+  height: 600px;
+  padding: 25px;
+  background-color: $neu-1;
+  box-shadow:
+      10px 10px 10px $neu-2,
+      -10px -10px 10px $white;
+  border-radius: 12px;
+  overflow: hidden;
+  @media (max-width: 1200px) {
+    transform: scale(0.7);
+  }
+  @media (max-width: 1000px) {
+    transform: scale(0.6);
+  }
+  @media (max-width: 800px) {
+    transform: scale(0.5);
+  }
+  @media (max-width: 600px) {
+    transform: scale(0.4);
+  }
+}
+.container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  position: absolute;
+
+  top: 0;
+  width: 600px;
+  height: 100%;
+  padding: 25px;
+
+  background-color: $neu-1;
+  transition: $transition;
+}
+.form {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+  width: 100%;
+  height: 100%;
+
+  &__icon {
+    object-fit: contain;
+    width: 30px;
+    margin: 0 5px;
+    opacity: 0.5;
+    transition: 0.15s;
+
+    &:hover {
+      opacity: 1;
+      transition: 0.15s;
+      cursor: pointer;
+    }
+  }
+  &__input {
+    width: 350px;
+    height: 40px;
+    margin: 4px 0;
+
+    padding-left: 25px;
+    font-size: 13px;
+    letter-spacing: 0.15px;
+    border: none;
+    outline: none;
+
+    font-family: 'Montserrat', sans-serif;
+    background-color: $neu-1;
+    transition: 0.25s ease;
+    border-radius: 8px;
+
+    box-shadow:
+        inset 2px 2px 4px $neu-2,
+        inset -2px -2px 4px $white;
+
+    &:focus {
+      box-shadow:
+          inset 4px 4px 4px $neu-2,
+          inset -4px -4px 4px $white;
+    }
+  }
+  &__span {
+    margin-top: 30px;
+    margin-bottom: 12px;
+  }
+  &__link {
+    color: $black;
+    font-size: 15px;
+    margin-top: 25px;
+    border-bottom: 1px solid $gray;
+    line-height: 2;
+  }
+}
+.title {
+  font-size: 34px;
+  font-weight: 700;
+  line-height: 3;
+  color: $black;
+}
+.description {
+  font-size: 14px;
+  letter-spacing: 0.25px;
+  text-align: center;
+  line-height: 1.6;
+}
+.button {
+  width: 180px;
+  height: 50px;
+  border-radius: 25px;
+  margin-top: 50px;
+  font-weight: 700;
+  font-size: 14px;
+  letter-spacing: 1.15px;
+
+  background-color: $purple;
+  color: $white;
+  box-shadow:
+      8px 8px 16px $neu-2,
+      -8px -8px 16px $white;
+
+  border: none;
+  outline: none;
+}
+/**/
+
+.a-container {
+  z-index: 100;
+  left: calc(100% - 600px);
+}
+.b-container {
+  left: calc(100% - 600px);
+  z-index: 0;
+}
+
+.switch {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 100%;
+  width: 400px;
+
+  padding: 50px;
+  z-index: 200;
+  transition: $transition;
+
+  background-color: $neu-1;
+  overflow: hidden;
+
+  box-shadow:
+      4px 4px 10px $neu-2,
+      -4px -4px 10px $white;
+
+  &__circle {
+    position: absolute;
+    width: 500px;
+    height: 500px;
+    border-radius: 50%;
+    background-color: $neu-1;
+    box-shadow:
+        inset 8px 8px 12px $neu-2,
+        inset -8px -8px 12px $white;
+
+    bottom: -60%;
+    left: -60%;
+    transition: $transition;
+
+    &--t {
+      top: -30%;
+      left: 60%;
+      width: 300px;
+      height: 300px;
+    }
+  }
+
+  &__container {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex-direction: column;
+    position: absolute;
+    width: 400px;
+    padding: 50px 55px;
+
+    transition: $transition;
+  }
+
+  &__button {
+    cursor: pointer;
+    &:hover {
+      box-shadow:
+          6px 6px 10px $neu-2,
+          -6px -6px 10px $white;
+      transform: scale(0.985);
+      transition: 0.25s;
+    }
+    &:active,
+    &:focus {
+      box-shadow:
+          2px 2px 6px $neu-2,
+          -2px -2px 6px $white;
+      transform: scale(0.97);
+      transition: 0.25s;
+    }
+  }
+}
+/**/
+.is-txr {
+  left: calc(100% - 400px);
+  transition: $transition;
+  transform-origin: left;
+}
+.is-txl {
+  left: 0;
+  transition: $transition;
+  transform-origin: right;
+}
+.is-z200 {
+  z-index: 200;
+  transition: $transition;
+}
+.is-hidden {
+  visibility: hidden;
+  opacity: 0;
+  position: absolute;
+  transition: $transition;
+}
+.is-gx {
+  animation: is-gx $transition;
+}
+@keyframes is-gx {
+  0%,
+  10%,
+  100% {
+    width: 400px;
+  }
+  30%,
+  50% {
+    width: 500px;
+  }
+}
+
+</style>

+ 10 - 2
BI_front/src/main.js

@@ -1,5 +1,13 @@
 import { createApp } from 'vue'
-import './style.css'
+// import './style.css'
 import App from './App.vue'
+import router from './router'
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+//全局样式
+// import 'virtual:uno.css'
+import {createPinia} from 'pinia'
+// 创建 Pinia 实例
+const pinia = createPinia()
 
-createApp(App).mount('#app')
+createApp(App).use(router).use(ElementPlus).use(pinia).mount('#app')

+ 74 - 0
BI_front/src/router/index.js

@@ -0,0 +1,74 @@
+// 引入需要的模块
+import { createRouter,  createWebHistory } from 'vue-router'
+import {getUserInfo, login} from "../common/api/user/index.js";
+import {getToken} from "../common/utils/auth.js";
+import {ElMessage} from "element-plus";
+
+// 下面使用了es6的对象增强写法,命名必须是routes
+const routes = [
+    {
+        path: '/',
+        redirect: 'index'
+    },
+    {
+        path: '/login',
+        name:'login',
+        // 已经配置了路径别名,@/view 就可以写成 view
+        // 配置了extensions,login.vue可以写成login
+        component: () => import('@/views/login'),
+        beforeEnter:(to, from, next) =>{
+             // 我们也可以自定义鉴权等逻辑操作
+            next();
+         }
+    },
+    {
+        path: '/index',
+        name: 'index',
+        component: () => import('@/views/home'),
+        meta:{
+            // 自定义一个字段,标识需要进行鉴权
+            logined:false
+        },
+    }
+]
+
+// 创建路由
+const router = createRouter({
+    history: createWebHistory(),
+    routes
+})
+
+// 全局前置路由守卫
+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()
+});
+
+//全局后置路由守卫
+router.afterEach((to,from)=>{
+    // to是目标路由对象,from当前路由对象
+    // console.log(to,from);
+    // 后置路由守卫没有next函数,因为已经切换路由,不需要进行放行
+    // 我们可以在这里进行一些后续的善后操作,比如设置切换路由后网页的标题
+});
+
+// 导出路由
+export default router

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

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

+ 13 - 0
BI_front/src/stores/message.js

@@ -0,0 +1,13 @@
+import { defineStore } from 'pinia'
+
+export const useMainStore = defineStore('main', {
+    state: () => ({
+        showSignup: true
+    }),
+    getters: {
+
+    },
+    actions: {
+
+    },
+})

+ 17 - 0
BI_front/src/views/home.vue

@@ -0,0 +1,17 @@
+<template>
+  <HelloWorld/>
+</template>
+
+<script>
+import HelloWorld from '@/components/HelloWorld.vue'
+export default {
+  components:{
+    HelloWorld
+  },
+  name: "home"
+}
+</script>
+
+<style scoped>
+
+</style>

+ 82 - 0
BI_front/src/views/login.vue

@@ -0,0 +1,82 @@
+<template>
+  <div class="body">
+    <div class="main">
+      <LoginSigin_in />
+      <LoginSign_up />
+      <LoginSwitch />
+    </div>
+  </div>
+
+</template>
+
+<script setup>
+import LoginSigin_in from '@/components/siginIn.vue'
+import LoginSign_up from '@/components/siginUp.vue'
+import LoginSwitch from '@/components/switch.vue'
+import '@/assets/styles/var.less'
+import '@/assets/styles/mixin.less'
+import '@/assets/styles/global.less'
+
+</script>
+
+<style scoped>
+*,
+*::after,
+*::before {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  user-select: none;
+}
+
+.body {
+  width: 100%;
+  height: 100vh;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-family: 'Montserrat', sans-serif;
+  font-size: 12px;
+  background-color: #ecf0f3;
+  color: #a0a5a8;
+}
+
+.main {
+  position: relative;
+  width: 1000px;
+  min-width: 1000px;
+  min-height: 600px;
+  height: 600px;
+  padding: 25px;
+  background-color: #ecf0f3;
+  box-shadow:
+      10px 10px 10px #d1d9e6,
+      -10px -10px 10px #f9f9f9;
+  border-radius: 20px;
+  overflow: hidden;
+}
+
+@media (max-width: 1200px) {
+  .main {
+    transform: scale(0.7);
+  }
+}
+
+@media (max-width: 1000px) {
+  .main {
+    transform: scale(0.6);
+  }
+}
+
+@media (max-width: 800px) {
+  .main {
+    transform: scale(0.5);
+  }
+}
+
+@media (max-width: 600px) {
+  .main {
+    transform: scale(0.4);
+  }
+}
+</style>

+ 42 - 1
BI_front/vite.config.js

@@ -1,7 +1,48 @@
 import { defineConfig } from 'vite'
 import vue from '@vitejs/plugin-vue'
+import AutoImport from 'unplugin-auto-import/vite'
+import Components from 'unplugin-vue-components/vite'
+import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
+import path from 'path'
 
 // https://vite.dev/config/
 export default defineConfig({
-  plugins: [vue()],
+  plugins: [
+      vue(),
+    AutoImport({
+      resolvers: [ElementPlusResolver()],
+    }),
+    Components({
+      resolvers: [ElementPlusResolver()],
+    }),
+  ],
+  resolve: {
+    // 配置路径别名
+    alias: {
+      '@': path.resolve(__dirname, 'src'),
+      'views': '@/views',
+      'assets': '@/assets',
+      'common': '@/common',
+      'components': '@/components',
+      'network': '@/network',
+      'router': '@/router',
+      'store': '@/store'
+    },
+    // 省略文件后缀
+    extensions: ['', '.js', '.json', '.vue', '.scss', '.css']
+  },
+  // 代理配置
+  server: {
+    // https: true,
+    port:80,
+    proxy: {
+      '/api': { // 配置需要代理的路径 --> 这里的意思是代理http://localhost:80/api/后的所有路由
+        target: 'http://localhost:8080', // 目标地址 --> 服务器地址
+        changeOrigin: true, // 允许跨域
+        ws: true,  // 允许websocket代理
+        // 重写路径 --> 作用与vue配置pathRewrite作用相同
+        rewrite: (path) => path.replace(/^\/api/, "")
+      }
+    },
+  },
 })

+ 1 - 0
serve/sql/init.sql

@@ -19,6 +19,7 @@ CREATE TABLE  if NOT EXISTS `user` (
 -- 创建一个名为charts的表,用于存储图表信息
 CREATE TABLE if NOT EXISTS `chart` (
                           `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '图表ID',
+                          `name` varchar(128) NULL COMMENT '图表名称',
                           `analysis_target` TEXT NOT NULL COMMENT '分析目标',
                           `chart_data` TEXT NOT NULL COMMENT '图标数据',
                           `chart_type` VARCHAR(255) NOT NULL COMMENT '图标类型',

+ 1 - 0
serve/src/main/java/space/anyi/BI/BIApplication.java

@@ -2,6 +2,7 @@ package space.anyi.BI;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.web.bind.annotation.CrossOrigin;
 
 /**
  * @ProjectName: BI

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

@@ -61,7 +61,7 @@ public class SecurityConfig  extends WebSecurityConfigurerAdapter {
                 .antMatchers(HttpMethod.GET,"/swagger/**","/v2/**","/v2/api-docs-ext/**","/swagger-resources/**").anonymous()
                 //放行对静态资源的访问
                 .antMatchers(HttpMethod.GET,"/","/*.html","/**/*.css","/**/*.js","/img/**","/fonts/**").permitAll()
-                .antMatchers(HttpMethod.GET,"/user/getVerifyCode","/book/getBookList","/book/*").permitAll()
+                .antMatchers(HttpMethod.GET,"/user/getUserInfo").permitAll()
                 .antMatchers(HttpMethod.POST,"/user/logout","/user/updatePassword").authenticated()
                 .antMatchers(HttpMethod.POST,"/uploadImage").permitAll()
                 // 除上面外的所有请求全部需要认证访问

+ 19 - 8
serve/src/main/java/space/anyi/BI/controller/UserController.java

@@ -1,15 +1,14 @@
 package space.anyi.BI.controller;
 
-import org.springframework.stereotype.Controller;
-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.ResponseBody;
+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.service.UserService;
+import space.anyi.BI.util.SecurityUtils;
 
 import javax.annotation.Resource;
+import java.util.Objects;
 
 /**
  * @ProjectName: BI
@@ -18,19 +17,31 @@ import javax.annotation.Resource;
  * @Data:2024/11/28 19:52
  * @Description:
  */
-@Controller()
 @RequestMapping("/user")
+@RestController
 public class UserController {
     @Resource
     private UserService userService;
+
     @PostMapping("/login")
-    @ResponseBody
     public ResponseResult login(@RequestBody User user){
         return userService.login(user);
     }
+
     @PostMapping("/register")
-    @ResponseBody
     public ResponseResult resister(@RequestBody User user){
         return userService.register(user);
     }
+
+    @GetMapping("/getUserInfo")
+    public ResponseResult getUserInfo(){
+        LoginUserDetails loginUser = SecurityUtils.getLoginUser();
+        if (Objects.isNull(loginUser)){
+            return ResponseResult.errorResult(ResponseResult.AppHttpCodeEnum.NEED_LOGIN);
+        }
+        User user = loginUser.getUser();
+        user.setUserPassword("");
+        System.out.println("user = " + user);
+        return ResponseResult.okResult(user);
+    }
 }

+ 5 - 0
serve/src/main/java/space/anyi/BI/entity/Chart.java

@@ -19,6 +19,11 @@ public class Chart implements Serializable {
     @TableId(type = IdType.AUTO)
     private Long id;
 
+    /**
+     * 图标名称
+     */
+    private String name;
+
     /**
      * 分析目标
      */

+ 16 - 1
serve/src/main/java/space/anyi/BI/util/SecurityUtils.java

@@ -4,6 +4,8 @@ import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
 import space.anyi.BI.entity.LoginUserDetails;
 
+import java.util.Objects;
+
 /**
  * @ProjectName: BI
  * @FileName: SecurityUtils
@@ -18,7 +20,20 @@ public class SecurityUtils {
      **/
     public static LoginUserDetails getLoginUser()
     {
-        return (LoginUserDetails) getAuthentication().getPrincipal();
+        Authentication authentication = getAuthentication();
+        if (Objects.isNull(authentication)) {
+            return null;
+        }
+
+        Object principal = authentication.getPrincipal();
+        if (Objects.isNull(principal)) {
+            return null;
+        }
+        //匿名用户
+        if (principal instanceof String){
+            return null;
+        }
+        return (LoginUserDetails) principal;
     }
 
     /**

+ 2 - 4
serve/src/main/resources/mapper/ChartMapper.xml

@@ -6,19 +6,17 @@
 
     <resultMap id="BaseResultMap" type="space.anyi.BI.entity.Chart">
             <id property="id" column="id" jdbcType="BIGINT"/>
+            <id property="name" column="name" jdbcType="VARCHAR"/>
             <result property="analysisTarget" column="analysis_target" jdbcType="VARCHAR"/>
             <result property="chartData" column="chart_data" jdbcType="VARCHAR"/>
             <result property="chartType" column="chart_type" jdbcType="VARCHAR"/>
             <result property="generatedChartData" column="generated_chart_data" jdbcType="VARCHAR"/>
             <result property="analysisConclusion" column="analysis_conclusion" jdbcType="VARCHAR"/>
-            <result property="createdBy" column="created_by" jdbcType="BIGINT"/>
-            <result property="createdAt" column="created_at" jdbcType="TIMESTAMP"/>
-            <result property="updatedAt" column="updated_at" jdbcType="TIMESTAMP"/>
             <result property="deleteFlag" column="delete_flag" jdbcType="TINYINT"/>
     </resultMap>
 
     <sql id="Base_Column_List">
-        id,analysis_target,chart_data,
+        id,name,analysis_target,chart_data,
         chart_type,generated_chart_data,analysis_conclusion,
         created_by,created_at,updated_at,
         delete_flag

Some files were not shown because too many files changed in this diff