Skip to content

lucky0513123/vue3-composition-api-blog

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

28 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Vue3CompositionAPI

第一章 最终效果演示

1. 下载依赖

npm install

2. 启动前端

npm run dev

3. 启动数据

json-server --watch data/db.json --port=3003

第二章 创建项目

1. vite 创建项目

npm init vite@latest vite-blog -- --template vue

2.App.vue

<script setup></script>

<template>
  <div>App.vue</div>
</template>

<style></style>

3. Home.vue

1. 测试setup的执行周期
<template>
  <div class="home">Home</div>
</template>

<script>
export default {
  name: "Home",
  setup() {
    console.log("setup");
  },
  created() {
    console.log("setup");
  },
  mounted() {
    console.log("mounted");
  },
};
</script>

第三章 组件嵌套

1. 创建 views/Home.vue

2. App.vue

import "Home" from './views/Home.vue'

Home.vue 定义属性

<template>
  <div class="home">
    <h1>Home</h1>
    <p>我的名字是: {{ name }} ,我的年龄是 {{ age }}</p>
    <button @click="handleClick">click me</button>
  </div>
</template>

<script setup>
console.log("setup");

let name = "米修在线";
let age = 30;

const handleClick = (e) => {
  console.log("you clicked me");
};
</script>

第三章 Refs

1. Refs

​ * 在 CompositionAPI 中,是不能使用 this 的, 所以也无法使用 this.$refs

<template>
  <div class="home">
    <h1>Home</h1>
    <p ref="p">我的名字是: {{ name }} 我的年龄是: {{ age }}</p>
    <button @click="handleClick">click me</button>
  </div>
</template>

<script setup>
import { ref } from "vue";

console.log(this);

const p = ref(null);
// const p = ref("hello")

let name = "mario";
let age = 30;

const handleClick = (e) => {
  console.log(p, p.value);
  p.value.classList.add("test");
  p.value.textContent = "hello, world";
};

console.log(p.value);
</script>

第四章 Refs 数据响应

1.Ref(数据驱动)

* 未使用ref定义的数据 不能与DOM进行响应
* 通过ref定义的数据 可以实现实时响应 跟(data)一样
<template>
  <div class="home">
    <h1>Home</h1>
    <p>My name is {{ name }} and my age is {{ age }}</p>
    <button @click="handleClick">click me</button>

    <input type="text" v-model="name" />
    <button @click="age++">add 1 to age</button>
  </div>
</template>

<script setup>
import { ref } from "vue";

// // 非ref属性(不响应)
// const name = "mario";
// const age = 30;

// // 通过事件修改值(不响应)
// const handleClick = (e) => {
//   name = "luigi";
//   age = 35;
// };

// ref(响应)
const name = ref("mario");
const age = ref(30);

// 修改值(响应)
const handleClick = (e) => {
  name.value = "luigi";
  age.value = 35;
};
</script>

第五章 Ref vs Reactive

1.引入 reactive

import { reactive, ref } from "vue";

2.Home.vue

<template>
  <div class="home">
    <h1>Home</h1>
    <h2>Refs</h2>
    <p>{{ promiseOne.name }} - {{ promiseOne.age }}</p>
    <button @click="updatePromiseOne">Update promise one</button>
    <h2>Reactive</h2>
    <p>{{ promiseTwo.name }} - {{ promiseTwo.age }}</p>
    <button @click="updatePromiseTwo">Update promise two</button>
  </div>
</template>

<script setup>
  import { reactive, ref } from "vue";

  const promiseOne = ref({ name: "mario", age: 30 });
  const promiseTwo = reactive({ name: "luigi", age: 35 });

  const updatePromiseOne = () => {
    promiseOne.value.age = 40;
  };
  const updatePromiseTwo = () => {
    promiseTwo.age = 45;
  };
</script>

3. Ref 和 Reactive 的区别

​ * 个人建议是尽量使用 ref

// ref和reactive的区别
const nameOne = ref("misterwu");
const nameTwo = reactive("promise");
// 对于基本数据类型来讲, reactive定义的,无法实现数据驱动的响应

// 实现值的更新(reactive)
const updatePromiseTwo = () => {
  nameTwo = "米修";
};

// 3. 获取测试
<p>{{ nameTwo }}</p>;

第六章 计算属性

1. computed 的基本写法

<template>
  <div class="home">
    <h1>{{ name }}</h1>
  </div>
</template>

<script setup>
import { computed } from "vue";

// computed的基本语法
const name = computed(() => {
  return "米斯特吴";
});
</script>

2. 实现搜索功能

<template>
  <div class="home">
    <input type="text" v-model="search" />
    <p>search term - {{ search }}</p>

    <div v-for="name in matchingNames" :key="name">{{ name }}</div>
  </div>
</template>

<script setup>
import { computed, reactive, ref } from "vue";

const names = ref(["小猪", "小狗", "小猫", "小爱", "小狼", "小虎", "狮子王"]);
const search = ref("");

const matchingNames = computed(() => {
  return names.value.filter((name) => name.includes(search.value));
});
</script>

第七章 watch & watchEffect

1. 监听 & 停止监听

<template>
  <div class="home">
    <h1>Home</h1>
    <input type="text" v-model="search" />
    <p>search term - {{ search }}</p>
    <div v-for="name in matchingNames" :key="name">{{ name }}</div>

    <button @click="handleClick">stop watching</button>
  </div>
</template>

<script setup>
import { computed, reactive, ref, watch, watchEffect } from "vue";

const search = ref("");
const names = ref(["小猪", "小狗", "小猫", "小爱", "小狼", "小虎", "狮子王"]);

const matchingNames = computed(() => {
  return names.value.filter((name) => name.includes(search.value));
});

watch(search, () => {
  console.log("watch function ran");
});

// watchEffect
watchEffect(() => {
  console.log("watchEffect ran"); // 仅初始化一次
  console.log("watchEffect ran", search.value); // 每次search属性发生变化都触发
});

// 停止监听 watch
const stopWatch = watch(search, () => {
  console.log("watch function ran");
});

// 停止监听 watchEffect
const stopEffect = watchEffect(() => {
  console.log("watchEffect ran", search.value);
  console.log(names.value);
});

const handleClick = () => {
  stopWatch();
  stopEffect();
};
</script>

第八章 属性 Props

1.Home.vue

​ 配置 Home.vue 的基本形态

<template>
  <div class="home">
    <h1>Home</h1>
  </div>
</template>

<script setup>
  import { ref } from "vue";
  const posts = ref([
    { title: "跟老吴学前端", body: "Lorem ipsum .", id: 1 },
    { title: "不跟老吴学前端", body: "lorem ipsum", id: 2 },
  ]);
</script>

2. PostList.vue

​ *创建 components/PostList.vue

<template>
  <div class="post-list"></div>
</template>

<script setup></script>

<style></style>

3. Home.vue (属性 props)传值

<script setup>
import PostList from "../components/PostList.vue";
import { ref, reactive, computed, watch, watchEffect } from "vue";

const posts = ref([
  { title: "跟米斯特吴学习全新的Vue.js", body: "vue", id: 1 },
  {
    title: "跟米斯特吴学习全新的React.js",
    body: "react Lorem ipsum!",
    id: 2,
  },
]);
</script>

<template>
  <div class="home">
    <PostList :posts="posts" />
  </div>
</template>

<style></style>

6. PostList.vue 使用 SinglePost 组件

<script setup>
import SinglePost from "./SinglePost.vue";

defineProps({
  posts: Array,
});
</script>

<template>
  <div class="post-list">
    <div v-for="post in posts" :key="post.id">
      <SinglePost :post="post" />
    </div>
  </div>
</template>

<style></style>

7. SinglePost.vue 渲染数据

<script setup>
import { computed } from "vue";

const props = defineProps({
  post: Object,
});

const snippet = computed(() => {
  return props.post.body.substring(0, 100) + "...";
});
</script>

<template>
  <div class="post">
    <h3>{{ post.title }}</h3>
    <p>{{ snippet }}</p>
  </div>
</template>

<style></style>

第九章 生命周期钩子函数

1. PostList.vue

<script setup>
import SinglePost from "./SinglePost.vue";
import { onMounted, onUnmounted, onUpdated } from "vue";

onMounted(() => {
  //   alert("页面渲染之前执行,执行完,页面就出来了");
  /** 数据请求 */
});

// watchEffect()

onUnmounted(() => {
  //   alert("组件注销之前执行,执行完组件就不在页面显示了");
  // better-scroll 销毁掉三方插件实例的一些对象
});

onUpdated(() => {
  alert("当组件内的内容发生变化, 就会执行这个更新钩子函数");
});

defineProps({
  posts: Array,
});
</script>

<template>
  <div class="post-list">
    <div v-for="post in posts" :key="post.id">
      <SinglePost :post="post" />
    </div>
  </div>
</template>

<style></style>

2. Home.vue

<script setup>
  import PostList from "../components/PostList.vue";
  import { ref, reactive, computed, watch, watchEffect } from "vue";

  const posts = ref([
    { title: "跟米斯特吴学习全新的Vue.js", body: "vue", id: 1 },
    {
      title: "跟米斯特吴学习全新的React.js",
      body: "react Lorem ipsum!",
      id: 2,
    },
  ]);

  const showPosts = ref(true);
</script>

<template>
  <div class="home">
    <PostList :posts="posts" v-if="showPosts" />
    <button @click="showPosts = !showPosts">显示/隐藏 PostList组件</button>

    <button @click="posts.pop()">删除一个博客信息</button>
  </div>
</template>

<style></style>

第十章 模拟数据

1. data/db.json

{
  "posts": [
    {
      "title": "跟米斯特吴学全新版本的Vue.js",
      "body": "本课程指南主要是为有 Vue 2 经验的、希望学习 Vue 3 的新功能和更改旧项目的用户而提供的。",
      "id": 1,
      "tags": ["vue3", "compositionApi", "blog"]
    },
    {
      "title": "跟米斯特吴学全新版本的React.js",
      "body": "用于构建用户界面的 JavaScript 库",
      "id": 2,
      "tags": ["react", "redux", "hooks"]
    },
    {
      "id": 3,
      "title": "跟米斯特吴学全新版本的Angular",
      "body": "学会用 Angular 构建应用,然后把这些代码和能力复用在多种多种不同平台的应用上 —— Web、移动 Web、移动应用、原生应用和桌面原生应用。",
      "tags": ["angular", "rxjs", "typescript"]
    },
    {
      "id": 4,
      "title": "跟米斯特吴学全新版本微信小程序",
      "body": "微信小程序,小程序的一种,英文名Wechat Mini Program,是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或搜一下即可打开应用。",
      "tags": ["微信", "小程序", "wechat"]
    },
    {
      "id": 5,
      "title": "跟米斯特吴学全新版本的uniapp",
      "body": "uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉/淘宝)、快应用等多个平台。",
      "tags": ["uniapp", "vue", "小程序", "iOS", "安卓"]
    }
  ]
}

2. 安装 json-server

npm install json-server
json-server --watch data/db.json

第十一章 axios 请求数据

1. Home.vue

  • 安装 axios
npm install axios
  • 请求数据
<script setup>
import PostList from "../components/PostList.vue";
import { ref, reactive, computed, watch, watchEffect } from "vue";
import axios from "axios";

const posts = ref([]);

const load = async () => {
  try {
    let { data } = await axios("http://localhost:3003/posts");
    posts.value = data;
  } catch (error) {
    console.log(error);
  }
};

load();

const showPosts = ref(true);
</script>

<template>
  <div class="home">
    <div v-if="posts.length">
      <PostList :posts="posts" v-if="showPosts" />
    </div>
    <div v-else>加载中....</div>
  </div>
</template>

<style></style>

第十二章 复用组件 composables

1. 将 Home.vue 中的 load 抽离

* src/composables 文件夹
* composables/getPosts.js
* 将Home.vue中setup的内容抽离过去

2. getPost.js

import { ref } from "vue";

const getPosts = () => {
  const posts = ref([]);
  const error = ref(null);

  const load = async () => {
    try {
      let data = await fetch("http://localhost:3000/posts");
      if (!data.ok) {
        throw Error("no available data");
      }
      posts.value = await data.json();
    } catch (err) {
      error.value = err.message;
    }
  };

  return { posts, error, load };
};

export default getPosts;

3. Home.vue 实现复用

<script>
import PostList from "../components/PostList.vue";
import getPosts from "../composables/getPosts";

const { posts, load } = getPosts();

load();
</script>

第十三章 渲染标签

1. SinglePost.vue

<template>
  <div class="post">
    <h3>{{ post.title }}</h3>
    <p>{{ snippet }}</p>
    <span v-for="tag in post.tags" :key="tag"> #{{ tag }} </span>
  </div>
</template>

2. App.vue 的样式

#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
}

3. Home.vue 的样式

<div v-if="posts.length" class="layout"></div>
.home {
  max-width: 1200px;
  margin: 0 auto;
  padding: 10px;
}

.layout {
  display: grid;
  grid-template-columns: 3fr 1fr;
  gap: 20px;
}

4. SinglePost.vue 的样式

.post {
  margin: 0 40px 30px;
  padding-bottom: 30px;
  border-bottom: 1px dashed #e7e7e7;
}
.post h3 {
  display: inline-block;
  position: relative;
  font-size: 26px;
  color: white;
  margin-bottom: 10px;
  max-width: 400px;
}
.post h3::before {
  content: "";
  display: block;
  width: 100%;
  height: 100%;
  background: #ff8800;
  position: absolute;
  z-index: -1;
  padding-right: 40px;
  left: -30px;
}

十四章 创建导航

1. 安装路由

npm install vue-router

2. 创建路由文件 src/router/index.js

import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue";

const routes = [{ path: "/", name: "Home", component: Home }];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;

3. main.js 挂载路由实例

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";

createApp(App).use(router).mount("#app");

4. 创建导航 components/Navbar.vue

<template>
  <header>
    <h1>个人博客</h1>
    <nav>
      <router-link :to="{ name: 'Home' }">首页</router-link>
      <router-link :to="{ name: 'Home' }">新建</router-link>
    </nav>
  </header>
</template>

<script></script>

<style>
header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  max-width: 1200px;
  margin: 0 auto;
  padding: 10px;
}
header h1 {
  color: #dfdfdf;
  font-size: 48px;
}
header a {
  color: #bbb;
  text-decoration: none;
  margin-left: 20px;
}
header a.router-link-active {
  color: #444;
  font-weight: bold;
}
</style>

5. App.vue 渲染组件

<script setup>
import Navbar from "./components/Navbar.vue";
</script>

<template>
  <Navbar />
  <router-view></router-view>
</template>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
}
</style>

十五章 Details

1. SinglePost.vue 添加 router-link

<template>
  <div class="post">
    <router-link :to="{ name: 'Details', params: { id: post.id } }">
      <h3>{{ post.title }}</h3>
    </router-link>
    <p>{{ snippet }}</p>
    <span v-for="tag in post.tags" :key="tag"> #{{ tag }} </span>
  </div>
</template>

2. router/index.js 配置路由

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home,
  },
  {
    path: "/posts/:id",
    name: "Details",
    component: Details,
    props: true,
  },
];

3. views/Details.vue

<template>Details</template>

<script>
export default {
  props: ["id"],
  setup(props) {},
};
</script>

<style scoped></style>

4. getPost.js

* 详情页面获取单个数据信息
import { ref } from "vue";

const getPost = (id) => {
  const post = ref(null);
  const error = ref(null);

  const load = async () => {
    try {
      let data = await fetch("http://localhost:3000/posts/" + id);
      if (!data.ok) {
        throw Error("That post does not exist");
      }
      post.value = await data.json();
    } catch (err) {
      error.value = err.message;
    }
  };

  return { post, error, load };
};

export default getPost;

5. Details.vue 引入并使用

<template>
  <div v-if="error">{{ error }}</div>
  <div v-if="post" class="post">
    <h3>{{ post.title }}</h3>
    <p class="pre">{{ post.body }}</p>
  </div>
  <div v-else>Loading...</div>
</template>

<script>
import getPost from "@/composables/getPost";

export default {
  props: ["id"],
  setup(props) {
    const { error, post, load } = getPost(props.id);

    load();

    return { error, post };
  },
};
</script>

6. 加入样式

.tags a {
  margin-right: 10px;
}
.post {
  max-width: 1200px;
  margin: 0 auto;
}
.post p {
  color: #444;
  line-height: 1.5em;
  margin-top: 40px;
}
.pre {
  white-space: pre-wrap;
}

十六章 实现加载动画

1. 创建 components/Spinner.vue

<template>
  <div class="spin"></div>
</template>

<style>
.spin {
  display: block;
  width: 40px;
  height: 40px;
  margin: 30px auto;
  border: 3px solid transparent;
  border-radius: 50%;
  border-top-color: #ff8800;
  animation: spin 1s ease infinite;
}

@keyframes spin {
  to {
    -webkit-transform: rotateZ(360deg);
  }
}
</style>

2. Home.vue 引入 Spinner 加载动画

<script>
import Spinner from "../components/Spinner.vue";

export default {
  name: "Home",
  components: { PostList, Spinner },
};
</script>

<div v-else>
  <Spinner />
</div>

3.Details.vue 引入 Spinner 加载动画

<template>
  <div v-else>
    <Spinner />
  </div>
</template>

<script>
import Spinner from "../components/Spinner.vue";

export default {
  props: ["id"],
  components: { Spinner },
};
</script>

4. 代码控制延迟,显示加载动画

  • 在 getPosts & getPost 里加入延迟代码
const load = async () => {
    try {
      await new Promise(resolve => {
        setTimeout(resolve, 2000)
      })
    }
}

十七章 创建表单

1. 创建 views/Create.vue

2. router/index.js 配置路由

{
  path: '/create',
  name: 'Create',
  component: Create
}

3. Create.vue 表单

<template>
  <div class="create">
    <form>
      <label>标题:</label>
      <input v-model="title" type="text" required />
      <label>内容:</label>
      <textarea v-model="body" required></textarea>
      <label>标签 (回车添加标签):</label>
      <input @keydown.enter.prevent="handleKeydown" v-model="tag" type="text" />

      <button>添加</button>
    </form>
  </div>
</template>

4. Create.vue 业务逻辑

<script>
import { ref } from "vue";

export default {
  setup() {
    const title = ref("");
    const body = ref("");
    const tags = ref([]);
    const tag = ref("");

    const handleKeydown = () => {
      if (!tags.value.includes(tag.value)) {
        tag.value = tag.value.replace(/\s/g, "");
        tags.value.push(tag.value);
      }
      tag.value = "";
    };

    return { body, title, tags, tag, handleKeydown };
  },
};
</script>

5.渲染 tag

<div v-for="tag in tags" :key="tag" class="pill">
  #{{ tag }}
</div>
<button>添加</button>

6.实现样式 Create.vue

form {
  max-width: 480px;
  margin: 0 auto;
  text-align: left;
}
input,
textarea {
  display: block;
  margin: 10px 0;
  width: 100%;
  box-sizing: border-box;
  padding: 10px;
  border: 1px solid #eee;
}
textarea {
  height: 160px;
}
label {
  display: inline-block;
  margin-top: 30px;
  position: relative;
  font-size: 20px;
  margin-bottom: 10px;
}

button {
  display: block;
  margin-top: 30px;
  background: #ff8800;
  color: white;
  border: none;
  padding: 8px 16px;
  font-size: 18px;
}
.pill {
  display: inline-block;
  margin: 10px 10px 0 0;
  color: #444;
  background: #ddd;
  padding: 8px;
  border-radius: 20px;
  font-size: 14px;
}

十九章 添加数据 fetch

1. 给 form 添加 submit 事件

<form @submit.prevent="handleSubmit">

2. setup 中实现 handleSubmit 方法

const handleSubmit = async () => {
  const post = {
    id: Math.floor(Math.random() * 10000),
    title: title.value,
    body: body.value,
    tags: tags.value,
  };

  await fetch("http://localhost:3000/posts", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(post),
  });
};
return { handleSubmit };

3.useRouter 实现路由跳转

  • Create.vue 引入并使用 useRouter
<script>
import { useRouter } from "vue-router";

export default {
  setup() {
    const router = useRouter();

    const handleSubmit = async () => {
      await fetch("http://localhost:3000/posts", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(post),
      });

      router.push({ name: "Home" });
    };
  },
};
</script>

4. useRoute

  • Details.vue
<script>
import { useRoute } from "vue-router";

export default {
  props: ["id"],
  components: { Spinner },
  setup(props) {
    const route = useRoute();
    const { error, post, load } = getPost(route.params.id);

    load();

    return { error, post };
  },
};
</script>

二十章 展示侧边标签

1. 创建 components/TagCloud.vue

<template>
  <div class="tag-cloud">
    <h3>标签</h3>
    <div v-for="tag in tags" :key="tag">
      <router-link :to="{ name: 'Tag', params: { tag } }">
        #{{ tag }}
      </router-link>
    </div>
  </div>
</template>
<script setup>
import { ref } from "vue";

const tags = ref([]);
const tagSet = new Set();

props.posts.forEach((item) => {
  item.tags.forEach((tag) => tagSet.add(tag));
});

tags.value = [...tagSet];

const props = defineProps({
  posts: Array,
});
</script>

<style scoped>
.tag-cloud {
  padding: 10px;
}
.tag-cloud h3 {
  border-bottom: 1px solid #eee;
  padding: 16px 8px;
  color: #444;
}
.tag-cloud div {
  display: inline-block;
  padding: 10px;
}
.tag-cloud a {
  color: #ccc;
  text-decoration: none;
}
.tag-cloud a.router-link-active {
  color: #ff8800;
  font-weight: bold;
}
</style>

2. Home.vue

<script setup>
import PostList from "../components/PostList.vue";
import getPosts from "../composibles/getPosts";
import Spinner from "../components/Spinner.vue";
import TagCloud from "../components/TagCloud.vue";

const { posts, load } = getPosts();
load();
</script>

<template>
  <div class="home">
    <div v-if="posts.length" class="layout">
      <PostList :posts="posts" />
      <TagCloud :posts="posts" />
    </div>
    <div v-else>
      <Spinner />
    </div>
  </div>
</template>

<style scoped>
.home {
  max-width: 1200px;
  margin: 0 auto;
  padding: 10px;
}

.layout {
  display: grid;
  grid-template-columns: 3fr 1fr;
  gap: 20px;
}
</style>

第二十一章 创建 tag 标签路由跳转

  • TagCloud.vue
 <router-link :to="{ name: 'Tag', params: { tag } }">

1. router/index.js

{
  path: '/tags/:tag',
  name: 'Tag',
  component: Tag
}

2. 创建 views/Tag.vue

<script setup>
import Spinner from "../components/Spinner.vue";
import PostList from "../components/PostList.vue";

import getPosts from "../composibles/getPosts";
import { useRoute } from "vue-router";
import { computed } from "vue";

const route = useRoute();
const { posts, load } = getPosts();

load();

const postsWithTag = computed(() => {
  return posts.value.filter((p) => p.tags.includes(route.params.tag));
});
</script>

<template>
  <div class="tag">
    <div v-if="posts.length">
      <PostList :posts="postsWithTag" />
    </div>
    <div v-else>
      <Spinner />
    </div>
  </div>
</template>

<style>
.tag {
  max-width: 1200px;
  margin: 0 auto;
  padding: 10px;
}
</style>

第二十二章 展示底部的标签

* 浏览器调试 点击标签 跳转到Tag页面 但Tag页面底部并没有 tag
* 所以需要在Tag.vue中也引入TagCloud.vue

9. Tag.vue 引入 TagCloud.vue

<template>
  <div class="tag">
    <div v-if="posts.length">
      <PostList :posts="postsWithTag" />
      <TagCloud :posts="posts" />
    </div>
  </div>
</template>

<script>
import TagCloud from "../components/TagCloud.vue";

export default {
  components: { TagCloud },
};
</script>

<style>
.tag {
  max-width: 1200px;
  margin: 0 auto;
  padding: 10px;
}
</style>

10. 将 tag 标签展示在右侧

* Home.vue 添加class
<div v-if="posts.length" class="layout">
  <PostList :posts="posts" />
  <TagCloud :posts="posts" />
</div>
  • Home.vue 添加样式
.layout {
  display: grid;
  grid-template-columns: 3fr 1fr;
  gap: 20px;
}

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Vue 79.6%
  • JavaScript 16.9%
  • HTML 3.5%