chenrong hai 1 ano
pai
achega
238f843c4f

+ 3 - 9
src/api/login.js

@@ -1,15 +1,9 @@
 import request from '@/utils/request'
 
 // 登录方法
-export function login(username, password, code, uuid) {
-  const data = {
-    username,
-    password,
-    code,
-    uuid
-  }
+export function login(data) {
   return request({
-    url: '/login',
+    url: '/system/app/loginByAccount',
     headers: {
       isToken: false
     },
@@ -21,7 +15,7 @@ export function login(username, password, code, uuid) {
 // 注册方法
 export function register(data) {
   return request({
-    url: '/register',
+    url: '/system/app/account',
     headers: {
       isToken: false
     },

BIN=BIN
src/assets/images/logo.webp


+ 24 - 22
src/permission.js

@@ -19,28 +19,30 @@ router.beforeEach((to, from, next) => {
       next({ path: "/" });
       NProgress.done();
     } else {
-      if (store.getters.roles.length === 0) {
-        isRelogin.show = true;
-        // 判断当前用户是否已拉取完user_info信息
-        store
-          .dispatch("GetInfo")
-          .then(() => {
-            isRelogin.show = false;
-            store.dispatch("GenerateRoutes").then((accessRoutes) => {
-              // 根据roles权限生成可访问的路由表
-              router.addRoutes(accessRoutes); // 动态添加可访问路由表
-              next({ ...to, replace: true }); // hack方法 确保addRoutes已完成
-            });
-          })
-          .catch((err) => {
-            store.dispatch("LogOut").then(() => {
-              Message.error(err);
-              next({ path: "/" });
-            });
-          });
-      } else {
-        next();
-      }
+      next();
+      // next({ ...to, replace: true })
+      // if (store.getters.roles.length === 0) {
+      //   isRelogin.show = true;
+      //   // 判断当前用户是否已拉取完user_info信息
+      //   store
+      //     .dispatch("GetInfo")
+      //     .then(() => {
+      //       isRelogin.show = false;
+      //       store.dispatch("GenerateRoutes").then((accessRoutes) => {
+      //         // 根据roles权限生成可访问的路由表
+      //         router.addRoutes(accessRoutes); // 动态添加可访问路由表
+      //         next({ ...to, replace: true }); // hack方法 确保addRoutes已完成
+      //       });
+      //     })
+      //     .catch((err) => {
+      //       store.dispatch("LogOut").then(() => {
+      //         Message.error(err);
+      //         next({ path: "/" });
+      //       });
+      //     });
+      // } else {
+      //   next();
+      // }
     }
   } else {
     // 没有token

+ 19 - 3
src/router/index.js

@@ -46,20 +46,36 @@ export const constantRoutes = [
     component: () => import("@/views/detail"),
     name: "Detail",
   },
+  {
+    path: "/detailH5",
+    component: () => import("@/views/detail/indexH5.vue"),
+    name: "DetailH5",
+  },
   {
     path: "/chat",
     component: () => import("@/views/chat"),
     name: "Chat",
   },
+  {
+    path: "/chatH5",
+    component: () => import("@/views/chat/indexH5"),
+    name: "ChatH5",
+  },
+  
   {
     path: "/create",
     component: () => import("@/views/create"),
     name: "Create",
   },
   {
-    path: "/login",
-    component: () => import("@/views/login"),
-    name: "Login",
+    path: "/registerH5",
+    component: () => import("@/views/loginAndRegister/registerH5"),
+    name: "RegisterH5",
+  },
+  {
+    path: "/loginH5",
+    component: () => import("@/views/loginAndRegister/loginH5"),
+    name: "LoginH5",
   },
 ];
 

+ 4 - 7
src/store/modules/user.js

@@ -31,14 +31,11 @@ const user = {
   actions: {
     // 登录
     Login({ commit }, userInfo) {
-      const username = userInfo.username.trim()
-      const password = userInfo.password
-      const code = userInfo.code
-      const uuid = userInfo.uuid
       return new Promise((resolve, reject) => {
-        login(username, password, code, uuid).then(res => {
-          setToken(res.token)
-          commit('SET_TOKEN', res.token)
+        login(userInfo).then(res => {
+          console.log(res, 'res')
+          setToken(res.data.userToken)
+          commit('SET_TOKEN', res.data.userToken)
           resolve()
         }).catch(error => {
           reject(error)

+ 490 - 0
src/views/chat/indexH5.vue

@@ -0,0 +1,490 @@
+<template>
+  <div class="chat bg-gray-900 comBg">
+    <div class="flex flex-col items-center justify-center h-screen px-4">
+      <img class="absolute h-full" :src="baseApi + info.picture" alt="">
+      <!-- onClick={() => navigate('/')} -->
+      <div class="top">
+        <button class="tm_button absolute text-xs bg-none py-1 px-3 rounded flex" @click="goBack">
+          <i class="el-icon-arrow-left"></i>
+        </button>
+      </div>
+      
+      <div class="chatContent">
+        <!-- <div class="leftImg">
+          <img :src="baseApi + info.picture" alt="">
+          <div class="aiInfo">
+            <span class="photo">
+              <img :src="baseApi + info.picture">
+            </span>
+            <div class="info">
+              <div class="name">{{ info.characterName }}</div>
+              <div class="tags">
+                <span class="tag" v-for="(item, index) in info.labelArr" :key="index">
+                  {{ item }}
+                </span>
+              </div>
+              <div class="infoContent">{{ info.prologue }}</div>
+            </div>
+          </div>
+        </div> -->
+        <div class="chat-box"> 
+          <div class="messages" ref="messages">
+            <template v-for="(item, index) in returnMessage">
+              <div v-if="item.role == 'assistant'" class="text-white mb-4 flex" :key="index">
+                <!-- ai返回的信息 -->
+                <div class="pt-2 photo">
+                  <img
+                    :src="baseApi + info.picture"
+                    class="rounded-full w-12 h-12 object-cover"
+                  />
+                </div>
+                <div class="message fex-1 ml-4 mt-2 p-2 rounded-r-md rounded-bl-md text-xs" >
+                  <div v-show="!item.content" class="loadingMessage" >
+                    <div v-loading="!item.content" element-loading-background="rgba(0, 0, 0, 0.0)"></div>
+                  </div>
+                  <p v-show="item.content">{{ item.content }} </p>
+                </div>
+              </div>
+              <!-- 用户发送的信息 -->
+              <div v-if="item.role == 'user'" class="text-white mb-4 flex me" :key="index">
+                <div class="pt-2 photo">
+                  <img
+                    src="@/assets/images/default_avatar_user.png"
+                    class="rounded-full w-12 h-12 object-cover"
+                  />
+                </div>
+                <div class="message2 fex-1 mr-4  mt-2 p-2 rounded-l-md rounded-br-md text-xs">
+                  <p>{{ item.content }}</p>
+                </div>
+              </div>
+            </template>
+          </div>
+          
+          <div class="absolute left-2 right-2 bottom-4 ">
+            <!-- <div class="flex items-center text-gray-400 text-xs mb-4 ">
+              <span>@Leon S Kennedy - Resident Evil</span>
+            </div> -->
+            <div class="flex">
+              <el-button class="buttonBg1 mButton" @click="newStart" style="margin-right: 5px;" type="primary" round icon="el-icon-plus"></el-button>
+              <input
+                type="text"
+                placeholder="输入消息..."
+                class="flex-1 py-2 px-4 rounded-l-lg text-sm text-white focus:outline-none"
+                style="background: #ffffff0f; border: 1px solid #5b5b5e;"
+                v-model="content"
+                @keydown="Enterkey"
+              />
+              <button @click="getStreamChatWithWeb" class="buttonBg1 text-white px-4 rounded-r-lg text-sm">
+                发送
+              </button>
+              
+            </div>
+          </div>
+        </div>
+      </div>
+      
+    </div>
+  </div>
+</template>
+
+<script>
+import { streamChatWithWebApi } from "@/api/chat.js"
+import { detailApi } from "@/api/detail.js"
+export default {
+  data() {
+    return {
+      info: {},
+      messageLoading: false,
+      returnMessage: [],
+      content: ''
+    }
+  },
+  mounted() {
+    if (this.$route.query.characterId) {
+      this.getDetail(this.$route.query.characterId)
+    }
+  },
+  watch: {
+    returnMessage: {
+      handler(newVal, lodVal) {
+        this.$nextTick(() => {
+          let messages = this.$refs.messages
+          messages.scrollTop = messages.scrollHeight
+        })
+      },
+      deep: true
+    }
+  },
+  methods: {
+    getDetail(id) {
+      detailApi(id).then(res => {
+        console.log(res, '角色详情');
+        this.info = res.data
+        let HistoryMessage = JSON.parse(localStorage.getItem(`[${123},${this.info.id}]`));
+        if (HistoryMessage) {
+          this.returnMessage = HistoryMessage
+        } else {
+          this.returnMessage.push({
+            role: 'assistant',
+            content: this.info.firstContent
+          })
+          localStorage.setItem(`[${123},${this.info.id}]`, JSON.stringify(this.returnMessage));
+        }
+      })
+    },
+    goBack() {
+      this.$router.back()
+    },
+    newStart() {
+      // 清空历史数据
+      localStorage.removeItem(`[${123},${this.info.id}]`);
+      this.returnMessage = []
+      this.getDetail(this.info.id)
+    },
+    Enterkey(e) {
+      if (e.keyCode == 13) {
+        this.getStreamChatWithWeb()
+      }
+    },
+    async getStreamChatWithWeb_old() {
+      this.messageLoading = true
+      if (!this.content) {
+        return
+      }
+      this.returnMessage.push({
+        role: 'user',
+        content: this.content
+      })
+      let HistoryMessage = JSON.parse(localStorage.getItem(`[${123},${this.info.id}]`));
+      // 历史记录取最新的20条传给后端
+      if (HistoryMessage && HistoryMessage.length > 20) {
+        HistoryMessage = HistoryMessage.slice(HistoryMessage.length - 1, 20)
+      }
+      let params = {
+        historyMessage: HistoryMessage || [],
+        characterId: this.info.id,
+        prompt: JSON.parse(JSON.stringify(this.content))
+      }
+      // 清空输入框的值
+      this.content = ''
+      // 新增一条ai信息
+      this.returnMessage.push({
+        role: 'assistant',
+        content: ''
+      })
+      let res = await streamChatWithWebApi(params)
+      console.log(res.data.message.content, 'res');
+      let content = res.data.message.content
+      let index = 0
+      let xing = 1
+      let messageInterval = setInterval(() => {
+        if (index > content.length - 1) {
+          console.log('结束');
+          // 消息全部显示后存到历史localStorage
+          let HistoryMessage = []
+          // 历史只存100条,长度超出截取最新的
+          if (this.returnMessage.length > 100) {
+            HistoryMessage = JSON.stringify(this.returnMessage.slice(this.returnMessage.length - 100, 100))
+          } else {
+            HistoryMessage = JSON.stringify(this.returnMessage)
+          }
+          localStorage.setItem(`[${123},${this.info.id}]`, HistoryMessage);
+
+          clearInterval(messageInterval)
+          return
+        }
+        console.log(content[index], 'content[index]');
+        if (content[index] == "*") {
+          if (xing == 1) {
+            xing = 2
+            this.returnMessage[this.returnMessage.length - 1].content += "("
+          } else if (xing == 2) {
+            xing = 1
+            this.returnMessage[this.returnMessage.length - 1].content += ")"
+          }
+        } else {
+          this.returnMessage[this.returnMessage.length - 1].content += content[index]
+        }
+        index += 1
+      }, 50)
+    },
+    async getStreamChatWithWeb() {
+      this.messageLoading = true
+      if (!this.content) {
+        return
+      }
+      this.returnMessage.push({
+        role: 'user',
+        content: this.content
+      })
+      let HistoryMessage = JSON.parse(localStorage.getItem(`[${123},${this.info.id}]`));
+      // 历史记录取最新的20条传给后端
+      if (HistoryMessage && HistoryMessage.length > 20) {
+        HistoryMessage = HistoryMessage.slice(HistoryMessage.length - 1, 20)
+      }
+      let params = {
+        HistoryMessage: HistoryMessage || [],
+        characterId: this.info.id,
+        prompt: this.content
+      }
+      // 新增一条ai信息
+      this.returnMessage.push({
+        role: 'assistant',
+        content: ''
+      })
+      // 清空输入框的值
+      this.content = ''
+
+      let res = await streamChatWithWebApi(params)
+      console.log(res, 'res');
+      this.messageLoading = false
+      
+      if (res.status != 200) {
+        this.$message.error(res.statusText)
+        this.returnMessage.splice(this.returnMessage.length - 1, 1)
+        return
+      }
+      const reader = res.body.getReader()
+      const decoder=new TextDecoder()
+      while(1){
+        const {done, value} = await reader.read()
+        // console.log(done, 'done');
+        if(done){
+          console.log(value, 'value');
+          // if (typeof(value) == "undefined") {
+          //   this.$message.error('错误')
+          //   this.returnMessage.splice(this.returnMessage.length - 1, 1)
+          // }
+          console.log('结束');
+          // 消息全部显示后存到历史localStorage
+          let HistoryMessage = []
+          // 历史只存100条,长度超出截取最新的
+          if (this.returnMessage.length > 100) {
+            HistoryMessage = JSON.stringify(this.returnMessage.slice(this.returnMessage.length - 100, 100))
+          } else {
+            HistoryMessage = JSON.stringify(this.returnMessage)
+          }
+          localStorage.setItem(`[${123},${this.info.id}]`, HistoryMessage);
+          break;
+        }
+        
+        //txt就是一个一个的字 然后添加到页面上就可以了
+        const txt = decoder.decode(value).split('data:')
+        // const txt = decoder.decode(value)
+        this.addMessage(txt)
+        // let data = JSON.parse(txt).message.content
+        // console.log(txt, 'txt');
+        // if (this.isJSON(txt) && JSON.parse(txt).code != 200) {
+        //   let data = JSON.parse(txt)
+        //   this.$message.error(data.msg)
+        //   this.returnMessage.splice(this.returnMessage.length - 1, 1)
+        //   break;
+        // }
+        
+      }
+    },
+    addMessage(value) {
+      for (let i = 0; i < value.length; i++) {
+        const element = value[i];
+        if (this.isJSON(element)) {
+          let txt = JSON.parse(element).content
+          this.returnMessage[this.returnMessage.length - 1].content += txt
+        }
+      }
+    },
+    isJSON(str) {
+      if (typeof str == 'string') {
+          try {
+              JSON.parse(str);
+              return true;
+          } catch(e) {
+              // console.log(e);
+              return false;
+          }
+      }
+    },
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+.chat {
+  min-height: 100vh;
+}
+.top {
+  position: relative;
+  width: 100%;
+  height: 60px;
+  display: flex;
+  align-items: center;
+}
+.chatContent {
+  // margin-top: 30px;
+  background-size: 100% 100%;
+  display: flex;
+  height: calc(100% - 60px);
+  justify-content: center;
+  width: 100vw;
+  
+  >.leftImg {
+      border-radius: 16px 16px 16px 16px;
+      height: 95%;
+      position: relative;
+      min-width: 380px;
+
+      >img {
+        border: 6px solid;
+        border-image: linear-gradient(180deg, #b48733, #e8cf97, #b48733) 6 6;
+        height: 99%;
+        margin-right: 5px;
+      }
+      >.aiInfo {
+        background-color: #ffffff1a;
+        border-radius: 24px 24px 24px 24px;
+        bottom: -70px;
+        display: flex;
+        height: 193px;
+        justify-content: center;
+        left: 50%;
+        position: absolute;
+        transform: translate(-50%, -50%);
+        width: 100%;
+
+        >.photo {
+          width: 64px;
+          height: 64px;
+          font-size: 18px;
+          position: absolute;
+          top: -20px;
+          box-sizing: border-box;
+          margin: 0;
+          padding: 0;
+          color: #fff;
+          line-height: 1.5714285714285714;
+          list-style: none;
+          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
+          display: inline-flex;
+          justify-content: center;
+          align-items: center;
+          overflow: hidden;
+          white-space: nowrap;
+          text-align: center;
+          vertical-align: middle;
+          background: rgba(0, 0, 0, 0.25);
+          border: 1px solid transparent;
+          border-radius: 50%;
+        }
+        >.info {
+          padding-top: 50px;
+          width: 80%;
+          >.name {
+            color: #fff;
+            font-size: 22px;
+            font-style: normal;
+            font-weight: 700;
+            line-height: 31px;
+            text-align: center;
+            text-transform: none;
+          }
+          >.tags {
+            padding-bottom: 8px;
+            text-align: center;
+            >.tag {
+              margin-right: 4px;
+              background: #ffffff1a;
+              border-radius: 4px 4px 4px 4px;
+              color: #fff;
+              font-size: 12px;
+              font-weight: 500;
+              line-height: 17px;
+              border-color: transparent;
+            }
+          }
+          >.infoContent {
+            color: #fff;
+            font-size: 16px;
+            font-style: normal;
+            font-weight: 500;
+            height: 80px;
+            line-height: 22px;
+            overflow-y: auto;
+            text-align: center;
+            text-transform: none;
+          }
+        }
+      }
+  }
+  >.chat-box {
+    position: relative;
+    background: linear-gradient(rgba(255, 255, 255, 0) 75%, rgb(34, 34, 34));
+    background-position: 50%;
+    background-repeat: no-repeat;
+    background-size: auto;
+    // border: 1px solid #635677 !important;
+    box-sizing: border-box;
+    color: #fff !important;
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    // margin: 0 auto;
+    width: 1010px;
+    padding: 25px;
+  }
+}
+
+.messages {
+  height: calc(100% - 68px );
+  overflow-y: auto;
+}
+.messages::-webkit-scrollbar {
+  width: 0px;
+}
+.message {
+  background: rgba(0, 0, 0, 0.6);
+  // border: 1px solid #5b5b5e;
+  max-width: calc(100% - 72px);
+  // flex: 1;
+  min-width: 80px;
+}
+.message2 {
+  background: rgba(143, 37, 255, 0.6);
+  // border: 1px solid #5b5b5e;
+  max-width: calc(100% - 72px);
+  // flex: 1;
+  min-width: 80px;
+}
+.photo {
+  width: 56px;
+}
+.me {
+  flex-direction: row-reverse;
+}
+.loadingMessage {
+  position: relative;
+  // display: flex;
+  // justify-content: center;
+  width: 100%;
+  height: 100%;
+}
+</style>
+<style scoped>
+
+  .loadingMessage >>> .el-loading-parent--relative {
+    width: 100%;
+    height: 100%;
+    
+  }
+  .loadingMessage >>> .el-loading-spinner {
+    display: flex;
+    justify-content: center;
+  }
+  .mButton {
+    border: solid 0px;
+  }
+  .tm_button {
+    border: 1px solid #FFF;
+    color: #FFF;
+    font-size: 16px;
+  }
+</style>

+ 169 - 0
src/views/detail/indexH5.vue

@@ -0,0 +1,169 @@
+<template>
+  <div class="detail bg-gray-900 comBg overflow-hidden relative">
+    <img :src="baseApi + info.picture" class="img w-full absolute" />
+    <button class="tm_button absolute text-xs bg-none py-1 px-3 rounded mt-6 ml-2 flex" @click="goBack">
+      <i class="el-icon-arrow-left"></i>
+    </button>
+    <div class="text-white mx-auto absolute infoBottom bg-gray-900 pt-4 px-4 h-full w-full">
+      <div class="flex justify-between">
+        <img :src="baseApi + info.picture" class="photo img w-20 h-20 rounded-full" />
+        <!-- <div class="info ">
+          <div>
+            <button class="text-xs bg-gray-700 py-1 px-2 rounded mt-2 flex">
+              <v-icon name="brands/twitter" class="mx-1" scale="1"/>
+              分享至Twitter
+            </button>
+          </div>
+          <div>
+            <button class="text-xs bg-gray-700 py-1 px-2 rounded mt-2 flex">
+              <v-icon name="brands/reddit" class="mx-1" scale="1"/>
+              分享至Reddit
+            </button>
+          </div>
+        </div> -->
+        <div class="flex-1 ml-4">
+          <div class="flex justify-between w-full">
+            <h3 class="text-lg font-bold">
+              {{ info.characterName }}
+            </h3>
+            <div class="flex items-center justify-end pb-3">
+              <div class="flex items-center mx-1 text-gray-200">
+                <v-icon name="heart" scale="1.2"/>
+                <!-- <div class="ml-1 w-9">{{ info.likeNum }}</div> -->
+              </div>
+ 
+              <!-- <div class="flex items-center">
+                <v-icon name="share" scale="1.5"/>
+                <span>{{ item.likeNum }}</span>
+              </div> -->
+            </div>
+          </div>
+          
+          <div class="flex justify-start mt-2">
+            <div class="labels">
+              <div v-for="(item, index) in info.labelArr" :key="index" class="bg-gray-700 px-2 py-1 rounded inline-flex items-center mx-1 text-xs h-8">
+                <!-- <i class="fas fa-smile text-yellow-400 mr-2"></i> -->
+                {{ item }}
+              </div>
+            </div>
+          </div>
+          <!-- onClick={() => navigate('/chat')} -->
+        </div>
+      </div>
+      
+      <!-- <div class="flex items-center space-x-4 mt-4">
+        <div class="flex-1">
+          <h1 class="text-2xl mb-2">
+            {{ info.characterName }}
+          </h1>
+          <h1 class="text-lg  mb-2">
+            性格
+          </h1>
+          <p class="text-sm">
+            {{ info.characterPersonalityTags }}
+          </p>
+        </div>
+      </div> -->
+
+      <div class="mt-2">
+        <h2 class="text-lg mb-2">描述</h2>
+        <p class="text-xs">
+          {{ info.prologue }}
+        </p>
+      </div>
+      <div class="mt-2 h-28 rounded bg-gray-600 p-2">
+        <h2 class="text-base mb-1">开场白</h2>
+        <p class="text-xs ">
+          {{ info.prologue }}
+        </p>
+      </div>
+      <div class="flex justify-center mt-8">
+        <i class="fas fa-chevron-up text-gray-400"></i>
+      </div>
+    </div>
+    <div class="w-full absolute bottom-0 bg-gray-900 flex justify-center py-2">
+      <button
+        class="bg-pink-600 text-xs py-1 px-4 w-10/12 h-10 text-white font-semibold"
+        style="border-radius: 2.5rem;"
+        @click="toChat"
+      >
+        隐私聊天
+      </button>
+    </div>
+  </div>
+  
+</template>
+
+<script>
+import Header from "@/views/homeComponents/Header.vue"
+import { detailApi } from "@/api/detail.js"
+// 引入需要的图标
+import 'vue-awesome/icons/heart'
+// import 'vue-awesome/icons/comment'
+import 'vue-awesome/icons/star'
+
+import 'vue-awesome/icons/brands/twitter'
+import 'vue-awesome/icons/brands/reddit'
+export default {
+  components: {
+    Header,
+  },
+  data() {
+    return {
+      info: {}
+    }
+  },
+  mounted() {
+    console.log(this.$route.query.id);
+    if (this.$route.query.id) {
+      this.getDetail(this.$route.query.id)
+    }
+  },
+  methods: {
+    goBack() {
+      this.$router.back()
+    },
+    toChat() {
+      this.$router.push({
+        path: '/chatH5',
+        query: {
+          characterId: this.info.id
+        }
+      })
+    },
+    getDetail(id) {
+      detailApi(id).then(res => {
+        console.log(res, '角色详情');
+        this.info = res.data
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.detail {
+  height: 100vh;
+}
+.labels {
+  width: 100%;
+  overflow-x: auto;
+}
+.photo {
+
+}
+.img {
+  object-fit: cover;
+  object-position: top;
+}
+.infoBottom {
+  overflow: hidden;
+  border-radius: 10px;
+  top: 60%;
+}
+.tm_button {
+  border: 1px solid #FFF;
+  color: #FFF;
+  font-size: 16px;
+}
+</style>

+ 195 - 10
src/views/homeComponents/Header.vue

@@ -33,31 +33,140 @@
         >
           创建机器人
         </button>
-        <button class="text-yellow-400 p-2">
+        <button class="text-yellow-400 p-2 w-18 mr-3 flex justify-center items-center">
           <v-icon name="coins" scale="1"/>
+          <span class="text-yellow-400 text-lg ml-2">{{ 0 }}</span>
         </button>
-        <span class="text-yellow-400 text-lg mr-2">40</span>
+        
 
-        <button class="text-white p-2">
-          <i class="fas el-icon-message-solid"></i>
-        </button>
-        <button class="text-white p-2">
-          <i class="fas el-icon-user-solid text-white mr-2"></i>
-        </button>
+        <div class="flex justify-center" v-if="getToken()">
+          <button class="text-white p-2">
+            <i class="fas el-icon-message-solid"></i>
+          </button>
+          <div class="ml-2">
+            <img src="@/assets/images/default_avatar_user.png" class=" w-10 h-10 rounded-full cursor-pointer" alt="">
+          </div>
+        </div>
+        <div v-else class="felx justify-center items-center">
+          <button
+            class="text-white text-sm mr-3 cursor-pointer px-3 py-2 rounded-md"
+            style="background-color: var(--bg-color1);"
+            @click="showLogin"
+          >
+            登录
+          </button>
+          <button
+            class="text-white text-sm mr-3 cursor-pointer px-3 py-2 rounded-md"
+            style="background-color: var(--bg-color1);"
+            @click="showRegister"
+          >
+            注册
+          </button>
+        </div>
       </div>
     </div>
+    <el-dialog
+      class="dialog"
+      :visible.sync="showPopup"
+      :show-close="false"
+      width="30%"
+      :before-close="handleClose">
+      <div slot="title" class="popupTitle">
+        {{ popupType == 'login' ? '登录' : '注册'}}
+      </div>
+      <!-- 登录表单 -->
+      <el-form class="form" :rules="loginRules" v-if="popupType == 'login'" ref="formLogin" :model="formLogin" label-width="0">
+        <el-form-item label="" prop="account">
+          <el-input v-model="formLogin.account" placeholder="账号"></el-input>
+        </el-form-item>
+        <el-form-item label="" prop="password">
+          <el-input v-model="formLogin.password" placeholder="密码" show-password></el-input>
+        </el-form-item>
+        <!-- <el-form-item label="">
+          <span class="textButton">忘记密码</span>
+        </el-form-item> -->
+        <el-form-item label="">
+          <button
+            class="formButton text-white text-lg mr-3 cursor-pointer px-3 py-2 rounded-md w-full"
+            style="background-color: var(--bg-color1);"
+            @click="clickLogin"
+          >
+            登录
+          </button>
+        </el-form-item>
+        <el-form-item label="">
+          <span style="font-size: 1rem;">
+            还没有账号吗?
+            <span class="textButton" @click="popupType = 'register'">注册</span>
+          </span>
+        </el-form-item>
+      </el-form>
+      <!-- 注册表单 -->
+      <el-form class="form" :rules="registerRules" v-if="popupType == 'register'" ref="formRegister" :model="formRegister" label-width="0">
+        <el-form-item label="" prop="account"> 
+          <el-input v-model="formRegister.account" placeholder="账号"></el-input>
+        </el-form-item>
+        <el-form-item label="" prop="password">
+          <el-input v-model="formRegister.password" placeholder="密码"></el-input>
+        </el-form-item>
+        <el-form-item label="" prop="repeatPassword">
+          <el-input v-model="formRegister.repeatPassword" placeholder="确认密码"></el-input>
+        </el-form-item>
+        <el-form-item label="">
+          <button
+            class="formButton text-white text-lg mr-3 cursor-pointer px-3 py-2 rounded-md w-full"
+            style="background-color: var(--bg-color1);"
+            @click="clickRegister"
+          >
+            注册
+          </button>
+        </el-form-item>
+      </el-form>
+    </el-dialog>
   </div>
 </template>
 
 <script>
 // 引入需要的图标
 import 'vue-awesome/icons/coins'
-
+import { register } from "@/api/login.js"
+import { getToken, setToken, removeToken } from '@/utils/auth'
 export default {
   props: ['searchName'],
   data() {
     return {
-      searchValue: ''
+      showPopup: false,
+      popupType: 'login',
+      getToken,
+      searchValue: '',
+      formLogin: {
+        account: null,
+        password: null,
+      },
+      formRegister: {
+        account: null,
+        password: null,
+        repeatPassword: null,
+      },
+      loginRules: {
+        account: [
+          { required: true, message: '请输入用户账号', trigger: 'blur' },
+        ],
+        password: [
+          { required: true, message: '请输入用户密码', trigger: 'blur' },
+        ]
+      },
+      registerRules: {
+        account: [
+          { required: true, message: '请输入用户账号', trigger: 'blur' },
+        ],
+        password: [
+          { required: true, message: '请输入用户密码', trigger: 'blur' },
+        ],
+        repeatPassword: [
+          { required: true, message: '请输入确认用户密码', trigger: 'blur' },
+        ],
+      }
     }
   },
   mounted() {
@@ -73,6 +182,54 @@ export default {
     },
   },
   methods: {
+    handleClose() {
+      this.showPopup = false
+    },
+    showLogin() {
+      this.showPopup = true
+      this.popupType = 'login'
+      this.$nextTick(() =>{
+        this.$refs.formLogin.resetFields()
+      })
+      
+    },
+    showRegister() {
+      this.showPopup = true
+      this.popupType = 'register'
+      this.$nextTick(() =>{
+        this.$refs.formRegister.resetFields()
+      })
+    },
+    clickLogin() {
+      this.$refs.formLogin.validate((valid) => {
+        if (valid) {
+          this.$store.dispatch('Login', this.formLogin).then(() => {
+            this.$message({
+              message: '登录成功',
+              type: 'success'
+            });
+            this.showPopup = false
+          })
+        }
+      })
+    },
+    clickRegister() {
+      this.$refs.formRegister.validate((valid) => {
+        if (valid) {
+          register(this.formRegister).then(res => {
+            console.log(res, '注册结果');
+            setToken(res.data.userToken)
+            this.$store.commit('SET_TOKEN', res.data.userToken)
+            this.$message({
+              message: '注册成功',
+              type: 'success'
+            });
+            this.showPopup = false
+
+          })
+        }
+      })
+    },
     Enterkey(e) {
       if (e.keyCode == 13) {
         this.toHomeSearch()
@@ -99,9 +256,37 @@ export default {
 </script>
 
 <style lang="scss" scoped>
+.popupTitle {
+  text-align: center;
+  font-size: 22px;
+  font-weight: 600;
+  color: var(--bg-color1);
+  letter-spacing: 6px;
+}
 .searchInput {
   background-color: #ffffff00;
   border: 1px solid #ffffff4d;
 }
 
 </style>
+<style scoped>
+  .textButton {
+    font-size: 1rem;
+    color: var(--bg-color1);
+    cursor: pointer;
+  }
+  .formButton {
+    letter-spacing: 4px;
+  }
+  .dialog >>> .el-dialog__body {
+    padding: 20px;
+  }
+  .dialog >>> .el-dialog {
+    border-radius: 10px;
+  }
+  .form >>> .el-input__inner {
+    font-size: 1rem;
+    padding: 5px 15px;
+    height: 40px;
+  }
+</style>

+ 3 - 3
src/views/homeComponents/HeaderH5.vue

@@ -1,6 +1,6 @@
 <template>
-  <div class="fontH5 px-4 py-2">
-    <div class="flex items-center justify-end pb-2">
+  <div class="fontH5 px-4 py-6">
+    <!-- <div class="flex items-center justify-end pb-2">
       <div class="flex items-center">
         
         <button class="text-yellow-400 p-2">
@@ -15,7 +15,7 @@
           <i class="fas el-icon-user-solid text-white mr-2"></i>
         </button>
       </div>
-    </div>
+    </div> -->
     <div class="flex justify-center items-center">
       <div class="relative flex-1">
         <input

+ 24 - 14
src/views/homeComponents/HomeH5.vue

@@ -5,7 +5,7 @@
       <div class="container mx-auto py-2">
         <div class="flex mb-3">
           <div class="flex space-x-4">
-            <button class="px-3 py-2 rounded topActive fontH5">这是h5</button>
+            <button class="px-3 py-2 rounded topActive fontH5">热门</button>
             <button class="bg-gray-800 px-3 py-2 rounded fontH5">趋势</button>
             <button class="bg-gray-800 px-3 py-2 rounded fontH5">最新</button>
           </div>
@@ -19,7 +19,7 @@
           <div
             v-for="(item, index) in selectLabelObj"
             :key="index"
-            class="fontH5 px-3 py-1 mx-1 rounded flex items-center cursor-pointer buttonBg1"
+            class="fontH5 px-3 py-1 mx-1 mb-1 rounded flex items-center cursor-pointer buttonBg1"
             @click="clickLabel(item)"
           >
             {{ item.labelName }}
@@ -55,28 +55,27 @@
           <div
             v-for="(item, index) in characterList"
             :key="index"
-            class="box2 rounded-lg overflow-hidden shadow-lg cursor-pointer"
+            class="box2 rounded-lg overflow-hidden shadow-lg cursor-pointer mb-3"
             @click="toDetail(item)"
           >
             
             <div class="p-3 flex justify-between">
-              <img :src="baseApi + item.picture" class="w-24 h-24 img" />
-              <div class="ml-3 flex-1">
+              <img :src="baseApi + item.picture" class="w-24 h-24 img rounded" />
+              <div class="info ml-3 flex-1">
                 <h3 class="text-lg font-bold">{{ item.characterName }}</h3>
-                <p class="text-sm">
-                  {{ item.prologue }}
-                </p>
-
-                <div class="flex my-2 flex-wrap labels">
+                <div class="flex flex-wrap labels">
                   <div v-for="(item2, index2) in item.labelArr" :key="index2" class="m-0.5 tag px-2 py-1 rounded flex items-center text-xs">
                     <!-- <i class="fas fa-smile text-yellow-400 mr-2"></i> -->
                     {{ item2 }}
                   </div>
                 </div>
+                <p class="text-sm prologue">
+                  {{ item.prologue }}
+                </p>
               </div>
               
             </div>
-            <div class="flex items-center justify-between mt-3 px-3 pb-3">
+            <div class="flex items-center justify-between px-3 pb-3">
               <div class="flex items-center box1">
                 <v-icon name="heart" scale="1"/>
                 <span class="ml-1">{{ item.likeNum }}</span>
@@ -202,7 +201,7 @@ export default {
     },
     toDetail(item) {
       this.$router.push({
-        path: '/detail',
+        path: '/detailH5',
         query: {
           id: item.id
         }
@@ -293,6 +292,7 @@ export default {
   background: var(--bg-color1);
 }
 .tag {
+  flex-shrink: 0;
   background: rgba(245, 174, 67, 0.2);
 }
 .searchInput {
@@ -311,13 +311,23 @@ export default {
   background: #ffffff0f; 
   border: 1px solid #ffffff2e;
 }
+.info {
+  width: calc(100% - 6.75rem);
+}
 .img {
   max-height: 220px;
   object-fit: cover;
   object-position: top;
 }
 .labels {
-  max-height: 84px;
-  overflow-y: auto;
+  flex-wrap: nowrap;
+  overflow-x: auto;
+}
+.prologue {
+  overflow:hidden;
+  text-overflow: ellipsis;
+  -webkit-line-clamp: 2;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
 }
 </style>

+ 119 - 0
src/views/loginAndRegister/loginH5.vue

@@ -0,0 +1,119 @@
+<template>
+  <div class="page bg-gray-900 h-full">
+    <div class="logo">
+      <img class="logoImg" src="@/assets/images/logo.webp" alt="">
+    </div>
+    <div class="loginForm">
+      <el-input
+        v-model="form.account"
+        type="text"
+        placeholder="用户账号"
+        class="imput focus:outline-none "
+      />
+      <el-input
+        v-model="form.password"
+        type="text"
+        placeholder="用户密码"
+        show-password
+        class="imput focus:outline-none "
+      />
+      <button
+        class="bg-pink-600 text-xs py-1 px-4 w-4/5 h-10 text-white font-semibold"
+        style="border-radius: 2.5rem; letter-spacing: 4px;"
+        @click="clickLogin"
+      >
+        登录
+      </button>
+      <div class="text">
+        还没有账号吗?<span class="text-blue-500" @click="goRegister">前往注册</span>
+      </div>
+      <div class="text">
+        <el-checkbox class="checkbox" v-model="checked"></el-checkbox>
+        登录即代表您已同意<span class="text-blue-500">《用户协议》</span>和<span class="text-blue-500">《隐私政策》</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      checked: false,
+      form: {
+        account: null,
+        password: null,
+      },
+    }
+  },
+  methods: {
+    radioInput(val) {
+      console.log(val)
+    },
+    goRegister() {
+      this.$router.push('/registerH5')
+    },
+    clickLogin() {
+      
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .page {
+    height: 100vh;
+    display: flex;
+    flex-direction: column;
+    .logo {
+      height: 55%;
+      margin: 0 auto;
+      >.logoImg {
+        width: 70vw;
+        position: relative;
+        top: 50%;
+        transform: translateY(-50%);
+      }
+    }
+    .loginForm {
+      padding: 20px;
+      height: 45%;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+
+      >.imput {
+        width: 80%;
+        margin-bottom: 20px;
+        // background: #363755;
+        // border: 1px solid #ffffff4d;
+        // border-radius: 9999px;
+      }
+      >.text {
+        font-size: 12px;
+        color: #969696;
+        >.checkbox {
+          margin-right: 5px;
+          border-radius: 50%;
+        }
+      }
+    }
+  }
+</style>
+<style scoped>
+.loginForm >>> .el-input__inner {
+  background: #363755;
+  border-radius: 9999px;
+  border-color: rgba(54, 55, 85, 0);
+  padding: 0 20px;
+  color: #fff;
+}
+.loginForm >>> .el-input__inner:focus {
+  outline: none;
+  border-color: rgba(54, 55, 85, 0);
+}
+.loginForm >>> .el-checkbox__inner {
+  border-radius: 50%;
+}
+</style>

+ 113 - 0
src/views/loginAndRegister/registerH5.vue

@@ -0,0 +1,113 @@
+<template>
+  <div class="page bg-gray-900 h-full">
+    <div class="logo">
+      <img class="logoImg" src="@/assets/images/logo.webp" alt="">
+    </div>
+    <div class="loginForm">
+      <el-input
+        v-model="form.account"
+        type="text"
+        placeholder="用户账号"
+        class="imput focus:outline-none "
+      />
+      <el-input
+        v-model="form.password"
+        type="text"
+        placeholder="用户密码"
+        show-password
+        class="imput focus:outline-none "
+      />
+      <el-input
+        v-model="form.repeatPassword"
+        type="text"
+        placeholder="确认用户密码"
+        show-repeatPassword
+        class="imput focus:outline-none "
+      />
+      <div class="text">
+        <el-checkbox class="checkbox" v-model="checked"></el-checkbox>
+        注册即代表您已同意<span class="text-blue-500">《用户协议》</span>和<span class="text-blue-500">《隐私政策》</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      checked: false,
+      form: {
+        account: null,
+        password: null,
+      },
+    }
+  },
+  methods: {
+    radioInput(val) {
+      console.log(val)
+    },
+    goRegister() {
+      this.$router.push('/registerH5')
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .page {
+    height: 100vh;
+    display: flex;
+    flex-direction: column;
+    .logo {
+      height: 55%;
+      margin: 0 auto;
+      >.logoImg {
+        width: 70vw;
+        position: relative;
+        top: 50%;
+        transform: translateY(-50%);
+      }
+    }
+    .loginForm {
+      padding: 20px;
+      height: 45%;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+
+      >.imput {
+        width: 80%;
+        margin-bottom: 20px;
+        // background: #363755;
+        // border: 1px solid #ffffff4d;
+        // border-radius: 9999px;
+      }
+      >.text {
+        font-size: 12px;
+        color: #969696;
+        >.checkbox {
+          margin-right: 5px;
+          border-radius: 50%;
+        }
+      }
+    }
+  }
+</style>
+<style scoped>
+.loginForm >>> .el-input__inner {
+  background: #363755;
+  border-radius: 9999px;
+  border-color: rgba(54, 55, 85, 0);
+  padding: 0 20px;
+  color: #fff;
+}
+.loginForm >>> .el-input__inner:focus {
+  outline: none;
+  border-color: rgba(54, 55, 85, 0);
+}
+.loginForm >>> .el-checkbox__inner {
+  border-radius: 50%;
+}
+</style>