一、文章分类架子--PageContainer
学习PageContainer组件的封装,这一组件用于搭建页面基础结构,为后续内容展示提供统一布局。它可能包含通用的页面样式、导航栏、侧边栏等基础元素的结构搭建。
在Vue组件中, <template> 标签内定义基础结构
<template>
<div class="page-container">
<!-- 导航栏部分 -->
<div class="nav-bar">...</div>
<!-- 内容区域 -->
<div class="content-area">
<!-- 这里后续会放置文章分类相关内容 -->
</div>
</div>
</template>
<script setup>
// 组件逻辑代码,可能包含数据定义、方法等
</script>
<style scoped>
.page-container {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.nav-bar {
height: 60px;
background-color: #333;
color: white;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
}
.content-area {
flex: 1;
padding: 20px;
}
</style>
二、文章分类渲染
<template>
<div class="category-list">
<div v-for="category in categories" :key="category.id" class="category-item">
{{ category.name }}
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
// 模拟数据,实际开发中从接口获取
const categories = ref([
{ id: 1, name: '技术' },
{ id: 2, name: '生活' },
{ id: 3, name: '娱乐' }
]);
</script>
<style scoped>
.category-list {
list-style: none;
padding: 0;
}
.category-item {
padding: 10px;
border-bottom: 1px solid #ccc;
}
</style>
三、添加分类
(一)显示弹层封装弹层组件
创建添加分类的弹层组件,实现点击按钮弹出弹层,用于用户输入新分类信息。包括弹层的显示隐藏逻辑、样式设计以及与父组件的交互。
弹层组件 <AddCategoryModal>
<template>
<div v-if="isVisible" class="add-category-modal">
<div class="modal-content">
<h2>添加分类</h2>
<input v-model="newCategoryName" placeholder="请输入分类名称">
<button @click="handleAdd">添加</button>
<button @click="handleClose">关闭</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const isVisible = ref(false);
const newCategoryName = ref('');
const handleAdd = () => {
// 这里可以添加将新分类数据发送到后端的逻辑
console.log('添加分类:', newCategoryName.value);
handleClose();
};
const handleClose = () => {
isVisible.value = false;
newCategoryName.value = '';
};
</script>
<style scoped>
.add-category-modal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
</style>
(二)添加完成
处理添加分类的逻辑,在用户点击添加按钮后,将新分类数据发送到后端保存,并在成功后更新前端分类列表,实现数据的实时同步。
<template>
<!-- 同176集弹层模板,主要修改handleAdd方法 -->
</template>
<script setup>
import { ref } from 'vue';
import axios from 'axios';
const isVisible = ref(false);
const newCategoryName = ref('');
const handleAdd = async () => {
try {
const response = await axios.post('/api/categories', { name: newCategoryName.value });
if (response.status === 200) {
// 假设获取到新分类数据
const newCategory = response.data;
// 这里更新前端分类列表,假设在父组件中定义了categories数组
// 并通过props传递到当前组件,需要更新后重新传递
console.log('添加成功,新分类:', newCategory);
}
} catch (error) {
console.error('添加分类失败:', error);
}
handleClose();
};
const handleClose = () => {
isVisible.value = false;
newCategoryName.value = '';
};
</script>
四、删除分类
为每个分类添加删除功能,点击删除按钮后,向后端发送删除请求,并同步更新前端分类列表,确保前后端数据一致。
<template>
<div class="category-list">
<div v-for="category in categories" :key="category.id" class="category-item">
{{ category.name }}
<button @click="handleDelete(category.id)">删除</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import axios from 'axios';
// 假设从父组件获取分类数据
const categories = ref([
{ id: 1, name: '技术' },
{ id: 2, name: '生活' },
{ id: 3, name: '娱乐' }
]);
const handleDelete = async (categoryId) => {
try {
const response = await axios.delete(`/api/categories/${categoryId}`);
if (response.status === 200) {
// 更新前端分类列表,过滤掉被删除的分类
categories.value = categories.value.filter(category => category.id!== categoryId);
console.log('删除成功');
}
} catch (error) {
console.error('删除分类失败:', error);
}
};
</script>
五、文章管理--静态结构搭建
<template>
<div class="article - management">
<h1 class="page - title">文章管理</h1>
<div class="article - list"></div>
<button class="add - article - btn">添加文章</button>
</div>
</template>
<style scoped>
.article - management {
padding: 20px;
}
.page - title {
font - size: 24px;
margin - bottom: 20px;
}
.article - list {
border: 1px solid #ccc;
padding: 10px;
min - height: 300px;
}
.add - article - btn {
background - color: #007BFF;
color: white;
border: none;
padding: 10px 20px;
border - radius: 5px;
margin - top: 20px;
cursor: pointer;
}
</style>
六、中文国际化处理和文章分类组件封装
使用工具(如vue - i18n)实现中文国际化,根据语言环境切换页面文字显示;封装文章分类组件,提高代码复用性,将分类展示逻辑抽离到单独组件。
<template>
<div>
<span>{{ $t('articleList') }}</span>
<ArticleCategory></ArticleCategory>
</div>
</template>
<script setup>
import { useI18n } from 'vue - i18n';
import ArticleCategory from './ArticleCategory.vue';
const { t } = useI18n();
</script>
<template>
<div class="category - list">
<div v - for="category in categories" : key="category.id" class="category - item">{{ category.name }}</div>
</div>
</template>
<script setup>
const categories = [
{ id: 1, name: '技术' },
{ id: 2, name: '生活' }
];
</script>
<style scoped>
.category - list {
list - style: none;
padding: 0;
}
.category - item {
padding: 5px 0;
}
</style>
七、文章列表
(一)封装API接口,请求渲染
1.封装文章列表数据请求的API接口,使用 axios 库发送HTTP请求获取文章数据,在组件中调用接口并将数据渲染到页面。
2.创建 api/article.js 封装接口
import axios from 'axios';
export const getArticleList = () => {
return axios.get('/api/articles');
};
<template>
<div class="article - list">
<div v - for="article in articles" : key="article.id" class="article - item">{{ article.title }}</div>
</div>
</template>
<script setup>
import { getArticleList } from '@/api/article.js';
import { ref } from 'vue';
const articles = ref([]);
const fetchArticles = async () => {
try {
const response = await getArticleList();
articles.value = response.data;
} catch (error) {
console.error('获取文章列表失败', error);
}
};
fetchArticles();
</script>
<style scoped>
.article - list {
list - style: none;
padding: 0;
}
.article - item {
padding: 10px 0;
border - bottom: 1px solid #ccc;
}
</style>
(二)分页渲染
<template>
<div class="article - management">
<div class="article - list">
<div v - for="article in articles" : key="article.id" class="article - item">{{ article.title }}</div>
</div>
<div class="pagination">
<button @click="prevPage" : disabled="page === 1">上一页</button>
<span>{{ page }}</span>
<button @click="nextPage">下一页</button>
</div>
</div>
</template>
<script setup>
import { getArticleList } from '@/api/article.js';
import { ref } from 'vue';
const articles = ref([]);
const page = ref(1);
const fetchArticles = async () => {
try {
const response = await getArticleList(page.value);
articles.value = response.data;
} catch (error) {
console.error('获取文章列表失败', error);
}
};
const prevPage = () => {
if (page.value > 1) {
page.value--;
fetchArticles();
}
};
const nextPage = () => {
page.value++;
fetchArticles();
};
fetchArticles();
</script>
<style scoped>
.pagination {
margin - top: 20px;
}
.pagination button {
margin: 0 5px;
}
</style>
(三)添加loading和处理搜索重置
在文章数据请求时展示loading状态,让用户知道数据正在加载;添加搜索功能和重置按钮,实现根据关键词搜索文章,重置按钮用于清空搜索条件。
<template>
<div class="article - management">
<input v - model="searchKey" placeholder="搜索文章">
<button @click="resetSearch">重置</button>
<div v - if="loading" class="loading">加载中...</div>
<div class="article - list">
<div v - for="article in filteredArticles" : key="article.id" class="article - item">{{ article.title }}</div>
</div>
<div class="pagination">
<button @click="prevPage" : disabled="page === 1">上一页</button>
<span>{{ page }}</span>
<button @click="nextPage">下一页</button>
</div>
</div>
</template>
<script setup>
import { getArticleList } from '@/api/article.js';
import { ref } from 'vue';
const articles = ref([]);
const filteredArticles = ref([]);
const searchKey = ref('');
const page = ref(1);
const loading = ref(false);
const fetchArticles = async () => {
loading.value = true;
try {
const response = await getArticleList(page.value);
articles.value = response.data;
filteredArticles.value = articles.value.filter(article => article.title.includes(searchKey.value));
} catch (error) {
console.error('获取文章列表失败', error);
} finally {
loading.value = false;
}
};
const resetSearch = () => {
searchKey.value = '';
fetchArticles();
};
const prevPage = () => {
if (page.value > 1) {
page.value--;
fetchArticles();
}
};
const nextPage = () => {
page.value++;
fetchArticles();
};
fetchArticles();
</script>
<style scoped>
.loading {
text - align: center;
margin - top: 20px;
}
</style>
八、文章新增
(一)准备抽屉组件--封装抽屉组件
<template>
<div v-if="isOpen" class="drawer">
<div class="drawer - content">
<button @click="closeDrawer">关闭</button>
<h2>新增文章</h2>
<!-- 后续添加表单内容 -->
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const isOpen = ref(false);
const closeDrawer = () => {
isOpen.value = false;
};
</script>
<style scoped>
.drawer {
position: fixed;
top: 0;
right: 0;
width: 300px;
height: 100vh;
background - color: #fff;
box - shadow: -5px 0 10px rgba(0, 0, 0, 0.3);
z - index: 1000;
transition: transform 0.3s ease - in - out;
transform: translateX(100%);
&.open {
transform: translateX(0);
}
}
.drawer - content {
padding: 20px;
}
</style>
(二)完善抽屉表单结构
<template>
<div v-if="isOpen" class="drawer">
<div class="drawer - content">
<button @click="closeDrawer">关闭</button>
<h2>新增文章</h2>
<form>
<label for="title">标题:</label>
<input type="text" id="title" v - model="article.title">
<label for="category">分类:</label>
<select id="category" v - model="article.category">
<option value="技术">技术</option>
<option value="生活">生活</option>
</select>
<label for="summary">摘要:</label>
<textarea id="summary" v - model="article.summary"></textarea>
</form>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const isOpen = ref(false);
const article = ref({
title: '',
category: '',
summary: ''
});
const closeDrawer = () => {
isOpen.value = false;
};
</script>
(三)上传文件
借助 input 的文件选择和 FormData 对象,将文件数据发送到后端服务器。
<template>
<div v-if="isOpen" class="drawer">
<div class="drawer - content">
<button @click="closeDrawer">关闭</button>
<h2>新增文章</h2>
<form>
<!-- 其他表单元素 -->
<label for="file">上传图片:</label>
<input type="file" id="file" @change="handleFileUpload">
</form>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import axios from 'axios';
const isOpen = ref(false);
const selectedFile = ref(null);
const handleFileUpload = (e) => {
selectedFile.value = e.target.files[0];
};
const uploadFile = async () => {
if (selectedFile.value) {
const formData = new FormData();
formData.append('file', selectedFile.value);
try {
const response = await axios.post('/api/upload', formData, {
headers: {
'Content - Type':'multipart/form - data'
}
});
console.log('文件上传成功', response.data);
} catch (error) {
console.error('文件上传失败', error);
}
}
};
</script>
(四)富文本编辑器--vue-quill
<template>
<div v-if="isOpen" class="drawer">
<div class="drawer - content">
<button @click="closeDrawer">关闭</button>
<h2>新增文章</h2>
<form>
<!-- 其他表单元素 -->
<vue - quill - editor v - model="article.content"></vue - quill - editor>
</form>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import VueQuillEditor from 'vue - quill - editor';
import 'quill/dist/quill.core.css';
import 'quill/dist/quill.snow.css';
import 'quill/dist/quill.bubble.css';
const isOpen = ref(false);
const article = ref({
title: '',
category: '',
summary: '',
content: ''
});
const closeDrawer = () => {
isOpen.value = false;
};
</script>
(五)添加文章完成
<template>
<div v-if="isOpen" class="drawer">
<div class="drawer - content">
<button @click="closeDrawer">关闭</button>
<h2>新增文章</h2>
<form @submit.prevent="addArticle">
<!-- 表单元素和富文本编辑器 -->
<button type="submit">提交</button>
</form>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import axios from 'axios';
const isOpen = ref(false);
const article = ref({
title: '',
category: '',
summary: '',
content: ''
});
const closeDrawer = () => {
isOpen.value = false;
};
const addArticle = async () => {
try {
const response = await axios.post('/api/articles', article.value);
console.log('文章添加成功', response.data);
closeDrawer();
} catch (error) {
console.error('文章添加失败', error);
}
};
</script>
九、文章编辑--编辑文章完成
实现文章编辑功能,从后端获取文章原有数据填充到编辑表单和富文本编辑器中,用户修改后提交更新请求,后端更新数据。
<template>
<div v-if="isOpen" class="drawer">
<div class="drawer - content">
<button @click="closeDrawer">关闭</button>
<h2>编辑文章</h2>
<form @submit.prevent="editArticle">
<!-- 表单元素和富文本编辑器,绑定文章数据 -->
<button type="submit">保存</button>
</form>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import axios from 'axios';
const isOpen = ref(false);
const articleId = ref(null);
const article = ref({
title: '',
category: '',
summary: '',
content: ''
});
const closeDrawer = () => {
isOpen.value = false;
};
const getArticleData = async (id) => {
articleId.value = id;
try {
const response = await axios.get(`/api/articles/${id}`);
article.value = response.data;
isOpen.value = true;
} catch (error) {
console.error('获取文章数据失败', error);
}
};
const editArticle = async () => {
try {
const response = await axios.put(`/api/articles/${articleId.value}`, article.value);
console.log('文章编辑成功', response.data);
closeDrawer();
} catch (error) {
console.error('文章编辑失败', error);
}
};
</script>