Vue学习笔记流(下)

Vue学习笔记流(下)

1. 好友列表

image-20240107154603807

image-20240107121410734

UserListView

<template>
  <contentBase>
    <!-- 函数调用传参直接加括号 不加括号是执行对象 在react中要封装成一个匿名函数 -->
    <div class="card" v-for="user in users" :key="user.id" @click='open_user_profile(user.id)'>
      <div class="card-body">
        <div class="row">
          <div class="col-1 img-field">
            <img class="img-fluid" :src="user.photo" alt="">
          </div>
          <div class="col-11">
            <div class="username">{{ user.username }}</div>
            <div class="follower-count">{{ user.followerCount }}</div>
          </div>
        </div>
      </div>
    </div>
  </contentBase>
</template>

<script>
import ContentBase from "../components/ContentBase"
import $ from 'jquery';
import { ref } from 'vue';
import router from '@/router/index';
import { useStore } from 'vuex';

export default {
  name: 'UserListView',
  components: {
    ContentBase,
  },
  setup() {
    const store = useStore();
    let users = ref([]);

    $.ajax({
      url: 'https://app165.acapp.acwing.com.cn/myspace/userlist/',
      type: "get",
      success(resp) {
        // console.log(resp);
        users.value = resp;
      }
    });

    const open_user_profile = userId => {
      // 在js里不用加$符号 在html里需要
      if (store.state.user.is_login) {
        router.push({
          name: "userprofile",
          params: {
            userId
          }
        })
      } else {
        router.push({
          name: "login",
        });
      }
    }

    return {
      users,
      open_user_profile,
    }
  }
}
</script>

<style scoped>
.container {
  margin-top: 20px;
}

img {
  border-radius: 50%;
}

.username {
  font-weight: bold;
  height: 50%;
}

.follower-count {
  font-size: 13px;
  color: grey;
  height: 50%;
}

.card {
  margin-bottom: 20px;
  cursor: pointer;
}

.card:hover {
  box-shadow: 2px 2px 10px lightgray;
  transition: 460ms;
}

.img-field {
    display: flex;
    flex-direction: column;
    justify-content: center;
}
</style>

至此,我们成功的把好友列表拉下来了。

2. 页面跳转优化

image-20240107131331825

页面跳转里面使用正则表达式匹配

.任意字符,*任意多个.

表示任意多个字符

  {
    path: '/:catchAll(.*)',
    redirect: "/404/"
  }

注:尽量严格最后加上/,因为有些框架是严格要求的。

导航栏加入参数

image-20240107121944162

通过route来取得参数

<script>
import { useRoute } from 'vue-router'

export default {
  name: 'UserList',
  components: {
    ContentBase,
    UserProfileInfo,
    UserProfilePost,
    UserProfileWrite,
  },	
    setup() {
        const route = useRoute();
        const userId = parseInt(route.params.userId);
        console.log(userId);
    },
}

至此,页面的跳转的bug成功修复了。

3. 前端登录页面grid system

image-20240107131305433

  • 使用到了grid系统

image-20240107154913183

  • @submit.prevent 阻止了该标签默认的行为,而我们不需要提交 所以阻止该行为
<template>
  <ContentBase>
    <div class="row justify-content-md-center">
      <div class="col-3">
        <form @submit.prevent="login">
          <div class="mb-3">
            <label for="username" class="form-label">用户名</label>
            <input v-model="username" type="username" class="form-control">
          </div>
          <div class="mb-3">
            <label for="password" class="form-label">密码</label>
            <input v-model="password" type="password" class="form-control" id="password">
          </div>
          <div class="error-message">{{ error_message }}</div>
          <button type="submit" class="btn btn-primary">登录</button>
        </form>
      </div>
    </div>
  </ContentBase>
</template>

<script>
import ContentBase from "../components/ContentBase";
import { ref } from 'vue';
import { useStore } from 'vuex';
import router from '@/router/index';

export default {
  name: 'LoginView',
  components: {
    ContentBase,
  },
  setup() {
    const store = useStore();
    let username = ref('');
    let password = ref('');
    let error_message = ref('');

    const login = () => {
      // console.log(username.value, password.value);
      error_message.value = "";
      store.dispatch("login", {
        username: username.value,
        password: password.value,
        success() {
          // console.log("success!");
          router.push({name: 'userlist', params: {}});
        },
        error() {
          // console.log("failed!");
          error_message.value = "用户名或密码错误!";
        }
      });
    };

    return {
      username,
      password,
      error_message,
      login,
    }
  }
}
</script>

<style scoped>
button {
  width: 100%;
}

.error-message {
  color: red;
}
</style>

至此,我们搭建好了前端登录界面

4. 后端登录页面逻辑+jwt-token + 退出登录

4.1 vuex容器

image-20240107131726685
  • 我们可以将很多登录信息存储到vuex里面

我们可以看到store里的属性

import { createStore } from 'vuex'
import ModuleUser from './user';

export default createStore({
  // 全局变量 访问方式:store.state.user.username
  state: {
  },
  // 需要获取一些变量进行简单计算
  getters: {
  },
  // 直接修改(不能执行异步操作 同步操作mutation action都行)
  mutations: {
  },
  // 复杂的修改 但对于变量的修改我们必须放到mutations里面执行
  actions: {
  },
  // 维护一个子对象 将user这个对象单独进行维护
  modules: {
    user: ModuleUser,
  }
});

  • user的具体实现见jwt后

*4.2 跨域访问进行通信(jwt)

  • 首先引入我们传统的登录方式(session)
    • 服务器判断是否登录是通过session_id查询数据库来定位该用户的状态
    • session_id大部分都是用cookie存储的,而由于其http-only的特性,这就会导致js无法访问,那么当我们用ajax时,就会有无法跨域访问的问题。
    • 跨域的场景:当前域名访问另外一个域名下的api
image-20240107135753975

我们使用到了一个安全通信的api-jwt来建立通信

jwt的通信过程如下

image-20240107140355371

  • 服务器会生成一个jwt,可以看成是一个json/字符串
    • 里面包括了使用hash加密后的用户信息,其中包括了过期时间
  • 这边为什么我们为什么最终是base64,主要是为了只用若干字符来表示各种字符串

image-20240107140513931

  • 服务器将【info+私钥】hash加密后的信息(公钥)传给用户
    • 注:公钥从颁发到失效是有时间的,里面就有一个过期时间的信息。
  • 而客户端将jwt签名返回后,服务器会在尾部加上自己的私钥求加密后的值,将其与客户的值进行比对
    • hash加密是几乎不可逆的,保证了用户不可篡改(因为篡改后加密后的哈希值会变),并且用户不知道私钥是什么,那么就安全
image-20240107140704340

image-20240107154634592

可以看到jwt的api返回值有两个

  • refresh是更新令牌,到期后我们需要用这个refresh来更新令牌(使用另外一个api实现)
    • 这里我们需要jwt base64解码包 npm i jwt-decode
    • image-20240107154623795
  • access就是jwt令牌

image-20240107141934647

我们使用ajax实现访问api

  • login: () => { } 可以简写成login(context, data)

    • context是我们的api,data是我们需要上传的数据
  • 使用ajax来实现api

    • 包括url type data,如果要安全登录,需要加上headers

    • 有一个resp属性,如果成功了success(resp)

    • 定期进行refresh操作,我们使用到了一个setInterval 间隔一段时间访问api。

    • action里更新数据的话,我们要在mutations里定义若干方法,在action使用commit来调用。

import { setInterval } from 'core-js';
import $ from 'jquery';
import { jwtDecode } from 'jwt-decode';

const ModuleUser = {
    state: {
        id: "",
        username: "",
        photo: "",
        followerCount: "",
        firstName: "",
        lastName: "",
        access: "",
        refresh: "",
        is_login: false,
    },
    getters: {
    },
    mutations: {
        updateUSer(state, user) {
            state.id = user.id;
            state.username = user.username;
            state.photo = user.photo;
            state.followerCount = user.followerCount;
            state.access = user.access;
            state.refresh = user.refresh,
            state.is_login = user.is_login;
        },
        updateAccess(state, access){
            state.access = access;
        },
        logout(state) {
            state.id = "";
            state.user_id = "";
            state.photo = "";
            state.followerCount = 0;
            state.access = "",
            state.refresh = "",
            state.is_login = false;
        }

    },
    // 凡是要修改全局变量的 都要写进action里
    actions: {
        login(context, data) {
            $.ajax({
                url: "https://app165.acapp.acwing.com.cn/api/token/",
                type: "POST",
                data: {
                    username: data.username,
                    password: data.password,
                },
                success(resp) {
                    // console.log(resp);
                    const {access, refresh} = resp;
                    const access_obj = jwtDecode(access);
                    // console.log(access_obj, refresh);

                    setInterval(() => {
                        $.ajax({
                            url: "https://app165.acapp.acwing.com.cn/api/token/refresh/",
                            type: "POST",
                            data: {
                                refresh,
                            },
                            success(resp) {
                                // console.log(resp);
                                context.commit('updateAccess', resp.access);
                            }
                        })
                    }, 4.5 * 60 * 1000)
                    $.ajax({
                        url: "https://app165.acapp.acwing.com.cn/myspace/getinfo/",
                        type: "GET",
                        data: {
                            user_id: access_obj.user_id,
                        },
                        headers: {
                            'Authorization' : "Bearer " + access,
                        },
                        success(resp) {
                            // console.log(resp);
                            context.commit("updateUSer", {
                                ...resp,
                                access: access,
                                refresh: refresh,
                                is_login: true,
                            });
                            data.success();
                        },
                    });
                },
                error() {
                    data.error();
                }
            });
        }
    },
    modules: {
    }
};

export default ModuleUser;

4.3 登录(使用dispatch调用actions)

  • 成功以后数据在data里,我们在原组件里使用data.success进行回调
  • 这边我们要实现登录成功后实现跳转,所以后续使用到了一个router进行路由跳转 router.push
    • 失败我们就使用error_message
<script>
import ContentBase from "../components/ContentBase";
import { ref } from 'vue';
import { useStore } from 'vuex';
import router from '@/router/index';

export default {
  name: 'LoginView',
  components: {
    ContentBase,
  },
  setup() {
    const store = useStore();
    let username = ref('');
    let password = ref('');
    let error_message = ref('');

    const login = () => {
      // console.log(username.value, password.value);
      error_message.value = "";
      store.dispatch("login", {
        username: username.value,
        password: password.value,
        success() {
          // console.log("success!");
          router.push({name: 'userlist', params: {}});
        },
        error() {
          // console.log("failed!");
          error_message.value = "用户名或密码错误!";
        }
      });
    };

    return {
      username,
      password,
      error_message,
      login,
    }
  }
}
</script>

4.4 使用store数据判断v-if更新界面

   <!--未登录 -->
      <ul class="navbar-nav" v-if="!$store.state.user.is_login">
        <li class="nav-item">
          <router-link class="nav-link active" :to="{name: 'login', params: {}}">登录</router-link>
        </li>
        <li class="nav-item">
          <router-link class="nav-link" :to="{name: 'register', params: {}}">注册</router-link>
        </li>
      </ul>
      <!--已登录 -->
      <ul class="navbar-nav" v-else>
        <li class="nav-item">
          <router-link class="nav-link active"
          :to="{name: 'userprofile', params: {userId: $store.state.user.id }}"
          >
            {{ $store.state.user.username }}
          </router-link>
        </li>
        <li class="nav-item">
          <a class="nav-link" style="cursor: pointer" @click="logout">退出</a>
        </li>
      </ul>

4.5 退出(使用commit调用mutations)

我们只需要把jwt令牌清空即可,仅需写一个事件。

我们在navbar里加一个事件

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

export default {
    name: "NavBar",
    setup() {
      const store = useStore();
      const logout = () => {
        // 调用mutation用commit 调用action 用dispatch
        store.commit('logout');
      }

      return {
        logout,
      }
    }
}
</script>

我们在user.js 的vuex容器里加入logout 把值更新掉

    mutations: {
        updateUSer(state, user) {
            state.id = user.id;
            state.username = user.username;
            state.photo = user.photo;
            state.followerCount = user.followerCount;
            state.access = user.access;
            state.refresh = user.refresh,
            state.is_login = user.is_login;
        },
        updateAccess(state, access){
            state.access = access;
        },
        logout(state) {
            state.id = "";
            state.user_id = "";
            state.photo = "";
            state.followerCount = 0;
            state.access = "",
            state.refresh = "",
            state.is_login = false;
        }

    },

至此,我们成功地实现了登录功能

待解决的问题:每次刷新登录状态会丢失,解决方法:我们可以将access存入localstorage内(JS库)

5. 通过好友列表进入用户动态(展示数据)

5.1 好友列表UserList个性化跳转

image-20240107150638106

image-20240107150700337

我们在导航栏将用户动态删掉,后续通过用户列表里的userId动态访问

  • 我们在UserListView里添加一个函数 如果登录了,那么跳转对应的id,否则进入登录界面
  • 我们在template的卡片部分添加一个鼠标点击回调函数,注意要加括号。
<template>
  <contentBase>
    <!-- 函数调用传参直接加括号 不加括号是执行对象 在react中要封装成一个匿名函数 -->
    <div class="card" v-for="user in users" :key="user.id" @click='open_user_profile(user.id)'>
      <div class="card-body">
        <div class="row">
          <div class="col-1 img-field">
            <img class="img-fluid" :src="user.photo" alt="">
          </div>
          <div class="col-11">
            <div class="username">{{ user.username }}</div>
            <div class="follower-count">{{ user.followerCount }}</div>
          </div>
        </div>
      </div>
    </div>
  </contentBase>
</template>

<script>
import ContentBase from "../components/ContentBase"
import $ from 'jquery';
import { ref } from 'vue';
import router from '@/router/index';
import { useStore } from 'vuex';

export default {
  name: 'UserListView',
  components: {
    ContentBase,
  },
  setup() {
    const store = useStore();
    let users = ref([]);

    $.ajax({
      url: 'https://app165.acapp.acwing.com.cn/myspace/userlist/',
      type: "get",
      success(resp) {
        // console.log(resp);
        users.value = resp;
      }
    });

    const open_user_profile = userId => {
      // 在js里不用加$符号 在html里需要
      if (store.state.user.is_login) {
        router.push({
          name: "userprofile",
          params: {
            userId
          }
        })
      } else {
        router.push({
          name: "login",
        });
      }
    }

    return {
      users,
      open_user_profile,
    }
  }
}
</script>

5.2 UserProfile根据id个性化展示

image-20240107150711807

  • 我们需要拉取的信息是:用户的信息user以及用户的帖子posts
    const user = reactive({});
    const posts = reactive({});

image-20240107154549084

我们使用到api来获取用户的信息

$.ajax({
      url: "https://app165.acapp.acwing.com.cn/myspace/getinfo/",
      type: "GET",
      data: {
        user_id: userId,
      },
      headers: {
        'Authorization': "Bearer " + store.state.user.access,
      },
      success(resp) {
        user.id = resp.id;
        user.username = resp.username;
        user.photo = resp.photo;
        user.followerCount = resp.followerCount;
        user.is_followed = resp.is_followed;
      }
    });

发现用户头像没有变,我们将user.photo传入UserProfileInfo内

                <div class="col-3 img-field">
                    <!-- 加冒号是将冒号变为变量 -->
                    <img width="200" class="img-fluid" :src="user.photo" alt="">
                </div>

使用flex执行竖着的居中

.img-field {
    display: flex;
    flex-direction: column;
    justify-content: center;
}

UserListView里面的头像也用一样的操作。

我们再使用api来获取用户的post信息

  • 注意帖子的数量参数的更新
    $.ajax({
      url: "https://app165.acapp.acwing.com.cn/myspace/post/",
      type: "GET",
      data: {
        user_id: userId,
      },
      headers: {
        'Authorization': "Bearer " + store.state.user.access,
      },
      success(resp) {
        // console.log(resp);
        posts.count = resp.length;
        posts.posts = resp;
      }
    });

至此,我们实现了个性化的帖子。

5.2.1 区分自己和别人

  • 最好使用===来判断,否则未来的调试的时候会很麻烦。

  • 注意:类型不一样的时候,要注意类型转换

image-20240107151336266

    const userId = parseInt(route.params.userId);
    const user = reactive({});
	const is_me = computed(() => userId === store.state.user.id)

这边我们根据是不是自己来展示发帖的模块

        <user-profile-write v-if="is_me" @post_a_post="post_a_post" />

5.2.2 bug:解决链接判断问题

我们发现在进入用户界面的时候,无法点击自己的界面,后来发现是前端判断的时候不包含后续的参数,我们需要在App.vue上加入一个key参数 表示我们要判断完整路径判断

<template>
  <NavBar />
  <!-- 使用完整路径判重 -->
  <router-view :key="$route.fullPath" />
</template>

5.3 实现云端帖子发送

image-20240107152916282

image-20240107154532936

调用api即可。

我们引入$对象通过ajax实现

我们修改UserProfileWrite中的Post_a_post函数

        const post_a_post = () => {
            $.ajax({
                url: "https://app165.acapp.acwing.com.cn/myspace/post/",
                type: "POST",
                data: {
                    content: content.value,
                },
                headers: {
                    'Authorization': "Bearer " + store.state.user.access,
                },
                success(resp) {
                    if (resp.result === "success") {
                        context.emit('post_a_post', content.value);
                        content.value = "";
                    }
                }
            });

        }

5.4 实现云端帖子删除

image-20240107152828284

image-20240107154525199

事件

  • 要判断的话,我们父组件需要传入一个user过来

image-20240107152639971

  • 父组件要实现这个函数
    • 我们用到了filter,遍历数组内的元素,如果true就保留,false就删除

image-20240107153043134

在父组件这里提供一个事件,那么就可用触发我们的函数了

image-20240107153312963

  • 这边我们子组件需要向父组件emit一个事件申请
  • 加入一个删除按钮,绑定事件即可 这里我们也要区分自己和别人,别人不能展示。
<template>
  <ContentBase>
    <div class="row">
      <div class="col-3">
        <UserProfileInfo @follow="follow" @unfollow="unfollow" :user="user" />
        <user-profile-write v-if="is_me" @post_a_post="post_a_post" />
      </div>
      <div class="col-9">
        <UserProfilePost :user="user" :posts="posts" @delete_a_post="delete_a_post"/>
      </div>
    </div>
  </ContentBase>
</template>

<script>
import { computed } from 'vue';
import { useStore } from 'vuex';
import $ from 'jquery';
export default {
    name: "UserProfilepost",
	props: {
        posts: {
            type: Object,
            required: true,
        },
        user: {
            type: Object,
            required: true,
        }
    },
	setup(props, context) {
        let store = useStore();
        let is_me = computed(() => store.state.user.id === props.user.id);
		const delete_a_post = post_id => {
            $.ajax({
                url: "https://app165.acapp.acwing.com.cn/myspace/post/",
                type: "DELETE",
                data: {
                    post_id,
                },
                headers: {
                    'Authorization': "Bearer " + store.state.user.access,
                },
                success(resp) {
                    if (resp.result === "success") {
                        context.emit('delete_a_post', post_id);
                    }
                }
            });
        }

        return {
            is_me,
            delete_a_post,
        }
    }
}
</script>

放到右边

<style scoped>
button {
    float: right;
}
</style>
image-20240107153616133

5.5 实现云端用户关注

image-20240107154516416

我们修改UserProfileInfo里的follow和unfollow函数,改用云端api

 const follow = () => {
            $.ajax({
                url: "https://app165.acapp.acwing.com.cn/myspace/follow/",
                type: "POST",
                data: {
                    target_id: props.user.id,
                },
                headers: {
                    'Authorization': "Bearer " + store.state.user.access,
                },
                success(resp) {
                    if (resp.result === "success") {
                        context.emit('follow');
                    }
                }
            });
        }

        const unfollow = () => {
            $.ajax({
                url: "https://app165.acapp.acwing.com.cn/myspace/follow/",
                type: "POST",
                data: {
                    target_id: props.user.id,
                },
                headers: {
                    'Authorization': "Bearer " + store.state.user.access,
                },
                success(resp) {
                    if (resp.result === "success") {
                        context.emit('unfollow');
                    }
                }
            });
        }

5.6 完整代码

image-20240102113145796

5.6.1 UserProfileView

<template>
  <ContentBase>
    <div class="row">
      <div class="col-3">
        <UserProfileInfo @follow="follow" @unfollow="unfollow" :user="user" />
        <user-profile-write v-if="is_me" @post_a_post="post_a_post" />
      </div>
      <div class="col-9">
        <UserProfilePost :user="user" :posts="posts" @delete_a_post="delete_a_post"/>
      </div>
    </div>
  </ContentBase>
</template>

<script>
import ContentBase from "../components/ContentBase";
import UserProfileInfo from "../components/UserProfileInfo";
import UserProfilePost from "../components/UserProfilePost";
import UserProfileWrite from "../components/UserProfileWrite";
import { reactive } from 'vue';
import { useRoute } from 'vue-router'
import $ from 'jquery';
import { useStore } from 'vuex';
import { computed } from 'vue';

export default {
  name: 'UserList',
  components: {
    ContentBase,
    UserProfileInfo,
    UserProfilePost,
    UserProfileWrite,
  },
  setup() {
    const store = useStore();
    const route = useRoute();
    const userId = parseInt(route.params.userId);
    // console.log(userId);
    const user = reactive({});
    const posts = reactive({});

    $.ajax({
      url: "https://app165.acapp.acwing.com.cn/myspace/getinfo/",
      type: "GET",
      data: {
        user_id: userId,
      },
      headers: {
        'Authorization': "Bearer " + store.state.user.access,
      },
      success(resp) {
        user.id = resp.id;
        user.username = resp.username;
        user.photo = resp.photo;
        user.followerCount = resp.followerCount;
        user.is_followed = resp.is_followed;
      }
    });

    $.ajax({
      url: "https://app165.acapp.acwing.com.cn/myspace/post/",
      type: "GET",
      data: {
        user_id: userId,
      },
      headers: {
        'Authorization': "Bearer " + store.state.user.access,
      },
      success(resp) {
        // console.log(resp);
        posts.count = resp.length;
        posts.posts = resp;
      }
    });

    const follow = () => {
      if (user.is_followed) return ;
      user.is_followed = true;
      user.followerCount ++ ;
    };

    const unfollow = () => {
      if (!user.is_followed) return ;
      user.is_followed = false;
      user.followerCount -- ;
    }

    const post_a_post = (content) => {
      posts.count ++ ;
      posts.posts.unshift({
        id: posts.count,
        userId: 1,
        content: content,
      })
    }

    const delete_a_post = post_id => {
      posts.posts = posts.posts.filter(post => post.id != post_id);
      posts.count = posts.posts.length;
    }

    const is_me = computed(() => userId === store.state.user.id)

    return {
      user,
      follow,
      unfollow,
      posts,
      post_a_post,
      delete_a_post,
      is_me,
    }
  }
}
</script>

<style scoped>
</style>

5.6.2 UserProfileInfo

<template>
    <div class="card">
        <div class="body">
            <div class="row">
                <div class="col-3 img-field">
                    <!-- 加冒号是将冒号变为变量 -->
                    <img width="200" class="img-fluid" :src="user.photo" alt="">
                </div>
                <div class="col-9">
                    <div class="username">{{ user.username }}</div>
                    <div class="fans">粉丝数:{{ user.followerCount }}</div>
                    <button @click="follow" v-if="!user.is_followed" type="button" class="btn btn-secondary btn-sm">+关注</button>
                    <button @click="unfollow" v-if="user.is_followed" type="button" class="btn btn-success btn-sm">取消关注</button>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
// import { computed } from 'vue';
import $ from 'jquery';
import { useStore } from 'vuex';

export default {
    name: 'UserProfileInfo',
    props: {
        user: {
            type: Object,
            required: true,
        },
    },
    setup(props, context) {
        // let fullName = computed(() => props.user.lastName + ' ' + props.user.firstName);
        const store = useStore();
        const follow = () => {
            $.ajax({
                url: "https://app165.acapp.acwing.com.cn/myspace/follow/",
                type: "POST",
                data: {
                    target_id: props.user.id,
                },
                headers: {
                    'Authorization': "Bearer " + store.state.user.access,
                },
                success(resp) {
                    if (resp.result === "success") {
                        context.emit('follow');
                    }
                }
            });
        }

        const unfollow = () => {
            $.ajax({
                url: "https://app165.acapp.acwing.com.cn/myspace/follow/",
                type: "POST",
                data: {
                    target_id: props.user.id,
                },
                headers: {
                    'Authorization': "Bearer " + store.state.user.access,
                },
                success(resp) {
                    if (resp.result === "success") {
                        context.emit('unfollow');
                    }
                }
            });
        }

        return {
        //  fullName,
            follow,
            unfollow,
        }
    }
}
</script>

<style scoped>
img {
    border-radius: 50%;
}

.username {
    font-weight: bold;
}

.fans {
    font-size: 12px;
    color: grey;
}

button {
    padding: 2px 4px;
    font-size: 12px;
}

.img-field {
    display: flex;
    flex-direction: column;
    justify-content: center;
}
</style>

5.6.3 UserProfileWrite

<template>
    <div class="card edit-field">
        <div class="card-body">
            <label for="edit-post" class="form-label">编辑帖子</label>
            <textarea v-model="content" class="form-control" id="edit-post" rows="3"></textarea>
            <button @click="post_a_post" type="button" class="btn btn-primary btn-sm">发帖</button>

        </div>
    </div>
</template>

<script>
import { ref } from 'vue' ;
import $ from 'jquery';
import { useStore } from 'vuex';

export default {
    name: "UserProfileWrite",
    setup(props, context) {
        const store = useStore();
        let content = ref('');

        const post_a_post = () => {
            $.ajax({
                url: "https://app165.acapp.acwing.com.cn/myspace/post/",
                type: "POST",
                data: {
                    content: content.value,
                },
                headers: {
                    'Authorization': "Bearer " + store.state.user.access,
                },
                success(resp) {
                    if (resp.result === "success") {
                        context.emit('post_a_post', content.value);
                        content.value = "";
                    }
                }
            });

        }

        return {
            content,
            post_a_post,
        }
    }
}
</script>

<style scoped>
.edit-field {
    margin-top: 20px;
}

button {
    margin-top: 10px;
}
</style>

5.6.4 UserProfilePost

<template>
    <div class="card">
        <div class="card-body">
            <div v-for="post in posts.posts" :key="post.id">
                <div class="card single-post">
                    <div class="card-body">
                        {{post.content}}
                            <button @click="delete_a_post(post.id)" v-if="is_me" type="button" class="btn btn-danger btn-sm">
                                删除
                            </button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
import { computed } from 'vue';
import { useStore } from 'vuex';
import $ from 'jquery';

export default {
    name: "UserProfilepost",
    props: {
        posts: {
            type: Object,
            required: true,
        },
        user: {
            type: Object,
            required: true,
        }
    },
    setup(props, context) {
        let store = useStore();
        let is_me = computed(() => store.state.user.id === props.user.id);

        const delete_a_post = post_id => {
            $.ajax({
                url: "https://app165.acapp.acwing.com.cn/myspace/post/",
                type: "DELETE",
                data: {
                    post_id,
                },
                headers: {
                    'Authorization': "Bearer " + store.state.user.access,
                },
                success(resp) {
                    if (resp.result === "success") {
                        context.emit('delete_a_post', post_id);
                    }
                }
            });
        }

        return {
            is_me,
            delete_a_post,
        }
    }
}
</script>


<style scoped>
.single-post {
    margin-bottom: 10px;
}

button {
    float: right;
}
</style>

5.6 注册

image-20240107154041069

与登录功能类似,我们修改如下:

  • 加入确认密码
<template>
  <ContentBase>
    <div class="row justify-content-md-center">
      <div class="col-3">
        <form @submit.prevent="register">
          <div class="mb-3">
            <label for="username" class="form-label">用户名</label>
            <input v-model="username" type="username" class="form-control">
          </div>
          <div class="mb-3">
            <label for="password" class="form-label">密码</label>
            <input v-model="password" type="password" class="form-control" id="password">
          </div>
          <div class="mb-3">
            <label for="password_confirm" class="form-label">确认密码</label>
            <input v-model="password_confirm" type="password" class="form-control" id="password">
          </div>
          <div class="error-message">{{ error_message }}</div>
          <button type="submit" class="btn btn-primary">登录</button>
        </form>
      </div>
    </div>
  </ContentBase>
</template>
  • 增加一个变量 password-confirm,多一个绑定
<script>
import ContentBase from "../components/ContentBase";
import { ref } from 'vue';
import { useStore } from 'vuex';
import $ from 'jquery';
import router from '@/router/index';

export default {
  name: 'RegisterView',
  components: {
    ContentBase,
  },
  setup() {
    const store = useStore();
    let username = ref('');
    let password = ref('');
    let password_confirm = ref('');
    let error_message = ref('');

    console.log(store, router);
      const register = () => {
      		// console.log(username.value, password.value);
        }
      });
    }

    return {
      username,
      password,
      password_confirm,
      error_message,
      register,
    }
  }
}
</script>

我们实现注册的方法

image-20240107154700146

  • 注册完成后,我们实现了自动登录的功能, 这边就需要在请求成功且返回结果是success的话,用store.dispach来实现login业务逻辑
  • 登录后,我们继续沿用router.push来实现跳转到用户列表
    const register = () => {
      // console.log(username.value, password.value);
      error_message.value = "";
      $.ajax({
        url: "https://app165.acapp.acwing.com.cn/myspace/user/",
        type: "POST",
        data: {
          username: username.value,
          password: password.value,
          password_confirm: password_confirm.value,
        },
        success(resp) {
          // console.log(resp);
          /* 执行login逻辑 */
          if (resp.result === "success") {
            store.dispatch("login", {
              username: username.value,
              password: password.value,
              success() {
                // console.log("success!");
                router.push({name: 'userlist', params: {}});
              },
              error() {
                // console.log("failed!");
                error_message.value = "系统异常,请稍后重试";
              }
            });
          } else {
            error_message.value = resp.result;
          }

        }

完整代码UserProfileRegister

<template>
  <ContentBase>
    <div class="row justify-content-md-center">
      <div class="col-3">
        <form @submit.prevent="register">
          <div class="mb-3">
            <label for="username" class="form-label">用户名</label>
            <input v-model="username" type="username" class="form-control">
          </div>
          <div class="mb-3">
            <label for="password" class="form-label">密码</label>
            <input v-model="password" type="password" class="form-control" id="password">
          </div>
          <div class="mb-3">
            <label for="password_confirm" class="form-label">确认密码</label>
            <input v-model="password_confirm" type="password" class="form-control" id="password">
          </div>
          <div class="error-message">{{ error_message }}</div>
          <button type="submit" class="btn btn-primary">登录</button>
        </form>
      </div>
    </div>
  </ContentBase>
</template>

<script>
import ContentBase from "../components/ContentBase";
import { ref } from 'vue';
import { useStore } from 'vuex';
import $ from 'jquery';
import router from '@/router/index';

export default {
  name: 'RegisterView',
  components: {
    ContentBase,
  },
  setup() {
    const store = useStore();
    let username = ref('');
    let password = ref('');
    let password_confirm = ref('');
    let error_message = ref('');

    console.log(store, router);
    const register = () => {
      // console.log(username.value, password.value);
      error_message.value = "";
      $.ajax({
        url: "https://app165.acapp.acwing.com.cn/myspace/user/",
        type: "POST",
        data: {
          username: username.value,
          password: password.value,
          password_confirm: password_confirm.value,
        },
        success(resp) {
          // console.log(resp);
          /* 执行login逻辑 */
          if (resp.result === "success") {
            store.dispatch("login", {
              username: username.value,
              password: password.value,
              success() {
                // console.log("success!");
                router.push({name: 'userlist', params: {}});
              },
              error() {
                // console.log("failed!");
                error_message.value = "系统异常,请稍后重试";
              }
            });
          } else {
            error_message.value = resp.result;
          }

        }
      });
    }

    return {
      username,
      password,
      password_confirm,
      error_message,
      register,
    }
  }
}
</script>

<style scoped>
button {
  width: 100%;
}

.error-message {
  color: red;
}
</style>

posted @ 2024-01-07 15:56  yuezi2048  阅读(17)  评论(0编辑  收藏  举报