目录
[1. 文章列表渲染](#1. 文章列表渲染)
[1.1 基本架子搭建](#1.1 基本架子搭建)
[1.2 中英国际化处理](#1.2 中英国际化处理)
[1.3 文章分类选择](#1.3 文章分类选择)
[1.4 封装 API 接口,请求渲染文章列表](#1.4 封装 API 接口,请求渲染文章列表)
[2. 分页渲染 [element-plus 分页]](#2. 分页渲染 [element-plus 分页])
[3. 添加 loading 处理](#3. 添加 loading 处理)
[4. 搜索 和 重置功能](#4. 搜索 和 重置功能)
[5. 文章发布&修改 [element-plus - 抽屉]](#5. 文章发布&修改 [element-plus - 抽屉])
[5.1 点击显示抽屉](#5.1 点击显示抽屉)
[5.2 封装抽屉组件 ArticleEdit](#5.2 封装抽屉组件 ArticleEdit)
[5.3 完善抽屉表单结构](#5.3 完善抽屉表单结构)
[5.4 上传图片文件预览 [element-plus - 文件预览]](#5.4 上传图片文件预览 [element-plus - 文件预览])
[5.5 富文本编辑器 [ vue-quill ]](#5.5 富文本编辑器 [ vue-quill ])
[5.6 添加文章功能](#5.6 添加文章功能)
[5.7 添加完成后的内容重置](#5.7 添加完成后的内容重置)
[5.8 编辑文章回显](#5.8 编辑文章回显)
[5.9 编辑文章功能](#5.9 编辑文章功能)
[6. 文章删除](#6. 文章删除)
1. 文章列表渲染
1.1 基本架子搭建
① 搜索表单
html
<!-- 表单区域 inline 将 el-form-item 在一行显示 -->
<el-form inline>
<el-form-item label="文章分类:">
<!-- <el-select placeholder="请选择" size="large" style="width: 240px">
<el-option label="体育" value="1111" />
<el-option label="新闻" value="2222" />
</el-select> -->
<!-- vue2.0 v-model 是 :value 和 @input 的简写,
相当于传递了一个 value 属性给子组件,自定义的事件是 input 事件 -->
<!-- vue3.0 v-model 是 :modelValue 和 @update:modelValue 的简写 -->
<!-- 或者也可以写为:
<ChannelSelect :modelValue="params.cate_id"></ChannelSelect>
-->
<ChannelSelect v-model="params.cate_id"></ChannelSelect>
</el-form-item>
<el-form-item label="发布状态:">
<el-select
placeholder="请选择"
size="large"
style="width: 240px"
v-model="params.state"
>
<el-option label="已发布" value="已发布" />
<el-option label="草稿" value="草稿" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSearch">搜索</el-button>
<el-button @click="onReset">重置</el-button>
</el-form-item>
</el-form>
<!-- 表单区域 -->
② 文章列表表格准备,模拟假数据渲染
-
el-link 文字超链接
|-----------|-------|
| underline | 是否下划线 |
html
<!-- 文章列表 -->
<el-table
style="width: 100%"
stripe
:data="tableData"
v-loading="loading"
>
<el-table-column label="文章标题" prop="title" align="center">
<template #default="scope">
<el-link type="primary" :underline="false">
{{ scope.row.title }}
</el-link>
</template>
</el-table-column>
<el-table-column
label="分类"
prop="cate_name"
align="center"
></el-table-column>
<el-table-column label="发布时间" prop="pub_date" align="center">
<template #default="scope">
{{ formatTime(scope.row.pub_date) }}
</template>
</el-table-column>
<el-table-column
label="状态"
prop="state"
align="center"
></el-table-column>
<el-table-column label="操作" align="center">
<template #default="scope">
<div
style="
display: flex;
justify-content: center;
align-items: center;
"
>
<el-button
type="primary"
:icon="Edit"
circle
plain
@click="onEditArticle(scope.row)"
></el-button>
<el-button
type="danger"
plain
@click="onDeleteArticle(scope.row)"
:icon="Delete"
circle
></el-button>
</div>
</template>
</el-table-column>
<template #empty>
<el-empty description="没有数据"></el-empty>
</template>
</el-table>
- Empty 空状态
- description:empty 组件的描述信息
1.2 中英国际化处理
默认是英文的,由于这里不涉及切换, 所以在 App.vue 中直接导入设置成中文即可,
① 全局配置
② ConfigProvider
1.3 文章分类选择
为了便于维护,直接拆分成一个小组件 ChannelSelect.vue
① 新建 article/manage/components/ChannelSelect.vue
② 页面中导入渲染
③ 调用接口,动态渲染下拉分类,设计成 v-model 的使用方式
-
vue2.0 v-model 是 :value 和 @input 的简写,相当于传递了一个 value 属性给子组件,自定义的事件是 input 事件。
-
vue3.0 v-model 是 :modelValue 和 @update:modelValue 的简写。相当于传递了一个 modelValue 属性给子组件,自定义的事件是 update:modelValue 事件。
④ 父组件定义参数绑定
⑤ 发布状态,也绑定一下,便于将来提交表单
1.4 封装 API 接口,请求渲染文章列表
① api/article.js 封装接口
② 页面中调用保存数据
③ 新建 utils/format.js 封装格式化日期函数
④ 导入使用(vue 3.0 废弃了过滤器)
2. 分页渲染 [element-plus 分页]
① 分页组件
-
current-page / v-model:current-page:当前页数
-
page-size / v-model:page-size:每页显示条目个数
-
page-sizes:每页显示个数选择器的选项设置
-
background:是否为分页按钮添加背景色
-
layout:组件布局,子组件名用逗号分隔
-
total:总条目数
-
size-change:page-size 改变时触发
-
current-change:current-page 改变时触发
② 提供分页修改逻辑
- 每页显示数据条数,处理函数
- 当前页改变的时候触发,
3. 添加 loading 处理
① 准备数据
② el-table上面绑定
③ 发送请求时添加 loading
4. 搜索 和 重置功能
① 注册事件
② 绑定处理
5. 文章发布&修改 [element-plus - 抽屉]
5.1 点击显示抽屉
① 准备数据
② 准备抽屉容器
③ 点击修改布尔值显示抽屉
javascript
<el-button type="primary" @click="onAddArticle">发布文章</el-button>
const drawer = ref(false)
const onAddArticle = () => {
drawer.value = true
}
5.2 封装抽屉组件 ArticleEdit
添加 和 编辑,可以共用一个抽屉,所以可以将抽屉封装成一个组件
组件对外暴露一个方法 open, 基于 open 的参数,初始化表单数据,并判断区分是添加 还是 编辑
- open({ }) => 添加操作,添加表单初始化无数据
- open({ id: xx, ... }) => 编辑操作,编辑表单初始化需回显
具体实现:
① 封装组件 article/manage/components/ArticleEdit.vue,向外暴露 open 方法
② 父组件中通过 ref 绑定抽屉组件
③ 点击调用方法显示弹窗
5.3 完善抽屉表单结构
① 准备数据
② 准备 form 表单结构
html
<el-form :model="formModel">
<el-form-item label="文章标题:" prop="title">
<el-input
placeholder="请输入文章标题"
v-model="formModel.title"
></el-input>
</el-form-item>
<el-form-item label="文章分类:" prop="cate_id">
<ChannelSelect
v-model="formModel.cate_id"
:width="'100%'"
></ChannelSelect>
</el-form-item>
<el-form-item label="文章封面:" prop="cover_img">
<!--
此处需要关闭 element-plus 的自动上传,不需要配置 action 等参数
只需要做前端的本地预览图片即可,无需在提交之前上传图片
语法: Window.ULR.createObjectURL() 创建本地预览的地址,来预览图片
show-file-list 是否显示已上传文件列表
on-change 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
auto-upload 是否自动上传文件
-->
<el-upload
class="avatar-uploader"
:show-file-list="false"
:on-change="onUploadFile"
:auto-upload="false"
>
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item label="文章内容:" prop="content">
<div class="editor">
<!-- 富文本编辑器 -->
<QuillEditor
ref="editorRef"
theme="snow"
v-model:content="formModel.content"
content-type="html"
/>
</div>
</el-form-item>
<el-form-item prop="state">
<el-button type="primary" @click="onPublicArticle('已发布')">
发布
</el-button>
<el-button type="info" @click="onPublicArticle('草稿')">草稿</el-button>
</el-form-item>
</el-form>
③ 一打开默认重置添加的 form 表单数据
④ 扩展 下拉菜单 width props
5.4 上传图片文件预览 [element-plus - 文件预览]
① 关闭自动上传,准备结构
-
此处需要关闭 element-plus 的自动上传,不需要配置 action 等参数
-
只需要做前端的本地预览图片即可,无需在提交之前上传图片
-
语法: Window.ULR.createObjectURL() 创建本地预览的地址,来预览图片
-
show-file-list 是否显示已上传文件列表
-
on-change 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
-
auto-upload 是否自动上传文件
② 准备数据 和 选择图片的处理逻辑
-
为选中的文件创建一个临时的、本地的 URL,以便在页面上可以直接预览或访问这个文件,而不需要立即上传到服务器
-
window.URL.createObjectURL(file):使用浏览器的 URL.createObjectURL() 方法,为传入的 file 对象(即用户选择的文件)生成一个本地的临时 URL。
-
这个 URL 是一个字符串,指向本地的文件资源。blob:http://localhost:5173/d2d71faf-2c60-4fd9-92cb-ea0eeae052cf
-
blob: URL 可以直接用作 HTML 标签(如 <img>、<video>、<audio> 等)的 src 属性,无需进一步处理。
-
-
立即将图片对象,存入 formModel.value.cover_img 中,将来点击发布按钮的时候,提交给后台的服务器
③ 样式美化
5.5 富文本编辑器 [ vue-quill ]
官网地址:VueQuill | Rich Text Editor Component for Vue 3
① 安装包
- pnpm add @vueup/vue-quill@latest
② 注册成局部组件
③ 页面中使用绑定
④ 样式美化
5.6 添加文章功能
① 封装添加接口
② 注册点击事件调用
- 提交给后台的 data 是一个 FormDate 对象
③ 父组件监听事件,重新渲染
5.7 添加完成后的内容重置
5.8 编辑文章回显
如果是编辑操作,一打开抽屉,就需要发送请求,获取数据进行回显
① 封装接口,根据 id 获取详情数据
② 页面中调用渲染
-
显示图片 "cover_img": "/uploads/885eb38f6270eb10b42aedadd84fea23.jpg",需要与基地址拼接
-
提交给后台的图片数据格式是 file 对象格式
-
所以需要将网络地址 "cover_img": "/uploads/885eb38f6270eb10b42aedadd84fea23.jpg",又转化成 file 对象,存储起来 (正常情况下, 后台应该两个都支持)
③ chatGPT prompt:封装一个函数,基于 axios, 网络图片地址,转 file 对象, 请注意:写中文注释
javascript
// 将网络图片地址转换为File对象
async function imageUrlToFile(url, fileName) {
try {
// 第一步:使用axios获取网络图片数据
const response = await axios.get(url, { responseType: 'arraybuffer' });
const imageData = response.data;
// 第二步:将图片数据转换为Blob对象
const blob = new Blob([imageData], { type: response.headers['content-type'] });
// 第三步:创建一个新的File对象
const file = new File([blob], fileName, { type: blob.type });
return file;
} catch (error) {
console.error('将图片转换为File对象时发生错误:', error);
throw error;
}
}
5.9 编辑文章功能
① 封装编辑接口
② 提交时调用
6. 文章删除
① 封装删除接口
② 页面中添加确认框调用