瀏覽代碼

修改样式

yangg 1 月之前
父節點
當前提交
99892c6d74
共有 10 個文件被更改,包括 1337 次插入121 次删除
  1. 0 0
      register
  2. 二進制
      src/assets/avter.png
  3. 二進制
      src/assets/logos.JPG
  4. 216 0
      src/components/ChangePasswordDialog.vue
  5. 2 2
      src/components/SearchResults/index.vue
  6. 7 0
      src/router/menu.js
  7. 309 0
      src/views/forget-password.vue
  8. 561 67
      src/views/login.vue
  9. 237 52
      src/views/report.vue
  10. 5 0
      vite.config.js

+ 0 - 0
register


二進制
src/assets/avter.png


二進制
src/assets/logos.JPG


+ 216 - 0
src/components/ChangePasswordDialog.vue

@@ -0,0 +1,216 @@
+<template>
+  <div class="dialog-overlay" v-if="visible" @click.self="closeDialog">
+    <div class="dialog-content">
+      <div class="dialog-header">
+        <h3>修改密码</h3>
+        <button class="close-button" @click="closeDialog">&times;</button>
+      </div>
+      <div class="dialog-body">
+        <div class="form-group">
+          <label>原密码:</label>
+          <input type="password" v-model="formData.old_password" placeholder="请输入原密码">
+        </div>
+        <div class="form-group">
+          <label>新密码:</label>
+          <input type="password" v-model="formData.new_password" placeholder="请输入新密码">
+        </div>
+        <div class="form-group">
+          <label>确认密码:</label>
+          <input type="password" v-model="formData.confirm_password" placeholder="请确认新密码">
+        </div>
+      </div>
+      <div class="dialog-footer">
+        <button class="cancel-button" @click="closeDialog">取消</button>
+        <button class="confirm-button" @click="handleSubmit">确认</button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import axios from "axios";
+export default {
+  name: 'ChangePasswordDialog',
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      formData: {
+        old_password: '',
+        new_password: '',
+        confirm_password: ''
+      }
+    }
+  },
+  methods: {
+    closeDialog() {
+      this.$emit('update:visible', false)
+      this.resetForm()
+    },
+    resetForm() {
+      this.formData = {
+        old_password: '',
+        new_password: '',
+        confirm_password: ''
+      }
+    },
+    async handleSubmit() {
+      if (!this.formData.old_password) {
+        this.$message.error('请输入原密码')
+        return
+      }
+      if (!this.formData.new_password) {
+        this.$message.error('请输入新密码')
+        return
+      }
+      if (!this.formData.confirm_password) {
+        this.$message.error('请确认新密码')
+        return
+      }
+      if (this.formData.new_password !== this.formData.confirm_password) {
+        this.$message.error('两次输入的新密码不一致')
+        return
+      }
+
+      try {
+        const response = await axios.post(`${import.meta.env.VITE_BASE_AI_API}/user/change_password`, {
+          old_password: this.formData.old_password,
+          new_password: this.formData.new_password,
+          confirm_password: this.formData.confirm_password
+        },
+        {
+          headers: {
+            'Authorization': `JWT ${localStorage.getItem('token')}`
+          }
+        }
+    )
+
+        if (response.data.code === 200) {
+          this.$message.success('密码修改成功')
+          this.closeDialog()
+          // 调用退出登录接口
+          try {
+            await axios.post(`${import.meta.env.VITE_BASE_AI_API}/user/logout`, {}, {
+              headers: {
+                'Authorization': `JWT ${localStorage.getItem('token')}`
+              }
+            })
+            // 清除token
+            localStorage.removeItem('token')
+            // 跳转到登录页面
+            this.$router.push('/login')
+          } catch (logoutError) {
+            console.error('退出登录失败:', logoutError)
+            // 即使退出失败,也清除token并跳转
+            localStorage.removeItem('token')
+            this.$router.push('/login')
+          }
+        } else {
+          this.$message.error(response.data.message || '密码修改失败')
+        }
+      } catch (error) {
+        this.$message.error('密码修改失败:' + (error.response?.data?.message || error.message))
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.dialog-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 1000;
+}
+
+.dialog-content {
+  background: white;
+  border-radius: 8px;
+  padding: 20px;
+  width: 400px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+.dialog-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.dialog-header h3 {
+  margin: 0;
+  color: #333;
+}
+
+.close-button {
+  background: none;
+  border: none;
+  font-size: 24px;
+  cursor: pointer;
+  color: #666;
+}
+
+.form-group {
+  margin-bottom: 15px;
+}
+
+.form-group label {
+  display: block;
+  margin-bottom: 5px;
+  color: #333;
+}
+
+.form-group input {
+  width: 100%;
+  padding: 8px;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  font-size: 14px;
+}
+
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+  margin-top: 20px;
+}
+
+.cancel-button, .confirm-button {
+  padding: 8px 20px;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+}
+
+.cancel-button {
+  background: #f5f5f5;
+  border: 1px solid #ddd;
+  color: #666;
+}
+
+.confirm-button {
+  background: #409EFF;
+  border: 1px solid #409EFF;
+  color: white;
+}
+
+.confirm-button:hover {
+  background: #66b1ff;
+}
+
+.cancel-button:hover {
+  background: #e8e8e8;
+}
+</style> 

+ 2 - 2
src/components/SearchResults/index.vue

@@ -6,8 +6,8 @@
           v-model="searchQuery"
           v-model="searchQuery"
           type="text"
           type="text"
           placeholder="Enter your search query"
           placeholder="Enter your search query"
-          @keyup.enter="handleSearch"
-        />
+        
+        /><!--   @keyup.enter="handleSearch" -->
         <span 
         <span 
           v-if="searchQuery" 
           v-if="searchQuery" 
           class="clear-icon" 
           class="clear-icon" 

+ 7 - 0
src/router/menu.js

@@ -21,6 +21,13 @@ export const ROUTE = [
     hidden: true,
     hidden: true,
     component: () => import('@views/login.vue')
     component: () => import('@views/login.vue')
   },
   },
+  {
+    path: '/forget-password',
+    name: 'forget-password',
+    title: '忘记密码',
+    hidden: true,
+    component: () => import('@views/forget-password.vue')
+  },
   {
   {
     path: '/:catchAll(.*)',
     path: '/:catchAll(.*)',
     redirect: '/404',
     redirect: '/404',

+ 309 - 0
src/views/forget-password.vue

@@ -0,0 +1,309 @@
+<template>
+  <div class="forget-password-page">
+    <div class="forget-password-container">
+      <div class="forget-password-content">
+        <div class="forget-password-header">
+          <h2>重置密码</h2>
+          <p class="sub-title">请填写以下信息以重置您的密码</p>
+        </div>
+        
+        <a-form 
+          ref="forgetPasswordForm"
+          :model="formData"
+          :rules="rules"
+          layout="vertical"
+        >
+          <a-form-item label="用户名或手机号" name="username">
+            <a-input
+              v-model:value="formData.username"
+              placeholder="请输入用户名或手机号"
+              size="large"
+            >
+              <template #prefix>
+                <UserOutlined style="color: rgba(0,0,0,.25)" />
+              </template>
+            </a-input>
+          </a-form-item>
+          
+          <a-form-item label="验证码" name="verification_code">
+            <div class="verification-code-input">
+              <a-input
+                v-model:value="formData.verification_code"
+                placeholder="请输入验证码"
+                size="large"
+              >
+                <template #prefix>
+                  <SafetyCertificateOutlined style="color: rgba(0,0,0,.25)" />
+                </template>
+              </a-input>
+              <a-button 
+                :disabled="!!countdown || !formData.username"
+                @click="sendVerificationCode"
+                size="large"
+              >
+                {{ countdown ? `${countdown}秒后重试` : '获取验证码' }}
+              </a-button>
+            </div>
+          </a-form-item>
+
+          <a-form-item label="新密码" name="new_password">
+            <a-input-password
+              v-model:value="formData.new_password"
+              placeholder="请输入新密码"
+              size="large"
+            >
+              <template #prefix>
+                <LockOutlined style="color: rgba(0,0,0,.25)" />
+              </template>
+            </a-input-password>
+          </a-form-item>
+
+          <a-form-item label="确认新密码" name="confirm_password">
+            <a-input-password
+              v-model:value="formData.confirm_password"
+              placeholder="请再次输入新密码"
+              size="large"
+            >
+              <template #prefix>
+                <LockOutlined style="color: rgba(0,0,0,.25)" />
+              </template>
+            </a-input-password>
+          </a-form-item>
+          
+          <a-form-item>
+            <a-button 
+              type="primary" 
+              :loading="loading"
+              size="large"
+              block
+              @click="handleSubmit"
+            >
+              重置密码
+            </a-button>
+          </a-form-item>
+
+          <div class="form-footer">
+            <a @click="$router.push('/login')" class="back-to-login">返回登录</a>
+          </div>
+        </a-form>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { defineComponent, reactive, ref } from 'vue';
+import { useRouter } from 'vue-router';
+import axios from 'axios';
+import { message } from 'ant-design-vue';
+import { 
+  UserOutlined,
+  LockOutlined,
+  SafetyCertificateOutlined
+} from '@ant-design/icons-vue';
+
+export default defineComponent({
+  name: 'forget-password',
+  components: {
+    UserOutlined,
+    LockOutlined,
+    SafetyCertificateOutlined
+  },
+  setup() {
+    const router = useRouter();
+    const forgetPasswordForm = ref();
+    const loading = ref(false);
+    const countdown = ref(0);
+    
+    const formData = reactive({
+      username: '',
+      verification_code: '',
+      new_password: '',
+      confirm_password: ''
+    });
+    
+    const rules = {
+      username: [
+        { required: true, message: '请输入用户名或手机号' },
+        { min: 4, message: '用户名长度不能小于4位' }
+      ],
+      verification_code: [
+        { required: true, message: '请输入验证码' },
+        { len: 6, message: '验证码长度必须为6位' }
+      ],
+      new_password: [
+        { required: true, message: '请输入新密码' },
+        { min: 6, message: '密码长度不能小于6位' }
+      ],
+      confirm_password: [
+        { required: true, message: '请确认新密码' },
+        { 
+          validator: (rule, value) => {
+            if (value && value !== formData.new_password) {
+              return Promise.reject('两次输入的密码不一致');
+            }
+            return Promise.resolve();
+          }
+        }
+      ]
+    };
+
+    // 发送验证码
+    const sendVerificationCode = async () => {
+      try {
+        // 验证用户名/手机号
+        await forgetPasswordForm.value.validateFields(['username']);
+        
+        const response = await axios.post(`${import.meta.env.VITE_BASE_AI_API}/user/send_verification_code`, {
+          mobile: formData.username
+        });
+
+        if (response.data.code === 200) {
+          message.success('验证码发送成功');
+          // 开始倒计时
+          countdown.value = 60;
+          const timer = setInterval(() => {
+            countdown.value--;
+            if (countdown.value <= 0) {
+              clearInterval(timer);
+            }
+          }, 1000);
+        } else {
+          message.error(response.data.message || '验证码发送失败');
+        }
+      } catch (error) {
+        console.error('验证码发送失败:', error);
+        message.error(error.response?.data?.message || '验证码发送失败');
+      }
+    };
+
+    // 提交表单
+    const handleSubmit = async () => {
+      try {
+        loading.value = true;
+        await forgetPasswordForm.value.validate();
+        
+        const response = await axios.post(`${import.meta.env.VITE_BASE_AI_API}/user/forget_password`, {
+          username: formData.username,
+          verification_code: formData.verification_code,
+          new_password: formData.new_password,
+          confirm_password: formData.confirm_password
+        });
+
+        if (response.data.code === 200) {
+          message.success('密码重置成功');
+          router.push('/login');
+        } else {
+          message.error(response.data.message || '密码重置失败');
+        }
+      } catch (error) {
+        console.error('密码重置失败:', error);
+        message.error(error.response?.data?.message || '密码重置失败,请检查输入信息');
+      } finally {
+        loading.value = false;
+      }
+    };
+
+    return {
+      forgetPasswordForm,
+      formData,
+      rules,
+      loading,
+      countdown,
+      handleSubmit,
+      sendVerificationCode
+    };
+  }
+});
+</script>
+
+<style lang="less" scoped>
+.forget-password-page {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  min-height: 100vh;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  
+  .forget-password-container {
+    width: 100%;
+    max-width: 420px;
+    margin: 0 20px;
+  }
+  
+  .forget-password-content {
+    background: rgba(255, 255, 255, 0.95);
+    padding: 40px;
+    border-radius: 16px;
+    box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15);
+    backdrop-filter: blur(8px);
+    animation: slideUp 0.6s ease-out;
+  }
+  
+  .forget-password-header {
+    text-align: center;
+    margin-bottom: 32px;
+    
+    h2 {
+      font-size: 28px;
+      color: #1a1a1a;
+      margin-bottom: 8px;
+      font-weight: 600;
+    }
+    
+    .sub-title {
+      color: #666;
+      font-size: 14px;
+    }
+  }
+
+  .verification-code-input {
+    display: flex;
+    gap: 8px;
+
+    .ant-input-affix-wrapper {
+      flex: 1;
+    }
+
+    .ant-btn {
+      min-width: 120px;
+    }
+  }
+
+  .form-footer {
+    text-align: center;
+    margin-top: 16px;
+
+    .back-to-login {
+      color: #667eea;
+      cursor: pointer;
+      
+      &:hover {
+        color: #764ba2;
+      }
+    }
+  }
+}
+
+@keyframes slideUp {
+  from {
+    opacity: 0;
+    transform: translateY(20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+// 响应式适配
+@media (max-width: 480px) {
+  .forget-password-content {
+    padding: 24px;
+  }
+  
+  .forget-password-header h2 {
+    font-size: 24px;
+  }
+}
+</style> 

+ 561 - 67
src/views/login.vue

@@ -13,33 +13,97 @@
           :rules="rules"
           :rules="rules"
           layout="vertical"
           layout="vertical"
         >
         >
-          <a-form-item label="账号" name="username">
-            <a-input
-              v-model:value="formData.username"
-              placeholder="请输入账号"
-              size="large"
-            >
-              <template #prefix>
-                <UserOutlined style="color: rgba(0,0,0,.25)" />
-              </template>
-            </a-input>
-          </a-form-item>
-          
-          <a-form-item label="密码" name="password">
-            <a-input-password
-              v-model:value="formData.password"
-              placeholder="请输入密码"
-              size="large"
-            >
-              <template #prefix>
-                <LockOutlined style="color: rgba(0,0,0,.25)" />
-              </template>
-            </a-input-password>
-          </a-form-item>
+          <!-- 账号密码登录表单 -->
+          <template v-if="loginType === 'account'">
+            <a-form-item label="账号" name="username">
+              <a-input
+                v-model:value="formData.username"
+                placeholder="请输入账号"
+                size="large"
+              >
+                <template #prefix>
+                  <UserOutlined style="color: rgba(0,0,0,.25)" />
+                </template>
+              </a-input>
+            </a-form-item>
+            
+            <a-form-item label="密码" name="password">
+              <a-input-password
+                v-model:value="formData.password"
+                placeholder="请输入密码"
+                size="large"
+              >
+                <template #prefix>
+                  <LockOutlined style="color: rgba(0,0,0,.25)" />
+                </template>
+              </a-input-password>
+            </a-form-item>
+            <a-form-item label="商户编码" name="merchant_code">
+              <a-input
+                v-model:value="formData.merchant_code"
+                placeholder="请输入商户编码"
+                size="large"
+              ></a-input>
+            </a-form-item>
+          </template>
+
+          <!-- 手机号登录表单 -->
+          <template v-else-if="loginType === 'mobile'">
+            <a-form-item label="手机号" name="mobile">
+              <a-input
+                v-model:value="formData.mobile"
+                placeholder="请输入手机号"
+                size="large"
+              >
+                <template #prefix>
+                  <MobileOutlined style="color: rgba(0,0,0,.25)" />
+                </template>
+              </a-input>
+            </a-form-item>
+            
+            <a-form-item label="验证码" name="verification_code">
+              <div class="verification-code-input">
+                <a-input
+                  v-model:value="formData.verification_code"
+                  placeholder="请输入验证码"
+                  size="large"
+                >
+                  <template #prefix>
+                    <SafetyCertificateOutlined style="color: rgba(0,0,0,.25)" />
+                  </template>
+                </a-input>
+                <a-button 
+                  :disabled="!!countdown || !formData.mobile"
+                  @click="sendVerificationCode"
+                  size="large"
+                >
+                  {{ countdown ? `${countdown}秒后重试` : '获取验证码' }}
+                </a-button>
+              </div>
+            </a-form-item>
+          </template>
+
+          <!-- 微信登录表单 -->
+          <template v-if="loginType === 'wechat'">
+            <div class="wechat-login-container">
+              <div v-if="wechatQrCode" class="wechat-qrcode-wrapper">
+                <img 
+                  :src="wechatQrCode" 
+                  alt="微信登录二维码"
+                  class="wechat-qrcode"
+                />
+                <p class="qrcode-tip">请使用微信扫描二维码登录</p>
+                <p class="qrcode-sub-tip">扫码后请在微信中确认登录</p>
+              </div>
+              <div v-else class="qrcode-loading">
+                <a-spin tip="正在加载二维码..." />
+              </div>
+            </div>
+          </template>
           
           
           <div class="form-options">
           <div class="form-options">
-            <a-checkbox v-model:checked="rememberMe">记住密码</a-checkbox>
-            <a href="#" class="forgot-password">忘记密码?</a>
+            <a-checkbox v-model:checked="rememberMe">记住账号</a-checkbox>
+            <router-link to="/forget-password" class="forgot-password">忘记密码?</router-link>
           </div>
           </div>
           
           
           <a-form-item>
           <a-form-item>
@@ -48,7 +112,7 @@
               :loading="loading"
               :loading="loading"
               size="large"
               size="large"
               block
               block
-              @click="login"
+              @click="handleLogin"
             >
             >
               登录
               登录
             </a-button>
             </a-button>
@@ -56,16 +120,30 @@
         </a-form>
         </a-form>
 
 
         <div class="login-footer">
         <div class="login-footer">
-          <p class="register-tip">
-            还没有账号?<a href="/register">立即注册</a>
-          </p>
+          <!-- <p class="register-tip">
+            还没有账号?<router-link to="/register" class="register-link">立即注册</router-link>
+          </p> -->
+          
+          <!-- 登录方式切换移动到这里 -->
+          <!-- <div class="login-type-switch">
+            <p class="switch-title">切换登录方式</p>
+            <a-radio-group v-model:value="loginType" button-style="solid">
+              <a-radio-button value="account">账号密码登录</a-radio-button>
+              <a-radio-button value="mobile">手机验证码登录</a-radio-button>
+            </a-radio-group>
+          </div> -->
+
           <div class="other-login">
           <div class="other-login">
             <div class="divider">
             <div class="divider">
               <span>其他登录方式</span>
               <span>其他登录方式</span>
             </div>
             </div>
             <div class="social-login">
             <div class="social-login">
               <a-tooltip title="微信登录">
               <a-tooltip title="微信登录">
-                <WechatOutlined class="social-icon" />
+                <WechatOutlined 
+                  class="social-icon"
+                  :class="{ active: loginType === 'wechat' }"
+                  @click="switchToWechatLogin"
+                />
               </a-tooltip>
               </a-tooltip>
               <a-tooltip title="QQ登录">
               <a-tooltip title="QQ登录">
                 <QqOutlined class="social-icon" />
                 <QqOutlined class="social-icon" />
@@ -73,24 +151,59 @@
               <a-tooltip title="钉钉登录">
               <a-tooltip title="钉钉登录">
                 <DingdingOutlined class="social-icon" />
                 <DingdingOutlined class="social-icon" />
               </a-tooltip>
               </a-tooltip>
+              <a-tooltip v-if="loginType === 'account'" title="手机号登录">
+                <MobileOutlined 
+                  class="social-icon" 
+                  @click="switchToMobileLogin"
+                />
+              </a-tooltip>
+              <a-tooltip v-else title="账号密码登录">
+                <UserOutlined 
+                  class="social-icon" 
+                  @click="switchToAccountLogin"
+                />
+              </a-tooltip>
             </div>
             </div>
           </div>
           </div>
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
+
+    <!-- 微信登录弹窗 -->
+    <a-modal
+      v-model:visible="wechatLoginModal"
+      title="微信扫码登录"
+      :footer="null"
+      @cancel="handleWechatLoginCancel"
+      width="360px"
+      centered
+    >
+      <div class="wechat-qrcode-container">
+        <img 
+          :src="wechatQrCode" 
+          alt="微信登录二维码"
+          class="wechat-qrcode"
+        />
+        <p class="qrcode-tip">请使用微信扫描二维码登录</p>
+      </div>
+    </a-modal>
   </div>
   </div>
 </template>
 </template>
 
 
 <script>
 <script>
-import { defineComponent, reactive, ref } from 'vue';
+import { defineComponent, reactive, ref, nextTick, onUnmounted } from 'vue';
 import { useRouter } from 'vue-router';
 import { useRouter } from 'vue-router';
 import { useStore } from 'vuex';
 import { useStore } from 'vuex';
+import axios from 'axios';
+import { message, Modal } from 'ant-design-vue';
 import { 
 import { 
   UserOutlined, 
   UserOutlined, 
   LockOutlined,
   LockOutlined,
   WechatOutlined,
   WechatOutlined,
   QqOutlined,
   QqOutlined,
-  DingdingOutlined
+  DingdingOutlined,
+  MobileOutlined,
+  SafetyCertificateOutlined
 } from '@ant-design/icons-vue';
 } from '@ant-design/icons-vue';
 
 
 export default defineComponent({
 export default defineComponent({
@@ -100,7 +213,9 @@ export default defineComponent({
     LockOutlined,
     LockOutlined,
     WechatOutlined,
     WechatOutlined,
     QqOutlined,
     QqOutlined,
-    DingdingOutlined
+    DingdingOutlined,
+    MobileOutlined,
+    SafetyCertificateOutlined
   },
   },
   setup() {
   setup() {
     const store = useStore();
     const store = useStore();
@@ -108,10 +223,15 @@ export default defineComponent({
     const loginForm = ref();
     const loginForm = ref();
     const loading = ref(false);
     const loading = ref(false);
     const rememberMe = ref(false);
     const rememberMe = ref(false);
+    const loginType = ref('account'); // 登录方式:'account', 'mobile', 'wechat'
+    const countdown = ref(0); // 验证码倒计时
     
     
     const formData = reactive({
     const formData = reactive({
       username: '',
       username: '',
-      password: ''
+      password: '',
+      merchant_code: 'E20250212000624fd2a92',
+      mobile: '',
+      verification_code: ''
     });
     });
     
     
     const rules = {
     const rules = {
@@ -122,59 +242,323 @@ export default defineComponent({
       password: [
       password: [
         { required: true, message: '请输入密码' },
         { required: true, message: '请输入密码' },
         { min: 6, message: '密码长度不能小于6位' }
         { min: 6, message: '密码长度不能小于6位' }
+      ],
+      merchant_code: [
+        { required: true, message: '请输入商户编码' },
+        { min: 1, message: '商户编码长度不能小于1位' }
+      ],
+      mobile: [
+        { required: true, message: '请输入手机号' },
+        { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号' }
+      ],
+      verification_code: [
+        { required: true, message: '请输入验证码' },
+        { len: 6, message: '验证码长度必须为6位' }
       ]
       ]
     };
     };
 
 
-    const login = async () => {
+    // 发送验证码
+    const sendVerificationCode = async () => {
+      try {
+        // 验证手机号
+        await loginForm.value.validateFields(['mobile']);
+        
+        const response = await axios.post(`${import.meta.env.VITE_BASE_AI_API}/user/send_verification_code`, {
+          mobile: formData.mobile,
+          merchant_code: formData.merchant_code
+        });
+
+        if (response.data.code === 200) {
+          message.success('验证码发送成功');
+          // 开始倒计时
+          countdown.value = 60;
+          const timer = setInterval(() => {
+            countdown.value--;
+            if (countdown.value <= 0) {
+              clearInterval(timer);
+            }
+          }, 1000);
+        } else {
+          message.error(response.data.message || '验证码发送失败');
+        }
+      } catch (error) {
+        console.error('验证码发送失败:', error);
+        message.error(error.response?.data?.message || '验证码发送失败');
+      }
+    };
+
+    // 处理登录
+    const handleLogin = async () => {
       try {
       try {
         loading.value = true;
         loading.value = true;
         await loginForm.value.validate();
         await loginForm.value.validate();
         
         
-        let permission = [
-          { type: 'menu', path: '/report', key: '' },
-          { type: 'btn', path: '/report', key: 'add' },
-          { type: 'btn', path: '/report', key: 'edit' },
-          { type: 'btn', path: '/report', key: 'delete' },
-          { type: 'btn', path: '/report', key: 'export' },
-          { type: 'menu', path: '/setting/user/add' },
-          { type: 'menu', path: '/setting/user/edit' },
-          { type: 'menu', path: '/setting/user/delete' },
-          { type: 'menu', path: '/setting/role' },
-          { type: 'menu', path: '/login' },
-        ];
-        
-        const auth = {};
-        permission.filter(item => item.type === 'btn').forEach(item => {
-          if(auth[item.path]) {
-            auth[item.path][item.key] = true;
-          } else {
-            auth[item.path] = {
-              [item.key]: true
-            };
+        let response;
+        if (loginType.value === 'account') {
+          // 账号密码登录
+          response = await axios.post(`${import.meta.env.VITE_BASE_AI_API}/user/login`, {
+            username: formData.username,
+            password: formData.password,
+            merchant_code: formData.merchant_code
+          });
+        } else if (loginType.value === 'mobile') {
+          // 手机验证码登录
+          response = await axios.post(`${import.meta.env.VITE_BASE_AI_API}/user/mobile_login`, {
+            mobile: formData.mobile,
+            verification_code: formData.verification_code,
+            merchant_code: formData.merchant_code
+          });
+        } else {
+          // 微信登录
+          response = await axios.post(`${import.meta.env.VITE_BASE_AI_API}/user/wechat_login`, {
+            wechat_state: formData.wechat_state
+          });
+        }
+
+        if (response.data.code === 200) {
+          // 保存token到localStorage
+          localStorage.setItem('token', response.data.data.token);
+          
+          // 获取用户详细信息
+          try {
+            const userInfoResponse = await axios.get(`${import.meta.env.VITE_BASE_AI_API}/user/info`, {
+              headers: {
+                'Authorization': `JWT ${response.data.data.token}`
+              }
+            });
+            console.log(userInfoResponse);
+            if (userInfoResponse.data.code === 200) {
+              // 存储用户信息和权限到 Vuex store
+              store.commit('app/setUser', {
+                user: userInfoResponse.data.data,
+                permission: userInfoResponse.data.data || [],
+             
+              });
+
+              // 同时存储到 localStorage
+              localStorage.setItem('userInfo', JSON.stringify(userInfoResponse.data.data));
+              localStorage.setItem('permissions', JSON.stringify(userInfoResponse.data.data || []));
+            } else {
+              message.warning('获取用户信息失败');
+            }
+          } catch (error) {
+            console.error('获取用户信息失败:', error);
+            message.warning('获取用户信息失败,请重新登录');
+            return;
           }
           }
-        });
-        
-        store.commit('app/setUser', {
-          user: { id: 1186, name: '张三' },
-          permission,
-          auth
-        });
-        
-        router.push('/');
+
+          // 记住账号
+          if (rememberMe.value) {
+            if (loginType.value === 'account') {
+              localStorage.setItem('username', formData.username);
+            } else if (loginType.value === 'mobile') {
+              localStorage.setItem('mobile', formData.mobile);
+            }
+          }
+
+          message.success('登录成功');
+          router.push('/');
+        } else {
+          message.error(response.data.message || '登录失败');
+        }
       } catch (error) {
       } catch (error) {
         console.error('登录失败:', error);
         console.error('登录失败:', error);
+        message.error(error.response?.data?.message || '登录失败,请检查网络连接');
       } finally {
       } finally {
         loading.value = false;
         loading.value = false;
       }
       }
     };
     };
 
 
+    // 处理权限数据
+    const processPermissions = (permissions = []) => {
+      const auth = {};
+      permissions.filter(item => item.type === 'btn').forEach(item => {
+        if(auth[item.path]) {
+          auth[item.path][item.key] = true;
+        } else {
+          auth[item.path] = {
+            [item.key]: true
+          };
+        }
+      });
+      return auth;
+    };
+
+    // 初始化时从本地存储获取记住的信息
+    if (localStorage.getItem('username')) {
+      formData.username = localStorage.getItem('username');
+      rememberMe.value = true;
+    } else if (localStorage.getItem('mobile')) {
+      formData.mobile = localStorage.getItem('mobile');
+      loginType.value = 'mobile';
+      rememberMe.value = true;
+    }
+
+    // 切换到手机号登录
+    const switchToMobileLogin = () => {
+      loginType.value = 'mobile';
+      // 清空之前的登录表单数据
+      formData.username = '';
+      formData.password = '';
+      // 如果之前保存过手机号,则自动填充
+      const savedMobile = localStorage.getItem('mobile');
+      if (savedMobile) {
+        formData.mobile = savedMobile;
+      }
+      // 重置表单验证状态
+      nextTick(() => {
+        loginForm.value?.resetFields();
+      });
+      // 显示切换提示
+      message.info('已切换到手机号登录模式');
+    };
+
+    // 切换到账号密码登录
+    const switchToAccountLogin = () => {
+      loginType.value = 'account';
+      // 清空之前的登录表单数据
+      formData.mobile = '';
+      formData.verification_code = '';
+      // 如果之前保存过手机号,则自动填充
+      const savedMobile = localStorage.getItem('mobile');
+      if (savedMobile) {
+        formData.mobile = savedMobile;
+      } 
+      // 重置表单验证状态
+      nextTick(() => {
+        loginForm.value?.resetFields();
+      });
+      // 显示切换提示
+      message.info('已切换到账号密码登录模式');
+    };
+
+    // 切换到微信登录
+    const switchToWechatLogin = async () => {
+      loginType.value = 'wechat';
+      // 清空之前的登录表单数据
+      formData.username = '';
+      formData.password = '';
+      formData.mobile = '';
+      formData.verification_code = '';
+      // 获取微信二维码
+      await getWechatQrCode();
+      // 重置表单验证状态
+      nextTick(() => {
+        loginForm.value?.resetFields();
+      });
+      message.info('已切换到微信扫码登录模式');
+    };
+
+    // 微信登录相关状态
+    const wechatLoginModal = ref(false);
+    const wechatQrCode = ref('');
+    const wechatState = ref('');
+    let wechatLoginTimer = null;
+
+    // 获取微信登录二维码
+    const getWechatQrCode = async () => {
+      try {
+        const response = await axios.get(`${import.meta.env.VITE_BASE_AI_API}/api/user/wechat/qrcode`);
+        if (response.data.code === 200) {
+          wechatQrCode.value = response.data.data.qrcode_url;
+          wechatState.value = response.data.data.state;
+          startWechatLoginCheck();
+        } else {
+          message.error('获取微信二维码失败');
+        }
+      } catch (error) {
+        console.error('获取微信二维码失败:', error);
+        message.error('获取微信二维码失败,请稍后重试');
+      }
+    };
+
+    // 开始轮询检查微信登录状态
+    const startWechatLoginCheck = () => {
+      wechatLoginTimer = setInterval(async () => {
+        try {
+          const response = await axios.get(`${import.meta.env.VITE_BASE_AI_API}/api/user/wechat/check_login`, {
+            params: { state: wechatState.value }
+          });
+          
+          if (response.data.code === 200) {
+            // 登录成功
+            clearInterval(wechatLoginTimer);
+            wechatLoginModal.value = false;
+            
+            // 保存token
+            localStorage.setItem('token', response.data.data.token);
+            
+            // 获取并保存用户信息
+            await getUserInfo(response.data.data.token);
+            
+            message.success('微信登录成功');
+            router.push('/');
+          }
+        } catch (error) {
+          console.error('检查微信登录状态失败:', error);
+        }
+      }, 2000); // 每2秒检查一次
+    };
+
+    // 关闭微信登录弹窗
+    const handleWechatLoginCancel = () => {
+      wechatLoginModal.value = false;
+      if (wechatLoginTimer) {
+        clearInterval(wechatLoginTimer);
+        wechatLoginTimer = null;
+      }
+    };
+
+    // 获取用户信息的公共方法
+    const getUserInfo = async (token) => {
+      try {
+        const userInfoResponse = await axios.get(`${import.meta.env.VITE_BASE_AI_API}/user/info`, {
+          headers: {
+            'Authorization': `JWT ${token}`
+          }
+        });
+        
+        if (userInfoResponse.data.code === 200) {
+          store.commit('app/setUser', {
+            user: userInfoResponse.data.data.userInfo,
+            permission: userInfoResponse.data.data.permissions || [],
+            auth: processPermissions(userInfoResponse.data.data.permissions)
+          });
+        } else {
+          message.warning('获取用户信息失败');
+        }
+      } catch (error) {
+        console.error('获取用户信息失败:', error);
+        message.warning('获取用户信息失败,请重新登录');
+        throw error;
+      }
+    };
+
+    // 组件卸载时清理定时器
+    onUnmounted(() => {
+      if (wechatLoginTimer) {
+        clearInterval(wechatLoginTimer);
+        wechatLoginTimer = null;
+      }
+    });
+
     return {
     return {
       loginForm,
       loginForm,
       formData,
       formData,
       rules,
       rules,
       loading,
       loading,
       rememberMe,
       rememberMe,
-      login
+      loginType,
+      countdown,
+      handleLogin,
+      sendVerificationCode,
+      switchToMobileLogin,
+      switchToAccountLogin,
+      switchToWechatLogin,
+      wechatLoginModal,
+      wechatQrCode,
+      getWechatQrCode,
+      handleWechatLoginCancel
     };
     };
   }
   }
 });
 });
@@ -237,6 +621,19 @@ export default defineComponent({
     }
     }
   }
   }
   
   
+  .verification-code-input {
+    display: flex;
+    gap: 8px;
+
+    .ant-input-affix-wrapper {
+      flex: 1;
+    }
+
+    .ant-btn {
+      min-width: 120px;
+    }
+  }
+  
   .login-footer {
   .login-footer {
     margin-top: 32px;
     margin-top: 32px;
     text-align: center;
     text-align: center;
@@ -246,7 +643,7 @@ export default defineComponent({
       font-size: 14px;
       font-size: 14px;
       margin-bottom: 24px;
       margin-bottom: 24px;
       
       
-      a {
+      .register-link {
         color: #667eea;
         color: #667eea;
         font-weight: 500;
         font-weight: 500;
         margin-left: 4px;
         margin-left: 4px;
@@ -257,6 +654,35 @@ export default defineComponent({
       }
       }
     }
     }
     
     
+    // 更新登录方式切换的样式
+    .login-type-switch {
+      margin-bottom: 24px;
+
+      .switch-title {
+        color: #666;
+        font-size: 14px;
+        margin-bottom: 12px;
+      }
+
+      .ant-radio-group {
+        display: flex;
+        justify-content: center;
+        gap: 12px;
+      }
+
+      .ant-radio-button-wrapper {
+        border-radius: 4px;
+        
+        &:first-child {
+          border-radius: 4px;
+        }
+        
+        &:last-child {
+          border-radius: 4px;
+        }
+      }
+    }
+    
     .divider {
     .divider {
       position: relative;
       position: relative;
       text-align: center;
       text-align: center;
@@ -304,6 +730,11 @@ export default defineComponent({
           color: #667eea;
           color: #667eea;
           transform: scale(1.1);
           transform: scale(1.1);
         }
         }
+
+        // 为手机图标添加特殊样式
+        &.mobile-active {
+          color: #667eea;
+        }
       }
       }
     }
     }
   }
   }
@@ -330,4 +761,67 @@ export default defineComponent({
     font-size: 24px;
     font-size: 24px;
   }
   }
 }
 }
+
+.wechat-qrcode-container {
+  text-align: center;
+  padding: 20px 0;
+
+  .wechat-qrcode {
+    width: 200px;
+    height: 200px;
+    margin-bottom: 16px;
+  }
+
+  .qrcode-tip {
+    color: #666;
+    font-size: 14px;
+  }
+}
+
+.wechat-login-container {
+  padding: 20px 0;
+  text-align: center;
+
+  .wechat-qrcode-wrapper {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 20px;
+    background: #f5f5f5;
+    border-radius: 8px;
+
+    .wechat-qrcode {
+      width: 200px;
+      height: 200px;
+      margin-bottom: 16px;
+      border-radius: 4px;
+      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+    }
+
+    .qrcode-tip {
+      color: #333;
+      font-size: 16px;
+      font-weight: 500;
+      margin-bottom: 8px;
+    }
+
+    .qrcode-sub-tip {
+      color: #666;
+      font-size: 14px;
+    }
+  }
+
+  .qrcode-loading {
+    padding: 60px 0;
+  }
+}
+
+// 修改社交图标样式
+.social-login {
+  .social-icon {
+    &.active {
+      color: #667eea;
+    }
+  }
+}
 </style>
 </style>

+ 237 - 52
src/views/report.vue

@@ -1,8 +1,38 @@
 <template>
 <template>
   <div class="layout">
   <div class="layout">
-    <div class="menu" :class="{ collapsed: isMenuCollapsed }">
-      <!-- 添加收缩按钮 -->
-      <div 
+    <!-- 添加密码修改弹窗组件 -->
+    <ChangePasswordDialog
+      v-model:visible="showPasswordDialog"
+    />
+    
+    <div class="header">
+      <div class="user-profile-bar">
+        <div class="user-info" @click="toggleDropdown">
+          <div class="user-name">{{ userInfo.username || '未登录用户' }}</div>
+          <!-- <div class="user-role">{{ userInfo.role || '未设置角色' }}</div> -->
+        </div>
+        <div class="avatar" @click="toggleDropdown">
+          <img src="../assets/avter.png" alt="用户头像" />
+        </div>
+        
+        <!-- 下拉菜单 -->
+        <div class="dropdown-menu" v-show="showDropdown">
+          <div class="dropdown-item" @click="handleModifyPassword">
+            <i class="fas fa-key"></i>
+            修改密码
+          </div>
+          <div class="dropdown-item" @click="handleResetPassword">
+            <i class="fas fa-redo"></i>
+            重置密码
+          </div>
+          <div class="dropdown-item" @click="handleLogout">
+            <i class="fas fa-sign-out-alt"></i>
+            退出登录
+          </div>
+        </div>
+      </div>
+    </div>
+    <div 
         class="collapse-left-button" 
         class="collapse-left-button" 
         :class="{ 'menu-collapsed': isMenuCollapsed }"
         :class="{ 'menu-collapsed': isMenuCollapsed }"
         @click="toggleMenu"
         @click="toggleMenu"
@@ -13,16 +43,19 @@
           class="collapse-icon"
           class="collapse-icon"
         />
         />
       </div>
       </div>
+    <div class="menu" :class="{ collapsed: isMenuCollapsed }">
+      <!-- 添加收缩按钮 -->
+      
 
 
       <!-- Logo -->
       <!-- Logo -->
       <div class="logo">
       <div class="logo">
         <img
         <img
-          src="http://ecsaas.raycos.com.cn/web/uploads/20230713/3690c0badc2c7be4552a253f72d6d701.png"
+          src="../assets/logos.JPG"
           :draggable="false"
           :draggable="false"
           alt="logo"
           alt="logo"
           class="logo-img"
           class="logo-img"
         />
         />
-        <span>Raycos Tech</span>
+       <!--  <span>Raycos Tech</span> -->
       </div>
       </div>
 
 
       <!-- New Conversation 按钮 -->
       <!-- New Conversation 按钮 -->
@@ -435,9 +468,9 @@
             class="message-input"
             class="message-input"
             v-model="inputContent"
             v-model="inputContent"
             @keyup.enter="sendMessage"
             @keyup.enter="sendMessage"
-            @input="handleInputChange"
+           
             placeholder="Type a message..."
             placeholder="Type a message..."
-          />
+          /><!--  @input="handleInputChange" -->
           <button
           <button
             class="send-btn"
             class="send-btn"
             @click="sendMessage"
             @click="sendMessage"
@@ -622,6 +655,8 @@ import ExportButton from "../components/ExportButton/index.vue";
 import dayjs from 'dayjs';
 import dayjs from 'dayjs';
 import DocumentPreview from "../components/DocumentPreview/index.vue";
 import DocumentPreview from "../components/DocumentPreview/index.vue";
 import KnowledgeResults from "../components/KnowledgeResults/index.vue";
 import KnowledgeResults from "../components/KnowledgeResults/index.vue";
+import ChangePasswordDialog from '../components/ChangePasswordDialog.vue'
+import { useRouter } from 'vue-router'
 
 
 // 初始化 markdown-it
 // 初始化 markdown-it
 const md = new MarkdownIt({
 const md = new MarkdownIt({
@@ -643,6 +678,7 @@ const currentConversationId = ref(1);
 const conversationCounter = ref(1);
 const conversationCounter = ref(1);
 const messageList = ref(null);
 const messageList = ref(null);
 const store = useStore();
 const store = useStore();
+const router = useRouter()
 
 
 // 会话数据
 // 会话数据
 const conversations = ref([
 const conversations = ref([
@@ -699,16 +735,17 @@ const checkMobile = () => {
   isMobile.value = window.innerWidth <= 768;
   isMobile.value = window.innerWidth <= 768;
 };
 };
 
 
-// 监听窗口大小变化
-onMounted(() => {
-  checkMobile();
-  window.addEventListener('resize', checkMobile);
-});
 
 
 onUnmounted(() => {
 onUnmounted(() => {
   window.removeEventListener('resize', checkMobile);
   window.removeEventListener('resize', checkMobile);
 });
 });
 
 
+const showDropdown = ref(false);
+const toggleDropdown = () => {
+  console.log(showDropdown.value);
+  showDropdown.value = !showDropdown.value;
+};
+
 // 添加遮罩层点击处理
 // 添加遮罩层点击处理
 const handleOverlayClick = () => {
 const handleOverlayClick = () => {
   if (isMobile.value) {
   if (isMobile.value) {
@@ -727,11 +764,15 @@ const toggleMenu = () => {
       isRightMenuCollapsed.value = true; // 确保右侧菜单关闭
       isRightMenuCollapsed.value = true; // 确保右侧菜单关闭
     }
     }
   }
   }
+  
+  // 更新聊天区域的位置
+  if (document.querySelector('.chat')) {
+    document.querySelector('.chat').style.left = isMenuCollapsed.value ? '0' : '260px';
+  }
 };
 };
 
 
 // 修改右侧菜单切换逻辑
 // 修改右侧菜单切换逻辑
 const toggleRightMenu = () => {
 const toggleRightMenu = () => {
-  // 不执行任何操作,保持展开状态
   isRightMenuCollapsed.value = !isRightMenuCollapsed.value;
   isRightMenuCollapsed.value = !isRightMenuCollapsed.value;
   
   
   if (isRightMenuCollapsed.value) {
   if (isRightMenuCollapsed.value) {
@@ -753,7 +794,6 @@ const toggleRightMenu = () => {
       isMenuCollapsed.value = true;
       isMenuCollapsed.value = true;
     }
     }
   }
   }
-/*   return; */
 };
 };
 
 
 // 修改自动展开右侧菜单的方法
 // 修改自动展开右侧菜单的方法
@@ -853,10 +893,6 @@ onUnmounted(() => {
   }
   }
 });
 });
 
 
-// 在组件挂载时获取摘要
-onMounted(() => {
-  fetchDocumentSummary();
-});
 
 
 // 方法
 // 方法
 const toggleAttachments = () => {
 const toggleAttachments = () => {
@@ -888,7 +924,7 @@ const uploadFile = async (file) => {
       }
       }
     );
     );
 
 
-    if (response.data.status === 200) {
+    if (response.data.status === 2000) {
       return response.data.data.fileUrl; // 返回文件上传后的URL
       return response.data.data.fileUrl; // 返回文件上传后的URL
     } else {
     } else {
       throw new Error(response.data.message || "文件上传失败");
       throw new Error(response.data.message || "文件上传失败");
@@ -1116,7 +1152,7 @@ const sendMessage = async () => {
       image_urls: imageUrls,
       image_urls: imageUrls,
       video_urls: videoUrls,
       video_urls: videoUrls,
       documents: documentUrls,
       documents: documentUrls,
-      merchant_id:3,
+      merchant_id:JSON.parse(localStorage.getItem('userInfo')).merchant.merchant_id,
       model_type: selectedModelInfo.value.type,
       model_type: selectedModelInfo.value.type,
       model_name: selectedModelInfo.value.name
       model_name: selectedModelInfo.value.name
     });
     });
@@ -1516,10 +1552,6 @@ const updateCollapsedState = () => {
   isCollapsed.value = window.innerWidth <= 480;
   isCollapsed.value = window.innerWidth <= 480;
 };
 };
 
 
-  onMounted(() => {
-    updateCollapsedState();
-    window.addEventListener('resize', updateCollapsedState);
-  });
 
 
   onUnmounted(() => {
   onUnmounted(() => {
     window.removeEventListener('resize', updateCollapsedState);
     window.removeEventListener('resize', updateCollapsedState);
@@ -1533,10 +1565,6 @@ const updateCollapsedState = () => {
     isCompact.value = window.innerWidth <= 768;
     isCompact.value = window.innerWidth <= 768;
   };
   };
 
 
-  onMounted(() => {
-    updateCompactState();
-    window.addEventListener('resize', updateCompactState);
-  });
 
 
   onUnmounted(() => {
   onUnmounted(() => {
     window.removeEventListener('resize', updateCompactState);
     window.removeEventListener('resize', updateCompactState);
@@ -1665,12 +1693,85 @@ const updateCollapsedState = () => {
   const handleModelChange = (modelId) => {
   const handleModelChange = (modelId) => {
     selectedModel.value = modelId;
     selectedModel.value = modelId;
   };
   };
+ // 用户信息
+  const userInfo = ref({
+    name: '',
+    role: ''
+  });
+  const getUserInfo = () => {
+    const storedUserInfo = localStorage.getItem('userInfo');
+    if (storedUserInfo) {
+      userInfo.value = JSON.parse(storedUserInfo);
+    }
+  }
 
 
   // 在组件挂载时获取模型列表
   // 在组件挂载时获取模型列表
   onMounted(() => {
   onMounted(() => {
+    updateCompactState();
+    window.addEventListener('resize', updateCompactState);
+    updateCollapsedState();
+    window.addEventListener('resize', updateCollapsedState);
+
+    checkMobile();
+  window.addEventListener('resize', checkMobile);
+
     fetchModels();
     fetchModels();
-    // ... existing onMounted code ...
+
+    fetchDocumentSummary();
+
+     const storedUserInfo = localStorage.getItem('userInfo');
+    if (storedUserInfo) {
+      try {
+        userInfo.value = JSON.parse(storedUserInfo);
+      } catch (error) {
+        console.error('解析用户信息失败:', error);
+      }
+    }
+    
   });
   });
+
+  const showPasswordDialog = ref(false)
+  const handleModifyPassword = () => {
+    showPasswordDialog.value = true
+    showDropdown.value = false // 关闭下拉菜单
+  }
+
+  const handleLogout = async () => {
+    try {
+      // 调用退出登录接口
+      await axios.post(`${import.meta.env.VITE_BASE_AI_API}/user/logout`, {}, {
+        headers: {
+          'Authorization': `JWT ${localStorage.getItem('token')}`
+        }
+      });
+
+      // 清除本地存储的信息
+      localStorage.removeItem('token');
+      localStorage.removeItem('userInfo');
+      localStorage.removeItem('permissions');
+      localStorage.removeItem('username');
+      localStorage.removeItem('mobile');
+
+      // 清除 Vuex store 中的用户信息
+      store.commit('app/setUser', {
+        user: {},
+        permission: [],
+        auth: {}
+      });
+
+      // 关闭下拉菜单
+      showDropdown.value = false;
+
+      // 跳转到登录页面
+      router.push('/login');
+    } catch (error) {
+      console.error('退出登录失败:', error);
+      // 即使接口调用失败,也清除本地信息并跳转
+      localStorage.clear();
+      router.push('/login');
+    }
+  };
+
  </script>
  </script>
 
 
 <style scoped>
 <style scoped>
@@ -1682,23 +1783,102 @@ const updateCollapsedState = () => {
   position: relative;
   position: relative;
 }
 }
 
 
+.header {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 64px;
+  background: #f7f7f8;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
+  z-index: 999;
+}
+
+.user-profile-bar {
+  margin: 0 auto;
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  cursor: pointer;
+  padding: 8px;
+ 
+}
+
+.avatar {
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  overflow: hidden;
+  margin-right: 12px;
+}
+
+.avatar img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.user-info {
+  display: flex;
+  flex-direction: column;
+  margin-right: 15px;
+}
+
+.user-name {
+  font-size: 14px;
+  font-weight: 500;
+  color: #333;
+}
+
+.dropdown-menu {
+  position: absolute;
+  top: 100%;
+  right: 0;
+  background: white;
+  border-radius: 8px;
+  box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+  min-width: 160px;
+  z-index: 1000;
+  margin-top: 4px;
+}
+
+.dropdown-item {
+  padding: 12px 16px;
+  display: flex;
+  align-items: center;
+  color: #333;
+  transition: background-color 0.2s;
+}
+
+.dropdown-item:hover {
+  background-color: #f5f5f5;
+}
+
+.dropdown-item i {
+  margin-right: 8px;
+  font-size: 14px;
+  color: #666;
+}
+
 .menu {
 .menu {
+  margin-top: 64px;
   background: #f7f7f8;
   background: #f7f7f8;
   width: 260px;
   width: 260px;
-  height: 100%;
+  height: 93%;
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
   border-right: 1px solid rgba(0, 0, 0, 0.06);
   border-right: 1px solid rgba(0, 0, 0, 0.06);
   position: absolute;
   position: absolute;
   left: 0;
   left: 0;
   transform: translateX(0);
   transform: translateX(0);
-  transition: transform 0.3s ease;
+  transition: all 0.3s ease;
   z-index: 2;
   z-index: 2;
 }
 }
 
 
 .menu.collapsed {
 .menu.collapsed {
   transform: translateX(-260px);
   transform: translateX(-260px);
-  width: 260px;
+  width: 0;
 }
 }
 
 
 .conversations {
 .conversations {
@@ -1717,7 +1897,7 @@ const updateCollapsedState = () => {
   overflow-x: hidden;
   overflow-x: hidden;
   margin-left: 0;
   margin-left: 0;
   margin-right: 0;
   margin-right: 0;
-  transition: margin 0.3s ease;
+  transition: all 0.3s ease;
   position: absolute;
   position: absolute;
   left: 0;
   left: 0;
   right: 0;
   right: 0;
@@ -1731,7 +1911,7 @@ const updateCollapsedState = () => {
 
 
 .chat {
 .chat {
   right: 0;
   right: 0;
-  transition: left 0.3s ease, right 0.3s ease;
+  transition: all 0.3s ease;
 }
 }
 
 
 .layout:has(.right_menu:not(.collapsed)) .chat {
 .layout:has(.right_menu:not(.collapsed)) .chat {
@@ -1762,10 +1942,10 @@ const updateCollapsedState = () => {
   padding: 0 20px;
   padding: 0 20px;
 }
 }
 
 
-.logo-img {
+/* .logo-img {
   width: 24px;
   width: 24px;
   height: 24px;
   height: 24px;
-}
+} */
 
 
 .logo span {
 .logo span {
   margin-left: 12px;
   margin-left: 12px;
@@ -2159,7 +2339,11 @@ const updateCollapsedState = () => {
   height: 40px;
   height: 40px;
   border-radius: 50%;
   border-radius: 50%;
 }
 }
-
+.avatar img{
+  width: 100%;
+  height: 100%;
+  border-radius: 50%;
+}
 .welcome-info {
 .welcome-info {
   flex: 1;
   flex: 1;
 }
 }
@@ -2192,7 +2376,7 @@ const updateCollapsedState = () => {
 
 
 /* 建议区域样式 */
 /* 建议区域样式 */
 .suggestion-section {
 .suggestion-section {
-  margin-top: 32px;
+  margin-top: 70px;
 }
 }
 
 
 .suggestion-section h3 {
 .suggestion-section h3 {
@@ -2398,14 +2582,16 @@ const updateCollapsedState = () => {
 
 
 .right_menu {
 .right_menu {
   /* 修改现有样式 */
   /* 修改现有样式 */
-  position: absolute;
-  right: 0;
-  height: 100%;
+ /*  position: absolute;
+  right: 0; */
+  margin-top: 64px;
+  height: 93%;
   background: #f7f7f8;
   background: #f7f7f8;
   border-left: 1px solid rgba(0, 0, 0, 0.06);
   border-left: 1px solid rgba(0, 0, 0, 0.06);
   transform: translateX(0);
   transform: translateX(0);
   transition: transform 0.3s ease, width 0.3s ease;
   transition: transform 0.3s ease, width 0.3s ease;
   z-index: 2;
   z-index: 2;
+  position: relative;
 }
 }
 
 
 .right_menu.collapsed {
 .right_menu.collapsed {
@@ -2434,14 +2620,14 @@ const updateCollapsedState = () => {
   justify-content: center;
   justify-content: center;
   cursor: pointer;
   cursor: pointer;
   z-index: 2;
   z-index: 2;
-  transition: left 0.3s ease;
+  transition: all 0.3s ease;
 }
 }
 
 
-.collapse-left-button.menu-collapsed {
-  left: 260px;
- /*  border-right: 1px solid rgba(0, 0, 0, 0.06);
+.menu-collapsed {
+  left: 0px;
+  border-right: 1px solid rgba(0, 0, 0, 0.06);
   border-left: none;
   border-left: none;
-  border-radius: 0 4px 4px 0; */
+  border-radius: 0 4px 4px 0;
 }
 }
 
 
 /* .collapse-button {
 /* .collapse-button {
@@ -2983,7 +3169,7 @@ const updateCollapsedState = () => {
     position: fixed;
     position: fixed;
     width: 100%;
     width: 100%;
     height: 100%;
     height: 100%;
-    z-index: 1000;
+    z-index: 998;
   }
   }
 
 
   .right_menu.collapsed {
   .right_menu.collapsed {
@@ -3150,7 +3336,6 @@ const updateCollapsedState = () => {
   .web-search-toggle {
   .web-search-toggle {
     position: relative;
     position: relative;
     width: auto;
     width: auto;
-/*     margin-left: auto; */
   }
   }
 
 
   /* 调整菜单样式 */
   /* 调整菜单样式 */
@@ -3174,7 +3359,7 @@ const updateCollapsedState = () => {
   }
   }
 
 
   /* 调整右侧菜单样式 */
   /* 调整右侧菜单样式 */
-  .right_menu {
+/*   .right_menu {
     position: fixed;
     position: fixed;
     right: 0;
     right: 0;
     top: 0;
     top: 0;
@@ -3182,8 +3367,8 @@ const updateCollapsedState = () => {
     width: 300px;
     width: 300px;
     transform: translateX(100%);
     transform: translateX(100%);
     transition: transform 0.3s ease;
     transition: transform 0.3s ease;
-    z-index: 1000;
-  }
+    z-index: 998;
+  } */
 
 
   .right_menu.collapsed {
   .right_menu.collapsed {
     transform: translateX(100%);
     transform: translateX(100%);
@@ -3631,7 +3816,7 @@ button,
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
   transition: transform 0.3s ease;
   transition: transform 0.3s ease;
-  z-index: 1000;
+  z-index: 998;
 }
 }
 
 
 .right_menu.collapsed {
 .right_menu.collapsed {

+ 5 - 0
vite.config.js

@@ -8,6 +8,11 @@ const resolve = (dir) => path.resolve(__dirname, dir)
 export default defineConfig({
 export default defineConfig({
   base: process.env.ELECTRON ? './' : '/',
   base: process.env.ELECTRON ? './' : '/',
   plugins: [vue()],
   plugins: [vue()],
+  server: {
+    host: '0.0.0.0',  // 允许通过IP访问
+    port: 3000,       // 设置端口号
+    open: true        // 自动打开浏览器
+  },
   resolve: {
   resolve: {
     alias: {
     alias: {
       '@': resolve('src'),
       '@': resolve('src'),