uni-app小程序获取微信头像 用户名,并使用云存储保存

984 阅读5分钟

这篇笔记主要记录uni-app小程序,获取微信用户的头像和用户名,并且使用云存储把获取的头像和用户名保存起来

  1. 获取微信用户的头像和用户名
  2. 使用云存储把获取的头像和用户名保存起来(我用的是是阿里云)
  3. 自定义云存储路径

image.png

获取微信头像

看下小程序文档,参考小程序头像呢称

image.png

需要将 button 组件 open-type 的值设置为 chooseAvatar,当用户选择需要使用的头像之后,可以通过 bindchooseavatar 事件回调获取到头像信息的临时路径

<button
  class="avatar-wrapper"
  open-type="chooseAvatar"
  @chooseavatar="onChooseAvatar">
  <image class="avatar" :src="avatarUrl" mode="aspectFill" />
</button>
<script setup>
// 获取微信头像
const onChooseAvatar = (e: any) => {
  console.log('选择的头像', e)
}
</script>

image.png

返回的头像在detail里, avatarUrl

detail: {
  avatarUrl: "http://tmp/nmy0ZYYM897Q73de4ee7436541d541c504878398ef78.jpeg"
}
使用云存储把获取的头像保存起来

上面获取的路径http://tmp/....jpeg, 是临时路径,并且它仅在当前用户会话中有效。为了确保头像路径长期有效,我们需要把头像上传到自己的服务器或云存储

我们看下云存储文档,

image.png

我们要用的是uniCloud.uploadFile, 不是uni.uploadFile, 我刚开始用的是uni.uploadFile,导致一直上传不成功

uniCloud.uploadFile接受cloudPath, 当cloudPathAsRealPath: true的时候,可以自定义路径,这里我是avatar/${fileName}, 我把用户头像都放到avatar文件夹;

如果不加路径,会默认都存到cloudstorage文件夹, 我把微信返回的临时路径截取文件名

// 提取文件名(包含扩展名)
const fileName = tempFilePath.substring(tempFilePath.lastIndexOf("/") + 1);

下面是完整代码,使用云存储成功后,我把头像放到用户信息的avatar字段,并且存到storage里,这样可以不用调用查询用户接口就可以回显

// 获取微信头像
const onChooseAvatar = (e: any) => {
  const tempFilePath = e.detail.avatarUrl; // 获取临时路径
  userInfo.avatar = e.detail.avatarUrl;
  console.log("临时路径:", tempFilePath);

  // 提取文件名(包含扩展名)
  const fileName = tempFilePath.substring(tempFilePath.lastIndexOf("/") + 1);

  // 上传头像到服务器
  uniCloud.uploadFile({
    filePath: tempFilePath, // 本地临时文件路径
    cloudPath: `avatar/${fileName}`, // 文件存储路径
    cloudPathAsRealPath: true, // 使用 cloudPath 作为绝对路径
    success: (uploadRes) => {
      console.log("uploadRes.data", uploadRes);
      // 确保上传成功后处理
      if (uploadRes.success) {
        const avatar = uploadRes.fileID; // 服务器返回的文件路径
        const existingUserInfo = uni.getStorageSync("userInfo") || {};
        uni.setStorageSync("userInfo", { ...existingUserInfo, avatar });
        // 查询是否已经有该用户的 avatar 数据
        collection
          .doc(userInfo.userId)
          .update({ avatar })
          .then((updateRes) => {
            console.log("头像更新成功:", updateRes);
          })
          .catch((err) => {
            console.error("头像更新失败:", err);
          });
      }
    },
    fail: (err) => {
      console.error("上传失败:", err);
    },
  });
};

我把上传到头像放到avatar文件夹

image.png

获取微信昵称

利用type=nickname的input, 就可以触发获取昵称

<script setup>
const onNameChange = (e) => {
  userInfo.nickname = e.detail.value;
  console.log("临时路径:", userInfo.nickname);
};
</script>
<template>
  <input
    v-model="userInfo.nickname"
    class="weui-input mb-5"
    @blur="onNameChange"
    type="nickname"
    placeholder="请输入昵称" />
</template>

返回的用户名在detail.value里

image.png

使用云存储把微信用户名存起来

逻辑和云存储微信头像一样,拿到呢称,查询到当前用户,更新昵称

const db = uniCloud.database();
const collection = db.collection("users");

const onNameChange = (e) => {
  userInfo.nickname = e.detail.value;
  console.log("微信返回的用户名:", e);

  const existingUserInfo = uni.getStorageSync("userInfo") || {};
  uni.setStorageSync("userInfo", {
    ...existingUserInfo,
    nickname: e.detail.value,
  });
  // 查询是否已经有该用户的 avatar 数据
  collection
    .doc(userInfo.userId)
    .update({ nickname: e.detail.value })
    .then((updateRes) => {
      console.log("名称更新成功:", updateRes);
    })
    .catch((err) => {
      console.error("名称更新失败:", err);
    });
};

从基础库2.24.4版本起,在onBlur 事件触发时,微信将异步对用户输入的内容进行安全监测,若未通过安全监测,微信将清空用户输入的内容,建议开发者通过 form 中form-type 为submit 的button 组件收集用户输入的内容。

这里为了简便,我直接在onBlur事件收集用户的用户名, 不是微信官方推荐的通过 form 中form-type 为submit 的button 组件

上面代码更新用户名,头像,昵称的时候,.doc(userInfo.userId), 这个userId,是怎么获取的呢, 我这里通过用户手机号去查询,如果用户没登录,没有手机号就点击获取头像,我会跳到登录页,拿到手机号

    <script setup>
    const getUser = () => {
      // 查询是否已经有该用户的 avatar 数据
      collection
        .where({ mobile: userInfo.mobile })
        .field("mobile")
        .get()
        .then((queryRes) => {
          console.log("queryRes----", queryRes);
          if (queryRes.result.errCode === 0 && queryRes.result.data.length) {
            // userInfo.avatar = queryRes.result.data[0].avatar;
            userInfo.userId = queryRes.result.data[0]._id;
          }
        });
    };

    onMounted(() => {
      const storedUserInfo = uni.getStorageSync("userInfo");
      if (storedUserInfo && storedUserInfo.mobile) {
        userInfo.mobile = storedUserInfo.mobile.replace(
          /(\d{3})\d{4}(\d{4})/,
          "$1****$2"
        ); // 直接更新 mobile 字段
        isLoggedIn.value = true;
        getUser();
      }
      if (storedUserInfo && storedUserInfo.avatar) {
        userInfo.avatar = storedUserInfo.avatar;
      }
      if (storedUserInfo && storedUserInfo.nickname) {
        userInfo.nickname = storedUserInfo.nickname;
      } else {
        userInfo.nickname = "请输入昵称";
      }
    });
    </script>

数据库里的用户昵称、avatar已经更新

image.png

开篇截图截图的完整代码

<template>
  <view class="me-container">
    <!-- 用户信息部分 -->
    <view class="user-info">
      <view class="left-section">
        <button
          class="avatar-wrapper"
          open-type="chooseAvatar"
          v-if="isLoggedIn"
          @chooseavatar="onChooseAvatar">
          <image class="avatar" :src="userInfo.avatar" mode="aspectFill" />
        </button>
        <view class="flex flex-col">
          <button v-if="!isLoggedIn" class="login-btn" @click="handleLogin">
            登录/注册
          </button>
          <input
            v-model="userInfo.nickname"
            class="weui-input mb-5"
            v-else
            @blur="onNameChange"
            type="nickname"
            placeholder="请输入昵称" />
          <view v-if="isLoggedIn">{{ encryptMobile }}</view>
        </view>
      </view>
    </view>

    <!-- 功能卡片 -->
    <view class="card">
      <view
        v-for="(item, index) in menuItems"
        :key="index"
        class="card-item"
        @click="navigateTo(item.path)">
        <uni-icons :type="item.icon" size="30"></uni-icons>
        <text>{{ item.label }}</text>
      </view>
    </view>
  </view>
</template>

<script setup lang="ts">
import { ref, reactive, onMounted, computed } from "vue";

const db = uniCloud.database();
const collection = db.collection("users");

// 登录状态
const isLoggedIn = ref(false);
const userInfo = reactive({
  mobile: "",
  avatar: "/static/images/defaultAvatar.jpeg", // 默认头像
  userId: "",
  nickname: "未登录",
});

// 获取用户电话获取用户id
// 获取用户信息的方法
const getUser = async () => {
  try {
    const queryRes = await collection
      .where({ mobile: userInfo.mobile })
      .field("mobile")
      .get();

    console.log("queryRes----", queryRes);
    if (queryRes.result.errCode === 0 && queryRes.result.data.length) {
      userInfo.userId = queryRes.result.data[0]._id;
      console.log("获取到的 userId:", userInfo.userId);
      return true;
    } else {
      console.warn("未查询到用户信息");
      return false;
    }
  } catch (error) {
    console.error("查询用户信息失败:", error);
    return false;
  }
};

const onChooseAvatar = async (e: any) => {
  const tempFilePath = e.detail.avatarUrl; // 获取临时路径
  userInfo.avatar = e.detail.avatarUrl;
  console.log("临时路径:", tempFilePath);

  // 提取文件名(包含扩展名)
  const fileName = tempFilePath.substring(tempFilePath.lastIndexOf("/") + 1);

  // 判断 userId 是否存在
  if (!userInfo.userId) {
    console.error("无法获取用户信息,无法上传头像");
    return;
  }

  // 上传头像到服务器
  uniCloud.uploadFile({
    filePath: tempFilePath, // 本地临时文件路径
    cloudPath: `avatar/${fileName}`, // 文件存储路径
    cloudPathAsRealPath: true, // 使用 cloudPath 作为绝对路径
    success: (uploadRes) => {
      console.log("uploadRes.data", uploadRes);
      // 确保上传成功后处理
      if (uploadRes.success) {
        const avatar = uploadRes.fileID; // 服务器返回的文件路径
        const existingUserInfo = uni.getStorageSync("userInfo") || {};
        uni.setStorageSync("userInfo", { ...existingUserInfo, avatar });

        // 查询是否已经有该用户的 avatar 数据
        collection
          .doc(userInfo.userId)
          .update({ avatar })
          .then((updateRes) => {
            console.log("头像更新成功:", updateRes);
          })
          .catch((err) => {
            console.error("头像更新失败:", err);
          });
      }
    },
    fail: (err) => {
      console.error("上传失败:", err);
    },
  });
};

const menuItems = ref([
  { label: "已约", path: "my-reservations", icon: "calendar" },
  { label: "上课记录", path: "course-records", icon: "info" },
  { label: "会员卡", path: "membership-cards", icon: "wallet" },
  {
    label: "课程表",
    path: "/pages-courses/courseList/courseList",
    icon: "wallet",
  },
  {
    label: "联系客服",
    path: "",
    icon: "chat",
    action: "contactCustomerService",
  },
  { label: "设置", path: "settings", icon: "gear" },
  { label: "意见反馈", path: "feedback", icon: "mail-open" },
  { label: "退出登录", path: "", icon: "mail-open" },
]);

const encryptMobile = computed(() => {
  return userInfo.mobile.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
});

const navigateTo = (path: string) => {
  if (path) {
    uni.navigateTo({
      url: path,
    });
  } else {
    logout();
  }
};

// 跳转到登录页
const handleLogin = () => {
  uni.navigateTo({
    url: `../login/login`,
  });
  console.log("用户昵称:", userInfo.nickname);
};

const goLoginPage = () => {
  if (!isLoggedIn.value) {
    uni.showToast({
      title: "未登录",
      icon: "none",
    });
    return true;
  }
  return false;
};

const logout = () => {
  console.log("logout");
  if (goLoginPage()) return;
  uni.showModal({
    title: "是否确认退出",
    success: (res) => {
      console.log(res);
      if (res.confirm) {
        uni.clearStorageSync();
      }
    },
  });
};

const onNameChange = async (e) => {
  userInfo.nickname = e.detail.value;
  console.log("微信返回的用户名:", e, userInfo.userId);

  // 判断 userId 是否存在
  if (!userInfo.userId) {
    console.error("无法获取用户信息,无法更新昵称");
    return;
  }

  const existingUserInfo = uni.getStorageSync("userInfo") || {};
  uni.setStorageSync("userInfo", {
    ...existingUserInfo,
    nickname: e.detail.value,
  });

  // 更新用户昵称
  collection
    .doc(userInfo.userId)
    .update({ nickname: e.detail.value })
    .then((updateRes) => {
      console.log("名称更新成功:", updateRes);
    })
    .catch((err) => {
      console.error("名称更新失败:", err);
    });
};

onMounted(async () => {
  const storedUserInfo = uni.getStorageSync("userInfo");
  // 回显用户电话 头像 昵称
  console.log("storedUserInfo", storedUserInfo);
  if (storedUserInfo && storedUserInfo.avatar) {
    userInfo.avatar = storedUserInfo.avatar;
  }
  if (storedUserInfo && storedUserInfo.nickname) {
    userInfo.nickname = storedUserInfo.nickname;
  } else {
    userInfo.nickname = "请输入昵称";
  }
  if (storedUserInfo && storedUserInfo.mobile) {
    userInfo.mobile = storedUserInfo.mobile; // 直接更新 mobile 字段
    isLoggedIn.value = true;
    const userFetched = await getUser();
    if (!userFetched) {
      console.error("用户信息获取失败");
    }
  }
});
</script>

<style>
button::after {
  border: none !important;
  box-shadow: none !important;
}
</style>
<style scoped lang="scss">
.me-container {
  padding: 70rpx 20rpx 30rpx;

  .user-info {
    display: flex;
    align-items: center;
    justify-content: space-between; /* Left section (avatar, nickname) on the left and login button on the right */
    margin-bottom: 40rpx;
  }

  .left-section {
    display: flex;
    align-items: center;
  }

  .avatar-wrapper {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 120rpx;
    height: 120rpx;
    border-radius: 50%;
    overflow: hidden;
    margin-right: 20rpx;
    padding-left: 0rpx;
    padding-right: 0rpx;
  }

  .avatar {
    width: 100%;
    height: 100%;
  }
  .avatar::after {
    border: 1px solid transparent !important;
  }
  uni-button:after {
    border: 1px solid transparent !important;
  }

  .weui-input {
    width: 280rpx;
    padding: 10rpx 0rpx;
    font-size: 32rpx;
    border-radius: 8rpx;
  }

  .login-btn {
    background-color: rgb(116, 219, 239);
    color: #fff;
    border-radius: 8rpx;
    margin-right: 5rpx;
  }

  .card {
    background-color: #fff;
    border-radius: 16rpx;
    box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
    padding: 20rpx;
    display: flex;
    // justify-content: space-between;
    flex-wrap: wrap;
    margin-top: 60rpx;

    .card-item {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      padding: 20rpx 0;
      width: 33%;
    }

    .card-item:last-child {
      border-bottom: none;
    }

    .arrow-icon {
      width: 20rpx;
      height: 20rpx;
    }

    /* CSS Arrow Right */
    .arrow-right {
      width: 0;
      height: 0;
      border-top: 10rpx solid transparent;
      border-bottom: 10rpx solid transparent;
      border-left: 10rpx solid #ccc;
    }
  }
}
</style>