09-用户登录

用户登录

9-1:开篇

在上一章中,我们说到:如果想要完成:关注、收藏、点赞、评论 这些功能的话,那么需要首先完成 用户登录 的功能。

那么这一章中,我们就来看一下,我们应该如何完成 用户登录 的功能实现。

首先我们先来看一下 用户登录 的业务逻辑。

对于 用户登录 来说,主要有两个登录的入口:

  1. 在 《我的页面》中
  2. 在调用需要登录权限的功能时

那么在明确了 用户登录 的业务逻辑之后,接下来我们就去实现用户登录的对应功能。

9-2:用户登录 - 登录页面基本样式

my.vue

<template>
  <view class="my-container">
    <!-- 用户未登录 -->
    <block>
      <image class="avatar avatar-img" src="/static/images/default-avatar.png" mode="scaleToFill" />
      <view class="login-desc">登录后可同步数据</view>
      <button class="login-btn" type="primary" @click="getUserInfo">微信用户一键登录</button>
    </block>
  </view>
</template>

<script>
export default {
  name: 'my-login',
  data() {
    return {};
  }
};
</script>

<style lang="scss" scoped>
.my-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-top: 25%;
  .avatar-img {
    width: 78px;
    height: 78px;
  }
  .login-desc {
    color: $uni-text-color-grey;
    font-size: $uni-font-size-base;
    margin-top: $uni-spacing-col-big;
  }
  .login-btn {
    margin-top: $uni-spacing-col-big;
    width: 85%;
  }
}
</style>

9-3:用户登录 - 封装登录组件

在开篇中,我们说到,对于 登录 功能来说提供了两个登录的入口。

那么大家想一下,现在我们已经在 我的 这个 tab页 中实现了 登录的 UI,难道我还需要在另外一个页面中再去实现一遍吗?

这个肯定是不需要的对不对。所以我们希望可以复用登录的 UI,而复用的方式则可以把 登录的 UI 封装称为一个 组件

创建登录组件 my-login

<template>
  <view class="my-container">
    <!-- 用户未登录 -->
    <block>
      <image class="avatar avatar-img" src="/static/images/default-avatar.png" mode="scaleToFill" />
      <view class="login-desc">登录后可同步数据</view>
      <button class="login-btn" type="primary" @click="getUserInfo">微信用户一键登录</button>
    </block>
  </view>
</template>

<script>
export default {
  name: 'my-login',
  data() {
    return {};
  }
};
</script>

<style lang="scss" scoped>
.my-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-top: 25%;
  .avatar-img {
    width: 78px;
    height: 78px;
  }
  .login-desc {
    color: $uni-text-color-grey;
    font-size: $uni-font-size-base;
    margin-top: $uni-spacing-col-big;
  }
  .login-btn {
    margin-top: $uni-spacing-col-big;
    width: 85%;
  }
}
</style>

my

<template>
  <my-login />
</template>

<script>
export default {
  name: 'login'
};
</script>

在调用需要登录权限的功能时,进入的登录页面 我们先不需要去创建,等到使用的时候,在创建就可以了。

9-4:用户登录 - 明确登录的实现思路

在实现登录的具体功能之前,为了避免一些没有开发经验的同学直接看代码一脸懵逼,我们需要先来明确一下登录的实现基本逻辑。

首先对于登录来说,我们会分为两个不同的端来进行适配实现:

  1. 微信小程序
  2. 非微信小程序(在讲解适配时实现)

我们这里先只讲解 【微信小程序的实现】,【非微信小程序】的实现将在后面的 适配环节进行

微信小程序:

  1. 想要实现登录功能,那么我们需要调用登录接口来进行实现,而登录接口所需要的参数,我们可以直接通过 getUserProfile 方法进行获取。
  2. 调用登录接口成功,服务端会返回用户的 token,这个 token 为当前的用户身份令牌。(拥有 token) 则表示用户已经登录了。
  3. 而此处的 token,我们需要在多个组件中进行使用,所以 token 需要被保存到 全局状态管理工具 - vuex 中,同时需要保存的还有通过 getUserProfile 获取到的用户基本信息。
  4. 而当前的用户登录状态,我们希望可以一直保存(PS:不需要每次都进行登录)。所以在登录完成后,我们需要把 token && userinfo 保存到 本地存储中
  5. 最后,为了实现 数据与组件的分离,我们需要把与 与登录相关的逻辑 都封装在 vuex 中进行。

这些业务是 前端用户登录的标准逻辑,大家在以后的前端登录业务处理中,也可以按照此逻辑进行。

那么从下一小节开始,我们就按照这个逻辑实现一下对应的代码。

9-5:用户登录 - 封装 action 调用登录接口

在上一小节中,我们分析了【微信小程序】中进行登录的实现逻辑,那么从这一小节开始,我们就实现对应的功能。

首先我们需要先获取到用户的信息,用作登录时的请求参数:

my-login

<script>
import { mapActions } from 'vuex';
export default {
  methods: {
    ...mapActions('user', ['login']),
    /**
     * 获取用户信息
     */
    getUserInfo() {
      // 展示加载框
      uni.showLoading({
        title: '加载中'
      });
      uni.getUserProfile({
        desc: '登录后可同步数据',
        success: async (obj) => {
          // 调用 action ,请求登录接口
          await this.login(obj);
        },
        fail: () => {
          uni.showToast({
            title: '授权已取消',
            icon: 'error',
            mask: true
          });
        },
        complete: () => {
          // 隐藏loading
          uni.hideLoading();
        }
      });
    }
  }
};
</script>

api/user.js

import request from '../utils/request';

/**
 * 用户登录
 */
export function login(data) {
  return request({
    url: '/sys/login',
    method: 'POST',
    data
  });
}

store/user.js

import { login } from 'api/user';
export default {
  namespaced: true,
  state: () => {
    return {};
  },
  actions: {
    /**
     * 完成登录
     */
    async login(context, userProfile) {
      console.log(userProfile);
      // 用户数据
      const rawData = userProfile.userInfo;
      // 调用登录接口
      const { data: res } = await login({
        signature: userProfile.signature,
        iv: userProfile.iv,
        nickName: rawData.nickName,
        gender: rawData.gender,
        city: rawData.city,
        province: rawData.province,
        avatarUrl: rawData.avatarUrl
      });
      // TODO: 登录逻辑
      console.log(res);
    }
  }
};

store/index.js

import user from './modules/user';

const store = new Vuex.Store({
  modules: {
    user
  }
});

9-6:用户登录 - 保存用户登录状态

用户的登录状态需要被保存到 vuex 中,同时需要进行 本地存储

store/user.js

import { login } from 'api/user';
const STORAGE_KEY = 'user-info';
const TOKEN_KEY = 'token';
export default {
  namespaced: true,
  state: () => {
    return {
      // 用户 token
      token: uni.getStorageSync(TOKEN_KEY) || '',
      // 用户信息
      userInfo: uni.getStorageSync(STORAGE_KEY) || {}
    };
  },
  mutations: {
    /**
     * 保存 token 到 vuex
     */
    setToken(state, token) {
      state.token = token;
      this.commit('user/saveToken');
    },
    /**
     * 保存 token 到 本地存储
     */
    saveToken(state) {
      uni.setStorage({
        key: TOKEN_KEY,
        data: state.token
      });
    },
    /**
     * 保存 用户信息 到 vuex
     */
    setUserInfo(state, userInfo) {
      state.userInfo = userInfo;
      this.commit('user/saveUserInfo');
    },
    /**
     * 保存 用户信息 到 本地存储
     */
    saveUserInfo(state) {
      uni.setStorage({
        key: STORAGE_KEY,
        data: state.userInfo
      });
    }
  },
  actions: {
    /**
     * 完成登录
     */
    async login(context, userProfile) {
      ...
      // 登录逻辑
      this.commit('user/setToken', res.token);
      this.commit('user/setUserInfo', JSON.parse(userProfile.rawData));
    }
  }
};

9-7:用户登录 - 完成已登录的用户视图

token 存在时,表示用户已经登录了,此时需要 展示用户登录完成的视图:

my-login

<template>
  <view class="my-container">
    <!-- 用户未登录 -->
    <block v-if="!token">
      ...
    </block>
    <!-- 已登录 -->
    <block v-else>
      <image class="avatar avatar-img" :src="userInfo.avatarUrl" mode="scaleToFill" />
      <view class="login-desc">{{ userInfo.nickName }}</view>
      <button class="login-btn" type="default" @click="onLogoutClick">退出登录</button>
    </block>
  </view>
</template>

<script>
import { mapState, mapActions } from 'vuex';
export default {
  name: 'my-login',
  data() {
    return {};
  },
  computed: {
    ...mapState('user', ['token', 'userInfo'])
  },
};
</script>

9-8:用户登录 - 实现退出登录功能

store/user

export default {
  ...
  mutations: {
    ...
    /**
     * 删除 token
     */
    removeToken(state) {
      state.token = '';
      this.commit('user/saveToken');
    },
    ...
    /**
     * 删除用户信息
     */
    removeUserInfo(state) {
      state.userInfo = {};
      this.commit('user/saveUserInfo');
    }
  },
  actions: {
    ...
    /**
     * 退出登录
     */
    logout(context) {
      this.commit('user/removeToken');
      this.commit('user/removeUserInfo');
    }
  }
};

my-login

<template>
  <view class="my-container">
    ...
    <!-- 已登录 -->
    <block v-else>
    	...
        <button class="login-btn" type="default" @click="onLogoutClick">退出登录</button>
    </block>
  </view>
</template>

<script>
export default {
  ...
  methods: {
    ...mapActions('user', ['login', 'logout']),
    ...
    /**
     * 退出登录
     */
    onLogoutClick() {
      uni.showModal({
        title: '提示',
        content: '退出登录将无法同步数据哦~',
        success: ({ confirm, cancel }) => {
          if (confirm) {
            this.logout();
          }
        }
      });
    }
  }
};
</script>

9-9:用户登录 - 判断用户登录状态

截止到目前为止, 用户登录 的功能其实就已经全部构建完毕了。

接下来我们就需要实现:

  • 关注用户
  • 文章点赞
  • 文章收藏
  • 文章评论

这四个对应的功能。

之前我们说过,想要实现这四个功能, 那么需要有一个前提条件就是:当前用户已登录。

所以说,我们就需要在用户使用这四个功能之前,来判断用户的登录状态。

也就是说,在 用户登录功能完成之后,我们其实还不可以立刻着手这四个功能的开发,我们还需要进行一步操作,那就是 判断用户的登录状态!

想要判断用户的登录状态,我们依然需要在 vuex 中进行:

store/user

export default {
  ...
  actions: {
    /**
     * 进行登录判定
     */
    isLogin(context) {
      if (context.state.token) return true;
      // TODO: 如果用户未登录,则引导用户进入登录页面
      return false;
    }
  }
};

如果用户未登录,则引导用户进入登录页面,那么这一步功能如何进行实现呢?

9-10:用户登录 - 新建登录页面,处理当前场景

开篇的时候,我们说过,对于 登录 来说,包含有两个入口:

  1. 在 《我的页面》中
  2. 在调用需要登录权限的功能时

那么此时,就是使用到第二个场景的时候了。

我们创建一个新页面,叫做 login-page,在这个页面中,导入 my-login 组件:

<template>
  <my-login />
</template>

<script>
export default {
  data() {
    return {};
  }
};
</script>

<style lang="scss"></style>

进行登录判定,用户未登录时,进入 login-page 页面:

export default {
  ...
  actions: {
    /**
     * 进行登录判定
     */
    async isLogin(context) {
      if (context.state.token) return true;
      // 如果用户未登录,则引导用户进入登录页面
      const [error, res] = await uni.showModal({
        title: '登录之后才可以进行后续操作',
        content: '立即跳转到登录页面?(登录后回自动返回当前页面哦~~~)'
      });
      const { cancel, confirm } = res;
      if (confirm) {
        uni.navigateTo({
          url: '/subpkg/pages/login-page/login-page'
        });
      }
      return false;
    }
  }
};

关注 用户时,调用该 action

blog-detail.vue

<template>
	...
   <!-- 关注按钮 -->
   <button class="follow" size="mini" @click="onFollowClick">关注</button>
	...
</template>

<script>
...
import { mapActions } from 'vuex';
export default {
  ...
  methods: {
    ...
    ...mapActions('user', ['isLogin']),
    /**
     *  关注按钮点击事件
     */
    async onFollowClick() {
      // 进行登录判定
      const isLogin = await this.isLogin();
      if (!isLogin) {
        return;
      }
    }
  }
};
</script>

9-11:用户登录 - 监听登录成功的状态,返回之前页面

在上一节,我们已经完成了 在调用需要登录权限的功能时,进入登录页面 ,但是当我们登录完成之后,我们 还需要返回之前页面,因为只有这样才能完成我们的功能闭环,所以在这一小节中,我们就去完成这一块的功能:

my-login:在登录成功后,发送事件

<script>
export default {
  methods: {
    /**
     * 获取用户信息
     */
    getUserInfo() {
      ...
      uni.getUserProfile({
        desc: '登录后可同步数据',
        success: async (obj) => {
          // 调用 action ,请求登录接口
          await this.login(obj);
          // 登录成功之后,发送事件
          this.$emit('onLoginSuccess');
        }
        ...
      });
    },
  }
};
</script>

login-page:监听登录成功的事件,并返回上一个页面

<template>
  <my-login @onLoginSuccess="onLoginSuccess" />
</template>

<script>
export default {
  methods: {
    /**
     * 登录成功的事件回调
     */
    onLoginSuccess() {
      uni.navigateBack({ delta: 1 });
    }
  }
};
</script>

9-12:用户登录 - 处理登录时无 loading 的 bug

异步操作没有处理:

    /**
			微信一键登录点击事件
		 */
    getUserInfo() {
      // 展示加载框
      uni.showLoading({
        title: '加载中'
      });

      // 获取用户信息,调用 login 接口
      uni.getUserProfile({
        desc: '登录后可同步数据',
        success: async (obj) => {
          // 调用登录接口
          await this.login(obj);
          // 登录成功,发送事件
          this.$emit('onLoginSuccess');
        },
        fail: () => {
          uni.showToast({
            title: '授权已取消',
            icon: 'error',
            mask: true
          });
        },
        complate: () => {
          // 关闭加载
          uni.hideLoading();
        }
      });
    },

9-13:文章操作 - 关注用户

在彻底完成了 登录 相关的内容之后,接下来就可以回过头去实现这四个功能了。

首先我们先去实现 关注用户 的功能:

定义 api 接口:

api/user

/**
 * 关注用户
 */
export function userFollow(data) {
  return request({
    url: '/user/follow',
    data
  });
}

关注用户接口需要传递 token 请求头,所以我们可以在 request.js 中,传递当前的 token:

import store from '../store';
function request({ url, data, method }) {
  return new Promise((resolve, reject) => {
    uni.request({
      header: {
        Authorization: store.state.user.token
      },
    });
  });
}

export default request;

blog-detail 中调用 接口:

<template>
 ...
<!-- 关注按钮 -->
<button
        class="follow"
        size="mini"
        :type="articleData.isFollow ? 'primary' : 'default'"
        :loading="isFollowLoading"
        @click="onFollowClick"
        >
    {{ articleData.isFollow ? '已关注' : '关注' }}
    </button>
...
</template>

<script>
...
import { userFollow } from 'api/user';
export default {
    ...
  data() {
    return {
      ...
      // 关注用户的 loading
      isFollowLoading: false
    };
  },
  methods: {
    /**
     *  关注按钮点击事件
     */
    async onFollowClick() {
      // 进行登录判定
      const isLogin = await this.isLogin();
      if (!isLogin) {
        return;
      }
      // 关注用户
      // 开启 button 的 loading
      this.isFollowLoading = true;
      const { data: res } = await userFollow({
        author: this.author,
        isFollow: !this.articleData.isFollow
      });
      // 修改用户数据
      this.articleData.isFollow = !this.articleData.isFollow;
      // 关闭 button 的 loading
      this.isFollowLoading = false;
    }
  }
};
</script>

9-14:文章操作 - 处理发表评论的 UI

监听 article-operate 中的 输入框点击事件:

<template>
  <view class="operate-container">
    <!-- 输入框 -->
    <view class="comment-box" @click="onCommitClick">
        ...
    </view>
  </view>
</template>

<script>
import { mapActions } from 'vuex';

export default {
  name: 'article-operate',
  props: {},
  data() {
    return {};
  },
  methods: {
    ...mapActions('user', ['isLogin']),
    /**
     * my-search 的点击事件
     */
    async onCommitClick() {
      // 进行登录判定,登录之后允许发布评论
      if (!(await this.isLogin())) {
        return;
      }
      this.$emit('commitClick');
    }
  }
};
</script>

blog-detail 中展示弹出层:

<template>
  ...
<!-- 底部功能区 -->
<article-operate @commitClick="onCommit" />
<!-- 输入评论的popup -->
<uni-popup ref="popup" type="bottom"> 发表评论的弹出层 </uni-popup>
  ...
</template>

<script>
...
export default {
  ...
  methods: {
    ...
    /**
     * 发布评论点击事件
     */
    onCommit() {
      // 通过组件定义的ref调用uni-popup方法
      this.$refs.popup.open();
    }
  }
};
</script>

创建 发表评论 的弹出层组件 - article-comment-commit

<template>
  <view class="comment-container">
    <uni-easyinput
      v-model="value"
      type="textarea"
      placeholder="说点什么..."
      :maxlength="50"
      :inputBorder="false"
    ></uni-easyinput>
    <button class="commit" type="primary" :disabled="!value" size="mini" @click="onBtnClick">
      发送
    </button>
  </view>
</template>

<script>
export default {
  name: 'article-comment-commit',
  data() {
    return {
      value: ''
    };
  },
  methods: {
    /**
     * 发送按钮点击事件
     */
    onBtnClick() {}
  }
};
</script>

<style lang="scss" scoped>
.comment-container {
  background-color: $uni-bg-color;
  text-align: right;
  padding: $uni-spacing-row-base;
  position: relative;
}
</style>

blog-detail 中使用 article-comment-commit 组件:

<template>
  ...
<!-- 底部功能区 -->
<article-operate @commitClick="onCommit" />
<!-- 输入评论的popup -->
<uni-popup ref="popup" type="bottom">
	<article-comment-commit />
</uni-popup>
  ...
</template>

9-15:文章操作 - 处理评论框的显示问题

现在评论框已经可以显示出来了,但是目前 评论框的显示存在两个问题:

  1. 输入内容之后,关闭评论框,再次展示评论框时,之前输入的内容依然存在
  2. 在真机中,软键盘会遮挡评论框的展示

那么在本小节中,我们就来处理一下这两个问题:

1. 输入内容之后,关闭评论框,再次展示评论框时,之前输入的内容依然存在:

原因:

popup 关闭时,article-comment-commit 组件 并未销毁,依然存在

解决方案:

监听 popup 的关闭事件,通过 v-if 控制 article-comment-commit 组件的销毁

<template>
  ...
<!-- 输入评论的popup -->
      <uni-popup ref="popup" type="bottom" @change="onCommitPopupChange">
        <article-comment-commit v-if="isShowCommit" />
      </uni-popup>
</template>

<script>
...
export default {
  ...
  data() {
    return {
      ...
      // popup 的显示状态
      isShowCommit: false
    };
  },
  methods: {
    ...
    /**
     * 发布评论的 popup 切换事件
     */
    onCommitPopupChange(e) {
      // 修改对应的标记,当 popup 关闭时,为了动画平顺,进行延迟处理
      if (e.show) {
        this.isShowCommit = e.show;
      } else {
        setTimeout(() => {
          this.isShowCommit = e.show;
        }, 200);
      }
    }
  }
};
</script>

2. 在真机中,软键盘会遮挡评论框的展示

原因:

软键盘弹出,占用了底部空间

解决方案:

检测软键盘的弹出事件,动态修改 article-comment-commit 组件的位置

article-comment-commit

<template>
  <view class="comment-container" :style="{ bottom: bottom + 'px' }">
    ...
  </view>
</template>

<script>
export default {
  data() {
    return {
      bottom: 0
    };
  },
  created() {
    // 检测软键盘的变化
    uni.onKeyboardHeightChange(({ height }) => {
      this.bottom = height;
    });
  },
};
</script>

9-16:文章操作 - 发表评论

在一切准备就绪之后,最后就可以实现 发表评论 的功能了。

api/user 中,定义发表评论的接口:

/**
 * 发表评论
 */
export function userArticleComment(data) {
  return request({
    url: '/user/article/comment',
    method: 'POST',
    data
  });
}

article-comment-commit 中调用接口,发表评论:

<template>
  <button class="commit" type="primary" :disabled="!value" size="mini" @click="onBtnClick">
      发送
    </button>
</template>

<script>
import { userArticleComment } from 'api/user';

export default {
  name: 'article-comment-commit',
  props: {
    articleId: {
      type: String,
      required: true
    }
  },
  ...
  methods: {
    /**
     * 发送按钮点击事件
     */
    async onBtnClick() {
      // 展示加载框
      uni.showLoading({
        title: '加载中'
      });
      // 异步处理即可
      await userArticleComment({
        articleId: this.articleId,
        content: this.value
      });
      uni.showToast({
        title: '发表成功',
        icon: 'success',
        mask: true
      });
      // 发表成功之后的回调
      this.$emit('success');
    }
  }
};
</script>

blog-detail 中传递 id ,处理评论成功之后的操作:

<template>
  <!-- 输入评论的popup -->
      <uni-popup ref="popup" type="bottom" @change="onCommitPopupChange">
        <article-comment-commit
          v-if="isShowCommit"
          :articleId="articleId"
          @success="onSendSuccess"
        />
      </uni-popup>
</template>

<script>

export default {
  ... 
  methods: {
    ...
    /**
     * 发表评论成功
     */
    onSendSuccess() {
      // 关闭弹出层
      this.$refs.popup.close();
      this.isShowCommit = false;
    }
  }
};
</script>

9-17:文章操作 - 回显评论数据

article-comment-commit :评论发布成功,传递评论数据对象

    /**
     * 发送按钮点击事件
     */
    async onBtnClick() {
      ...
      // 异步处理即可
      const { data: res } = await userArticleComment({
        articleId: this.articleId,
        content: this.value
      });
	 ...
      // 发表成功之后的回调
      this.$emit('success', res);
    }

article-comment-list:增加添加评论项的方法

    /**
     * 为 comment 增加一个评论
     */
    addCommentList(data) {
      this.commentList.unshift(data);
    }

blog-detail:评论成功后,调用添加评论项的方法

    /**
     * 发表评论成功
     */
    onSendSuccess(data) {
      ...
      // 显示评论数据
      this.$refs.mescrollItem.addCommentList(data);
    }

9-18:文章操作 - 关于点赞和收藏的功能实现

因为 点赞收藏 的功能实现和 关注,几乎一致,所以:

点赞 和 收藏 的功能作为课下作业让大家进行实现,视频中不在进行讲解。

实现代码会在 文档最终代码 中进行体现,供大家进行参考。


点赞功能实现代码:

api/user

/**
 * 用户点赞
 */
export function userPraise(data) {
  return request({
    url: '/user/praise',
    data
  });
}

article-praise

<template>
  <view class="praise-box" @click="onClick">
    <image class="img" :src="getIsPraise" />
    <text class="txt">点赞</text>
  </view>
</template>

<script>
import { mapActions } from 'vuex';
import { userPraise } from 'api/user';
export default {
  name: 'article-praise',
  props: {
    /**
     * 数据源
     */
    articleData: {
      type: Object,
      required: true
    }
  },
  computed: {
    getIsPraise() {
      return this.articleData && this.articleData.isPraise
        ? '/static/images/praise.png'
        : '/static/images/un-praise.png';
    }
  },
  methods: {
    ...mapActions('user', ['isLogin']),
    async onClick() {
      // 进行登录判定,登录之后允许发布评论
      if (!(await this.isLogin())) {
        return;
      }
      // 展示加载框
      uni.showLoading({
        title: '加载中'
      });
      await userPraise({
        articleId: this.articleData.articleId,
        isPraise: !this.articleData.isPraise
      });
      // 关闭加载
      uni.hideLoading();
      // 更新数据
      this.$emit('changePraise', !this.articleData.isPraise);
    }
  }
};
</script>

article-operate

<template>
  <!-- 点赞 -->
    <view class="options-box">
      <article-praise :articleData="articleData" @changePraise="$emit('changePraise', $event)" />
    </view>
</template>

<script>
export default {
  name: 'article-operate',
  props: {
    articleData: {
      type: Object,
      required: true
    }
  },
};
</script>

blog-detail

<template>
   <!-- 底部功能区 -->
        <article-operate
          :articleData="articleData"
          @commitClick="onCommit"
          @changePraise="onChangePraise"
        />
</template>

<script>
export default {
  methods: {
    ...
    /**
     * 点赞处理回调
     */
    onChangePraise(isPraise) {
      this.articleData.isPraise = isPraise;
    }
  }
};
</script>

收藏功能实现代码:

api/user

/**
 * 用户收藏
 */
export function userCollect(data) {
  return request({
    url: '/user/collect',
    data
  });
}

article-collect

<template>
  <view class="collect-box" @click="onClick">
    <image class="img" :src="getIsCollect" />
    <text class="txt">收藏</text>
  </view>
</template>

<script>
import { mapActions } from 'vuex';
import { userCollect } from 'api/user';
export default {
  name: 'article-collect',
  props: {
    /**
     * 数据源
     */
    articleData: {
      type: Object,
      required: true
    }
  },
  computed: {
    getIsCollect() {
      return this.articleData && this.articleData.isCollect
        ? '/static/images/collect.png'
        : '/static/images/un-collect.png';
    }
  },
  methods: {
    ...mapActions('user', ['isLogin']),
    async onClick() {
      // 进行登录判定,登录之后允许发布评论
      if (!(await this.isLogin())) {
        return;
      }
      // 展示加载框
      uni.showLoading({
        title: '加载中'
      });
      await userCollect({
        articleId: this.articleData.articleId,
        isCollect: !this.articleData.isCollect
      });
      // 关闭加载
      uni.hideLoading();
      // 更新数据
      this.$emit('changeCollect', !this.articleData.isCollect);
    }
  }
};
</script>

article-operate

<template>
   <!-- 收藏 -->
    <view class="options-box">
      <article-collect :articleData="articleData" @changeCollect="$emit('changeCollect', $event)" />
    </view>
</template>

<script>
export default {
  name: 'article-operate',
  props: {
    articleData: {
      type: Object,
      required: true
    }
  },
};
</script>

blog-detail

<template>
   <!-- 底部功能区 -->
        <article-operate
          :articleData="articleData"
          @commitClick="onCommit"
          @changePraise="onChangePraise"
          @changeCollect="onChangeCollect"
        />
</template>

<script>
export default {
  methods: {
    ...
    /**
     * 收藏处理回调
     */
    onChangeCollect(isCollect) {
      this.articleData.isCollect = isCollect;
    }
  }
};
</script>

9-19:总结

在这一大章中,我们完成了整个的【用户登录】以及【文章详情】的功能。

针对于【用户登录】来说,我们使用 vuex 来对 【组件】和【数据】进行了分离。

所有与【用户登录】相关的数据操作,都被封装到了 vuex 之中,这样做的好处在于:我们可以在多个组件中对数据进行操作,而不需要担心其影响 单向数据流的简洁性。

而【文章详情】的剩余功能,我们在视频中完成了【关注】和【发布评论】两个功能,而把【点赞】和【收藏】留做了课下作业。这样做的目的是 可以让大家有能够独立思考,以及独立完成功能的机会。

那么到现在为止,我们项目还剩余 【热播】模块没有完成,那么从下一章开始,我们就要搞定【热播】模块啦。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值