فهرست منبع

feat: 登录接口对接

sunxiao 9 ماه پیش
والد
کامیت
5ec8b97970

+ 4 - 0
.env.development

@@ -0,0 +1,4 @@
+# 请求地址
+VITE_BASE_URL=http://10.0.0.28:8080
+# 请求前缀
+VITE_BASE_PREFIX='' 

+ 9 - 0
env.d.ts

@@ -1 +1,10 @@
 /// <reference types="vite/client" />
+
+interface ImportMetaEnv {
+  readonly VITE_BASE_URL: string
+  readonly VITE_BASE_PREFIX: string
+}
+
+interface ImportMeta {
+  readonly env: ImportMetaEnv
+}

+ 10 - 1
package-lock.json

@@ -10,8 +10,9 @@
       "dependencies": {
         "axios": "^1.6.8",
         "load-awesome": "^1.1.0",
-        "naive-ui": "^2.24.0",
+        "naive-ui": "^2.38.2",
         "pinia": "^2.1.7",
+        "pinia-plugin-persistedstate": "^3.2.1",
         "sass": "^1.77.1",
         "sass-loader": "^14.2.1",
         "vue": "^3.4.21",
@@ -4767,6 +4768,14 @@
         }
       }
     },
+    "node_modules/pinia-plugin-persistedstate": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmmirror.com/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-3.2.1.tgz",
+      "integrity": "sha512-MK++8LRUsGF7r45PjBFES82ISnPzyO6IZx3CH5vyPseFLZCk1g2kgx6l/nW8pEBKxxd4do0P6bJw+mUSZIEZUQ==",
+      "peerDependencies": {
+        "pinia": "^2.0.0"
+      }
+    },
     "node_modules/pirates": {
       "version": "4.0.6",
       "resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.6.tgz",

+ 1 - 0
package.json

@@ -15,6 +15,7 @@
     "load-awesome": "^1.1.0",
     "naive-ui": "^2.38.2",
     "pinia": "^2.1.7",
+    "pinia-plugin-persistedstate": "^3.2.1",
     "sass": "^1.77.1",
     "sass-loader": "^14.2.1",
     "vue": "^3.4.21",

+ 16 - 13
src/App.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
 import { RouterView } from 'vue-router';
-import { NConfigProvider } from 'naive-ui';
+import { NConfigProvider, NMessageProvider } from 'naive-ui';
 import type { GlobalThemeOverrides } from 'naive-ui';
 import { SelectProps, InputProps, TableProps } from 'naive-ui'
 
@@ -14,7 +14,10 @@ const primaryColor = '#1A2029';
 const themeOverrides: GlobalThemeOverrides = {
   common: {
     primaryColor: '#2454FF',
-    tableHeaderColor: '#F3F6F9'
+    primaryColorHover: '#2454FF',
+    primaryColorPressed: '#2454FF',
+    tableHeaderColor: '#F3F6F9',
+
   },
   Menu: {
     itemTextColor: primaryColor,
@@ -56,6 +59,14 @@ const themeOverrides: GlobalThemeOverrides = {
     tabTextColorLine: '#272D35',
     tabFontWeightActive: 'bold'
   },
+  LoadingBar: {
+    colorLoading: '#000',
+  },
+  Button: {
+    colorHoverPrimary: '#1D43CC',
+    colorPressedPrimary: '#1D43CC',
+    borderPressedPrimary: '#1D43CC',
+  },
   DataTable: {
     // thTextColor: '#1A2029',
     // tdTextColor: primaryColor,
@@ -86,17 +97,9 @@ const themeOverrides: GlobalThemeOverrides = {
 
 <template>
   <NConfigProvider :theme-overrides="themeOverrides">
-    <RouterView />
-    <!-- <div class="w-full h-36 bg-black px-3 py-4 relative">
-
-      <div style="color: #9988cd" class="la-ball-running-dots la-sm">
-        <div></div>
-        <div></div>
-        <div></div>
-        <div></div>
-        <div></div>
-      </div>
-    </div> -->
+    <NMessageProvider>
+      <RouterView />
+    </NMessageProvider>
   </NConfigProvider>
 </template>
 

+ 15 - 0
src/api/chat.js

@@ -0,0 +1,15 @@
+import http from "@/utils/request";
+
+export const chatApi = {
+  /**
+   * 问答历史记录
+   * module
+   * 0 专家问答
+   * 1 智能工单 
+   * 2 智能体助手
+   * 3 告警
+   */
+  getAnswerHistoryList: params => http.get('/front/bigModel/qa/pageList', { params })
+}
+
+

+ 10 - 0
src/api/login.js

@@ -0,0 +1,10 @@
+import http from "@/utils/request";
+
+export const loginApi = {
+  /**
+   * 用户登录
+   */
+  postLogin: data => http.post('/login', data),
+}
+
+

+ 1 - 1
src/components/Layout/TheLogo.vue

@@ -1,6 +1,6 @@
 <script setup>
 import { storeToRefs } from 'pinia';
-import { useAppStore } from '@/stores/modules/app';
+import { useAppStore } from '@/stores/modules/appStore';
 
 import SvgIcon from '@/components/SvgIcon';
 import BasePopover from "@/components/BasePopover"

+ 2 - 2
src/components/Layout/TheSubMenu.vue

@@ -1,7 +1,7 @@
 <script setup lang="jsx">
 import { storeToRefs } from 'pinia';
 import { NScrollbar, NInfiniteScroll } from 'naive-ui';
-import { useAppStore } from '@/stores/modules/app';
+import { useAppStore } from '@/stores/modules/appStore';
 import SvgIcon from '@/components/SvgIcon';
 
 defineProps({
@@ -68,4 +68,4 @@ const handleLoad = () => emits('on-load');
     color: #666;
   }
 }
-</style>
+</style>@/stores/modules/appStore

+ 26 - 5
src/components/RecodeCardItem/index.vue

@@ -1,13 +1,34 @@
 <script setup>
-import SvgIcon from '@/components/SvgIcon';
+import { ref } from 'vue';
+import { NPopconfirm, useMessage  } from 'naive-ui';
+import { SvgIcon } from '@/components';
+
+defineProps(['question', 'createTime']);
+
+const message = useMessage();
+
+const handlePositiveClick = () => {
+  message.success('删除成功');
+}
+
 </script>
 
 <template>
   <div class="recode-card-item">
-    <p class="content">曝气池产生茶色也许文字很很少</p>
+    <p class="content">{{ question }}</p>
     <p class="time flex item-center justify-between w-full mt-[2px]">
-      <span>2024-04-26 10:12:13</span>
-      <SvgIcon name="tool-bucket-del" size="16" class="del-icon cursor-pointer hidden"></SvgIcon>
+      <span>{{ createTime }}</span>
+      <NPopconfirm
+        negative-text="取消"
+        positive-text="删除"
+        @positive-click="handlePositiveClick"
+      >
+      <!-- :on-update:show="" -->
+        <template #trigger>
+          <SvgIcon name="tool-bucket-del" size="16" class="del-icon cursor-pointer hidden"></SvgIcon>
+        </template>
+        删除后无法恢复,是否继续删除?
+      </NPopconfirm>
     </p>
   </div>
 </template>
@@ -27,7 +48,7 @@ import SvgIcon from '@/components/SvgIcon';
 
   &:hover {
     border-radius: 2px;
-    background-color: #F5F8FA;
+    background-color: #fff;
     box-shadow: 0.5px 0.5px 4px 0px #93A1B233;
     overflow: hidden;
 

+ 7 - 10
src/main.ts

@@ -1,21 +1,18 @@
-import 'virtual:svg-icons-register'
-
-import './assets/styles/tailwind.css'
-import './assets/styles/index.scss'
-
 import { createApp } from 'vue'
 
 import App from './App.vue'
-import router from './router/index.js'
-import pinia from './stores'
+import router from './router'
+import pinia from './stores/index.js'
 
-import SvgIcon from '@/components/SvgIcon/index.vue'
+import './permission'
+
+import 'virtual:svg-icons-register'
+import './assets/styles/tailwind.css'
+import './assets/styles/index.scss'
 
 const app = createApp(App)
 
 app.use(pinia)
 app.use(router)
 
-app.component('svg-icon', SvgIcon)
-
 app.mount('#app')

+ 43 - 0
src/permission.js

@@ -0,0 +1,43 @@
+import router from './router';
+import { createDiscreteApi} from 'naive-ui';
+import { useUserStore } from '@/stores/modules/userStore';
+
+const { loadingBar } = createDiscreteApi(['loadingBar'], {
+  loadingBarProviderProps: {
+    loadingBarStyle: {
+      backgroundColor: "#2454FF"
+    }
+  }
+})
+
+const whiteList = ['/login'];
+const TITLE_SUFFIX = ' - LibraAI人工智能运营体';
+
+router.beforeEach(async (to, from, next) => {
+
+  loadingBar.start();
+
+  const isRouterAuth = whiteList.includes(to.path);
+
+  if (isRouterAuth) {
+
+    next()
+
+  } else {
+
+    const userStore = useUserStore();
+    const { token } = userStore.userInfo;
+    
+    if ( !token ) {
+      return next('/login');
+    } else {
+      next()
+    }
+    
+  }
+})
+
+router.afterEach((to) => {
+  document.title = to?.meta?.title + TITLE_SUFFIX;
+  loadingBar.finish();
+})

+ 45 - 31
src/router/index.js

@@ -1,37 +1,51 @@
 import { createRouter, createWebHistory } from 'vue-router'
 
+const constantRouterMap = [
+  {
+    path: '/',
+    name: 'Scrren',
+    component: () => import('@/views/screen/ScreenView.vue'),
+    meta: {
+      title: "智慧总控"
+    }
+  },
+  {
+    path: '/login',
+    name: 'Login',
+    component: () => import('@/views/login/LoginView.vue'),
+    meta: {
+      title: "登录"
+    }
+  },
+  {
+    path: '/',
+    name: 'theBaseLayout',
+    component: () => import('@/components/Layout/ThePublicLayout.vue'),
+    children: [
+      {
+        path: 'answer',
+        name: 'Answer',
+        component: () => import('@/views/answer/AnswerView.vue'),
+        meta: {
+          title: '专家问答'
+        }
+      },
+      {
+        path: 'water-warn',
+        name: 'WaterWarn',
+        component: () => import('@/views/analyse/WaterView.vue'),
+        meta: {
+          title: '水质报警'
+        }
+      },
+    ]
+  },
+]
+
 const router = createRouter({
   history: createWebHistory(import.meta.env.BASE_URL),
-  routes: [
-    {
-      path: '/login',
-      name: 'Login',
-      component: () => import('@/views/login/LoginView.vue')
-    },
-    {
-      path: '/',
-      name: 'theBaseLayout',
-      component: () => import('@/components/Layout/ThePublicLayout.vue'),
-      children: [
-        {
-          path: 'answer',
-          name: 'Answer',
-          component: () => import('@/views/answer/AnswerView.vue'),
-          meta: {
-            title: '专家问答'
-          }
-        },
-        {
-          path: 'water-warn',
-          name: 'WaterWarn',
-          component: () => import('@/views/analyse/WaterView.vue'),
-          meta: {
-            title: '水质报警'
-          }
-        },
-      ]
-    },
-  ]
+  routes: constantRouterMap,
+  scrollBehavior: () => ({ left: 0, top: 0 })
 })
 
-export default router
+export default router;

+ 3 - 0
src/stores/index.ts

@@ -1,5 +1,8 @@
 import { createPinia } from "pinia";
+import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
 
 const pinia = createPinia();
 
+pinia.use(piniaPluginPersistedstate);
+
 export default pinia;

+ 17 - 17
src/stores/modules/app.js → src/stores/modules/appStore.js

@@ -1,18 +1,18 @@
-import { ref, computed } from 'vue'
-import { defineStore } from 'pinia'
-
-export const useAppStore = defineStore('app', () => {
-  const subMenuCollapse  = ref(true);
-
-  const toggleSubMenuCollapse = () => {
-    subMenuCollapse.value = !subMenuCollapse.value;
-  }
-
-  const count = ref(0)
-  const doubleCount = computed(() => count.value * 2)
-  function increment() {
-    count.value++
-  }
-
-  return { subMenuCollapse, toggleSubMenuCollapse}
+import { ref, computed } from 'vue'
+import { defineStore } from 'pinia'
+
+export const useAppStore = defineStore('app', () => {
+  const subMenuCollapse  = ref(true);
+
+  const toggleSubMenuCollapse = () => {
+    subMenuCollapse.value = !subMenuCollapse.value;
+  }
+
+  const count = ref(0)
+  const doubleCount = computed(() => count.value * 2)
+  function increment() {
+    count.value++
+  }
+
+  return { subMenuCollapse, toggleSubMenuCollapse}
 })

+ 17 - 0
src/stores/modules/userStore.js

@@ -0,0 +1,17 @@
+import { ref, computed } from 'vue'
+import { defineStore } from 'pinia'
+
+export const useUserStore = defineStore('user', () => {
+  const userInfo = ref({});
+
+  const setUserInfo = user => {
+    userInfo.value = user;
+  }
+
+  return {
+    userInfo,
+    setUserInfo
+  }
+}, {
+  persist: true,
+})

+ 14 - 8
src/types/data.d.ts

@@ -1,11 +1,17 @@
 /** 基础数据结构 - 子类选项 */
+// export type Result<T = any> = {
+//   code: number | null,
+//   data: T,
+//   message: string | null,
+//   pageCount: number,
+//   pageNum: number,
+//   pageSize: number,
+//   success: boolean,
+//   total: number
+// }
 export type Result<T = any> = {
-  code: number | null,
-  data: T,
-  message: string | null,
-  pageCount: number,
-  pageNum: number,
-  pageSize: number,
-  success: boolean,
-  total: number
+  code: number,
+  message: string,
+  rows?: T,
+  [key]: any
 }

+ 13 - 9
src/utils/request.ts

@@ -1,7 +1,8 @@
 import axios from 'axios';
+import { useUserStore } from '@/stores/modules/userStore';
 // import { createDiscreteApi } from 'naive-ui';
 
-import { tansParams, LocalCache, getQueryParamsAsObject } from "@/utils/tools";
+import { tansParams } from "@/utils/tools";
 
 import type { Result } from '@/types/data';
 import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig, AxiosError } from 'axios';
@@ -10,11 +11,13 @@ import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosReq
 
 // const { notification } = createDiscreteApi(["notification"]);
 
-const CACHE_KEY = 'userInfo';
+const useStore = useUserStore();
+
 const url = import.meta.env.VITE_BASE_URL;
 const prefix = import.meta.env.VITE_BASE_PREFIX;
 const baseURL = url + prefix;
 
+
 enum errorCode {
   '请求错误'          = 400,
   '未授权,请重新登录' = 401,
@@ -48,17 +51,16 @@ export class Request {
     this.instance = axios.create({ ...this.baseConfig, ...config });
 
     this.instance.interceptors.request.use((config: InternalAxiosRequestConfig<Result>) => {
+      const { token } = useStore.userInfo;
+
       if (config.method === "get" && config.params) {
         let url = config.url + '?' + tansParams(config.params);
         url = url.slice(0, -1);
         config.params = {};
         config.url = url;
       }
-      const urlParams = getQueryParamsAsObject();
-
-      const userInfo = LocalCache.getCache(CACHE_KEY);
-
-      config.headers.Authorization = urlParams.token || userInfo?.token ;
+      
+      token && (config.headers.Authorization = 'Bearer ' + token);
 
       return config;
     }, (err: any) => {
@@ -66,12 +68,14 @@ export class Request {
     });
 
     this.instance.interceptors.response.use(res => {
-      const { success, message } = res.data;
+      console.log("res", res);
+      const { code } = res.data;
       // !success && showNotification("error", message);
-      return success ? res.data : Promise.reject(res);
+      return code === 200 ? res.data : Promise.reject(res.data);
     }, (error: AxiosError) => {
       const errorMessage = errorCode[error.response?.status as number] || '未知错误';
       // showNotification("error", errorMessage);
+      console.log("error", error);
       return error;
     })
   }

+ 23 - 9
src/views/answer/AnswerView.vue

@@ -1,11 +1,23 @@
 <script setup>
-import SvgIcon from '@/components/SvgIcon';
-import BaseButton from "@/components/BaseButton";
-import RecodeCardItem from '@/components/RecodeCardItem';
+import { ref, reactive,onMounted } from 'vue';
+import { NPopconfirm } from 'naive-ui';
+import { SvgIcon, BaseButton, BaseInput, RecodeCardItem, TheSubMenu, TheChatView } from '@/components';
+import { chatApi } from '@/api/chat';
 
-import TheSubMenu from '@/components/Layout/TheSubMenu.vue';
-import TheChatView from '@/components/Layout/TheChatView.vue';
-import BaseInput from '@/components/BaseInput';
+const switchModelState = ref(true);
+
+const historyRecord = reactive({
+  rows: [],
+  total: 0
+})
+
+// const historyRecordData = ref({rows: [], total: 0});
+
+onMounted(async () => {
+  // 查询历史记录
+  const { rows, total } = await chatApi.getAnswerHistoryList({ module: 0 });
+  historyRecord.rows = rows;
+})
 
 // 新建对话
 const handleCreateDialog = () => {
@@ -18,19 +30,21 @@ const handleCreateDialog = () => {
     <TheSubMenu title="历史记录">
 
       <template #top>
+        
+     
         <div class="create-btn px-[11px] pb-[22px]">
           <BaseButton @click="handleCreateDialog">新建对话</BaseButton>
         </div>
       </template>
 
       <div class="pr-[4px] text-[#5e5e5e]">
-        <RecodeCardItem v-for="item in 100" :key="item" />
+        <RecodeCardItem v-for="item in historyRecord.rows" :key="item" v-bind="item" />
       </div>
 
     </TheSubMenu>
 
     <TheChatView>
-      <div class="chat-welcome">
+      <div class="chat-welcome" v-show="switchModelState">
         <div class="
           welcome
           flex flex-col items-center justify-between
@@ -51,7 +65,7 @@ const handleCreateDialog = () => {
         </dl>
       </div>
 
-      <div class="ask-inner">
+      <div class="ask-inner" v-show="!switchModelState">
         <div class="chat-ask_icon">
           <SvgIcon name="chat-avatar" size="20" />
         </div>

+ 60 - 7
src/views/login/LoginView.vue

@@ -1,7 +1,47 @@
 <script setup>
-import { ref, onMounted } from 'vue';
-import { NForm, useMessage } from 'naive-ui';
+import { ref, unref } from 'vue';
+import { useRouter } from 'vue-router';
+import { NButton } from 'naive-ui';
 import { SvgIcon } from '@/components';
+import { useUserStore } from '@/stores/modules/userStore';
+import { loginApi } from '@/api/login';
+
+const router = useRouter();
+const userStore = useUserStore();
+
+const errorMsg = ref('');
+const loading = ref(false);
+
+const loginFormData = ref({
+  username: 'admin',
+  password: 'admin123'
+})
+
+const handleSubmit = async () => {
+  const { username, password } = unref(loginFormData);
+  
+  if ( !username ) {
+    return errorMsg.value = '请输入用户名称'
+  }
+  if ( !password ) {
+    return errorMsg.value = '请输入登录密码'
+  }
+  
+  try {
+    loading.value = true;
+    const { token } = await loginApi.postLogin({ username, password });
+    errorMsg.value = '';
+    userStore.setUserInfo({ token });
+    router.push("/");
+  }
+  catch (error) {
+    errorMsg.value = error.msg;
+  }
+  finally {
+    loading.value = false;
+  }
+
+}
 
 </script>
 
@@ -27,26 +67,39 @@ import { SvgIcon } from '@/components';
         <div class="form-inner w-full">
           <ul class="form-inp-list">
             <li class="inp-item-inner">
-              <input type="text" placeholder="请输入用户名称">
+              <input type="text" placeholder="请输入用户名称" v-model="loginFormData.username">
             </li>
             <li class="inp-item-inner">
-              <input type="password" placeholder="请输入登录密码">
+              <input type="password" placeholder="请输入登录密码" v-model="loginFormData.password">
             </li>
           </ul>
           <div class="waring-feedback h-[32px] text-[12px] text-[#FF7152]">
-            <p class="flex items-center justify-center h-full space-x-[8px] bg-[#FFF1F0]" v-show="true">
+            <p class="flex items-center justify-center h-full space-x-[8px] bg-[#FFF1F0]" v-show="errorMsg">
               <SvgIcon name="login-warning-tips"></SvgIcon>
-              <span>账号或者密码错误</span>
+              <span>{{ errorMsg }}</span>
             </p>
           </div>
         </div>
-        <button class="btn w-full h-[54px] rounded-[4px] bg-[#2454FF] text-white test-[18ox]">登 录</button>
+        <NButton
+          block
+          color="#2454FF"
+          size="large"
+          :loading="loading"
+          @click="handleSubmit"
+        >登 录</NButton>
       </div>
     </main>
   </div>
 </template>
 
 <style scoped lang="scss">
+.n-button {
+  height: 54px;
+  &:hover {
+    background: #1d43cc;
+  }
+}
+
 .login-viewport {
   position: relative;
   @include flex(x, center, center);

+ 17 - 0
src/views/screen/ScreenView.vue

@@ -0,0 +1,17 @@
+<script setup>
+import { NButton } from 'naive-ui';
+import { useRouter } from 'vue-router';
+
+const router = useRouter();
+
+const tempJump = () => {
+  router.push('/answer')
+}
+</script>
+<template>
+
+  - Big Screen -
+
+  <NButton @click="tempJump">跳转</NButton>
+
+</template>