Ver código fonte

Merge branch 'master' of http://gitlab.smartai.com/zhupei/smartai2dian0

张涛 1 ano atrás
pai
commit
9226aa54d1
54 arquivos alterados com 827 adições e 152 exclusões
  1. 18 0
      docs/dir-structure.md
  2. 6 6
      docs/vue-sfc.md
  3. 19 0
      public/i18n/lang-en.json
  4. 19 0
      public/i18n/lang-zh.json
  5. BIN
      public/logo.png
  6. BIN
      src/assets/arrow-down.png
  7. BIN
      src/assets/header-agent-1.png
  8. BIN
      src/assets/header-huihua-1.png
  9. BIN
      src/assets/header-moxingguanli-1.png
  10. BIN
      src/assets/header-quanping-1.png
  11. BIN
      src/assets/header-quanxian-1.png
  12. BIN
      src/assets/header-search-1.png
  13. BIN
      src/assets/header-setting-1.png
  14. BIN
      src/assets/header-zhinengti-1.png
  15. BIN
      src/assets/header-zhishiku-1.png
  16. BIN
      src/assets/image/login/bg.png
  17. BIN
      src/assets/image/login/login_group.png
  18. BIN
      src/assets/image/login/logo.png
  19. BIN
      src/assets/language-1.png
  20. BIN
      src/assets/logo.png
  21. BIN
      src/assets/theme.png
  22. BIN
      src/assets/属性=上@2x.png
  23. BIN
      src/assets/属性=标签.png
  24. BIN
      src/assets/属性=用户.png
  25. 2 3
      src/base/ajax.js
  26. 31 0
      src/base/api/login.js
  27. 1 1
      src/base/storage.js
  28. 1 1
      src/base/storage/cookie.js
  29. 60 1
      src/base/store/use-current-user.js
  30. 6 0
      src/components/HeadManage.vue
  31. 15 0
      src/index.css
  32. 1 1
      src/index.html
  33. 4 0
      src/index.js
  34. 1 0
      src/modules/chat-index/README.md
  35. 14 0
      src/utils/auth.js
  36. 12 0
      src/views/ConversationBoard.vue
  37. 12 0
      src/views/ConversationHistory.vue
  38. 23 0
      src/views/Home.vue
  39. 2 1
      src/views/Knowledge.vue
  40. 69 1
      src/views/Login.vue
  41. 0 24
      src/views/common/BtnLanguage.vue
  42. 0 40
      src/views/common/BtnTheme.vue
  43. 51 28
      src/views/common/Header.vue
  44. 73 4
      src/views/common/NavItem.vue
  45. 78 6
      src/views/common/PermissionDropdown.vue
  46. 13 0
      src/views/common/header-btns/BtnFullScreen.vue
  47. 44 0
      src/views/common/header-btns/BtnLanguage.vue
  48. 13 0
      src/views/common/header-btns/BtnSearch.vue
  49. 13 0
      src/views/common/header-btns/BtnSettings.vue
  50. 58 0
      src/views/common/header-btns/BtnTheme.vue
  51. 0 33
      src/views/common/header-i18n.js
  52. 163 0
      src/views/login-register/FormItem.vue
  53. 4 1
      src/views/routes.js
  54. 1 1
      vite.config.mjs

+ 18 - 0
docs/dir-structure.md

@@ -73,3 +73,21 @@
 基本原则是:`高层能引用低层`,但是`低层不能引用高层`
 
 例如:`views`目录能引用其他任意目录的功能,但是其他目录不能引入`views`目录的功能
+
+## 目录深度
+
+目录深度请保持在`2-3`层,以`2`层为佳,`3`层次之,最多`4`层,不能超过`4`层(项目中目录深度,最深最深为4层)。
+
+以下为`错误示例`
+
+```
+|-/
+  |- modules/
+    |- account/
+      |- user/
+        |- profile/
+          |- add/
+            |- addForm.js
+```
+
+> 目录深度相对于`index.html`文件而言。并非`package.json`

+ 6 - 6
docs/vue-sfc.md

@@ -267,12 +267,12 @@ const status = ref(1);
 1. `import`
 2. `defineProps`
 3. `defineEmits`
-4. `defineExpose`
-5. `ref`、`reactive`、`let`、`const`、`var`等
-6. `computed`
-7. `watch`
-8. `事件处理`方法
-9. `生命周期钩子`:`onCreated`、`onMounted`、`onUpdated`、`onBeforeUnmount`
+4. `ref`、`reactive`、`let`、`const`、`var`等
+5. `computed`
+6. `watch`
+7. `事件处理`方法
+8. `生命周期钩子`:`onCreated`、`onMounted`、`onUpdated`、`onBeforeUnmount`
+9. `defineExpose`
 
 ### css属性规则的顺序
 

+ 19 - 0
public/i18n/lang-en.json

@@ -1,4 +1,11 @@
 {
+  "site": {
+    "name": "SmartAI LM Platform"
+  },
+  "theme": {
+    "light": "Light Mode",
+    "dark": "Dark Mode"
+  },
   "message": {
     "hello": "hello world"
   },
@@ -14,5 +21,17 @@
     "language": "Language",
     "theme": "Theme",
     "setting": "Settings"
+  },
+  "login": {
+    "logoTitle": "SmartAI Large Model Platform",
+    "titleTips": "Covering Most Typical Business Scenarios",
+    "loginModel": "User Login",
+    "registerModel": "User Registration",
+    "usernameTips": "Please enter your username",
+    "passwordTips": "Please enter your password",
+    "emailTips": "Please enter your email",
+    "loginBtn": "Login",
+    "registerBtn": "Register",
+    "noAccount": "Don't have an account?"
   }
 }

+ 19 - 0
public/i18n/lang-zh.json

@@ -1,4 +1,11 @@
 {
+  "site": {
+    "name": "SmartAI大模型平台"
+  },
+  "theme": {
+    "light": "亮色模式",
+    "dark": "暗色模式"
+  },
   "message": {
     "hello": "欢迎光临"
   },
@@ -14,5 +21,17 @@
     "language": "语言",
     "theme": "主题",
     "setting": "设置"
+  },
+  "login": {
+    "logoTitle": "SmartAI大模型平台",
+    "titleTips": "覆盖大多数典型业务场景",
+    "loginModel": "用户登录",
+    "registerModel": "用户注册",
+    "usernameTips": "请输入用户名",
+    "passwordTips": "请输入密码",
+    "emailTips": "请输入邮箱",
+    "loginBtn": "登录",
+    "registerBtn": "注册",
+    "noAccount": "没有账号?"
   }
 }

BIN
public/logo.png


BIN
src/assets/arrow-down.png


BIN
src/assets/header-agent-1.png


BIN
src/assets/header-huihua-1.png


BIN
src/assets/header-moxingguanli-1.png


BIN
src/assets/header-quanping-1.png


BIN
src/assets/header-quanxian-1.png


BIN
src/assets/header-search-1.png


BIN
src/assets/header-setting-1.png


BIN
src/assets/header-zhinengti-1.png


BIN
src/assets/header-zhishiku-1.png


BIN
src/assets/image/login/bg.png


BIN
src/assets/image/login/login_group.png


BIN
src/assets/image/login/logo.png


BIN
src/assets/language-1.png


BIN
src/assets/logo.png


BIN
src/assets/theme.png


BIN
src/assets/属性=上@2x.png


BIN
src/assets/属性=标签.png


BIN
src/assets/属性=用户.png


+ 2 - 3
src/base/ajax.js

@@ -1,6 +1,6 @@
 import axios from 'axios';
 
-import { localST_Authorization } from './storage';
+import { kuky_Authorization } from './storage';
 
 const ajax = axios.create({
   withCredentials: true, // 跨域发送 cookie
@@ -12,8 +12,7 @@ const ajax = axios.create({
 
 ajax.interceptors.request.use(
   (config) => {
-    const token = localST_Authorization.getItem();
-
+    const token = kuky_Authorization.get();
     if (token) {
       config.headers['Authorization'] = `Bearer ${token}`;
     }

+ 31 - 0
src/base/api/login.js

@@ -0,0 +1,31 @@
+import request from '../ajax';
+// 登录
+export function login(data) {
+  return request({
+    url: '/api/auth/v2/login',
+    method: 'post',
+    data
+  });
+}
+// 注册
+export function register(data) {
+  return request({
+    url: '/api/auth/v2/register',
+    method: 'post',
+    data
+  });
+}
+// 获取用户信息
+export function getInfo() {
+  return request({
+    url: '/api/user/user_info',
+    method: 'get'
+  });
+}
+// 获取用户路由
+export function generateRoutes() {
+  return request({
+    url: '/api/user/user_routers',
+    method: 'get'
+  });
+}

+ 1 - 1
src/base/storage.js

@@ -10,7 +10,6 @@ import { MyCookie } from './storage/cookie';
  */
 export const localST_Theme = new MyLocalStorage('theme'); // 主题
 export const localST_Locale = new MyLocalStorage('locale'); // 多语言
-export const localST_Authorization = new MyLocalStorage('token'); // 用户身份token
 
 /**
  * session storage定义
@@ -23,3 +22,4 @@ export const sessionST_Expire = new MySessionStorage('expire');
  * 任意 cookie 的定义,必须添加 kuky_ 前缀
  */
 export const kuky_UserName = new MyCookie('user_name');
+export const kuky_Authorization = new MyCookie('token'); // 用户身份token

+ 1 - 1
src/base/storage/cookie.js

@@ -15,7 +15,7 @@ export class MyCookie {
   }
 
   get() {
-    Cookies.get(this.key);
+    return Cookies.get(this.key);
   }
 
   set(value) {

+ 60 - 1
src/base/store/use-current-user.js

@@ -1,4 +1,6 @@
 import { defineStore } from 'pinia';
+import { login, getInfo } from '../../base/api/login';
+import { kuky_Authorization } from '../storage';
 
 /**
  * 当前登录的用户信息
@@ -6,12 +8,69 @@ import { defineStore } from 'pinia';
 export const useCurrentUserStore = defineStore('current_user', {
   state: () => {
     return {
-      name: 'Jack'
+      name: 'Jack',
+      token_type: '',
+      token: kuky_Authorization.get(),
+      username: '',
+      nickname: '',
+      dept: [],
+      roles: [],
+      permissions: [],
+      user: {}
     };
   },
   actions: {
     updateName(name) {
       this.name = name;
+    },
+    toLogin(data) {
+      const { username, password } = data;
+      return new Promise((resolve, reject) => {
+        login({ username, password })
+          .then((res) => {
+            if (res.code == 200 && res.data.access_token.length) {
+              this.token = res.data.access_token;
+              this.token_type = res.data.token_type;
+              this.username = res.data.username;
+              this.nickname = res.data.nickname;
+              kuky_Authorization.set(res.data.access_token);
+              resolve(res.msg);
+              /**
+               * 获取用户信息
+               * 暂时先在这里去调用,后续可以考虑放在路由拦截中去调用
+               */
+              this.getInfo();
+            } else {
+              reject(res.msg);
+            }
+          })
+          .catch((err) => {
+            reject(err);
+          });
+      });
+    },
+    getInfo() {
+      return new Promise((resolve, reject) => {
+        getInfo()
+          .then((res) => {
+            if (res.code == 200) {
+              this.dept = res.data.dept;
+              if (res.roles && res.roles.length > 0) {
+                // 验证返回的roles是否是一个非空数组
+                this.roles = res.roles;
+                this.permissions = res.permissions;
+              } else {
+                this.roles = ['ROLE_DEFAULT'];
+              }
+              resolve(res.msg);
+            } else {
+              reject(res.msg);
+            }
+          })
+          .catch((err) => {
+            reject(err);
+          });
+      });
     }
   }
 });

+ 6 - 0
src/components/HeadManage.vue

@@ -0,0 +1,6 @@
+<template>
+  <div>管理页面头部通用组件</div>
+</template>
+
+<script setup></script>
+<style scoped></style>

+ 15 - 0
src/index.css

@@ -0,0 +1,15 @@
+body {
+  font-size: 14px;
+  font-family: Inter, '-apple-system', BlinkMacSystemFont, 'PingFang SC', 'Hiragino Sans GB', 'noto sans',
+    'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+}
+
+body {
+  color: var(--color-text-2);
+  /* font-size: 14px; */
+  background-color: var(--color-bg-1);
+}
+
+a {
+  text-decoration: none;
+}

+ 1 - 1
src/index.html

@@ -2,7 +2,7 @@
 <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="/logo.png" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>大模型2.0</title>
     <script type="module" src="/config.js"></script>

+ 4 - 0
src/index.js

@@ -6,6 +6,9 @@ import { createApp } from 'vue';
  */
 import ArcoVue from '@arco-design/web-vue';
 import '@arco-design/web-vue/dist/arco.css';
+import ArcoVueIcon from '@arco-design/web-vue/es/icon';
+
+import './index.css';
 
 /**
  * 无界 微前端
@@ -28,6 +31,7 @@ const app = createApp(App);
 app.use(WujieVue);
 app.use(pinia);
 app.use(ArcoVue);
+app.use(ArcoVueIcon);
 app.use(router);
 
 setupI18n().then((i18n) => {

+ 1 - 0
src/modules/chat-index/README.md

@@ -0,0 +1 @@
+# 会话

+ 14 - 0
src/utils/auth.js

@@ -0,0 +1,14 @@
+export const loginFormRules = {
+  username: [
+    { required: true, message: '请输入用户名', trigger: 'blur' },
+    { min: 3, max: 20, message: '用户名长度在 3 到 20 个字符', trigger: 'blur' }
+  ],
+  email: [
+    { required: true, message: '请输入邮箱', trigger: 'blur' },
+    { type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
+  ],
+  password: [
+    { required: true, message: '请输入密码', trigger: 'blur' },
+    { min: 6, max: 20, message: '密码长度在 6 到 20 个字符', trigger: 'blur' }
+  ]
+};

+ 12 - 0
src/views/ConversationBoard.vue

@@ -0,0 +1,12 @@
+<template>
+  <LayoutExperience>
+    <ContentContainer>
+      <div class="conversation">智能体大全</div>
+    </ContentContainer>
+  </LayoutExperience>
+</template>
+
+<script setup>
+import LayoutExperience from './layout/LayoutExperience.vue';
+import ContentContainer from './layout/ContentContainer.vue';
+</script>

+ 12 - 0
src/views/ConversationHistory.vue

@@ -0,0 +1,12 @@
+<template>
+  <LayoutExperience>
+    <ContentContainer>
+      <div class="conversation">历史会话</div>
+    </ContentContainer>
+  </LayoutExperience>
+</template>
+
+<script setup>
+import LayoutExperience from './layout/LayoutExperience.vue';
+import ContentContainer from './layout/ContentContainer.vue';
+</script>

+ 23 - 0
src/views/Home.vue

@@ -6,6 +6,29 @@
   <ApiGetExample2 />
 
   <ApiGetExample3 />
+
+  <div>
+    <ul>
+      <li><router-link class="arco-link" to="/">首页</router-link></li>
+      <li><router-link class="arco-link" to="/login">登录</router-link></li>
+      <li><router-link class="arco-link" to="/reg">注册</router-link></li>
+      <li><router-link class="arco-link" to="/chat">会话</router-link></li>
+      <li><router-link class="arco-link" to="/chat/history">历史会话</router-link></li>
+      <li><router-link class="arco-link" to="/chat/board">会话-智能体</router-link></li>
+      <li><router-link class="arco-link" to="/knowledge">知识库</router-link></li>
+      <li><router-link class="arco-link" to="/model">模型管理</router-link></li>
+      <li><router-link class="arco-link" to="/agent">Agent</router-link></li>
+      <li><router-link class="arco-link" to="/intelligent">智能体管理</router-link></li>
+      <li><router-link class="arco-link" to="/permission">权限</router-link></li>
+      <li><router-link class="arco-link" to="/permission/org">机构</router-link></li>
+      <li><router-link class="arco-link" to="/permission/account">账号</router-link></li>
+      <li><router-link class="arco-link" to="/permission/role">角色</router-link></li>
+      <li><router-link class="arco-link" to="/permission/resource">资源</router-link></li>
+      <li><router-link class="arco-link" to="/profile">个人中心</router-link></li>
+      <li><router-link class="arco-link" to="/setting">设置</router-link></li>
+      <li><router-link class="arco-link" to="/about">关于</router-link></li>
+    </ul>
+  </div>
 </template>
 
 <script setup>

+ 2 - 1
src/views/Knowledge.vue

@@ -1,7 +1,7 @@
 <template>
   <LayoutExperience>
     <ContentContainer>
-      <div class="knowledge">知识库</div>
+      <HeadManage></HeadManage>
     </ContentContainer>
   </LayoutExperience>
 </template>
@@ -9,4 +9,5 @@
 <script setup>
 import LayoutExperience from './layout/LayoutExperience.vue';
 import ContentContainer from './layout/ContentContainer.vue';
+import HeadManage from '../components/HeadManage.vue';
 </script>

+ 69 - 1
src/views/Login.vue

@@ -1,3 +1,71 @@
 <template>
-  <div>登录</div>
+  <div class="login">
+    <div class="login-logo">
+      <img src="../assets/image/login/logo.png" alt="logo" />
+      <div class="logo-title">{{ $t('login.logoTitle') }}</div>
+    </div>
+    <div class="login-section">
+      <div class="intro">
+        <div class="intro-title">{{ $t('login.logoTitle') }}</div>
+        <div class="intro-title">{{ $t('login.titleTips') }}</div>
+        <img src="../assets/image/login/login_group.png" alt="" />
+      </div>
+      <div class="login-form">
+        <FormItem />
+      </div>
+    </div>
+  </div>
 </template>
+
+<script setup>
+import FormItem from './login-register/FormItem.vue';
+</script>
+<style scoped lang="css">
+.login {
+  width: 100vw;
+  height: 100vh;
+  background: url('../assets/image/login/bg.png') no-repeat;
+  background-size: cover;
+}
+.login-logo {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 30px;
+}
+.logo-title {
+  font-size: 24px;
+  font-weight: 600;
+}
+.login-section {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  gap: 100px;
+  height: calc(100% - 110px);
+}
+.intro {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+.intro-title {
+  font-size: 38px;
+  font-weight: 500;
+}
+.login-form {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 20px;
+  width: 500px;
+  height: 512px;
+  background-color: var(--color-bg-white);
+  border-radius: 10px;
+  box-shadow: 0 0 1px var(--color-neutral-6);
+}
+</style>

+ 0 - 24
src/views/common/BtnLanguage.vue

@@ -1,24 +0,0 @@
-<template>
-  <a-dropdown trigger="hover" @select="handleSelect">
-    <a-button>语言</a-button>
-
-    <template #content>
-      <a-doption value="zh">中文</a-doption>
-      <a-doption value="en">English</a-doption>
-    </template>
-  </a-dropdown>
-</template>
-
-<script setup>
-import { setI18nLanguage } from '../../i18n';
-
-const props = defineProps({
-  label: String
-});
-
-const handleSelect = (value) => {
-  setI18nLanguage(value);
-};
-</script>
-
-<style lang="css" scoped></style>

+ 0 - 40
src/views/common/BtnTheme.vue

@@ -1,40 +0,0 @@
-<template>
-  <a-dropdown trigger="hover" @select="handleSelect">
-    <a-button>主题</a-button>
-
-    <template #content>
-      <a-doption value="light">浅色主题</a-doption>
-      <a-doption value="dark">深色主题</a-doption>
-    </template>
-  </a-dropdown>
-</template>
-
-<script setup>
-import { setI18nLanguage } from '../../i18n';
-
-import { getCurrentTheme, setThemeDark, setThemeLight } from '../../base/theme';
-
-const props = defineProps({
-  label: String
-});
-
-/**
- * 设置系统主题
- * @param {string} theme - 主题 dark/theme
- */
-const handleThemeChange = (theme) => {
-  if (theme === 'dark') {
-    setThemeDark();
-  }
-
-  if (theme === 'light') {
-    setThemeLight();
-  }
-};
-
-const handleSelect = (value) => {
-  handleThemeChange(value);
-};
-</script>
-
-<style lang="css" scoped></style>

+ 51 - 28
src/views/common/Header.vue

@@ -1,43 +1,35 @@
 <template>
   <div class="header">
-    <div class="header-left">logo</div>
+    <div class="header-left">
+      <img class="header-logo-img" src="../../assets/logo.png" />
+
+      <span>{{ $t('site.name') }}</span>
+    </div>
 
     <div class="header-center">
       <a-space>
-        <NavItem :label="$t('header.conversation')" to="/conversation" />
-        <NavItem :label="$t('header.knowledge')" to="/knowledge" />
-        <NavItem :label="$t('header.model')" to="/model" />
-        <NavItem :label="$t('header.agent')" to="/agent" />
-        <NavItem :label="$t('header.intelligent')" to="/intelligent" />
+        <NavItem :label="$t('header.conversation')" to="/chat" icon_name="header-huihua" />
+        <NavItem :label="$t('header.knowledge')" to="/knowledge" icon_name="header-zhishiku" />
+        <NavItem :label="$t('header.model')" to="/model" icon_name="header-moxingguanli" />
+        <NavItem :label="$t('header.agent')" to="/agent" icon_name="header-agent" />
+        <NavItem :label="$t('header.intelligent')" to="/intelligent" icon_name="header-zhinengti" />
         <PermissionDropdown :label="$t('header.permission')" />
       </a-space>
     </div>
 
     <div class="header-right">
       <a-space>
-        <div class="header-right-item">
-          <span>{{ $t('search') }}</span>
-        </div>
+        <BtnSearch class="header-right-item" />
 
-        <!-- <div class="header-right-item" @click="handleLangaugeChange('zh')">
-          <span>{{ $t('language') }}</span>
-        </div> -->
+        <BtnLanguage class="header-right-item" />
 
-        <BtnLanguage />
+        <BtnTheme class="header-right-item" />
 
-        <!-- <div class="header-right-item" @click="handleThemeChange('dark')">
-          <span>{{ $t('theme') }}</span>
-        </div> -->
+        <BtnFullScreen class="header-right-item" @click="handleFullScreen" />
 
-        <BtnTheme />
+        <BtnSettings class="header-right-item" @click="handleNameChange" />
 
-        <div class="header-right-item" @click="handleFullScreen"><span>全屏</span></div>
-
-        <div class="header-right-item" @click="handleNameChange">
-          <span>{{ $t('setting') }}</span>
-        </div>
-
-        <div class="header-right-item">
+        <div class="header-right-profile">
           <span>{{ currentUser.name }}</span>
         </div>
       </a-space>
@@ -56,8 +48,11 @@ import NavItem from './NavItem.vue';
 
 import PermissionDropdown from './PermissionDropdown.vue';
 
-import BtnLanguage from './BtnLanguage.vue';
-import BtnTheme from './BtnTheme.vue';
+import BtnSearch from './header-btns/BtnSearch.vue';
+import BtnLanguage from './header-btns/BtnLanguage.vue';
+import BtnTheme from './header-btns/BtnTheme.vue';
+import BtnFullScreen from './header-btns/BtnFullScreen.vue';
+import BtnSettings from './header-btns/BtnSettings.vue';
 
 const currentUser = useCurrentUserStore();
 
@@ -108,20 +103,48 @@ const handleNameChange = () => {
 }
 
 .header-left {
-  width: 200px;
+  width: 240px;
   flex-shrink: 0;
-  background-color: var(--test-bg-color1);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  color: var(--color-text-1);
+  font-size: 18px;
+  font-weight: bold;
+}
+
+.header-logo-img {
+  width: 46px;
 }
 
 .header-center {
   flex-grow: 1;
+
+  padding-left: 15px;
+  padding-right: 15px;
 }
 
 .header-right {
   display: flex;
   justify-content: flex-end;
+  padding-right: 15px;
 }
 
 .header-right-item {
+  color: var(--color-text-2);
+  width: 30px;
+  height: 30px;
+  border-radius: 20px;
+  border: solid 1px var(--color-border-2);
+
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  cursor: pointer;
+}
+
+.header-right-profile {
 }
 </style>

+ 73 - 4
src/views/common/NavItem.vue

@@ -1,16 +1,85 @@
 <template>
   <router-link :to="to">
-    <a-button>
+    <div class="nav-item" :class="{ 'is-active': is_active }">
+      <span class="nav-item-icon"><img :src="img_src" /></span>
       <span>{{ label }}</span>
-    </a-button>
+    </div>
   </router-link>
 </template>
 
 <script setup>
+import { ref, computed } from 'vue';
+import { useRoute } from 'vue-router';
+
 const props = defineProps({
   label: String,
-  to: String
+  to: String,
+  icon_name: String
+});
+
+const is_active = computed(() => {
+  const target = props.to;
+  const route = useRoute();
+
+  const cur_path = route.path;
+
+  if (target === '/chat') {
+    // 对话
+    if (cur_path === '/chat' || cur_path === '/chat/history' || cur_path === '/chat/board') {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  return cur_path === props.to;
+});
+
+const img_src = computed(() => {
+  if (is_active.value) {
+    // TODO: 如果是激活状态,应该改为 2
+    // 资源目录应该添加名为 2 的图标
+    return `../../assets/${props.icon_name}-1.png`;
+  } else {
+    return `../../assets/${props.icon_name}-1.png`;
+  }
 });
 </script>
 
-<style lang="css" scoped></style>
+<style lang="css" scoped>
+.nav-item {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  height: 40px;
+  border-radius: 20px;
+
+  padding-left: 15px;
+  padding-right: 15px;
+
+  margin-left: 5px;
+  margin-right: 5px;
+
+  color: var(--color-text-1);
+}
+.nav-item:hover {
+  background-color: var(--color-fill-4);
+  color: #fff;
+}
+.nav-item.is-active {
+  background-color: rgb(var(--primary-6));
+  color: #fff;
+}
+
+.nav-item-icon {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  margin-right: 5px;
+}
+.nav-item-icon img {
+  width: 18px;
+}
+</style>

+ 78 - 6
src/views/common/PermissionDropdown.vue

@@ -1,22 +1,94 @@
 <template>
   <a-space size="large">
     <a-dropdown trigger="hover">
-      <a-button>{{ label }}</a-button>
+      <div class="nav-item" :class="{ 'is-active': is_active }">
+        <span class="nav-item-icon"><img :src="img_src" /></span>
+        <span>{{ label }}</span>
+        <span class="nav-item-icon arrow-down"><img src="../../assets/arrow-down.png" /></span>
+      </div>
 
       <template #content>
-        <a-doption><router-link to="/permission/role">角色</router-link></a-doption>
-        <a-doption><router-link to="/permission/account">账号</router-link></a-doption>
-        <a-doption><router-link to="/permission/org">机构</router-link></a-doption>
-        <a-doption><router-link to="/permission/resource">资源</router-link></a-doption>
+        <a-doption><router-link class="arco-link" to="/permission/role">角色</router-link></a-doption>
+        <a-doption><router-link class="arco-link" to="/permission/account">账号</router-link></a-doption>
+        <a-doption><router-link class="arco-link" to="/permission/org">机构</router-link></a-doption>
+        <a-doption><router-link class="arco-link" to="/permission/resource">资源</router-link></a-doption>
       </template>
     </a-dropdown>
   </a-space>
 </template>
 
 <script setup>
+import { ref, computed } from 'vue';
+import { useRoute } from 'vue-router';
+
 const props = defineProps({
   label: String
 });
+
+const is_active = computed(() => {
+  const route = useRoute();
+
+  const cur_path = route.path;
+
+  const active_list = ['/permission/role', '/permission/account', '/permission/org', '/permission/resource'];
+
+  return active_list.indexOf(cur_path) >= 0;
+});
+
+const img_src = computed(() => {
+  if (is_active.value) {
+    // TODO: 如果是激活状态,应该改为 2
+    // 资源目录应该添加名为 2 的图标
+    return `../../assets/header-quanxian-1.png`;
+  } else {
+    return `../../assets/header-quanxian-1.png`;
+  }
+});
 </script>
 
-<style lang="css" scoped></style>
+<style lang="css" scoped>
+.nav-item {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  height: 40px;
+  border-radius: 20px;
+
+  padding-left: 15px;
+  padding-right: 15px;
+
+  margin-left: 5px;
+  margin-right: 5px;
+
+  color: var(--color-text-1);
+
+  cursor: pointer;
+}
+.nav-item:hover {
+  background-color: var(--color-fill-4);
+  color: #fff;
+}
+.nav-item.is-active {
+  background-color: rgb(var(--primary-6));
+  color: #fff;
+}
+
+.nav-item-icon {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  margin-right: 5px;
+}
+.nav-item-icon img {
+  width: 18px;
+}
+
+.nav-item-icon.arrow-down {
+  margin-left: 2px;
+}
+.nav-item-icon.arrow-down img {
+  width: 14px;
+}
+</style>

+ 13 - 0
src/views/common/header-btns/BtnFullScreen.vue

@@ -0,0 +1,13 @@
+<template>
+  <div>
+    <img class="icon-fullscreen-img" src="../../../assets/header-quanping-1.png" />
+  </div>
+</template>
+
+<script setup></script>
+
+<style lang="css" scoped>
+.icon-fullscreen-img {
+  width: 16px;
+}
+</style>

+ 44 - 0
src/views/common/header-btns/BtnLanguage.vue

@@ -0,0 +1,44 @@
+<template>
+  <a-dropdown trigger="hover" @select="handleSelect">
+    <div class="header-right-language-item">
+      <img class="icon-language-img" src="../../../assets/language-1.png" />
+    </div>
+
+    <template #content>
+      <a-doption value="zh">中文</a-doption>
+      <a-doption value="en">English</a-doption>
+    </template>
+  </a-dropdown>
+</template>
+
+<script setup>
+import { setI18nLanguage } from '../../../i18n';
+
+const props = defineProps({
+  label: String
+});
+
+const handleSelect = (value) => {
+  setI18nLanguage(value);
+};
+</script>
+
+<style lang="css" scoped>
+.header-right-language-item {
+  color: var(--color-text-2);
+  width: 30px;
+  height: 30px;
+  border-radius: 20px;
+  border: solid 1px var(--color-border-2);
+
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  cursor: pointer;
+}
+
+.icon-language-img {
+  width: 16px;
+}
+</style>

+ 13 - 0
src/views/common/header-btns/BtnSearch.vue

@@ -0,0 +1,13 @@
+<template>
+  <div>
+    <img class="icon-search-img" src="../../../assets/header-search-1.png" />
+  </div>
+</template>
+
+<script setup></script>
+
+<style lang="css" scoped>
+.icon-search-img {
+  width: 16px;
+}
+</style>

+ 13 - 0
src/views/common/header-btns/BtnSettings.vue

@@ -0,0 +1,13 @@
+<template>
+  <div>
+    <img class="icon-fullscreen-img" src="../../../assets/header-setting-1.png" />
+  </div>
+</template>
+
+<script setup></script>
+
+<style lang="css" scoped>
+.icon-fullscreen-img {
+  width: 16px;
+}
+</style>

+ 58 - 0
src/views/common/header-btns/BtnTheme.vue

@@ -0,0 +1,58 @@
+<template>
+  <a-dropdown trigger="hover" @select="handleSelect">
+    <div class="header-right-theme-item">
+      <img class="icon-theme-img" src="../../../assets/theme.png" />
+    </div>
+
+    <template #content>
+      <a-doption value="light">{{ $t('theme.light') }}</a-doption>
+      <a-doption value="dark">{{ $t('theme.dark') }}</a-doption>
+    </template>
+  </a-dropdown>
+</template>
+
+<script setup>
+import { setThemeDark, setThemeLight } from '../../../base/theme';
+
+const props = defineProps({
+  label: String
+});
+
+/**
+ * 设置系统主题
+ * @param {string} theme - 主题 dark/theme
+ */
+const handleThemeChange = (theme) => {
+  if (theme === 'dark') {
+    setThemeDark();
+  }
+
+  if (theme === 'light') {
+    setThemeLight();
+  }
+};
+
+const handleSelect = (value) => {
+  handleThemeChange(value);
+};
+</script>
+
+<style lang="css" scoped>
+.header-right-theme-item {
+  color: var(--color-text-2);
+  width: 30px;
+  height: 30px;
+  border-radius: 20px;
+  border: solid 1px var(--color-border-2);
+
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  cursor: pointer;
+}
+
+.icon-theme-img {
+  width: 16px;
+}
+</style>

+ 0 - 33
src/views/common/header-i18n.js

@@ -1,33 +0,0 @@
-/**
- * 定义中文
- */
-export const header_zh = {
-  conversation: '会话',
-  knowledge: '知识库管理',
-  model: '模型管理',
-  agent: 'Agent',
-  intelligent: '智能体',
-  permission: '权限管理',
-
-  search: '搜索',
-  language: '语言',
-  theme: '主题',
-  setting: '设置'
-};
-
-/**
- * 定义英文
- */
-export const header_en = {
-  conversation: 'Conversation',
-  knowledge: 'Knowledge Base Management',
-  model: 'Model Management',
-  agent: 'Agent',
-  intelligent: 'Intelligent',
-  permission: 'Permission',
-
-  search: 'Search',
-  language: 'Language',
-  theme: 'Theme',
-  setting: 'Settings'
-};

+ 163 - 0
src/views/login-register/FormItem.vue

@@ -0,0 +1,163 @@
+<template>
+  <div class="form-title">
+    {{ formType ? $t('login.registerModel') : $t('login.loginModel') }}
+  </div>
+  <a-form
+    ref="refForm"
+    :model="form"
+    :rules="rules"
+    layout="validate"
+    :style="{ width: 'calc(100% - 40px)' }"
+    @submit-success="handleSubmit"
+  >
+    <a-form-item field="username">
+      <a-input v-model="form.username" :placeholder="$t('login.usernameTips')">
+        <template #prefix> <icon-user /> </template>
+      </a-input>
+    </a-form-item>
+    <a-form-item field="email" v-if="formType">
+      <a-input v-model="form.email" :placeholder="$t('login.emailTips')">
+        <template #prefix> <icon-email /> </template>
+      </a-input>
+    </a-form-item>
+    <a-form-item field="password">
+      <a-input-password
+        v-model="form.password"
+        :placeholder="$t('login.passwordTips')"
+        :defaultVisibility="true"
+        allow-clear
+      >
+        <template #prefix>
+          <icon-user />
+        </template>
+      </a-input-password>
+    </a-form-item>
+    <a-form-item>
+      <a-button v-if="!formType" :loading="loading" class="submit-btn" type="primary" html-type="submit"
+        >{{ $t('login.loginBtn') }}
+      </a-button>
+      <a-button v-if="formType" :loading="loading" class="submit-btn" type="primary" html-type="submit"
+        >{{ $t('login.registerBtn') }}
+      </a-button>
+    </a-form-item>
+  </a-form>
+  <div class="tips-register" v-if="!formType">
+    {{ $t('login.noAccount') }} <a class="reg-link" href="#" @click="changeFormType">{{ $t('login.registerBtn') }}</a>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, getCurrentInstance, nextTick } from 'vue';
+import { useRouter } from 'vue-router';
+import { useCurrentUserStore } from '../../base/store/use-current-user';
+import { loginFormRules } from '../../utils/auth';
+import { register } from '../../base/api/login';
+
+const router = useRouter();
+
+const { appContext } = getCurrentInstance();
+const userStore = useCurrentUserStore();
+
+const $message = appContext.config.globalProperties.$message;
+const refForm = ref();
+const form = reactive({
+  username: '',
+  email: '', // 非必填
+  password: ''
+});
+const rules = loginFormRules;
+/**
+ * 表单类型
+ * 0:登录
+ * 1:注册
+ */
+const formType = ref(0);
+const loading = ref(false);
+
+const handleSubmit = (data) => {
+  loading.value = true;
+  if (formType.value) {
+    // 注册
+    toRegister();
+  } else {
+    // 登录
+    userStore
+      .toLogin({ username: data.username, password: data.password })
+      .then((res) => {
+        $message.success(res);
+        router.push('/');
+      })
+      .catch((err) => {
+        $message.error(err);
+      })
+      .finally(() => {
+        loading.value = false;
+      });
+  }
+};
+const toRegister = () => {
+  register({ username: form.username, email: form.email, password: form.password })
+    .then((res) => {
+      if (res.code == 200 && res.data.username) {
+        $message.success(res.msg);
+        formType.value = 0;
+        refForm.value.resetFields();
+        nextTick(() => {
+          form.username = res.data.username;
+        });
+      } else {
+        $message.error(res.msg);
+      }
+    })
+    .finally(() => {
+      loading.value = false;
+    });
+};
+const changeFormType = (e) => {
+  e.preventDefault();
+  refForm.value.resetFields();
+  formType.value = 1;
+};
+</script>
+<style scoped>
+.form-title {
+  font-size: 30px;
+  line-height: 42px;
+  font-weight: 500;
+}
+.submit-btn {
+  width: 100%;
+  height: 54px;
+  background-color: rgb(var(--arcoblue-6));
+  border-radius: 3px;
+  color: var(--color-neutral-1);
+  font-size: 20px;
+}
+.submit-btn:hover {
+  background-color: rgba(var(--arcoblue-6), 0.8);
+  color: var(--color-neutral-1);
+}
+.tips-register {
+  position: absolute;
+  right: 20px;
+  bottom: 100px;
+  font-size: 20px;
+  color: var(--color-neutral-4);
+}
+.reg-link {
+  color: rgb(var(--arcoblue-6));
+}
+</style>
+<style>
+.arco-form-item-label-col {
+  padding: 0px;
+}
+.arco-input-wrapper {
+  height: 54px;
+  background-color: var(--color-bg-1);
+  border-color: var(--color-neutral-3);
+}
+.arco-form-item-label {
+  display: none;
+}
+</style>

+ 4 - 1
src/views/routes.js

@@ -11,7 +11,10 @@ const routes = [
   { path: '/reg', component: () => import('./Register.vue') },
 
   // 会话
-  { path: '/conversation', component: () => import('./Conversation.vue') },
+  { path: '/chat', component: () => import('./Conversation.vue') },
+  { path: '/chat/history', component: () => import('./ConversationHistory.vue') },
+  { path: '/chat/board', component: () => import('./ConversationBoard.vue') },
+
   // 知识库
   { path: '/knowledge', component: () => import('./Knowledge.vue') },
   // 模型

+ 1 - 1
vite.config.mjs

@@ -20,7 +20,7 @@ export default defineConfig({
     host: '0.0.0.0', // IP配置,支持从IP启动
     proxy: {
       '/api': {
-        target: 'http://smartai.com:8191',
+        target: 'http://192.168.20.119:9301',
         // target: "http://192.168.20.119:9301",
         changeOrigin: true,
         secure: false,