Преглед на файлове

feat:前端项目实现用户登录和注册

yang yi преди 11 месеца
родител
ревизия
22560de0af

+ 1 - 1
ui/yangyi-open-api-platform-ui/index.html

@@ -4,7 +4,7 @@
     <meta charset="UTF-8" />
     <link rel="icon" type="image/svg+xml" href="/vite.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Vite + Vue</title>
+    <title>open api platform</title>
   </head>
   <body>
     <div id="app"></div>

+ 1 - 0
ui/yangyi-open-api-platform-ui/package-lock.json

@@ -8,6 +8,7 @@
       "name": "yangyi-open-api-platform-ui",
       "version": "0.0.0",
       "dependencies": {
+        "@element-plus/icons-vue": "^2.3.2",
         "axios": "^1.12.2",
         "element-plus": "^2.11.4",
         "js-cookie": "^3.0.5",

+ 1 - 0
ui/yangyi-open-api-platform-ui/package.json

@@ -9,6 +9,7 @@
     "preview": "vite preview"
   },
   "dependencies": {
+    "@element-plus/icons-vue": "^2.3.2",
     "axios": "^1.12.2",
     "element-plus": "^2.11.4",
     "js-cookie": "^3.0.5",

+ 5 - 22
ui/yangyi-open-api-platform-ui/src/App.vue

@@ -1,30 +1,13 @@
 <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);
-}
+/*body{*/
+/*  margin: 0;*/
+/*  padding: 0;*/
+/*}*/
 </style>

+ 19 - 0
ui/yangyi-open-api-platform-ui/src/commom/api/user.js

@@ -0,0 +1,19 @@
+import request from '/src/commom/utils/request.js'
+
+// 用户登录
+export function login(data) {
+    return request({
+        url: '/user/login',
+        method: 'post',
+        data
+    })
+}
+
+// 用户注册
+export function register(data) {
+    return request({
+        url: '/user/register',
+        method: 'post',
+        data
+    })
+}

+ 1 - 1
ui/yangyi-open-api-platform-ui/src/main.js

@@ -1,6 +1,6 @@
 import { createApp } from 'vue'
 import ElementPlus from 'element-plus'
-import router from './router/index.js'
+import router from '/src/router/main.js'
 import 'element-plus/dist/index.css'
 import './style.css'
 import App from './App.vue'

+ 809 - 0
ui/yangyi-open-api-platform-ui/src/views/user/login.vue

@@ -0,0 +1,809 @@
+<template>
+  <div class="login-container">
+    <!-- 背景装饰元素 -->
+    <div class="background-shapes">
+      <div class="shape shape-1"></div>
+      <div class="shape shape-2"></div>
+      <div class="shape shape-3"></div>
+      <div class="shape shape-4"></div>
+    </div>
+
+    <div class="login-content">
+      <!-- 左侧品牌区域 -->
+      <div class="brand-section">
+        <div class="brand-content">
+          <div class="logo">
+            <i class="el-icon-star-filled logo-icon"></i>
+            <h1 class="brand-name">Your Logo</h1>
+          </div>
+          <h2 class="brand-title">
+            {{ isLogin ? '欢迎回来' : '加入我们' }}
+          </h2>
+          <p class="brand-description">
+            {{ isLogin
+              ? '请登录您的账户,继续享受我们的服务'
+              : '创建新账户,开启全新的体验旅程'
+            }}
+          </p>
+          <div class="feature-list">
+            <div class="feature-item">
+              <i class="el-icon-check feature-icon"></i>
+              <span>安全可靠的数据保护</span>
+            </div>
+            <div class="feature-item">
+              <i class="el-icon-check feature-icon"></i>
+              <span>简单易用的操作界面</span>
+            </div>
+            <div class="feature-item">
+              <i class="el-icon-check feature-icon"></i>
+              <span>全天候的技术支持</span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 右侧表单区域 -->
+      <div class="form-section">
+        <div class="form-wrapper">
+          <!-- 移动端品牌头 -->
+          <div class="mobile-header">
+            <div class="mobile-logo">
+              <i class="el-icon-star-filled"></i>
+              <span>Your Logo</span>
+            </div>
+          </div>
+
+          <div class="form-header">
+            <h1>{{ isLogin ? '用户登录' : '用户注册' }}</h1>
+            <p>{{ isLogin ? '请输入您的账户信息' : '请填写注册信息' }}</p>
+          </div>
+
+          <!-- 登录表单 -->
+          <el-form
+              v-if="isLogin"
+              ref="loginFormRef"
+              :model="loginForm"
+              :rules="loginRules"
+              class="auth-form"
+              size="large"
+              @submit.prevent="handleLogin"
+          >
+            <el-form-item prop="userAccount">
+              <el-input
+                  v-model="loginForm.userAccount"
+                  placeholder="请输入用户账号"
+                  :prefix-icon="User"
+                  clearable
+              />
+            </el-form-item>
+
+            <el-form-item prop="userPassword">
+              <el-input
+                  v-model="loginForm.userPassword"
+                  type="password"
+                  placeholder="请输入用户密码"
+                  :prefix-icon="Lock"
+                  show-password
+                  clearable
+                  @keyup.enter="handleLogin"
+              />
+            </el-form-item>
+
+            <div class="form-options">
+              <el-checkbox v-model="rememberMe">记住我</el-checkbox>
+              <el-button type="text" class="forgot-password">忘记密码?</el-button>
+            </div>
+
+            <el-form-item>
+              <el-button
+                  type="primary"
+                  :loading="loading"
+                  @click="handleLogin"
+                  class="auth-btn"
+              >
+                {{ loading ? '登录中...' : '登录' }}
+              </el-button>
+            </el-form-item>
+
+            <div class="divider">
+              <span>或</span>
+            </div>
+
+            <div class="social-login">
+              <el-button class="social-btn wechat">
+                <i class="el-icon-chat-dot-round"></i>
+                微信登录
+              </el-button>
+              <el-button class="social-btn qq">
+                <i class="el-icon-chat-line-round"></i>
+                QQ登录
+              </el-button>
+            </div>
+          </el-form>
+
+          <!-- 注册表单 -->
+          <el-form
+              v-else
+              ref="registerFormRef"
+              :model="registerForm"
+              :rules="registerRules"
+              class="auth-form"
+              size="large"
+              @submit.prevent="handleRegister"
+          >
+            <el-form-item prop="userAccount">
+              <el-input
+                  v-model="registerForm.userAccount"
+                  placeholder="请输入用户账号"
+                  :prefix-icon="User"
+                  clearable
+              />
+            </el-form-item>
+
+            <el-form-item prop="userPassword">
+              <el-input
+                  v-model="registerForm.userPassword"
+                  type="password"
+                  placeholder="请输入用户密码"
+                  :prefix-icon="Lock"
+                  show-password
+                  clearable
+              />
+            </el-form-item>
+
+            <el-form-item prop="userName">
+              <el-input
+                  v-model="registerForm.userName"
+                  placeholder="请输入用户名称"
+                  :prefix-icon="EditPen"
+                  clearable
+              />
+            </el-form-item>
+
+            <el-form-item>
+              <el-button
+                  type="primary"
+                  :loading="loading"
+                  @click="handleRegister"
+                  class="auth-btn"
+              >
+                {{ loading ? '注册中...' : '注册' }}
+              </el-button>
+            </el-form-item>
+
+            <div class="agreement">
+              <el-checkbox v-model="agreeTerms">
+                我已阅读并同意
+                <el-button type="text" class="link-btn">服务协议</el-button>
+                和
+                <el-button type="text" class="link-btn">隐私政策</el-button>
+              </el-checkbox>
+            </div>
+          </el-form>
+
+          <div class="form-footer">
+            <span class="switch-text">
+              {{ isLogin ? '还没有账户?' : '已有账户?' }}
+            </span>
+            <el-button type="text" @click="toggleForm" class="switch-btn">
+              {{ isLogin ? '立即注册' : '立即登录' }}
+            </el-button>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import { ElMessage } from 'element-plus'
+import { User, Lock, EditPen } from '@element-plus/icons-vue'
+import { login, register } from '/src/commom/api/user.js'
+
+const router = useRouter()
+
+// 响应式数据
+const isLogin = ref(true)
+const loading = ref(false)
+const rememberMe = ref(false)
+const agreeTerms = ref(false)
+const loginFormRef = ref()
+const registerFormRef = ref()
+
+// 表单数据
+const loginForm = reactive({
+  userAccount: '',
+  userPassword: ''
+})
+
+const registerForm = reactive({
+  userAccount: '',
+  userPassword: '',
+  userName: ''
+})
+
+// 表单验证规则
+const loginRules = reactive({
+  userAccount: [
+    { required: true, message: '请输入用户账号', trigger: 'blur' },
+    { min: 4, max: 20, message: '账号长度在 4 到 20 个字符', trigger: 'blur' }
+  ],
+  userPassword: [
+    { required: true, message: '请输入用户密码', trigger: 'blur' },
+    { min: 6, message: '密码长度不能少于6位', trigger: 'blur' }
+  ]
+})
+
+const registerRules = reactive({
+  userAccount: [
+    { required: true, message: '请输入用户账号', trigger: 'blur' },
+    { min: 4, max: 20, message: '账号长度在 4 到 20 个字符', trigger: 'blur' }
+  ],
+  userPassword: [
+    { required: true, message: '请输入用户密码', trigger: 'blur' },
+    { min: 6, message: '密码长度不能少于6位', trigger: 'blur' }
+  ],
+  userName: [
+    { required: true, message: '请输入用户名称', trigger: 'blur' },
+    { min: 2, max: 20, message: '名称长度在 2 到 20 个字符', trigger: 'blur' }
+  ]
+})
+
+// 方法
+const toggleForm = () => {
+  isLogin.value = !isLogin.value
+  if (loginFormRef.value) loginFormRef.value.resetFields()
+  if (registerFormRef.value) registerFormRef.value.resetFields()
+}
+
+const handleLogin = async () => {
+  if (!loginFormRef.value) return
+
+  try {
+    const valid = await loginFormRef.value.validate()
+    if (valid) {
+      loading.value = true
+      // 这里调用登录API
+      await new Promise(resolve => setTimeout(resolve, 1500)) // 模拟API调用
+      ElMessage.success('登录成功!')
+      router.push('/')
+    }
+  } catch (error) {
+    ElMessage.error('登录失败,请检查账号密码')
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleRegister = async () => {
+  if (!registerFormRef.value) return
+
+  if (!agreeTerms.value) {
+    ElMessage.warning('请同意服务协议和隐私政策')
+    return
+  }
+
+  try {
+    const valid = await registerFormRef.value.validate()
+    if (valid) {
+      loading.value = true
+      // 这里调用注册API
+      await new Promise(resolve => setTimeout(resolve, 1500)) // 模拟API调用
+      ElMessage.success('注册成功!')
+      toggleForm()
+    }
+  } catch (error) {
+    ElMessage.error('注册失败,请稍后重试')
+  } finally {
+    loading.value = false
+  }
+}
+
+onMounted(() => {
+  const token = localStorage.getItem('token')
+  if (token) {
+    router.push('/')
+  }
+})
+</script>
+
+<style scoped>
+.login-container {
+  min-height: 100vh;
+  min-width: 100vw;
+  height: 100vh;
+  width: 100vw;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  overflow: hidden;
+  margin: 0;
+  padding: 0;
+}
+
+.background-shapes {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  pointer-events: none;
+  z-index: 1;
+}
+
+.shape {
+  position: absolute;
+  border-radius: 50%;
+  background: rgba(255, 255, 255, 0.1);
+}
+
+.shape-1 {
+  width: 300px;
+  height: 300px;
+  top: -150px;
+  right: -150px;
+}
+
+.shape-2 {
+  width: 200px;
+  height: 200px;
+  bottom: -100px;
+  left: -100px;
+}
+
+.shape-3 {
+  width: 150px;
+  height: 150px;
+  top: 50%;
+  left: 10%;
+}
+
+.shape-4 {
+  width: 100px;
+  height: 100px;
+  bottom: 20%;
+  right: 10%;
+}
+
+.login-content {
+  display: flex;
+  width: 95%;
+  max-width: 1200px;
+  height: 90vh;
+  max-height: 800px;
+  background: white;
+  border-radius: 20px;
+  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
+  overflow: hidden;
+  position: relative;
+  z-index: 2;
+}
+
+/* 品牌区域样式 */
+.brand-section {
+  flex: 1;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: white;
+  padding: 60px 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  position: relative;
+  overflow: hidden;
+}
+
+.brand-section::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse"><path d="M 10 0 L 0 0 0 10" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="1"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>');
+}
+
+.brand-content {
+  position: relative;
+  z-index: 2;
+  text-align: center;
+  max-width: 400px;
+}
+
+.logo {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 12px;
+  margin-bottom: 40px;
+}
+
+.logo-icon {
+  font-size: 32px;
+  color: #ffd700;
+}
+
+.brand-name {
+  font-size: 24px;
+  font-weight: 700;
+  margin: 0;
+}
+
+.brand-title {
+  font-size: 32px;
+  font-weight: 700;
+  margin-bottom: 20px;
+  line-height: 1.2;
+}
+
+.brand-description {
+  font-size: 16px;
+  line-height: 1.6;
+  margin-bottom: 40px;
+  opacity: 0.9;
+}
+
+.feature-list {
+  text-align: left;
+}
+
+.feature-item {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 16px;
+  font-size: 14px;
+}
+
+.feature-icon {
+  color: #ffd700;
+  font-size: 16px;
+}
+
+/* 表单区域样式 */
+.form-section {
+  flex: 1;
+  padding: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: white;
+  overflow-y: auto;
+}
+
+.form-wrapper {
+  width: 100%;
+  max-width: 400px;
+}
+
+.mobile-header {
+  display: none;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  margin-bottom: 30px;
+  color: #667eea;
+  font-weight: 600;
+  font-size: 18px;
+}
+
+.form-header {
+  text-align: center;
+  margin-bottom: 40px;
+}
+
+.form-header h1 {
+  font-size: 28px;
+  font-weight: 700;
+  color: #333;
+  margin-bottom: 8px;
+}
+
+.form-header p {
+  color: #666;
+  font-size: 14px;
+}
+
+.auth-form {
+  width: 100%;
+}
+
+:deep(.el-input) {
+  font-size: 14px;
+}
+
+:deep(.el-input__wrapper) {
+  border-radius: 10px;
+  padding: 12px 16px;
+}
+
+:deep(.el-input__inner) {
+  font-size: 14px;
+}
+
+.form-options {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.forgot-password {
+  color: #667eea;
+  font-size: 14px;
+}
+
+.auth-btn {
+  width: 100%;
+  height: 48px;
+  border-radius: 10px;
+  font-size: 16px;
+  font-weight: 600;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  border: none;
+  transition: all 0.3s ease;
+}
+
+.auth-btn:hover {
+  transform: translateY(-1px);
+  box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
+}
+
+.auth-btn:active {
+  transform: translateY(0);
+}
+
+.divider {
+  position: relative;
+  text-align: center;
+  margin: 30px 0;
+  color: #999;
+  font-size: 14px;
+}
+
+.divider::before {
+  content: '';
+  position: absolute;
+  top: 50%;
+  left: 0;
+  right: 0;
+  height: 1px;
+  background: #eee;
+}
+
+.divider span {
+  background: white;
+  padding: 0 15px;
+  position: relative;
+}
+
+.social-login {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 12px;
+  margin-bottom: 30px;
+}
+
+.social-btn {
+  height: 44px;
+  border-radius: 10px;
+  border: 1px solid #e4e7ed;
+  background: white;
+  color: #333;
+  font-size: 14px;
+  transition: all 0.3s ease;
+}
+
+.social-btn:hover {
+  border-color: #667eea;
+  color: #667eea;
+  transform: translateY(-1px);
+}
+
+.agreement {
+  margin-top: 20px;
+  text-align: center;
+}
+
+.link-btn {
+  color: #667eea;
+  font-size: 14px;
+}
+
+.form-footer {
+  text-align: center;
+  margin-top: 30px;
+  padding-top: 20px;
+  border-top: 1px solid #f0f0f0;
+}
+
+.switch-text {
+  color: #666;
+  font-size: 14px;
+}
+
+.switch-btn {
+  color: #667eea;
+  font-size: 14px;
+  font-weight: 600;
+  margin-left: 5px;
+  transition: color 0.3s ease;
+}
+
+.switch-btn:hover {
+  color: #764ba2;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .login-container {
+    position: fixed;
+    padding: 0;
+    background: white;
+    overflow-y: auto;
+  }
+
+  .login-content {
+    flex-direction: column;
+    border-radius: 0;
+    box-shadow: none;
+    height: auto;
+    min-height: 100vh;
+    width: 100%;
+    max-width: 100%;
+    margin: 0;
+  }
+
+  .brand-section {
+    display: none;
+  }
+
+  .mobile-header {
+    display: flex;
+  }
+
+  .form-section {
+    padding: 40px 24px;
+    height: auto;
+    min-height: 100vh;
+    align-items: flex-start;
+    padding-top: 80px;
+  }
+
+  .form-wrapper {
+    max-width: 100%;
+  }
+
+  .background-shapes {
+    display: none;
+  }
+}
+
+@media (max-width: 480px) {
+  .form-section {
+    padding: 30px 20px;
+    padding-top: 60px;
+  }
+
+  .form-header h1 {
+    font-size: 24px;
+  }
+
+  .social-login {
+    grid-template-columns: 1fr;
+  }
+
+  .form-options {
+    flex-direction: column;
+    gap: 12px;
+    align-items: flex-start;
+  }
+
+  .brand-title {
+    font-size: 28px;
+  }
+}
+
+@media (min-width: 769px) and (max-width: 1024px) {
+  .login-content {
+    max-width: 900px;
+    height: 85vh;
+  }
+
+  .brand-section {
+    padding: 40px 30px;
+  }
+
+  .form-section {
+    padding: 40px 30px;
+  }
+}
+
+@media (min-width: 1025px) and (max-height: 800px) {
+  .login-content {
+    height: 95vh;
+    max-height: 95vh;
+  }
+
+  .brand-section {
+    padding: 40px 30px;
+  }
+
+  .form-section {
+    padding: 30px;
+  }
+}
+
+/* 超大屏幕适配 */
+@media (min-width: 1920px) {
+  .login-content {
+    max-width: 1400px;
+    max-height: 900px;
+  }
+
+  .brand-section {
+    padding: 80px 60px;
+  }
+
+  .form-section {
+    padding: 60px;
+  }
+
+  .form-header h1 {
+    font-size: 32px;
+  }
+
+  .brand-title {
+    font-size: 36px;
+  }
+}
+
+/* 动画效果 */
+.login-container {
+  animation: fadeIn 0.8s ease-out;
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+/* 确保body和html也是全屏 */
+:global(html), :global(body) {
+  margin: 0;
+  padding: 0;
+  height: 100%;
+  width: 100%;
+  overflow: hidden;
+}
+
+/* 滚动条样式优化 */
+.form-section::-webkit-scrollbar {
+  width: 6px;
+}
+
+.form-section::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 3px;
+}
+
+.form-section::-webkit-scrollbar-thumb {
+  background: #c1c1c1;
+  border-radius: 3px;
+}
+
+.form-section::-webkit-scrollbar-thumb:hover {
+  background: #a8a8a8;
+}
+</style>

+ 12 - 0
ui/yangyi-open-api-platform-ui/vite.config.js

@@ -15,4 +15,16 @@ export default defineConfig({
       resolvers: [ElementPlusResolver()],
     }),
   ],
+  server: {
+    port:3000,
+    proxy: {
+      '/api': { // 配置需要代理的路径 --> 这里的意思是代理http://localhost:80/api/后的所有路由
+        target: 'http://127.0.0.1:8080', // 目标地址 --> 服务器地址
+        changeOrigin: true, // 允许跨域
+        ws: true,  // 允许websocket代理
+        // 重写路径 --> 作用与vue配置pathRewrite作用相同
+        rewrite: (path) => path.replace(/^\/api/, "")
+      }
+    },
+  },
 })