Prechádzať zdrojové kódy

添加仿 wechat 的 chat 模块

reghao 1 mesiac pred
rodič
commit
8b18d05509

+ 10 - 2
src/main.js

@@ -45,7 +45,11 @@ import {
   Switch,
   Swipe,
   SwipeItem,
-  Calendar
+  Calendar,
+  SwipeCell,
+  Popover,
+  IndexBar,
+  IndexAnchor
 } from 'vant'
 
 const components = [
@@ -83,7 +87,11 @@ const components = [
   Switch,
   Swipe,
   SwipeItem,
-  Calendar
+  Calendar,
+  SwipeCell,
+  Popover,
+  IndexBar,
+  IndexAnchor
 ]
 components.forEach((cmp) => Vue.use(cmp))
 

+ 68 - 0
src/router/index.js

@@ -139,6 +139,74 @@ const routes = [
       }
     ]
   },
+  {
+    path: '/chat',
+    name: 'ChatLayout',
+    component: () => import('@/views/chat/ChatLayout.vue'),
+    redirect: '/chat/message',
+    children: [
+      {
+        path: 'message',
+        name: 'ChatMessage',
+        component: () => import('@/views/chat/ChatMessage.vue'),
+        meta: {
+          title: '消息列表',
+          showNav: false,
+          showTabbar: true,
+          loginRequired: true,
+          role: 'tnb_disk'
+        }
+      },
+      {
+        path: 'contact',
+        name: 'ChatContact',
+        component: () => import('@/views/chat/ChatContact.vue'),
+        meta: {
+          title: '联系人',
+          showNav: true,
+          showTabbar: true,
+          loginRequired: true,
+          role: 'tnb_disk'
+        }
+      },
+      {
+        path: 'discover',
+        name: 'ChatDiscover',
+        component: () => import('@/views/chat/ChatDiscover.vue'),
+        meta: {
+          title: '发现',
+          showNav: true,
+          showTabbar: true,
+          loginRequired: true,
+          role: 'tnb_disk'
+        }
+      },
+      {
+        path: 'me',
+        name: 'ChatMe',
+        component: () => import('@/views/chat/ChatMe.vue'),
+        meta: {
+          title: '我的',
+          showNav: true,
+          showTabbar: true,
+          loginRequired: true,
+          role: 'tnb_disk'
+        }
+      },
+      {
+        path: 'dialog',
+        name: 'ChatDialog',
+        component: () => import('@/views/chat/ChatDialog.vue'),
+        meta: {
+          title: '聊天',
+          showNav: false,
+          showTabbar: false,
+          loginRequired: true,
+          role: 'tnb_disk'
+        }
+      }
+    ]
+  },
   {
     path: '/login',
     component: () => import('@/views/Login.vue'),

+ 236 - 0
src/views/chat/ChatContact.vue

@@ -0,0 +1,236 @@
+<template>
+  <div class="contacts-page">
+    <van-nav-bar title="通讯录" fixed placeholder class="wechat-nav">
+      <template #right>
+        <van-icon name="user-plus-o" size="22" color="#181818" @click="$toast('添加联系人')" />
+      </template>
+    </van-nav-bar>
+
+    <van-search v-model="searchKey" placeholder="搜索" class="wechat-search" />
+
+    <div class="top-services">
+      <van-cell title="新的朋友" is-link class="wechat-cell">
+        <template #icon>
+          <div class="service-icon-wrapper orange">
+            <van-icon name="friends-o" />
+          </div>
+        </template>
+      </van-cell>
+      <van-cell title="仅聊天的朋友" is-link class="wechat-cell">
+        <template #icon>
+          <div class="service-icon-wrapper blue">
+            <van-icon name="comment-circle-o" />
+          </div>
+        </template>
+      </van-cell>
+      <van-cell title="群聊" is-link class="wechat-cell">
+        <template #icon>
+          <div class="service-icon-wrapper green">
+            <van-icon name="cluster-o" />
+          </div>
+        </template>
+      </van-cell>
+      <van-cell title="标签" is-link class="wechat-cell">
+        <template #icon>
+          <div class="service-icon-wrapper blue-dark">
+            <van-icon name="label-o" />
+          </div>
+        </template>
+      </van-cell>
+      <van-cell title="公众号" is-link class="wechat-cell">
+        <template #icon>
+          <div class="service-icon-wrapper blue-light">
+            <van-icon name="service-o" />
+          </div>
+        </template>
+      </van-cell>
+    </div>
+
+    <van-index-bar :index-list="indexList" highlight-color="#07c160" class="wechat-index-bar">
+      <div v-for="group in filteredContacts" :key="group.letter">
+        <van-index-anchor :index="group.letter" class="wechat-anchor" />
+
+        <van-cell
+          v-for="user in group.list"
+          :key="user.id"
+          :title="user.name"
+          class="wechat-cell contact-item"
+          @click="goChat(user)"
+        >
+          <template #icon>
+            <van-image
+              radius="4"
+              width="40"
+              height="40"
+              :src="user.avatar"
+              class="contact-avatar"
+            />
+          </template>
+        </van-cell>
+      </div>
+    </van-index-bar>
+
+    <div class="contact-footer" v-if="!searchKey">{{ totalContacts }} 位联系人</div>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      searchKey: '',
+      contactData: [
+        {
+          letter: 'A',
+          list: [{ id: 1, name: '阿强', avatar: 'https://img01.yzcdn.cn/vant/cat.jpeg' }]
+        },
+        {
+          letter: 'B',
+          list: [{ id: 2, name: '白老板', avatar: 'https://img01.yzcdn.cn/vant/apple-1.jpg' }]
+        },
+        {
+          letter: 'G',
+          list: [{ id: 3, name: 'Gemini', avatar: 'https://img01.yzcdn.cn/vant/apple-2.jpg' }]
+        },
+        {
+          letter: 'L',
+          list: [
+            { id: 4, name: '老王', avatar: 'https://img01.yzcdn.cn/vant/cat.jpeg' },
+            { id: 5, name: '李四', avatar: 'https://img01.yzcdn.cn/vant/apple-3.jpg' }
+          ]
+        },
+        {
+          letter: 'Z',
+          list: [{ id: 6, name: '张三', avatar: 'https://img01.yzcdn.cn/vant/apple-1.jpg' }]
+        }
+      ]
+    }
+  },
+  computed: {
+    indexList() {
+      return this.filteredContacts.map((item) => item.letter)
+    },
+    filteredContacts() {
+      if (!this.searchKey) return this.contactData
+      return this.contactData
+        .map((group) => ({
+          ...group,
+          list: group.list.filter((user) => user.name.includes(this.searchKey))
+        }))
+        .filter((group) => group.list.length > 0)
+    },
+    totalContacts() {
+      return this.contactData.reduce((acc, cur) => acc + cur.list.length, 0)
+    }
+  },
+  methods: {
+    goChat(user) {
+      this.$router.push({ path: '/chat/dialog', query: { userId: user.id, name: user.name } })
+    }
+  }
+}
+</script>
+
+<style scoped lang="less">
+/* 微信配色变量 */
+@wechat-bg: #ededed;
+@wechat-green: #07c160;
+@wechat-anchor-bg: #ededed;
+@wechat-border: #e5e5e5;
+
+.contacts-page {
+  background-color: @wechat-bg;
+  min-height: 100vh;
+
+  .wechat-search {
+    background-color: @wechat-bg;
+    padding: 10px 12px;
+    ::v-deep .van-search__content {
+      background-color: #fff;
+      border-radius: 6px;
+    }
+  }
+
+  .top-services {
+    margin-bottom: 0;
+  }
+
+  /* 微信风格的服务图标 */
+  .service-icon-wrapper {
+    width: 40px;
+    height: 40px;
+    border-radius: 6px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-right: 12px;
+    color: #fff;
+    font-size: 24px;
+
+    &.orange {
+      background-color: #fa9d3b;
+    }
+    &.blue {
+      background-color: #2782d7;
+    }
+    &.green {
+      background-color: #07c160;
+    }
+    &.blue-dark {
+      background-color: #576b95;
+    }
+    &.blue-light {
+      background-color: #2782d7;
+    }
+  }
+
+  .contact-avatar {
+    margin-right: 12px;
+    border-radius: 4px; // 微信头像偏向方圆
+  }
+
+  /* 单元格样式覆盖 */
+  .wechat-cell {
+    padding: 10px 16px;
+    align-items: center;
+    &::after {
+      left: 68px; // 线条避开头像
+      border-bottom: 1px solid @wechat-border;
+    }
+  }
+
+  /* 索引锚点样式 */
+  .wechat-anchor {
+    background-color: @wechat-anchor-bg;
+    color: #888;
+    line-height: 32px;
+    padding: 0 16px;
+    font-size: 14px;
+  }
+
+  .contact-footer {
+    text-align: center;
+    padding: 30px;
+    color: #888;
+    font-size: 16px;
+    background: #fff;
+  }
+
+  /* 右侧索引栏 */
+  .wechat-index-bar {
+    ::v-deep .van-index-bar__index {
+      padding: 2px 8px;
+      font-size: 11px;
+      line-height: 14px;
+    }
+  }
+}
+
+/* 覆盖导航栏底色 */
+.wechat-nav {
+  background-color: @wechat-bg;
+  ::v-deep .van-nav-bar__title {
+    font-weight: 500;
+  }
+}
+</style>

+ 250 - 0
src/views/chat/ChatDialog.vue

@@ -0,0 +1,250 @@
+<template>
+  <div class="chat-container">
+    <van-nav-bar :title="nickname" left-arrow fixed placeholder @click-left="$router.back()">
+      <template #right>
+        <van-icon name="ellipsis" size="20" />
+      </template>
+    </van-nav-bar>
+
+    <div class="message-list" ref="messageList">
+      <div
+          v-for="(msg, index) in messages"
+          :key="index"
+          :class="['message-item', msg.sender === 'me' ? 'msg-me' : 'msg-other']"
+      >
+        <van-image round width="40" height="40" :src="msg.avatar" class="avatar" />
+
+        <div class="content-wrapper">
+          <div v-if="msg.type === 'text'" class="bubble">
+            {{ msg.text }}
+          </div>
+
+          <div v-else-if="msg.type === 'image'" class="img-content">
+            <van-image :src="msg.url" radius="8" @click="onPreview(msg.url)" />
+          </div>
+
+          <div v-else-if="msg.type === 'code'" class="code-content">
+            <div class="code-lang">{{ msg.lang }}</div>
+            <pre><code>{{ msg.text }}</code></pre>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="bottom-bar-wrapper">
+      <div class="input-bar">
+        <van-icon name="audio" size="24" class="icon-btn" />
+        <van-field
+            v-model="inputText"
+            rows="1"
+            autosize
+            type="textarea"
+            placeholder="发送消息..."
+            :border="false"
+            class="input-field"
+        />
+        <van-icon name="smile-o" size="24" class="icon-btn" />
+        <van-button
+            v-if="inputText.trim()"
+            type="primary"
+            size="small"
+            color="#07c160"
+            @click="onSend"
+        >发送</van-button
+        >
+        <van-icon v-else name="add-o" size="24" class="icon-btn" />
+      </div>
+      <div class="van-safe-area-bottom"></div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { ImagePreview } from 'vant'
+
+export default {
+  data() {
+    return {
+      nickname: '技术交流群',
+      inputText: '',
+      messages: [
+        {
+          type: 'text',
+          sender: 'other',
+          text: '哈喽!代码写完了吗?',
+          avatar: 'https://img01.yzcdn.cn/vant/cat.jpeg'
+        },
+        {
+          type: 'text',
+          sender: 'me',
+          text: '刚写好,正准备测试。',
+          avatar: 'https://img01.yzcdn.cn/vant/apple-1.jpg'
+        },
+        {
+          type: 'code',
+          sender: 'other',
+          lang: 'javascript',
+          text: 'console.log("牛逼!");',
+          avatar: 'https://img01.yzcdn.cn/vant/cat.jpeg'
+        }
+      ]
+    }
+  },
+  methods: {
+    onSend() {
+      const text = this.inputText.trim()
+      if (!text) return
+
+      this.messages.push({
+        type: 'text',
+        sender: 'me',
+        text: text,
+        avatar: 'https://img01.yzcdn.cn/vant/apple-1.jpg'
+      })
+
+      this.inputText = ''
+      this.scrollToBottom()
+
+      // 模拟回复
+      setTimeout(() => {
+        this.messages.push({
+          type: 'text',
+          sender: 'other',
+          text: '收到了,我这就去看一眼。',
+          avatar: 'https://img01.yzcdn.cn/vant/cat.jpeg'
+        })
+        this.scrollToBottom()
+      }, 1000)
+    },
+    scrollToBottom() {
+      this.$nextTick(() => {
+        const container = this.$refs.messageList
+        if (container) {
+          container.scrollTop = container.scrollHeight
+        }
+      })
+    },
+    onPreview(url) {
+      ImagePreview([url])
+    }
+  },
+  mounted() {
+    this.scrollToBottom()
+  }
+}
+</script>
+
+<style scoped lang="less">
+.chat-container {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  background-color: #ededed; // 微信背景灰
+
+  .message-list {
+    flex: 1;
+    overflow-y: auto;
+    padding: 15px;
+
+    .message-item {
+      display: flex;
+      margin-bottom: 20px;
+      .avatar {
+        flex-shrink: 0;
+      }
+
+      .content-wrapper {
+        max-width: 70%;
+        margin: 0 10px;
+
+        .bubble {
+          padding: 10px 12px;
+          font-size: 15px;
+          line-height: 1.4;
+          border-radius: 6px;
+          position: relative;
+          word-break: break-all;
+        }
+
+        .img-content {
+          max-width: 150px;
+          border-radius: 8px;
+          overflow: hidden;
+        }
+
+        .code-content {
+          background: #2d2d2d;
+          color: #ccc;
+          padding: 8px;
+          border-radius: 6px;
+          font-size: 13px;
+          .code-lang {
+            color: #888;
+            font-size: 10px;
+            border-bottom: 1px solid #444;
+            margin-bottom: 4px;
+          }
+          pre {
+            margin: 0;
+            white-space: pre-wrap;
+          }
+        }
+      }
+    }
+
+    .msg-other {
+      flex-direction: row;
+      .bubble {
+        background-color: #fff;
+        &::before {
+          content: '';
+          position: absolute;
+          left: -10px;
+          top: 12px;
+          border: 5px solid transparent;
+          border-right-color: #fff;
+        }
+      }
+    }
+
+    .msg-me {
+      flex-direction: row-reverse;
+      .bubble {
+        background-color: #95ec69;
+        &::before {
+          content: '';
+          position: absolute;
+          right: -10px;
+          top: 12px;
+          border: 5px solid transparent;
+          border-left-color: #95ec69;
+        }
+      }
+    }
+  }
+
+  // 底部包装层
+  .bottom-bar-wrapper {
+    background-color: #f7f7f7;
+    border-top: 1px solid #ddd;
+
+    .input-bar {
+      display: flex;
+      align-items: flex-end;
+      padding: 8px 10px;
+
+      .icon-btn {
+        margin: 0 5px 8px;
+        color: #333;
+      }
+      .input-field {
+        flex: 1;
+        background-color: #fff;
+        border-radius: 4px;
+        padding: 8px 10px;
+        margin: 0 5px;
+      }
+    }
+  }
+}
+</style>

+ 142 - 0
src/views/chat/ChatDiscover.vue

@@ -0,0 +1,142 @@
+<template>
+  <div class="discover-page">
+    <div class="section-group">
+      <van-cell title="朋友圈" is-link class="wechat-cell" @click="$toast('朋友圈')">
+        <template #icon>
+          <div class="icon-box moments-bg">
+            <van-icon name="medal" />
+          </div>
+        </template>
+        <template #right-icon>
+          <div class="moment-update">
+            <van-image radius="4" width="32" height="32" src="https://img01.yzcdn.cn/vant/cat.jpeg" />
+            <div class="red-dot"></div>
+          </div>
+          <van-icon name="arrow" class="van-cell__right-icon" />
+        </template>
+      </van-cell>
+    </div>
+
+    <div class="section-group">
+      <van-cell title="扫一扫" is-link class="wechat-cell">
+        <template #icon>
+          <div class="icon-box scan-bg"><van-icon name="scan" /></div>
+        </template>
+      </van-cell>
+      <van-cell title="摇一摇" is-link class="wechat-cell">
+        <template #icon>
+          <div class="icon-box shake-bg"><van-icon name="shake" /></div>
+        </template>
+      </van-cell>
+    </div>
+
+    <div class="section-group">
+      <van-cell title="看一看" is-link class="wechat-cell">
+        <template #icon>
+          <div class="icon-box look-bg"><van-icon name="eye" /></div>
+        </template>
+      </van-cell>
+      <van-cell title="搜一搜" is-link class="wechat-cell">
+        <template #icon>
+          <div class="icon-box search-bg"><van-icon name="search" /></div>
+        </template>
+      </van-cell>
+    </div>
+
+    <div class="section-group">
+      <van-cell title="购物" is-link class="wechat-cell">
+        <template #icon>
+          <div class="icon-box shop-bg"><van-icon name="shopping-cart" /></div>
+        </template>
+      </van-cell>
+      <van-cell title="游戏" is-link class="wechat-cell">
+        <template #icon>
+          <div class="icon-box game-bg"><van-icon name="fire" /></div>
+        </template>
+      </van-cell>
+    </div>
+
+    <div class="section-group">
+      <van-cell title="小程序" is-link class="wechat-cell">
+        <template #icon>
+          <div class="icon-box app-bg"><van-icon name="apps-o" /></div>
+        </template>
+      </van-cell>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="less">
+/* 微信标准配色 */
+@wechat-bg: #ededed;
+@wechat-border: #e5e5e5;
+
+.discover-page {
+  background-color: @wechat-bg;
+  min-height: 100vh;
+  /* 微信发现页顶部通常有一定的间距 */
+  padding-top: 0;
+
+  .section-group {
+    margin-bottom: 8px; /* 微信标准的块间距 */
+    background: #fff;
+
+    &:first-child {
+      margin-top: 0;
+    }
+  }
+
+  /* 微信风格图标盒子 */
+  .icon-box {
+    width: 24px;
+    height: 24px;
+    border-radius: 4px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-right: 12px;
+    color: #fff;
+    font-size: 16px;
+
+    /* 微信各功能对应色值 */
+    &.moments-bg { background-color: #2b85e4; }
+    &.scan-bg    { background-color: #2b85e4; }
+    &.shake-bg   { background-color: #2b85e4; }
+    &.look-bg    { background-color: #f29c00; }
+    &.search-bg  { background-color: #ee0a24; }
+    &.shop-bg    { background-color: #ee0a24; }
+    &.game-bg    { background-color: #f29c00; }
+    &.app-bg     { background-color: #5d6b92; }
+  }
+
+  .wechat-cell {
+    padding: 12px 16px;
+    align-items: center;
+    font-size: 16px;
+
+    &::after {
+      left: 52px; /* 分隔线避开头像,对齐文字 */
+      border-bottom: 1px solid @wechat-border;
+    }
+  }
+
+  /* 朋友圈更新提醒详情 */
+  .moment-update {
+    position: relative;
+    display: flex;
+    align-items: center;
+    margin-right: 4px;
+
+    .red-dot {
+      position: absolute;
+      top: -4px;
+      right: -4px;
+      width: 10px;
+      height: 10px;
+      background-color: #fa5151; /* 微信通知红 */
+      border-radius: 50%;
+      border: 2px solid #fff;
+    }
+  }
+}
+</style>

+ 107 - 0
src/views/chat/ChatLayout.vue

@@ -0,0 +1,107 @@
+<template>
+  <div class="chat-layout">
+    <van-nav-bar
+      v-if="$route.meta.showNav"
+      :title="$route.meta.title"
+      fixed
+      placeholder
+      z-index="100"
+      class="wechat-nav"
+    >
+      <template #right v-if="$route.path === '/chat/message'">
+        <van-icon name="plus" size="20" color="#181818" @click="$toast('发起聊天')" />
+      </template>
+      <template #right v-else-if="$route.path === '/chat/contact'">
+        <van-icon name="user-plus-o" size="20" color="#181818" @click="$toast('添加好友')" />
+      </template>
+    </van-nav-bar>
+
+    <div class="chat-main">
+      <transition name="van-fade">
+        <router-view />
+      </transition>
+    </div>
+
+    <van-tabbar
+      v-if="$route.meta.showTabbar"
+      v-model="active"
+      route
+      active-color="#07c160"
+      inactive-color="#181818"
+      placeholder
+      class="wechat-tabbar"
+    >
+      <van-tabbar-item replace to="/chat/message" icon="chat-o" :badge="totalUnread || null">
+        消息
+      </van-tabbar-item>
+      <van-tabbar-item replace to="/chat/contact" icon="friends-o"> 通讯录 </van-tabbar-item>
+      <van-tabbar-item replace to="/chat/discover" icon="eye-o"> 发现 </van-tabbar-item>
+      <van-tabbar-item replace to="/chat/me" icon="contact"> 我 </van-tabbar-item>
+    </van-tabbar>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      active: 0,
+      totalUnread: 5
+    }
+  }
+}
+</script>
+
+<style scoped lang="less">
+/* 微信配色变量 */
+@wechat-bg: #ededed;
+@wechat-nav-bg: #f7f7f7;
+@wechat-green: #07c160;
+@wechat-black: #181818;
+
+.chat-layout {
+  display: flex;
+  flex-direction: column;
+  min-height: 100vh;
+  background-color: @wechat-bg; // 全局背景设为微信灰
+}
+
+.chat-main {
+  flex: 1;
+}
+
+/* 覆盖 Vant 导航栏样式 */
+::v-deep .wechat-nav {
+  background-color: @wechat-nav-bg;
+  .van-nav-bar__title {
+    color: @wechat-black;
+    font-weight: 600;
+    font-size: 17px;
+  }
+  /* 去除导航栏底部的细线,或者使其颜色更浅 */
+  &::after {
+    border-bottom: 1px solid #dcdcdc;
+  }
+}
+
+/* 覆盖 Vant Tabbar 样式 */
+::v-deep .wechat-tabbar {
+  background-color: @wechat-nav-bg;
+  border-top: 1px solid #dcdcdc;
+
+  .van-tabbar-item {
+    background-color: transparent;
+  }
+
+  /* 未读消息红点样式微调 */
+  .van-info {
+    background-color: #fa5151; // 微信标准的报错/通知红
+    border: none;
+  }
+}
+
+.van-fade-enter-active,
+.van-fade-leave-active {
+  transition: opacity 0.2s;
+}
+</style>

+ 154 - 0
src/views/chat/ChatMe.vue

@@ -0,0 +1,154 @@
+<template>
+  <div class="me-page">
+    <div class="user-header" @click="$router.push('/user/profile')">
+      <van-image
+        round
+        width="64"
+        height="64"
+        :src="accountInfo?.avatarUrl || 'https://img01.yzcdn.cn/vant/cat.jpeg'"
+        class="avatar"
+      />
+      <div class="user-detail">
+        <h3 class="nickname">{{ accountInfo?.screenName || '未登录' }}</h3>
+        <div class="account-id">
+          <span>微信号:{{ accountInfo?.userIdStr || 'wx_123456' }}</span>
+          <van-icon name="qr" class="qr-icon" />
+          <van-icon name="arrow" class="arrow-icon" />
+        </div>
+      </div>
+    </div>
+
+    <div class="cell-group">
+      <van-cell title="服务" is-link to="/wallet">
+        <template #icon>
+          <van-icon name="wechat-pay" class="cell-icon pay" />
+        </template>
+      </van-cell>
+    </div>
+
+    <div class="cell-group">
+      <van-cell title="收藏" is-link>
+        <template #icon><van-icon name="star-o" class="cell-icon fav" /></template>
+      </van-cell>
+      <van-cell title="朋友圈" is-link to="/chat/discover">
+        <template #icon><van-icon name="photo-o" class="cell-icon moments" /></template>
+      </van-cell>
+      <van-cell title="卡包" is-link>
+        <template #icon><van-icon name="card" class="cell-icon cards" /></template>
+      </van-cell>
+      <van-cell title="表情" is-link>
+        <template #icon><van-icon name="smile-o" class="cell-icon emoji" /></template>
+      </van-cell>
+    </div>
+
+    <div class="cell-group">
+      <van-cell title="设置" is-link @click="$toast('前往设置')">
+        <template #icon><van-icon name="setting-o" class="cell-icon setting" /></template>
+      </van-cell>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+
+export default {
+  name: 'Me',
+  computed: {
+    ...mapState(['accountInfo'])
+  }
+}
+</script>
+
+<style scoped lang="less">
+// 微信经典背景色
+@wechat-bg: #ededed;
+@wechat-text-main: #000000;
+@wechat-text-sub: #888888;
+
+.me-page {
+  background-color: @wechat-bg;
+  min-height: 100vh;
+  padding-bottom: 20px;
+
+  .user-header {
+    background-color: #fff;
+    padding: 40px 24px 30px;
+    display: flex;
+    align-items: center;
+    margin-bottom: 8px; // 微信风格间距
+
+    .avatar {
+      border-radius: 8px; // 微信头像其实带一点点圆角,而非全圆,根据喜好调整
+    }
+
+    .user-detail {
+      flex: 1;
+      margin-left: 20px;
+
+      .nickname {
+        font-size: 20px;
+        font-weight: 600;
+        margin: 0 0 8px 0;
+        color: @wechat-text-main;
+      }
+
+      .account-id {
+        display: flex;
+        align-items: center;
+        font-size: 14px;
+        color: @wechat-text-sub;
+
+        .qr-icon {
+          margin-left: auto;
+          margin-right: 10px;
+          font-size: 18px;
+        }
+        .arrow-icon {
+          font-size: 16px;
+          color: #ccc;
+        }
+      }
+    }
+  }
+
+  .cell-group {
+    margin-bottom: 8px;
+    background-color: #fff;
+
+    // 微信图标颜色适配
+    .cell-icon {
+      font-size: 22px;
+      margin-right: 16px;
+
+      &.pay {
+        color: #07c160;
+      } // 微信绿
+      &.fav {
+        color: #ed6a0c;
+      } // 收藏橙
+      &.moments {
+        color: #1989fa;
+      } // 朋友圈蓝
+      &.cards {
+        color: #1989fa;
+      } // 卡包蓝
+      &.emoji {
+        color: #ff976a;
+      } // 表情黄
+      &.setting {
+        color: #1989fa;
+      } // 设置蓝
+    }
+
+    // 去掉 Vant Cell 的默认左右内边距微调,更贴合微信
+    ::v-deep .van-cell {
+      padding: 14px 16px;
+      align-items: center;
+      &::after {
+        left: 54px; // 线条对齐文字,避开头像/图标
+      }
+    }
+  }
+}
+</style>

+ 180 - 0
src/views/chat/ChatMessage.vue

@@ -0,0 +1,180 @@
+<template>
+  <div class="message-list-page">
+    <van-nav-bar title="消息" fixed placeholder>
+      <template #right>
+        <van-icon name="plus" size="20" @click="showAction = true" />
+      </template>
+    </van-nav-bar>
+
+    <van-search v-model="searchKey" placeholder="搜索聊天记录" />
+
+    <div class="list-container">
+      <van-swipe-cell v-for="chat in filteredList" :key="chat.id">
+        <van-cell center :border="true" class="chat-item" @click="goChat(chat)">
+          <template #icon>
+            <div class="avatar-wrapper">
+              <van-image round width="48" height="48" :src="chat.avatar" />
+              <div v-if="chat.unread > 0" class="badge">
+                {{ chat.unread > 99 ? '99+' : chat.unread }}
+              </div>
+            </div>
+          </template>
+
+          <template #title>
+            <div class="chat-header">
+              <span class="nickname van-ellipsis">{{ chat.nickname }}</span>
+              <span class="time">{{ chat.lastTime }}</span>
+            </div>
+          </template>
+
+          <template #label>
+            <div class="last-msg van-ellipsis">
+              <span v-if="chat.isDraft" class="draft-prefix">[草稿] </span>
+              {{ chat.lastMsg }}
+            </div>
+          </template>
+        </van-cell>
+
+        <template #right>
+          <van-button square text="置顶" type="primary" class="swipe-btn" />
+          <van-button
+            square
+            text="删除"
+            type="danger"
+            class="swipe-btn"
+            @click="deleteChat(chat.id)"
+          />
+        </template>
+      </van-swipe-cell>
+    </div>
+
+    <div style="height: 50px"></div>
+
+    <van-popover
+      v-model="showAction"
+      trigger="click"
+      :actions="actions"
+      placement="bottom-end"
+      @select="onActionSelect"
+    />
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      searchKey: '',
+      showAction: false,
+      actions: [
+        { text: '发起群聊', icon: 'chat-o' },
+        { text: '添加朋友', icon: 'user-plus-o' },
+        { text: '扫一扫', icon: 'scan' }
+      ],
+      // 模拟会话列表数据
+      chats: [
+        {
+          id: 1,
+          nickname: '技术交流群',
+          avatar: 'https://img01.yzcdn.cn/vant/cat.jpeg',
+          lastMsg: 'Gemini: 那个Bug修复了吗?',
+          lastTime: '14:20',
+          unread: 12,
+          isDraft: false
+        },
+        {
+          id: 2,
+          nickname: '文件传输助手',
+          avatar: 'https://img01.yzcdn.cn/vant/apple-1.jpg',
+          lastMsg: '[图片]',
+          lastTime: '昨天',
+          unread: 0,
+          isDraft: true
+        }
+      ]
+    }
+  },
+  computed: {
+    filteredList() {
+      return this.chats.filter((c) => c.nickname.includes(this.searchKey))
+    }
+  },
+  methods: {
+    goChat(chat) {
+      this.$router.push({ path: '/chat/dialog', query: { id: chat.id, name: chat.nickname } })
+    },
+    deleteChat(id) {
+      this.$dialog.confirm({ message: '确定删除该聊天吗?' }).then(() => {
+        this.chats = this.chats.filter((c) => c.id !== id)
+      })
+    },
+    onActionSelect(action) {
+      this.$toast(action.text)
+    }
+  }
+}
+</script>
+
+<style scoped lang="less">
+.message-list-page {
+  background: #fff;
+  min-height: 100vh;
+
+  .avatar-wrapper {
+    position: relative;
+    margin-right: 12px;
+
+    .badge {
+      position: absolute;
+      top: -4px;
+      right: -4px;
+      background-color: #ee0a24;
+      color: #fff;
+      font-size: 10px;
+      padding: 0 4px;
+      min-width: 16px;
+      height: 16px;
+      border-radius: 8px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      border: 1px solid #fff;
+    }
+  }
+
+  .chat-item {
+    padding: 12px 16px;
+
+    .chat-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+
+      .nickname {
+        font-size: 16px;
+        color: #323233;
+        font-weight: 500;
+        max-width: 70%;
+      }
+      .time {
+        font-size: 12px;
+        color: #c8c9cc;
+      }
+    }
+
+    .last-msg {
+      font-size: 13px;
+      color: #969799;
+      margin-top: 4px;
+
+      .draft-prefix {
+        color: #ee0a24;
+      }
+    }
+  }
+
+  .swipe-btn {
+    height: 100%;
+  }
+}
+</style>