使用 Vite、TypeScript 、 Vue 3 Composition API、Vue Router、UnoCSS 搭建的一个简易知乎应用。
创建项目
npm init vite@latest my-zhihu-clone --template vue-ts
cd my-zhihu-clone
npm install
安装 Vue Router 和 unocss 依赖:
npm install vue-router@4
npm install unocss --save-dev
配置 UnoCSS
在 vite.config.ts 中配置 UnoCSS:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import UnoCSS from 'unocss/vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), UnoCSS()],
})
根目录创建一个 uno.config.ts 文件来配置 UnoCSS:
import { defineConfig } from 'unocss'
export default defineConfig({
rules: [
// 在此处定义自定义规则
],
shortcuts: {
btn: 'px-4 py-2 rounded bg-blue-500 text-white hover:bg-blue-600',
// 更多的快捷方式
},
theme: {
colors: {
primary: '#007bff',
secondary: '#6c757d',
// 更多的主题颜色
},
},
})
编写代码
1. 设置路由
在 src/router/index.ts 中配置路由:
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import Question from '../views/Question.vue'
const routes = [
{ path: '/', component: Home },
{ path: '/question/:id', component: Question },
]
const router = createRouter({
history: createWebHistory(),
routes,
})
export default router
2. 主应用文件 main.ts
在 src/main.ts 中初始化 Vue 应用:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import 'uno.css'
createApp(App).use(router).mount('#app')
3. 主组件 App.vue
在 src/App.vue 中设置主要布局:
<template>
<div id="app">
<Header />
<router-view class="p-6" />
</div>
</template>
<script setup lang="ts">
import Header from './components/Header.vue'
</script>
<style>
body {
margin: 0;
}
</style>
4. Header 组件
创建组件 src/components/Header.vue:
<template>
<header class="bg-primary text-white p-4">
<RouterLink to="/">
<h1>Zhihu Clone</h1>
</RouterLink>
</header>
</template>
<style scoped>
a {
color: white;
text-decoration: none;
}
</style>
5. 抽离数据
添加模拟数据,这些数据包含图像和文本内容,数据保存在 src/data/questions.ts 文件中。
// src/data/questions.ts
export const questions = [
{
id: 1,
title: 'What is Vue.js?',
content:
'Vue.js is a progressive JavaScript framework for building user interfaces...',
image: 'https://niit-soft.oss-cn-hangzhou.aliyuncs.com/blog/1.jpg',
author: {
name: 'John Doe',
avatar: 'https://niit-soft.oss-cn-hangzhou.aliyuncs.com/avatar/1.jpg',
},
},
{
id: 2,
title: 'How to use TypeScript with Vue?',
content:
'Using TypeScript with Vue.js can greatly improve your development experience...',
image: 'https://niit-soft.oss-cn-hangzhou.aliyuncs.com/blog/2.jpg',
author: {
name: 'Jane Smith',
avatar: 'https://niit-soft.oss-cn-hangzhou.aliyuncs.com/avatar/2.jpg',
},
},
]
6. 子组件 QuestionCard 组件
创建 components/QuestionCard 组件,用来显示每个问题的信息,并实现父子组件通信。
<template>
<div class="border p-4 rounded mb-4 bg-blue-100">
<div class="flex items-center mb-2">
<img
:src="question.author.avatar"
alt="avatar"
class="w-10 h-10 rounded-full mr-2"
/>
<span class="font-bold">{{ question.author.name }}</span>
</div>
<img :src="question.image" alt="question image" class="mb-2 w-100" />
<h3 class="text-xl font-bold">{{ question.title }}</h3>
<p>{{ question.content }}</p>
<button class="btn mt-2" @click="viewDetails">View Details</button>
</div>
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
question: {
type: Object,
required: true,
},
})
const emits = defineEmits(['view-details'])
const viewDetails = () => {
emits('view-details', props.question.id)
}
</script>
7. 父组件 Home.vue
创建页面 src/views/Home.vue,引入 data 中的数据,并监听子组件 QuestionCard 的 view-details 事件并处理。
<template>
<div>
<QuestionCard
v-for="question in questions"
:key="question.id"
:question="question"
@view-details="handleViewDetails"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { questions } from '../data/questions'
import QuestionCard from '../components/QuestionCard.vue'
import { useRouter } from 'vue-router'
const questionList = ref(questions)
const router = useRouter()
const handleViewDetails = (id: number) => {
router.push(`/question/${id}`)
}
</script>
8. 详情页
创建 src/views/Question.vue 详情页,获取路由传递的值,展示问题的详细信息。
<template>
<div v-if="question">
<div class="flex items-center mb-6">
<img
:src="question.author.avatar"
alt="avatar"
class="w-10 h-10 rounded-full mr-2"
/>
<span class="font-bold">{{ question.author.name }}</span>
</div>
<img :src="question.image" alt="question image" class="mb-2 w-100" />
<h3 class="text-xl font-bold">{{ question.title }}</h3>
<p>{{ question.content }}</p>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { questions } from '../data/questions'
const route = useRoute()
const question = ref(null)
onMounted(() => {
const id = parseInt(route.params.id as string)
question.value = questions.find((q) => q.id === id) || null
})
</script>
运行项目
npm run dev
打开浏览器 http://localhost:5173/,将呈现一个带有数据和图文并茂的仿知乎应用,并实现了父子组件通信和基本的页面导航。