1.知识点
1.1.定义属性和方法(响应式)
<script setup>
import {ref} from "vue"
const name=ref("hahaha");
const age=ref(18);
const handleClick=()=>{
name.value="响应式属性";
}
</script>
<template>
<div>
<p>{{name}}-{{age}}</p>
<button @click="handleClick">click me</button>
</div>
</template>
1.2.ref和reactive
区别:
1.ref可以对基本数据类型(数值,字符串,布尔等)、对象、数组定义,并是数据驱动的形式
2.reactive仅仅可以对对象、数组进行实时渲染,不能对基本数据类型进行定义(即,以后定义对象用reactive)
refData.value.name
reactiveData.name
1.3.v-for,v-model
<script setup>
import { ref } from "vue"
const names = ref([{ name: "小猫" }, { name: "小狗" }, { name: "小明" }, { name: "小红" }, { name: "小猪" }])
const search=ref("");
</script>
<template>
<div>
<input type="text" v-model="search" />
<!-- input textarea select -->
<p>{{search}}</p>
<p v-for="(item,index) in names" :key="index">{{item.name}}</p>
</div>
</template>
1.4.搜索实战案例
<script setup>
import { ref ,computed} from "vue"
const names = ref([{ name: "小猫" }, { name: "小狗" }, { name: "小明" }, { name: "小红" }, { name: "小猪" }])
const search=ref("");
const matchNames=computed(() => {
return names.value.filter((item)=>item.name.includes(search.value))
})
</script>
<template>
<div>
<input type="text" v-model="search" />
<p v-for="(item,index) in matchNames" :key="index">{{item.name}}</p>
</div>
</template>
1.5.watch和watchEffect
<script setup>
import { ref ,computed, watch, watchEffect} from "vue"
const search=ref("");
watch(search,(newSearch,preSearch)=>{
console.log("watch函数触发了",newSearch,preSearch)
})
watchEffect(()=>{
console.log("watchEffect函数触发了",search.value)
})
</script>
<template>
<div>
<input type="text" v-model="search" />
</div>
</template>
1.6.组件通信
1.6.1.父传子
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<HelloWorld msg="Vite + Vue" />
</template>
<script setup>
import { ref } from 'vue'
defineProps({
msg: String
})
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<button type="button" @click="count++">count is {{ count }}</button>
</template>
==================================================================
<script setup>
import { ref } from 'vue'
import PostList from "../components/PostList.vue"
const posts = ref([{ title: "学习前端", body: "学习vue", id: 1 }, { title: "学习后端", body: "学习.net core", id: 2 }])
</script>
<template>
<PostList :posts="posts" />
</template>
<script setup>
defineProps({
posts: Array
})
</script>
<template>
<div class="post-list">
<div v-for="item in posts" :key="item.id">
<h3>{{item.body}}</h3>
</div>
</div>
</template>
1.6.2.子传父
见另一篇博文
1.7.钩子函数(onMounted,onUnmounted,onUpdated)
onMounted
watchEffect
2.开始项目
2.1.创建vite项目
npm init vite@latest vue3-vite-blog
1.入口文件index.html
2.src中main.js,mount挂载index.html中id为app的容器,所有的组件都会渲染到app这个容器中,App为根组件
3.进入App这个组件中
2.2.配置路由
npm install vue-router
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;
import router from "./router"
.use(router)
<script setup>
</script>
<template>
<router-view></router-view>
</template>
2.3.组件嵌套
<script setup lang="ts">
</script>
<template>
<div>这是Home.vue页面</div>
</template>
2.4.模拟数据json-server
npm install -g json-server
在根目录下创建data文件夹,db.json,写json数据
json-server --watch data/db.json --port=3303
在package.json 中的scripts中加:
"generate":"json-server ./data/db.json"
npm run generate
解决方案:
以管理员身份运行powershell(快捷键:windows+X)
set-executionpolicy remotesigned
y
2.5.数据请求封装
import { ref } from 'vue'
import axios from "axios"
const getPosts = () => {
const posts = ref([])
const load = async () => {
try {
let { data } = await axios(" http://localhost:3303/posts")
posts.value = data
} catch (error) {
console.log(error)
}
}
return {posts,load}
}
export default getPosts
import { ref } from "vue";
import axios from "axios";
const getPost = (id) => {
const post = ref({});
const load = async () => {
try {
let { data } = await axios(" http://localhost:3303/posts/"+id);
post.value = data;
} catch (error) {
console.log(error);
}
};
return { post, load };
};
export default getPost;
2.6.Home.vue嵌套PostList.vue组件,并将请求数据传递过去
<script setup>
import PostList from "../components/PostList.vue";
import getPosts from "../composibles/getPosts";
const {posts,load} = getPosts();
load();
</script>
<template>
<div class="Home">
<div v-if="posts.length" class="layout">
<PostList :posts="posts" />
</div>
<div v-else>
加载中...
</div>
</div>
</template>
<script setup>
defineProps({
posts: Array
})
</script>
<template>
<div class="post-list">
<div v-for="item in posts" :key="item.id">
{{item.title}}
</div>
</div>
</template>
2.7.PostList.vue嵌套Singlepost.vue组件(第二层组件嵌套;在js中使用从父组件传过来的属性,得用一个变量接收才可以使用)
<script setup>
import SinglePost from "../components/SinglePost.vue"
defineProps({
posts: Array
})
</script>
<template>
<div class="post-list">
<div v-for="item in posts" :key="item.id">
<SinglePost :post="item" />
</div>
</div>
</template>
<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>
<span v-for="tag in post.tags" :key="tag">#{{ tag }}</span>
</div>
</template>
2.8.显示详情页
2.8.1.跳转并传递参数
<template>
<div class="post">
<!-- 传参,需要一个绑定: -->
<router-Link :to="{ name: 'Details', params: { id: post.id } }">
<!-- 以下两种写下是一样的 -->
<!-- <router-Link :to="{ name: 'Details' }"> -->
<!-- <router-Link to="/posts"> -->
<h3>{{ post.title }}</h3>
</router-Link>
<!-- <p>{{ post.body }}</p> -->
<p>{{ snippet }}</p>
<span v-for="tag in post.tags" :key="tag">#{{ tag }}</span>
</div>
</template>
{ path: "/posts/:id", name: 'Details', component: Details},
<script setup>
defineProps({
id: Number,
});
</script>
<template>
<div class="detail">details--{{ id }}</div>
</template>
2.8.2.根据传过来的id请求对应的数据
import { ref } from "vue";
import axios from "axios";
const getPost = (id) => {
const post = ref({});
const load = async () => {
try {
let { data } = await axios(" http://localhost:3303/posts/"+id);
post.value = data;
} catch (error) {
console.log(error);
}
};
return { post, load };
};
export default getPost;
<script setup>
import getPost from "../composibles/getPost";
import axios from "axios";
import { useRoute, useRouter } from "vue-router";
const route = useRoute();
const router = useRouter();
const { post, load } = getPost(route.params.id);
load();
</script>
<template>
<div class="post" v-if="post">
<h3>{{ post.title }}</h3>
<p class="pre">{{ post.body }}</p>
</div>
<div v-else>
加载中...
</div>
</template>
2.9.导航栏
<script setup>
import Navbar from './components/Navbar.vue';
</script>
<template>
<Navbar />
<router-view></router-view>
</template>
2.10.新建组件博客组件
import Create from "../views/Create.vue";
{ path: "/create", name: "Create", component: Create },
const title = ref("");
const body = ref("");
const tags = ref([]);
const tag = ref("");
const handleSubmit = async () => {
const post = {
title: title.value,
body: body.value,
tags: tags.value,
};
const data = await axios.post("http://localhost:3303/posts", post);
router.push("/");
};
2.11.右侧标签
2.11.1.布局组件
import TagCloud from "../components/TagCloud.vue";
<template>
<div class="Home">
<div v-if="posts.length" class="layout">
<PostList :posts="posts" />
<TagCloud :posts="posts" />
</div>
<div v-else>加载中...</div>
</div>
</template>
<template>
<div class="tag-cloud">
<h3>标签</h3>
<div v-for="tag in tags" :key="tag">
#{{ tag }}
</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>
2.11.2.点击标签跳转(包含标签内容的所有博客)
<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>
<template>
<div class="tag">
<div v-if="posts.length">
<!-- 包含标签中的博客内容 -->
<PostList :posts="postsWithTag" />
<!-- 重复利用Home.vue中右侧的标签 -->
<TagCloud :posts="posts" />
</div>
</div>
</template>
<script setup>
import { useRoute } from "vue-router";
import getPosts from "../composibles/getPosts";
import { computed } from "vue";
import PostList from "../components/PostList.vue";
import TagCloud from "../components/TagCloud.vue";
const route = useRoute();
const { posts, load } = getPosts();
load();
const postsWithTag = computed(() => {
return posts.value.filter((p) => p.tags.includes(route.params.tag));
});
</script>
2.12.详情中的编辑和删除
<router-link :to="{ name: 'edit' }">编辑</router-link>
<button @click="deleteCustomer">删除</button>
const deleteCustomer = async () => {
ElMessageBox.confirm("确定要删除此条记录?", "提示", {
confirmButtonText: "是",
cancelButtonText: "否",
type: "warning",
}).then(async () => {
await axios.delete("http://localhost:3303/posts/" + route.params.id);
ElMessage({
type: "success",
message: "用户删除成功!",
});
router.push("/");
});
};
import Edit from "../views/Edit.vue";
{
path: "/edit/:id",
name: "edit",
component: Edit,
},
<template>
<div class="create">
<form @submit.prevent="handleSubmit">
<label for="title">标题</label>
<input type="text" v-model="blog.title" required />
<label for="body">内容</label>
<textarea v-model="blog.body"></textarea>
<label for="tag">标签(回车添加标签)</label>
<input type="text" v-model="tag" @keydown.enter.prevent="handleKeydown" />
<!-- 显示标签 -->
<!-- <div v-for="tag in blog.tags" :key="tag" class="pill">
#{{ tag }}
</div> -->
<el-tag v-for="tag in blog.tags" :key="tag" class="mx-1" closable :disable-transitions="false"
@close="handleClose(tag)">
#{{ tag }}
</el-tag>
<button>确定</button>
</form>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, reactive } from "vue";
import axios from "axios";
import { ElMessage } from "element-plus";
import { useRoute, useRouter } from "vue-router";
const router = useRouter();
const route = useRoute();
const tagsss = ref<any>([]);
const tag = ref("");
const blog = ref<any>({});
onMounted(async () => {
const res = await axios.get("http://localhost:3303/posts/" + route.params.id);
blog.value = res.data;
tagsss.value = res.data.tags;
});
const handleKeydown = () => {
if (!tagsss.value.includes(tag.value)) {
tag.value = tag.value.replace(/\s/g, "");
tagsss.value.push(tag.value);
console.log(blog.value);
}
tag.value = "";
};
const handleClose = (tag: string) => {
tagsss.value.splice(tagsss.value.indexOf(tag), 1)
}
const handleSubmit = async () => {
console.log(blog.value);
const data = await axios.put("http://localhost:3303/posts/" + route.params.id, blog.value);
router.push("/");
ElMessage({
message: "修改成功!",
type: "success",
});
};
</script>
2.13.加载动画
<template>
<div class="spin"></div>
</template>
<style scoped>
.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>
将加载中...均换为<Spinner />
|