Răsfoiți Sursa

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

zhupei@smartai.com 1 an în urmă
părinte
comite
853bf998f4

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

@@ -14,5 +14,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?"
   }
 }

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

@@ -14,5 +14,17 @@
     "language": "语言",
     "theme": "主题",
     "setting": "设置"
+  },
+  "login": {
+    "logoTitle": "SmartAI大模型平台",
+    "titleTips": "覆盖大多数典型业务场景",
+    "loginModel": "用户登录",
+    "registerModel": "用户注册",
+    "usernameTips": "请输入用户名",
+    "passwordTips": "请输入密码",
+    "emailTips": "请输入邮箱",
+    "loginBtn": "登录",
+    "registerBtn": "注册",
+    "noAccount": "没有账号?"
   }
 }

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


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


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


+ 2 - 3
src/base/ajax.js

@@ -1,6 +1,6 @@
 import axios from 'axios';
 
-import { localST_Authorization } from './storage';
+import { cookieST_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 = cookieST_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 cookieST_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 { cookieST_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: cookieST_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;
+              cookieST_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>

+ 2 - 0
src/index.js

@@ -6,6 +6,7 @@ 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';
 
@@ -30,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) => {

+ 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' }
+  ]
+};

+ 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>

+ 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 { register } from '../../base/api/login';
+import { loginFormRules } from '../../utils/auth';
+import { ref, reactive, getCurrentInstance, nextTick } from 'vue';
+import { useCurrentUserStore } from '../../base/store/use-current-user';
+import { useRouter } from 'vue-router';
+
+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>

+ 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,