浏览代码

修改登录页及ai聊天

yangg 9 月之前
父节点
当前提交
1272fe35bc

+ 1 - 1
.env.development

@@ -2,5 +2,5 @@
 ENV = 'development'
 port = 8080
 # base api
-VUE_APP_BASE_API = 'http://192.168.1.178:8084'
+VUE_APP_BASE_API = 'http://120.46.190.49:8084'
 #192.168.1.178

文件差异内容过多而无法显示
+ 0 - 0
dist/index.html


文件差异内容过多而无法显示
+ 0 - 0
dist/static/css/app.5ec4c5fe.css


文件差异内容过多而无法显示
+ 0 - 0
dist/static/css/chunk-31cf4da1.b128be17.css


文件差异内容过多而无法显示
+ 0 - 0
dist/static/css/chunk-4139ed56.cf6b7d77.css


文件差异内容过多而无法显示
+ 0 - 0
dist/static/css/chunk-48e93132.6733818d.css


二进制
dist/static/img/bgimg.588698b6.jpg


二进制
dist/static/img/login-head.4ebecb6f.png


文件差异内容过多而无法显示
+ 0 - 0
dist/static/js/app.0f181d18.js


文件差异内容过多而无法显示
+ 0 - 0
dist/static/js/app.ca40b060.js


文件差异内容过多而无法显示
+ 0 - 0
dist/static/js/chunk-31cf4da1.9c8b6a26.js


文件差异内容过多而无法显示
+ 0 - 0
dist/static/js/chunk-4139ed56.6394a94f.js


文件差异内容过多而无法显示
+ 0 - 0
dist/static/js/chunk-48e93132.5aa1b9b5.js


文件差异内容过多而无法显示
+ 0 - 0
dist/static/js/chunk-76bddf0f.ff4dcbba.js


二进制
src/assets/images/bgimg.jpg


+ 36 - 4
src/components/webAi/css/ChatBox.css

@@ -24,9 +24,30 @@
         margin-bottom: 120px;
     }
 }
-
+.user-message {
+    display: flex;
+    flex-direction: column !important;
+    align-items: flex-end !important;
+  }
+  
+  .user-message .message-content {
+    order: 1;
+    text-align: right;
+  }
+  
+  .user-message .avatar {
+    order: 2;
+    margin-left: 10px;
+    margin-right: 0;
+  }
+  
+  .message-text {
+    margin-top: 5px;
+  }
 .messages li {
     display: flex;
+    flex-direction: column;
+
     align-items: flex-start; /* 这将确保所有子元素对齐到容器的顶部 */
     margin-bottom: 10px;
 }
@@ -66,6 +87,17 @@
     }
 }
 
+.message_name{
+    display: flex;
+    align-items: center;
+    padding-top: 5px;
+    padding-bottom: 5px;
+}
+.name{
+    margin-left: 8px;
+    margin-bottom: 5px;
+}
+
 .message-content {
     background-color: #f0f0f0; /* 消息背景 */
     padding-left: 20px; /* 上下左右的内边距 */
@@ -73,7 +105,7 @@
     padding-top: 10px; /* 上下左右的内边距 */
     padding-bottom: 0px; /* 上下左右的内边距 */
     max-width: calc(90% - 10px); /* 计算头像和间距 */
-    border-radius: 15px; /* 边框圆角 */
+    border-radius: 7px; /* 边框圆角 */
     margin-bottom: 10px; /* 和下一条消息之间的间距 */
     /* 消息内容样式 */
     display: flex;
@@ -93,7 +125,7 @@
     margin: 5px 0 10px;
     padding: 0;
     font-size: inherit;
-    line-height: inherit;
+    line-height: 22px;
     font-weight: inherit;
   }
 .input-container {
@@ -143,7 +175,7 @@ textarea {
 .sender-name {
     font-weight: bold;
     color: #333;
-    
+    margin-left: -5px;
 }
 
 .btn {

+ 45 - 21
src/components/webAi/index.vue

@@ -1,31 +1,55 @@
 <template>
-    <div class="chat-container">
-      <div class="messages">
-        <ul>
-          <li v-for="message in messages" :key="message.id" :class="message.user">
-            <!-- 添加判断,如果是机器人的消息也显示头像 -->
-            <div class="avatar" :class="{ 'user-avatar': message.user === 'user', 'bot-avatar': message.user === 'bot' }"></div>
-            <div class="message-content">
-              <span class="sender-name" v-if="message.user === 'user'">你</span>
-              <span class="sender-name" v-if="message.user === 'bot'">轻良AI助理</span>
-              <!-- 使用 v-html 来渲染 Markdown 文本 -->
-              <div class="message-text" v-html="renderMarkdown(message.message)"></div>
+  <div class="chat-container">
+    <div class="messages">
+      <ul>
+        <li
+          v-for="message in messages"
+          :key="message.id"
+          :class="[message.user, message.user === 'user' ? 'user-message' : '']"
+        >
+          <!-- 添加判断,如果是机器人的消息也显示头像 -->
+          <div class="message_name">
+            <div
+              class="avatar"
+              :class="{
+                'user-avatar': message.user === 'user',
+                'bot-avatar': message.user === 'bot',
+              }"
+            ></div>
+            <div class="name">
+              <span class="sender-name" style="color: #666666;" v-if="message.user === 'user'">你</span>
+              <span class="sender-name" v-if="message.user === 'bot'"
+                >轻良AI助理</span
+              >
             </div>
-          </li>
-        </ul>
-      </div>
-      <div class="input-container">
-        <textarea v-model="userInput" rows="4" @keydown="handleKeydown" placeholder="发送消息..."></textarea>
-        <button @click="sendMessage" class="btn">发送</button>
-      </div>
-      <!-- 添加 LoginModal 组件 -->
+          </div>
+          <!-- 使用 v-html 来渲染 Markdown 文本 -->
+          <div class="message-content">
+            <div
+              class="message-text"
+              v-html="renderMarkdown(message.message)"
+            ></div>
+          </div>
+        </li>
+      </ul>
+    </div>
+    <div class="input-container">
+      <textarea
+        v-model="userInput"
+        rows="4"
+        @keydown="handleKeydown"
+        placeholder="发送消息..."
+      ></textarea>
+      <button @click="sendMessage" class="btn">发送</button>
+    </div>
+    <!-- 添加 LoginModal 组件 -->
     <LoginModal
       :visible.sync="showLoginModal"
       @close="showLoginModal = false"
       @login-success="handleLoginSuccess"
     />
-    </div>
-  </template>
+  </div>
+</template>
   
   <script src="./js/ChatBox.js"></script>
   <style src="./css/ChatBox.css" scoped></style>

+ 83 - 18
src/components/webAi/js/ChatBox.js

@@ -9,6 +9,8 @@ export default {
   },
   data() {
     return {
+      isThinking: false,
+      thinkingDots: '',
       messages: [
         {
           user: "bot",
@@ -29,7 +31,7 @@ export default {
     };
   },
   created() {
-    this.connectWebSocket();
+   // this.connectWebSocket();
   },
   mounted() {
     if(this.$route.name=='ai'){
@@ -71,10 +73,10 @@ export default {
       // 移除这里的fetch调用,改用WebSocket
       this.websocket = new WebSocket(wsUrl);
     
-      this.websocket.onerror = (event) => {
+     /*  this.websocket.onerror = (event) => {
         console.error("WebSocket 连接错误:", event);
       };
-    
+     */
       this.websocket.onclose = (event) => {
         console.log("WebSocket 连接关闭:", event);
       };
@@ -86,7 +88,7 @@ export default {
         // 解析收到的消息
         const result = JSON.parse(event.data);
 
-        console.log(result.content);
+       
 
         // 检查消息是否完结
         if (result.done) {
@@ -133,7 +135,19 @@ export default {
           done: true,
         });
         this.userInput = "";
-        console.log(this.messages);
+       // 添加机器人的响应消息(初始为空)
+       const botMessage = {
+        user: "bot",
+        messageType: "TEXT",
+        message: "",
+        html: "",
+        time: Date.now(),
+        done: false,
+      };
+      this.messages.push(botMessage);
+
+      // 开始模拟思考
+      const thinkingPromise = this.simulateThinking(botMessage);
         // 通过 HTTP 发送消息
         try {
           ///send-message
@@ -168,21 +182,72 @@ export default {
           }
 
           const result = await response.json();
-          this.handleResponse(result);
-        } catch (error) {
-          console.error("Error sending message:", error);
-        }
+         // 等待思考动画完成
+         await thinkingPromise;
+          
+         this.handleResponse(result, botMessage);
+       } catch (error) {
+         console.error("Error sending message:", error);
+         // 如果发生错误,停止思考动画
+         botMessage.done = true;
+         botMessage.message = "抱歉,发生了错误,请稍后再试。";
+       }
       }
     },
-    handleResponse(value) {
-       const data =value.outputs[0].outputs[0].results.message
-      this.messages.push({
-        user: "bot",
-        messageType: data.text_key,
-        message: data.text,
-        html: "",
-        time: data.timestamp,
-        done: true,
+    async simulateThinking(message) {
+      this.isThinking = true;
+      const thinkingTime = Math.random() * 1000 + 500; // 思考时间为 0.5-1.5 秒
+      const dotInterval = 200; // 每 200ms 更新一次点
+      
+      const updateDots = () => {
+        this.thinkingDots += '.';
+        if (this.thinkingDots.length > 3) {
+          this.thinkingDots = '';
+        }
+        message.message =  this.thinkingDots;
+      };
+  
+      const intervalId = setInterval(updateDots, dotInterval);
+  
+      await new Promise(resolve => setTimeout(resolve, thinkingTime));
+      
+      clearInterval(intervalId);
+      this.isThinking = false;
+      this.thinkingDots = '';
+      message.message = '';
+    },
+  
+    async handleResponse(value, existingMessage) {
+      const data = value.outputs[0].outputs[0].results.message;
+      
+      // 更新现有的消息对象
+      existingMessage.messageType = data.text_key;
+      existingMessage.time = data.timestamp;
+      
+      // 移除思考动画
+      existingMessage.message = '';
+      
+      // 开始打字效果
+      await this.typeMessage(existingMessage, data.text);
+    },
+  
+    typeMessage(message, fullText) {
+      return new Promise(resolve => {
+        const delay = 30; // 每个字符之间的延迟时间(毫秒)
+        let i = 0;
+        
+        const typeChar = () => {
+          if (i < fullText.length) {
+            message.message += fullText[i];
+            i++;
+            setTimeout(typeChar, delay);
+          } else {
+            message.done = true;
+            resolve();
+          }
+        };
+        
+        typeChar();
       });
     },
     renderMarkdown(rawMarkdown) {

+ 2 - 2
src/styles/element-ui.scss

@@ -83,14 +83,14 @@
   box-sizing: content-box;
 }
 
-.el-button{
+/* .el-button{
   background-color: #3cbade;
   
 }
 .el-button:hover{
   background-color: #339ebd;
   
-}
+} */
 .el-button--primary{
   background-color: #3cbade;
 }

+ 3 - 3
src/views/knowledgeMenu/category/knowledgeSet.vue

@@ -295,7 +295,7 @@
             </el-form-item>
           </el-form>
           <span slot="footer" class="dialog-footer">
-            <el-button @click="handleAnalClose">取 消</el-button>
+            <el-button type="primary" @click="handleAnalClose">取 消</el-button>
             <el-button type="primary" @click="submitAnal">确 定</el-button>
           </span>
         </el-dialog>
@@ -314,7 +314,7 @@
             </el-form-item>
           </el-form>
           <span slot="footer" class="dialog-footer">
-            <el-button @click="newFolderDialogVisible = false">取 消</el-button>
+            <el-button type="primary" @click="newFolderDialogVisible = false">取 消</el-button>
             <el-button type="primary" @click="submitNewFolder">确 定</el-button>
           </span>
         </el-dialog>
@@ -345,7 +345,7 @@
             </el-form-item>
           </el-form>
           <span slot="footer" class="dialog-footer">
-            <el-button @click="batchModifyFolderDialogVisible = false"
+            <el-button type="primary" @click="batchModifyFolderDialogVisible = false"
               >取 消</el-button
             >
             <el-button type="primary" @click="submitBatchModifyFolder"

+ 38 - 19
src/views/login/index.scss

@@ -48,39 +48,58 @@ $dark_gray:#333;
 $light_gray:#eee;
 
 .login-container {
+ 
+  background-image: url("../../assets/images/bgimg.jpg");
+  background-size: contain;
   min-height: 100%;
   width: 100%;
-  background-color: $bg;
+ /*  background-color: $bg; */
   overflow: hidden;
   display:flex;
-  flex-direction: column;
-  justify-content: center;
-  align-items: center;
+  justify-content: flex-end;
+  .gradient-background {
+    position: absolute;
+    background: linear-gradient(to bottom, #4f46e5, #3b82f6); /* from-indigo-600 to-blue-500 */
+    opacity: 0.75;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    z-index: 0;
+  }
   .form-content{
-
-    .title-container{
+     background: linear-gradient(to bottom, #4f46e5, #3b82f6);
+    z-index: 999;
+     width: 40%;
+     .login_form{
+      display: flex;
+      overflow: hidden;
+      background: #FFF;
+      height: 100vh;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+    /* .title-container{
       background-color: #899fe1;
       .images{
         height:100px;
       }
+    } */
+      .login-form {
+
+        width: 60%;
+        position: relative;
+        background:#FFF;
+        padding:40px;
+        
+      }
     }
-    .login-form {
-      position: relative;
-      width: 350px;
-      
-      margin: 0 auto;
-      overflow: hidden;
-      background:#FFF;
-      padding:40px;
-
-    }
-
   }
 
   .tips {
     font-size: 14px;
-    color: #666;
-    margin-bottom: 10px;
+    color: #818cf8;
+    margin-bottom: 24px;
     width:100%;
     display: flex;
     justify-content: flex-end;

+ 246 - 68
src/views/login/index.vue

@@ -1,78 +1,83 @@
 <template>
   <div class="login-container">
+    <div class="gradient-background"></div>
+    <canvas id="canvas1"></canvas>
     <div class="form-content">
-      <div class="title-container">
+      <!-- <div class="title-container">
         <img src="../../assets/images/login-head.png" class="images" />
-      </div>
-      <el-form
-        v-if="loginOrRetrieve == 'login'"
-        ref="loginForm"
-        :model="loginForm"
-        :rules="loginRules"
-        class="login-form"
-        autocomplete="on"
-        label-position="left"
-      >
-        <el-form-item prop="username">
-          <span class="svg-container">
-            <svg-icon icon-class="user" />
-          </span>
-          <el-input
-            ref="username"
-            v-model="loginForm.username"
-            placeholder="登录用户名"
-            name="username"
-            type="text"
-            tabindex="1"
-            autocomplete="on"
-          />
-        </el-form-item>
-
-        <el-tooltip
-          v-model="capsTooltip"
-          content="Caps lock is On"
-          placement="right"
-          manual
+      </div> -->
+      <div class="login_form" v-if="loginOrRetrieve == 'login'">
+        <h2>AI知识库管理</h2>
+        <el-form
+          ref="loginForm"
+          :model="loginForm"
+          :rules="loginRules"
+          class="login-form"
+          autocomplete="on"
+          label-position="left"
         >
-          <el-form-item prop="password">
+          <el-form-item prop="username">
             <span class="svg-container">
-              <svg-icon icon-class="password" />
+              <svg-icon icon-class="user" />
             </span>
             <el-input
-              :key="passwordType"
-              ref="password"
-              v-model="loginForm.password"
-              :type="passwordType"
-              placeholder="登录密码"
-              name="password"
-              tabindex="2"
+              ref="username"
+              v-model="loginForm.username"
+              placeholder="登录用户名"
+              name="username"
+              type="text"
+              tabindex="1"
               autocomplete="on"
-              @keyup.native="checkCapslock"
-              @blur="capsTooltip = false"
-              @keyup.enter.native="handleLogin"
             />
-            <span class="show-pwd" @click="showPwd">
-              <svg-icon
-                :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"
-              />
-            </span>
           </el-form-item>
-        </el-tooltip>
 
-        <el-button
-          :loading="loading"
-          type="primary"
-          style="width: 100%; margin-bottom: 30px"
-          @click="handleLogin"
-          >立即登录</el-button
-        >
-        <div class="tips">
-          <div @click="Retrieve('retrieve')" style="cursor: pointer">
-            找回密码
+          <el-tooltip
+            v-model="capsTooltip"
+            content="Caps lock is On"
+            placement="right"
+            manual
+          >
+            <el-form-item prop="password">
+              <span class="svg-container">
+                <svg-icon icon-class="password" />
+              </span>
+              <el-input
+                :key="passwordType"
+                ref="password"
+                v-model="loginForm.password"
+                :type="passwordType"
+                placeholder="登录密码"
+                name="password"
+                tabindex="2"
+                autocomplete="on"
+                @keyup.native="checkCapslock"
+                @blur="capsTooltip = false"
+                @keyup.enter.native="handleLogin"
+              />
+              <span class="show-pwd" @click="showPwd">
+                <svg-icon
+                  :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"
+                />
+              </span>
+            </el-form-item>
+          </el-tooltip>
+          <div class="tips">
+            <div @click="Retrieve('retrieve')" style="cursor: pointer">
+              找回密码?
+            </div>
           </div>
-        </div>
-      </el-form>
-      <div v-if="loginOrRetrieve == 'retrieve'">
+          <el-button
+            :loading="loading"
+            class="btns"
+            type="primary"
+            style="width: 100%; margin-bottom: 30px"
+            @click="handleLogin"
+            >立即登录</el-button
+          >
+        </el-form>
+      </div>
+      <div class="login_form" v-if="loginOrRetrieve == 'retrieve'">
+        <h2>AI知识库管理</h2>
         <el-form
           ref="RetrieveForm"
           :model="RetrieveForm"
@@ -109,7 +114,7 @@
                 autocomplete="on"
               />
               <span class="show-pwd">
-                <el-button :disabled="isDisabled" @click="getCode">
+                <el-button type="primary" :disabled="isDisabled" @click="getCode">
                   {{ buttonText }}
                 </el-button>
               </span>
@@ -176,17 +181,19 @@
               </span>
             </el-form-item>
           </el-tooltip>
-
+          <div class="tips">
+            <div @click="Retrieve('login')" style="cursor: pointer">
+              返回登录
+            </div>
+          </div>
           <el-button
             :loading="loading"
             type="primary"
+            class="btns"
             style="width: 100%; margin-bottom: 30px"
             @click="retPass"
             >重置密码</el-button
           >
-          <div class="tips">
-            <div @click="Retrieve('login')" style="cursor: pointer">返回登录</div>
-          </div>
         </el-form>
       </div>
     </div>
@@ -316,6 +323,7 @@ export default {
     } else if (this.loginForm.password === "") {
       this.$refs.password.focus();
     }
+    this.initParticles();
   },
   destroyed() {
     // window.removeEventListener('storage', this.afterQRScan)
@@ -447,12 +455,182 @@ export default {
     Retrieve(val) {
       this.loginOrRetrieve = val;
     },
+
+    /* 粒子动画 */
+    initParticles() {
+      const canvas = document.getElementById("canvas1");
+      const ctx = canvas.getContext("2d");
+      canvas.width = window.innerWidth;
+      canvas.height = window.innerHeight;
+
+      let area = Math.sqrt(canvas.width * canvas.height);
+      let radiusLength = (canvas.width * canvas.height) / 8000;
+
+      let particlesArray;
+
+      let mouse = {
+        x: null,
+        y: null,
+        radius: radiusLength,
+      };
+
+      window.addEventListener("mousemove", function (event) {
+        mouse.x = event.x;
+        mouse.y = event.y;
+      });
+
+      window.addEventListener("mouseout", function (event) {
+        mouse.x = undefined;
+        mouse.y = undefined;
+      });
+
+      window.addEventListener("resize", function (event) {
+        canvas.width = innerWidth;
+        canvas.height = innerHeight;
+        radiusLength = (canvas.width * canvas.height) / 9000;
+        createParticles();
+      });
+
+      class Particle {
+        constructor(x, y, velX, velY, size, color) {
+          this.x = x;
+          this.y = y;
+          this.velX = velX;
+          this.velY = velY;
+          this.size = size;
+          this.color = color;
+        }
+
+        draw() {
+          ctx.beginPath();
+          ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2, false);
+          ctx.fillStyle = "#ffffff";
+          ctx.fill();
+        }
+
+        update() {
+          if (this.x > canvas.width || this.x < 0) {
+            this.velX = -this.velX;
+          }
+          if (this.y > canvas.height || this.y < 0) {
+            this.velY = -this.velY;
+          }
+
+          let dx = mouse.x - this.x;
+          let dy = mouse.y - this.y;
+          let dist = Math.sqrt(dx * dx + dy * dy);
+          if (dist < mouse.radius + this.size) {
+            const buffer = this.size * 10;
+            if (mouse.x < this.x && this.x < canvas.width - buffer) {
+              this.x += 10;
+            }
+            if (mouse.x > this.x && this.x > buffer) {
+              this.x -= 10;
+            }
+            if (mouse.y < this.y && this.y < canvas.height - buffer) {
+              this.y += 10;
+            }
+            if (mouse.y > this.y && this.y > buffer) {
+              this.y -= 10;
+            }
+          }
+
+          this.x += this.velX;
+          this.y += this.velY;
+          this.draw();
+        }
+      }
+
+      function createParticles() {
+        particlesArray = [];
+        let noOfParticles = (canvas.width * canvas.height) / 10000;
+        for (let i = 0; i < noOfParticles; i++) {
+          let size = Math.random() * 5 + 1;
+          let x = Math.random() * (innerWidth - 2 * size) + 2 * size;
+          let y = Math.random() * (innerHeight - 2 * size) + 2 * size;
+          let velX = Math.random() * 2;
+          let velY = Math.random() * 2;
+          let color = "#ffffff";
+          particlesArray.push(new Particle(x, y, velX, velY, size, color));
+        }
+      }
+
+      function connect() {
+        let vicinityDist = (canvas.width * canvas.height) / 81;
+        for (let i = 0; i < particlesArray.length; i++) {
+          for (let j = i; j < particlesArray.length; j++) {
+            let distance =
+              Math.pow(particlesArray[i].x - particlesArray[j].x, 2) +
+              Math.pow(particlesArray[i].y - particlesArray[j].y, 2);
+            let opacity = 1 - distance / 25000;
+            if (distance < vicinityDist) {
+              ctx.strokeStyle = "rgba(255,255,255," + opacity + ")";
+              ctx.lineWidth = 1;
+              ctx.beginPath();
+              ctx.moveTo(particlesArray[i].x, particlesArray[i].y);
+              ctx.lineTo(particlesArray[j].x, particlesArray[j].y);
+              ctx.stroke();
+            }
+          }
+        }
+      }
+
+      function animate() {
+        requestAnimationFrame(animate);
+        ctx.clearRect(0, 0, innerWidth, innerHeight);
+        for (let i = 0; i < particlesArray.length; i++) {
+          particlesArray[i].update();
+        }
+        connect();
+      }
+
+      createParticles();
+      animate();
+    },
   },
 };
 </script>
 
-<style lang="scss">
+<style lang="scss" >
 /* 修复input 背景不协调 和光标变色 */
 /* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
+
+.btns {
+  width: 100%;
+  font-size: 16px;
+  display: flex;
+  justify-content: center;
+  background-image: linear-gradient(to right, #6366f1, #2563eb);
+  color: #f3f4f6;
+  padding: 1rem;
+  border-radius: 9999px;
+  font-weight: 600;
+  cursor: pointer;
+  transition: all 0.5s ease-in;
+}
+.el-button {
+  border: none;
+  box-shadow: none;
+}
+.btns:hover {
+  background-image: linear-gradient(to left, #3b82f6, #6366f1) !important;
+}
+.btns:focus {
+  border-color: #3b82f6 !important;
+  background-image: linear-gradient(to left, #3b82f6, #6366f1) !important;
+}
+.login-container {
+  position: relative;
+  overflow: hidden;
+}
+
+#canvas1 {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 1;
+  width: 100%;
+  height: 100%;
+}
 @import "./index.scss";
 </style>

部分文件因为文件数量过多而无法显示