Vue笔记(九)

news/2025/2/19 14:43:24

一、文章分类架子--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>


http://www.niftyadmin.cn/n/5852404.html

相关文章

小爱音箱控制手机和电视听歌的尝试

最近买了小爱音箱pro&#xff0c;老婆让我扔了&#xff0c;吃灰多年的旧音箱。当然舍不得&#xff0c;比小爱还贵&#xff0c;刚好还有一台红米手机&#xff0c;能插音箱&#xff0c;为了让音箱更加灵活&#xff0c;买了个2元的蓝牙接收模块Type-c供电3.5接口。这就是本次尝试起…

2025-2-14算法打卡

一&#xff0c;右旋字符串 1.题目描述&#xff1a; 字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k&#xff0c;请编写一个函数&#xff0c;将字符串中的后面 k 个字符移到字符串的前面&#xff0c;实现字符串的右旋转操…

repo学习使用

Repo 是以 Git 为基础构建的代码库管理工具。Repo 可以在必要时整合多个 Git 代码库&#xff0c;将相关内容上传到版本控制系统。借助单个 Repo 命令&#xff0c;可以将文件从多个代码库下载到本地工作目录。 Repo 命令是一段可执行的 Python 脚本&#xff0c;你可以将其放在路…

【第3章:卷积神经网络(CNN)——3.8 迁移学习与微调策略】

迁移学习示意图 一、灵魂拷问:为什么你的CNN总在重复造轮子? 当你试图用500张狗狗照片训练一个世界级分类器时,是不是觉得就像让小学生直接攻读量子物理一样力不从心?这时,迁移学习的魔法就显现了。想象一下,如果能把ImageNet冠军模型变成你的专属AI助手,哪怕你的训练数…

Linux | 进程相关概念(进程、进程状态、进程优先级、环境变量、进程地址空间)

文章目录 进程概念1、冯诺依曼体系结构2、进程2.1基本概念2.2描述进程-PCB2.3组织进程2.4查看进程2.5通过系统调用获取进程标识符2.6通过系统调用创建进程-fork初识fork の 头文件与返回值fork函数的调用逻辑和底层逻辑 3、进程状态3.1状态3.2进程状态查看命令3.2.1 ps命令3.2.…

Vue2/Vue3分别如何使用Watch

在 Vue 2 和 Vue 3 中&#xff0c;watch 用于监听数据的变化并执行相应的逻辑。虽然两者的核心功能相同&#xff0c;但在语法和使用方式上有一些区别。以下是 Vue 2 和 Vue 3 中使用 watch 的详细说明&#xff1a; Vue 2 中的 watch 在 Vue 2 中&#xff0c;watch 是通过选项式…

【AI-34】机器学习常用七大算法

以下是对这七大常用算法的浅显易懂解释&#xff1a; 1. k 邻近算法&#xff08;k - Nearest Neighbors&#xff0c;KNN&#xff09; 想象你在一个满是水果的大广场上&#xff0c;现在有个不认识的水果&#xff0c;想知道它是什么。k 邻近算法就是去看离这个水果最近的 k 个已…

Kafka分区管理大师指南:扩容、均衡、迁移与限流全解析

#作者&#xff1a;孙德新 文章目录 分区分配操作(kafka-reassign-partitions.sh)1.1 分区扩容、数据均衡、迁移(kafka-reassign-partitions.sh)1.2、修改topic分区partition的副本数&#xff08;扩缩容副本&#xff09;1.3、Partition Reassign场景限流1.4、节点内副本移动到不…