一、实战篇(前端)
JavaScript-导入导出
JS提供的导入导出机制,可以实现按需导入。
或者
导入和导出的时间, 可以使用 as 重命名:如complexMessage as cm
默认导出
1、Vue
Vue 是一款用于构建用户界面的渐进式的JavaScript框架。 (官方:https://cn.vuejs.org/)
学习路径
1、快速入门
https://cn.vuejs.org
–准备
1、准备html页面,并引入Vue模块(官方提供)
2、创建Vue程序的应用实例
3、准备元素(div),被Vue控制
–构建用户界面
1、准备数据
2、通过插值表达式渲染页面
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Document</title>
- </head>
- <body>
- <div id="app"><!-- 准备元素(div),被Vue控制 -->
- <h1>{{msg}}</h1><!-- 通过插值表达式渲染页面 -->
- </div>
- <div >
- <h1>{{msg}}</h1>
- </div>
- <!-- 引入vue模块 -->
- <script type="module">
- import {createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';//引入Vue模块
- /* 创建vue的应用实例 */
- createApp({//创建Vue程序的应用实例
- data(){//准备数据
- return {
- //定义数据
- msg: 'hello vue3'
- }
- }
- }).mount("#app");
- </script>
- </body>
- </html>
复制代码 2、常用指令
指令:HTML标签上带有 v-前缀的特殊属性,不同的指令具有不同的寄义,可以实现不同的功能。
常用指令:
v-for

作用:列表渲染,遍历容器的元素或者对象的属性
语法: v-for = “(item,index) in items”
–参数阐明:
items 为遍历的数组
item 为遍历出来的元素
index 为索引/下标,从0开始 ;可以省略,省略index语法: v-for = “item in items”
注意:遍历的数组,必须在data中定义; 要想让哪个标签循环展示多次,就在哪个标签上使用 v-for 指令。
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Document</title>
- </head>
- <body>
- <div id="app">
- <table border="1 solid" colspa="0" cellspacing="0">
- <tr>
- <th>文章标题</th>
- <th>分类</th>
- <th>发表时间</th>
- <th>状态</th>
- <th>操作</th>
- </tr>
- <!-- 哪个元素要出现多次,v-for指令就添加到哪个元素上 -->
- <tr v-for="(article,index) in articleList">
- <td>{{article.title}}</td>
- <td>{{article.category}}</td>
- <td>{{article.time}}</td>
- <td>{{article.state}}</td>
- <td>
- <button>编辑</button>
- <button>删除</button>
- </td>
- </tr>
- <!-- <tr>
- <td>标题2</td>
- <td>分类2</td>
- <td>2000-01-01</td>
- <td>已发布</td>
- <td>
- <button>编辑</button>
- <button>删除</button>
- </td>
- </tr>
- <tr>
- <td>标题3</td>
- <td>分类3</td>
- <td>2000-01-01</td>
- <td>已发布</td>
- <td>
- <button>编辑</button>
- <button>删除</button>
- </td>
- </tr> -->
- </table>
- </div>
- <script type="module">
- //导入vue模块
- import { createApp} from
- 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
- //创建应用实例
- createApp({
- data() {
- return {
- //定义数据
- articleList:[{
- title:"医疗反腐绝非砍医护收入",
- category:"时事",
- time:"2023-09-5",
- state:"已发布"
- },
- {
- title:"中国男篮缘何一败涂地?",
- category:"篮球",
- time:"2023-09-5",
- state:"草稿"
- },
- {
- title:"华山景区已受大风影响阵风达7-8级,未来24小时将持续",
- category:"旅游",
- time:"2023-09-5",
- state:"已发布"
- }]
- }
- }
- }).mount("#app")//控制页面元素
-
- </script>
- </body>
- </html>
复制代码 v-bind
作用:动态为HTML标签绑定属性值,如设置href,src,style样式等。
语法:v-bind:属性名=“属性值”
简化::属性名=“属性值”
注意:v-bind所绑定的数据,必须在data中定义 。
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Document</title>
- </head>
- <body>
- <div id="app">
- <!-- <a v-bind:href="url">黑马官网</a> -->
- <a :href="url">黑马官网</a>
- </div>
- <script type="module">
- //引入vue模块
- import { createApp} from
- 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
- //创建vue应用实例
- createApp({
- data() {
- return {
- url: 'https://www.itheima.com'
- }
- }
- }).mount("#app")//控制html元素
- </script>
- </body>
- </html>
复制代码 v-if & v-show
作用:这两类指令,都是用来控制元素的显示与隐藏的
v-if
语法:v-if=“表达式”,表达式值为 true,显示;false,隐藏
别的:可以共同 v-else-if / v-else 举行链式调用条件判断
原理:基于条件判断,来控制创建或移除元素节点(条件渲染)
场景:要么显示,要么不显示,不频繁切换的场景
v-show
语法:v-show=“表达式”,表达式值为 true,显示;false,隐藏
原理:基于CSS样式display来控制显示与隐藏
场景:频繁切换显示隐藏的场景
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Document</title>
- </head>
- <body>
- <div id="app">
- 手链价格为: <span v-if="customer.level>=0 && customer.level<=1">9.9</span>
- <span v-else-if="customer.level>=2 && customer.level<=4">19.9</span>
- <span v-else>29.9</span>
- <br/>
- 手链价格为: <span v-show="customer.level>=0 && customer.level<=1">9.9</span>
- <span v-show="customer.level>=2 && customer.level<=4">19.9</span>
- <span v-show="customer.level>=5">29.9</span>
- </div>
- <script type="module">
- //导入vue模块
- import { createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
- //创建vue应用实例
- createApp({
- data() {
- return {
- customer:{
- name:'张三',
- level:2
- }
- }
- }
- }).mount("#app")//控制html元素
- </script>
- </body>
- </html>
复制代码 v-on
作用:为html标签绑定事件
语法:
v-on:事件名=“函数名”
简写为 @事件名=“函数名”
createApp({ data(){需要用到的数据}, methods:{需要用到的方法} })
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Document</title>
- </head>
- <body>
- <div id="app">
- <button v-on:click="money">点我有惊喜</button>
- <button @click="love">再点更惊喜</button>
- </div>
- <script type="module">
- //导入vue模块
- import { createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
- //创建vue应用实例
- createApp({
- data() {
- return {
- //定义数据
- }
- },
- methods:{
- money: function(){
- alert('送你钱100')
- },
- love: function(){
- alert('爱你一万年')
- }
- }
- }).mount("#app");//控制html元素
- </script>
- </body>
- </html>
复制代码 v-model
作用:在表单位素上使用,双向数据绑定。可以方便的 获取 或 设置 表单项数据
语法:v-model=“变量名”


注意:v-model 中绑定的变量,必须在data中定义。
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Document</title>
- </head>
- <body>
- <div id="app">
- 文章分类: <input type="text" v-model="searchConditions.category"/> <span>{{searchConditions.category}}</span>
- 发布状态: <input type="text" v-model="searchConditions.state"/> <span>{{searchConditions.state}}</span>
- <button>搜索</button>
- <button v-on:click="clear">重置</button>
- <br />
- <br />
- <table border="1 solid" colspa="0" cellspacing="0">
- <tr>
- <th>文章标题</th>
- <th>分类</th>
- <th>发表时间</th>
- <th>状态</th>
- <th>操作</th>
- </tr>
- <tr v-for="(article,index) in articleList">
- <td>{{article.title}}</td>
- <td>{{article.category}}</td>
- <td>{{article.time}}</td>
- <td>{{article.state}}</td>
- <td>
- <button>编辑</button>
- <button>删除</button>
- </td>
- </tr>
- </table>
- </div>
- <script type="module">
- //导入vue模块
- import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
- //创建vue应用实例
- createApp({
- data() {
- return {
- //定义数据
- searchConditions:{
- category:'',
- state:''
- },
- articleList: [{
- title: "医疗反腐绝非砍医护收入",
- category: "时事",
- time: "2023-09-5",
- state: "已发布"
- },
- {
- title: "中国男篮缘何一败涂地?",
- category: "篮球",
- time: "2023-09-5",
- state: "草稿"
- },
- {
- title: "华山景区已受大风影响阵风达7-8级,未来24小时将持续",
- category: "旅游",
- time: "2023-09-5",
- state: "已发布"
- }]
- }
- }
- ,
- methods:{
- clear:function(){
- //清空category以及state的数据
- //在methods对应的方法里面,使用this就代表的是vue实例,可以使用this获取到vue实例中准备的数据
- this.searchConditions.category='';
- this.searchConditions.state='';
- }
- }
- ,
- mounted:function(){
- console.log('Vue挂载完毕,发送请求获取数据')
- }
- }).mount("#app")//控制html元素
- </script>
- </body>
- </html>
复制代码 3、生命周期
生命周期:指一个对象从创建到销毁的整个过程。
生命周期的八个阶段:每个阶段会自动实行一个生命周期方法(钩子), 让开辟者有机会在特定的阶段实行自己的代码
Axios
介绍:Axios 对原生的Ajax举行了封装,简化誊写,快速开辟。
官网:https://www.axios-http.cn/
Axios使用步骤
引入Axios的js文件(参照官网)
使用Axios发送哀求,并获取相应效果
Axios-哀求方式别名
为了方便起见,Axios已经为所有支持的哀求方法提供了别名
格式:axios.哀求方式(url [, data [, config]])

- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Document</title>
- </head>
- <body>
- <!-- 引入axios的js文件 -->
- <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
- <script>
- /* 发送请求 */
- /* axios({
- method:'get',
- url:'http://localhost:8080/article/getAll'
- }).then(result=>{
- //成功的回调
- //result代表服务器响应的所有的数据,包含了响应头,响应体. result.data 代表的是接口响应的核心数据
- console.log(result.data);
- }).catch(err=>{
- //失败的回调
- console.log(err);
- }); */
- let article = {
- title: '明天会更好',
- category: '生活',
- time: '2000-01-01',
- state: '草稿'
- }
- /* axios({
- method:'post',
- url:'http://localhost:8080/article/add',
- data:article
- }).then(result=>{
- //成功的回调
- //result代表服务器响应的所有的数据,包含了响应头,响应体. result.data 代表的是接口响应的核心数据
- console.log(result.data);
- }).catch(err=>{
- //失败的回调
- console.log(err);
- }); */
- //别名的方式发送请求
- /* axios.get('http://localhost:8080/article/getAll').then(result => {
- //成功的回调
- //result代表服务器响应的所有的数据,包含了响应头,响应体. result.data 代表的是接口响应的核心数据
- console.log(result.data);
- }).catch(err => {
- //失败的回调
- console.log(err);
- }); */
- axios.post('http://localhost:8080/article/add', article).then(result => {
- //成功的回调
- //result代表服务器响应的所有的数据,包含了响应头,响应体. result.data 代表的是接口响应的核心数据
- console.log(result.data);
- }).catch(err => {
- //失败的回调
- console.log(err);
- });
- </script>
- </body>
- </html>
复制代码 案例
使用表格展示所有文章的数据, 并完成条件搜刮功能
钩子函数mounted中, 获取所有的文章数据
使用v-for指令,把数据渲染到表格上展示
使用v-model指令完成表单数据的双向绑定
使用v-on指令为搜刮按钮绑定单击事件
2、整站使用Vue(工程化)
1、情况准备
选择安装到一个,没有中文,没有空格的目录下(新建一个文件夹NodeJS)
NodeJS 安装完毕后,会自动配置好情况变量,我们验证一下是否安装成功,通过: node -v
使用管理员身份运行命令行,在命令行中,实行如下指令:
- npm config set prefix "D:\develop\NodeJS"
复制代码 注意:D:\develop\NodeJS 这个目录是NodeJS的安装目录
5.更换安装包的源
设置
npm config set registry http://registry.npm.taobao.org/
查抄
npm config get registry
2、Vue项目创建和启动
创建一个工程化的Vue项目,实行命令:npm init vue@latest
进入项目目录,实行命令安装当前项目标依赖:npm install
Vue项目-目录布局
Vue项目-启动
实行命令:npm run dev ,就可以启动vue项目了。
或者
访问项目:打开浏览器,在浏览器地址栏访问 http://127.0.0.1:5173 就可以访问到vue项目。
3、Vue项目开辟流程
Vue项目-目录布局
.vue是Vue项目中的组件文件,在Vue项目中也称为单文件组件(SFC,Single-File Components)。Vue 的单文件组件会将一个组件的逻辑 (JS),模板 (HTML) 和样式 (CSS) 封装在同一个文件里(.vue) 。
- <!-- <script>
- //写数据
- export default{
- data(){
- return {
- msg:'上海'
- }
- }
- }
- </script> -->
- <script setup>
- import {ref} from 'vue';
- //调用ref函数,定义响应式数据
- const msg = ref('西安');
- //导入 Api.vue文件
- import ApiVue from './Api.vue'
- //导入Article.vue文件
- import ArticleVue from './Article.vue'
- </script>
- <template>
- <!-- html -->
- <!-- <h1>北京</h1> -->
- <!-- <h1>{{ msg }}</h1>
- <br>
- <ApiVue/> -->
- <ArticleVue/>
- </template>
- <style scoped>
- /* 样式 */
- h1{
- color: red;
- }
- </style>
复制代码 4、API风格
Vue的组件有两种不同的风格:组合式API 和 选项式API
setup:是一个标识,告诉Vue需要举行一些处理,让我们可以更简便的使用组合式API。
ref():接收一个内部值,返回一个相应式的ref对象,此对象只有一个指向内部值的属性 value。
onMounted():在组合式API中的钩子方法,注册一个回调函数,在组件挂载完成后实行。
- <script setup>
- import {ref,onMounted} from 'vue'
- //声明响应式数据 ref 响应式对象有一个内部的属性value
- const count = ref(0); //在组合式api中,一般需要把数据定义为响应式数据
- //const count=0;
- //声明函数
- function increment(){
- count.value++;
- }
- //声明钩子函数 onMounted
- onMounted(()=>{
- console.log('vue 已经挂载完毕了...');
- });
- </script>
- <template>
- <!-- 写html元素 -->
- <button @click="increment">count: {{ count }}</button>
- </template>
复制代码
选项式API,可以用包罗多个选项的对象来形貌组件的逻辑,如:data,methods,mounted等。
5、案例
使用表格展示所有文章的数据, 并完成条件搜刮功能

钩子函数mounted中, 获取所有的文章数据
使用v-for指令,把数据渲染到表格上展示
使用v-model指令完成表单数据的双向绑定
使用v-on指令为搜刮按钮绑定单击事件



在哀求或相应被 then 或 catch 处理前拦截它们

Artcle.vue
- <script setup>
- import {articleGetAllService,articleSearchService} from '@/api/article.js';
- import {ref} from 'vue';
-
- //定义响应式数据 ref
- const articleList = ref([]);
- //获取所有文章数据
- //同步获取articleGetAllService的返回结果 async await
- const getAllArticle=async function(){
- let data = await articleGetAllService();
- articleList.value=data;
- }
- getAllArticle();
-
-
- //定义响应式数据 searchConditions
- const searchConditions = ref({
- category:'',
- state:''
- })
- //声明search函数
- const search = async function(){
- //文章搜索
- let data = await articleSearchService({...searchConditions.value});
- articleList.value = data;
- }
- </script>
- <template>
- <!-- html元素 -->
- <div>
- 文章分类: <input type="text" v-model="searchConditions.category">
- 发布状态: <input type="text" v-model="searchConditions.state">
- <button v-on:click="search">搜索</button>
- <br />
- <br />
- <table border="1 solid" colspa="0" cellspacing="0">
- <tr>
- <th>文章标题</th>
- <th>分类</th>
- <th>发表时间</th>
- <th>状态</th>
- <th>操作</th>
- </tr>
- <tr v-for="(article,index) in articleList">
- <td>{{article.title}}</td>
- <td>{{article.category}}</td>
- <td>{{article.time}}</td>
- <td>{{article.state}}</td>
- <td>
- <button>编辑</button>
- <button>删除</button>
- </td>
- </tr>
- </table>
- </div>
- </template>
复制代码 artcle.js
- /* //导入axios npm install axios
- import axios from 'axios';
- //定义一个变量,记录公共的前缀 , baseURL
- const baseURL = 'http://localhost:8080';
- const instance = axios.create({baseURL}) */
- import request from '@/util/request.js'
- export function articleGetAllService() {
- return request.get('/article/getAll');
- }
- export function articleSearchService(conditions) {
- return request.get('/article/search', { params: conditions });
- }
复制代码 request.js
- //定制请求的实例
- //导入axios npm install axios
- import axios from 'axios';
- //定义一个变量,记录公共的前缀 , baseURL
- const baseURL = 'http://localhost:8080';
- const instance = axios.create({baseURL})
- //添加响应拦截器
- instance.interceptors.response.use(
- result=>{
- return result.data;
- },
- err=>{
- alert('服务异常');
- return Promise.reject(err);//异步的状态转化成失败的状态
- }
- )
- export default instance;
复制代码 3、Element Plus
Element:是饿了么团队研发的,基于 Vue 3,面向设计师和开辟者的组件库。
组件:组成网页的部件,例如 超链接、按钮、图片、表格、表单、分页条等等。
官网:https://element-plus.org/zh-CN/#/zh-CN
1、快速入门
准备工作:
创建一个工程化的vue项目
参照官方文档,安装Element Plus组件库(在当前工程的目录下):npm install element-plus --save
main.js中引入Element Plus组件库(参照官方文档)
- import { createApp } from 'vue'//导入vue
- import ElementPlus from 'element-plus'//导入element-plus
- import 'element-plus/dist/index.css'//导入element-plus的样式
- import App from './App.vue'//导入app.vue
- import locale from 'element-plus/dist/locale/zh-cn.js'
- const app = createApp(App)//创建应用实例
- app.use(ElementPlus,{locale})//使用element-plus
- app.mount('#app')//控制html元素
复制代码 制作组件:
访问Element官方文档,复制组件代码,调解
Button.vue
- <script lang="ts" setup>
- import {
- Check,
- Delete,
- Edit,
- Message,
- Search,
- Star,
- } from '@element-plus/icons-vue'
- </script>
- <template>
- <el-row class="mb-4">
- <el-button>Default</el-button>
- <el-button type="primary" disabled="true">编辑</el-button>
- <el-button type="success" loading="true">查看</el-button>
-
- </el-row>
- <el-row class="mb-4">
-
- <el-button type="info" plain>Info</el-button>
- <el-button type="warning" plain>Warning</el-button>
- <el-button type="danger" plain>Danger</el-button>
- </el-row>
- </template>
复制代码 App.vue
- <script setup>
- import ButtonVue from './Button.vue'
- import ArticleVue from './Article.vue'
- </script>
- <template>
- <!-- <ButtonVue/> -->
- <ArticleVue/>
- </template>
复制代码 2、常用组件

Article.vue
- <script lang="ts" setup>
- import { reactive } from 'vue'
- const formInline = reactive({
- user: '',
- region: '',
- date: '',
- })
- const onSubmit = () => {
- console.log('submit!')
- }
- import { ref } from 'vue'
- const currentPage4 = ref(2)
- const pageSize4 = ref(5)
- const small = ref(false)
- const background = ref(false)
- const disabled = ref(false)
- const total = ref(20)
- const handleSizeChange = (val: number) => {
- console.log(`${val} items per page`)
- }
- const handleCurrentChange = (val: number) => {
- console.log(`current page: ${val}`)
- }
- import {
- Delete,
- Edit,
- } from '@element-plus/icons-vue'
- const tableData = [
- {
- title: '标题1',
- category: '时事',
- time: '2000-01-01',
- state: '已发布',
- },
- {
- title: '标题1',
- category: '时事',
- time: '2000-01-01',
- state: '已发布',
- },
- {
- title: '标题1',
- category: '时事',
- time: '2000-01-01',
- state: '已发布',
- },
- {
- title: '标题1',
- category: '时事',
- time: '2000-01-01',
- state: '已发布',
- },
- {
- title: '标题1',
- category: '时事',
- time: '2000-01-01',
- state: '已发布',
- }
- ]
- </script>
- <template>
- <el-card class="box-card">
- <div class="card-header">
- <span>文章管理</span>
- <el-button type="primary">发布文章</el-button>
- </div>
- <div style="margin-top: 20px;">
- <hr>
- </div>
- <el-form :inline="true" :model="formInline" class="demo-form-inline">
- <el-form-item label="文章分类:">
- <el-select v-model="formInline.region" placeholder="请选择" clearable>
- <el-option label="时事" value="时事" />
- <el-option label="篮球" value="篮球" />
- </el-select>
- </el-form-item>
- <el-form-item label="发布状态:">
- <el-select v-model="formInline.region" placeholder="请选择" clearable>
- <el-option label="已发布" value="已发布" />
- <el-option label="草稿" value="草稿" />
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="onSubmit">搜索</el-button>
- </el-form-item>
- <el-form-item>
- <el-button type="default" @click="onSubmit">重置</el-button>
- </el-form-item>
- </el-form>
- <el-table :data="tableData" style="width: 100%">
- <el-table-column prop="title" label="文章标题" />
- <el-table-column prop="category" label="分类" />
- <el-table-column prop="time" label="发表时间" />
- <el-table-column prop="state" label="状态" />
- <el-table-column label="操作" width="180">
- <el-row>
- <el-button type="primary" :icon="Edit" circle />
- <el-button type="danger" :icon="Delete" circle />
- </el-row>
- </el-table-column>
- </el-table>
- <el-pagination class="el-p" v-model:current-page="currentPage4" v-model:page-size="pageSize4"
- :page-sizes="[5, 10, 15, 20]" :small="small" :disabled="disabled" :background="background"
- layout="jumper,total, sizes, prev, pager, next" :total="total" @size-change="handleSizeChange"
- @current-change="handleCurrentChange" />
- </el-card>
- </template>
- <style scoped>
- .el-p {
- margin-top: 20px;
- display: flex;
- justify-content: flex-end;
- }
- .card-header {
- display: flex;
- justify-content: space-between;
- }
- </style>
复制代码 4、大事件
需求
1. 情况准备
1.创建Vue工程
npm init vue@latest
2. 安装依赖
Element-Plus
npm install element-plus --save
Axios
npm install axios
Sass
npm install sass -D
3. 目录调解
4. 删除components下面自动生成的内容
新建目录api、utils、views
将资料中的静态资源拷贝到assets目录下
删除App.uve中自动生成的内容
main.js
- import './assets/main.scss'
- import { createApp } from 'vue'
- import ElementPlus from 'element-plus'
- import 'element-plus/dist/index.css'
- import router from '@/router'
- import App from './App.vue'
- import {createPinia} from 'pinia'
- import { createPersistedState } from 'pinia-persistedstate-plugin'
- import locale from 'element-plus/dist/locale/zh-cn.js'
- const app = createApp(App);
- const pinia = createPinia();
- const persist = createPersistedState();
- pinia.use(persist)
- app.use(pinia)
- app.use(router)
- app.use(ElementPlus,{locale});
- app.mount('#app')
复制代码 2. 功能开辟
注册登录

搭建页面
数据绑定:参考接口文档给属性起名
表单校验:
el-form标签上通过rules属性,绑定校验规则
el-form-item标签上通过prop属性,指定校验项
Login.vue
- <script setup>
- import { User, Lock } from '@element-plus/icons-vue'
- import { ref } from 'vue'
- import { ElMessage } from 'element-plus'
- //控制注册与登录表单的显示, 默认显示注册
- const isRegister = ref(false)
- //定义数据模型
- const registerData = ref({
- username: '',
- password: '',
- rePassword: ''
- })
- //校验密码的函数
- const checkRePassword = (rule, value, callback) => {
- if (value === '') {
- callback(new Error('请再次确认密码'))
- } else if (value !== registerData.value.password) {
- callback(new Error('请确保两次输入的密码一样'))
- } else {
- callback()
- }
- }
- //定义表单校验规则
- const rules = {
- username: [
- { required: true, message: '请输入用户名', trigger: 'blur' },
- { min: 5, max: 16, message: '长度为5~16位非空字符', trigger: 'blur' }
- ],
- password: [
- { required: true, message: '请输入密码', trigger: 'blur' },
- { min: 5, max: 16, message: '长度为5~16位非空字符', trigger: 'blur' }
- ],
- rePassword: [
- { validator: checkRePassword, trigger: 'blur' }
- ]
- }
- //调用后台接口,完成注册
- import { userRegisterService, userLoginService} from '@/api/user.js'
- const register = async () => {
- //registerData是一个响应式对象,如果要获取值,需要.value
- let result = await userRegisterService(registerData.value);
- /* if (result.code === 0) {
- //成功了
- alert(result.msg ? result.msg : '注册成功');
- }else{
- //失败了
- alert('注册失败')
- } */
- //alert(result.msg ? result.msg : '注册成功');
- ElMessage.success(result.msg ? result.msg : '注册成功')
- }
- //绑定数据,复用注册表单的数据模型
- //表单数据校验
- //登录函数
- import {useTokenStore} from '@/stores/token.js'
- import {useRouter} from 'vue-router'
- const router = useRouter()
- const tokenStore = useTokenStore();
- const login =async ()=>{
- //调用接口,完成登录
- let result = await userLoginService(registerData.value);
- /* if(result.code===0){
- alert(result.msg? result.msg : '登录成功')
- }else{
- alert('登录失败')
- } */
- //alert(result.msg? result.msg : '登录成功')
- ElMessage.success(result.msg ? result.msg : '登录成功')
- //把得到的token存储到pinia中
- tokenStore.setToken(result.data)
- //跳转到首页 路由完成跳转
- router.push('/')
- }
- //定义函数,清空数据模型的数据
- const clearRegisterData = ()=>{
- registerData.value={
- username:'',
- password:'',
- rePassword:''
- }
- }
- </script>
- <template>
- <el-row class="login-page">
- <el-col :span="12" class="bg"></el-col>
- <el-col :span="6" :offset="3" class="form">
- <!-- 注册表单 -->
- <el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData" :rules="rules">
- <el-form-item>
- <h1>注册</h1>
- </el-form-item>
- <el-form-item prop="username">
- <el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>
- </el-form-item>
- <el-form-item prop="password">
- <el-input :prefix-icon="Lock" type="password" placeholder="请输入密码"
- v-model="registerData.password"></el-input>
- </el-form-item>
- <el-form-item prop="rePassword">
- <el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码"
- v-model="registerData.rePassword"></el-input>
- </el-form-item>
- <!-- 注册按钮 -->
- <el-form-item>
- <el-button class="button" type="primary" auto-insert-space @click="register">
- 注册
- </el-button>
- </el-form-item>
- <el-form-item class="flex">
- <el-link type="info" :underline="false" @click="isRegister = false;clearRegisterData()">
- ← 返回
- </el-link>
- </el-form-item>
- </el-form>
- <!-- 登录表单 -->
- <el-form ref="form" size="large" autocomplete="off" v-else :model="registerData" :rules="rules">
- <el-form-item>
- <h1>登录</h1>
- </el-form-item>
- <el-form-item prop="username">
- <el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>
- </el-form-item>
- <el-form-item prop="password">
- <el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input>
- </el-form-item>
- <el-form-item class="flex">
- <div class="flex">
- <el-checkbox>记住我</el-checkbox>
- <el-link type="primary" :underline="false">忘记密码?</el-link>
- </div>
- </el-form-item>
- <!-- 登录按钮 -->
- <el-form-item>
- <el-button class="button" type="primary" auto-insert-space @click="login">登录</el-button>
- </el-form-item>
- <el-form-item class="flex">
- <el-link type="info" :underline="false" @click="isRegister = true;clearRegisterData()">
- 注册 →
- </el-link>
- </el-form-item>
- </el-form>
- </el-col>
- </el-row>
- </template>
- <style lang="scss" scoped>
- /* 样式 */
- .login-page {
- height: 100vh;
- background-color: #fff;
- .bg {
- background: url('@/assets/logo2.png') no-repeat 60% center / 240px auto,
- url('@/assets/login_bg.jpg') no-repeat center / cover;
- border-radius: 0 20px 20px 0;
- }
- .form {
- display: flex;
- flex-direction: column;
- justify-content: center;
- user-select: none;
- .title {
- margin: 0 auto;
- }
- .button {
- width: 100%;
- }
- .flex {
- width: 100%;
- display: flex;
- justify-content: space-between;
- }
- }
- }
- </style>
复制代码 api/user.js
- //导入request.js请求工具
- import request from '@/utils/request.js'
- //提供调用注册接口的函数
- export const userRegisterService = (registerData)=>{
- //借助于UrlSearchParams完成传递
- const params = new URLSearchParams()
- for(let key in registerData){
- params.append(key,registerData[key]);
- }
- return request.post('/user/register',params);
- }
- //提供调用登录接口的函数
- export const userLoginService = (loginData)=>{
- const params = new URLSearchParams();
- for(let key in loginData){
- params.append(key,loginData[key])
- }
- return request.post('/user/login',params)
- }
- //获取用户详细信息
- export const userInfoService = ()=>{
- return request.get('/user/userInfo')
- }
- //修改个人信息
- export const userInfoUpdateService = (userInfoData)=>{
- return request.put('/user/update',userInfoData)
- }
- //修改头像
- export const userAvatarUpdateService = (avatarUrl)=>{
- const params = new URLSearchParams();
- params.append('avatarUrl',avatarUrl)
- return request.patch('/user/updateAvatar',params)
- }
复制代码 跨域
由于浏览器的同源计谋限制,向不同源(不同协议、不同域名、不同端口)发送ajax哀求会失败
优化axios相应拦截器



request.js
- //定制请求的实例
- //导入axios npm install axios
- import axios from 'axios';
- import { ElMessage } from 'element-plus'
- //定义一个变量,记录公共的前缀 , baseURL
- //const baseURL = 'http://localhost:8080';
- const baseURL = '/api';
- const instance = axios.create({ baseURL })
- import {useTokenStore} from '@/stores/token.js'
- //添加请求拦截器
- instance.interceptors.request.use(
- (config)=>{
- //请求前的回调
- //添加token
- const tokenStore = useTokenStore();
- //判断有没有token
- if(tokenStore.token){
- config.headers.Authorization = tokenStore.token
- }
- return config;
- },
- (err)=>{
- //请求错误的回调
- Promise.reject(err)
- }
- )
- /* import {useRouter} from 'vue-router'
- const router = useRouter(); */
- import router from '@/router'
- //添加响应拦截器
- instance.interceptors.response.use(
- result => {
- //判断业务状态码
- if(result.data.code===0){
- return result.data;
- }
- //操作失败
- //alert(result.data.msg?result.data.msg:'服务异常')
- ElMessage.error(result.data.msg?result.data.msg:'服务异常')
- //异步操作的状态转换为失败
- return Promise.reject(result.data)
-
- },
- err => {
- //判断响应状态码,如果为401,则证明未登录,提示请登录,并跳转到登录页面
- if(err.response.status===401){
- ElMessage.error('请先登录')
- router.push('/login')
- }else{
- ElMessage.error('服务异常')
- }
-
- return Promise.reject(err);//异步的状态转化成失败的状态
- }
- )
- export default instance;
复制代码 vite.config.js
- import { fileURLToPath, URL } from 'node:url'
- import { defineConfig } from 'vite'
- import vue from '@vitejs/plugin-vue'
- import path from 'node:path'
- // https://vitejs.dev/config/
- export default defineConfig({
- plugins: [
- vue(),
- ],
- resolve: {
- alias: {
- '@': fileURLToPath(new URL('./src', import.meta.url))
- }
- }
- ,
- server:{
- proxy:{
- '/api':{//获取路径中包含了/api的请求
- target:'http://localhost:8080',//后台服务所在的源
- changeOrigin:true,//修改源
- rewrite:(path)=>path.replace(/^\/api/,'')///api替换为''
- }
- }
- }
- })
复制代码 主页面布局

Layout.vue
- <script setup>
- import {
- Management,
- Promotion,
- UserFilled,
- User,
- Crop,
- EditPen,
- SwitchButton,
- CaretBottom
- } from '@element-plus/icons-vue'
- import avatar from '@/assets/default.png'
- import {userInfoService} from '@/api/user.js'
- import useUserInfoStore from '@/stores/userInfo.js'
- import {useTokenStore} from '@/stores/token.js'
- const tokenStore = useTokenStore();
- const userInfoStore = useUserInfoStore();
- //调用函数,获取用户详细信息
- const getUserInfo = async()=>{
- //调用接口
- let result = await userInfoService();
- //数据存储到pinia中
- userInfoStore.setInfo(result.data);
- }
- getUserInfo();
- //条目被点击后,调用的函数
- import {useRouter} from 'vue-router'
- const router = useRouter();
- import {ElMessage,ElMessageBox} from 'element-plus'
- const handleCommand = (command)=>{
- //判断指令
- if(command === 'logout'){
- //退出登录
- ElMessageBox.confirm(
- '您确认要退出吗?',
- '温馨提示',
- {
- confirmButtonText: '确认',
- cancelButtonText: '取消',
- type: 'warning',
- }
- )
- .then(async () => {
- //退出登录
- //1.清空pinia中存储的token以及个人信息
- tokenStore.removeToken()
- userInfoStore.removeInfo()
- //2.跳转到登录页面
- router.push('/login')
- ElMessage({
- type: 'success',
- message: '退出登录成功',
- })
-
- })
- .catch(() => {
- ElMessage({
- type: 'info',
- message: '用户取消了退出登录',
- })
- })
- }else{
- //路由
- router.push('/user/'+command)
- }
- }
- </script>
- <template>
- <!-- element-plus中的容器 -->
- <el-container class="layout-container">
- <!-- 左侧菜单 -->
- <el-aside width="200px">
- <div class="el-aside__logo"></div>
- <!-- element-plus的菜单标签 -->
- <el-menu active-text-color="#ffd04b" background-color="#232323" text-color="#fff"
- router>
- <el-menu-item index="/article/category">
- <el-icon>
- <Management />
- </el-icon>
- <span>文章分类</span>
- </el-menu-item>
- <el-menu-item index="/article/manage">
- <el-icon>
- <Promotion />
- </el-icon>
- <span>文章管理</span>
- </el-menu-item>
- <el-sub-menu >
- <template #title>
- <el-icon>
- <UserFilled />
- </el-icon>
- <span>个人中心</span>
- </template>
- <el-menu-item index="/user/info">
- <el-icon>
- <User />
- </el-icon>
- <span>基本资料</span>
- </el-menu-item>
- <el-menu-item index="/user/avatar">
- <el-icon>
- <Crop />
- </el-icon>
- <span>更换头像</span>
- </el-menu-item>
- <el-menu-item index="/user/resetPassword">
- <el-icon>
- <EditPen />
- </el-icon>
- <span>重置密码</span>
- </el-menu-item>
- </el-sub-menu>
- </el-menu>
- </el-aside>
- <!-- 右侧主区域 -->
- <el-container>
- <!-- 头部区域 -->
- <el-header>
- <div>黑马程序员:<strong>{{ userInfoStore.info.nickname }}</strong></div>
- <!-- 下拉菜单 -->
- <!-- command: 条目被点击后会触发,在事件函数上可以声明一个参数,接收条目对应的指令 -->
- <el-dropdown placement="bottom-end" @command="handleCommand">
- <span class="el-dropdown__box">
- <el-avatar :src="userInfoStore.info.userPic? userInfoStore.info.userPic:avatar" />
- <el-icon>
- <CaretBottom />
- </el-icon>
- </span>
- <template #dropdown>
- <el-dropdown-menu>
- <el-dropdown-item command="info" :icon="User">基本资料</el-dropdown-item>
- <el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item>
- <el-dropdown-item command="resetPassword" :icon="EditPen">重置密码</el-dropdown-item>
- <el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item>
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- </el-header>
- <!-- 中间区域 -->
- <el-main>
- <!-- <div style="width: 1290px; height: 570px;border: 1px solid red;">
- 内容展示区
- </div> -->
- <router-view></router-view>
- </el-main>
- <!-- 底部区域 -->
- <el-footer>大事件 ©2023 Created by 黑马程序员</el-footer>
- </el-container>
- </el-container>
- </template>
- <style lang="scss" scoped>
- .layout-container {
- height: 100vh;
- .el-aside {
- background-color: #232323;
- &__logo {
- height: 120px;
- background: url('@/assets/logo.png') no-repeat center / 120px auto;
- }
- .el-menu {
- border-right: none;
- }
- }
- .el-header {
- background-color: #fff;
- display: flex;
- align-items: center;
- justify-content: space-between;
- .el-dropdown__box {
- display: flex;
- align-items: center;
- .el-icon {
- color: #999;
- margin-left: 10px;
- }
- &:active,
- &:focus {
- outline: none;
- }
- }
- }
- .el-footer {
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 14px;
- color: #666;
- }
- }
- </style>
复制代码 路由
路由,决定从起点到终点的路径的历程
在前端工程中,路由指的是根据不同的访问路径,展示不同组件的内容
Vue Router是Vue.js的官方路由
Vue Router
安装vue-router npm install vue-router@4
在src/router/index.js中创建路由器,并导出
在vue应用实例中使用vue-router
声明router-view标签,展示组件内容
App.vue
index.js
子路由
复制资料中提供好的五个组件
配置子路由
声明router-view标签
为菜单项 el-menu-item 设置index属性,设置点击后的路由路径
index.js
- import { createRouter, createWebHistory } from 'vue-router'
- //导入组件
- import LoginVue from '@/views/Login.vue'
- import LayoutVue from '@/views/Layout.vue'
- import ArticleCategoryVue from '@/views/article/ArticleCategory.vue'
- import ArticleManageVue from '@/views/article/ArticleManage.vue'
- import UserAvatarVue from '@/views/user/UserAvatar.vue'
- import UserInfoVue from '@/views/user/UserInfo.vue'
- import UserResetPasswordVue from '@/views/user/UserResetPassword.vue'
- //定义路由关系
- const routes = [
- { path: '/login', component: LoginVue },
- {
- path: '/', component: LayoutVue,redirect:'/article/manage', children: [
- { path: '/article/category', component: ArticleCategoryVue },
- { path: '/article/manage', component: ArticleManageVue },
- { path: '/user/info', component: UserInfoVue },
- { path: '/user/avatar', component: UserAvatarVue },
- { path: '/user/resetPassword', component: UserResetPasswordVue }
- ]
- }
- ]
- //创建路由器
- const router = createRouter({
- history: createWebHistory(),
- routes: routes
- })
- //导出路由
- export default router
复制代码 文章分类
api/article.js
- import request from '@/utils/request.js'
- import { useTokenStore } from '@/stores/token.js'
- //文章分类列表查询
- export const articleCategoryListService = ()=>{
- //const tokenStore = useTokenStore();
- //在pinia中定义的响应式数据,都不需要.value
- //return request.get('/category',{headers:{'Authorization':tokenStore.token}})
- return request.get('/category')
- }
- //文章分类添加
- export const articleCategoryAddService = (categoryData)=>{
- return request.post('/category',categoryData)
- }
- //文章分类修改
- export const articleCategoryUpdateService = (categoryData)=>{
- return request.put('/category',categoryData)
- }
- //文章分类删除
- export const articleCategoryDeleteService = (id)=>{
- return request.delete('/category?id='+id)
- }
- //文章列表查询
- export const articleListService = (params)=>{
- return request.get('/article',{params:params})
- }
- //文章添加
- export const articleAddService = (articleData)=>{
- return request.post('/article',articleData);
- }
复制代码 AticleCategory.vue
- <script setup>
- import {
- Edit,
- Delete
- } from '@element-plus/icons-vue'
- import { ref } from 'vue'
- const categorys = ref([
- {
- "id": 3,
- "categoryName": "美食",
- "categoryAlias": "my",
- "createTime": "2023-09-02 12:06:59",
- "updateTime": "2023-09-02 12:06:59"
- },
- {
- "id": 4,
- "categoryName": "娱乐",
- "categoryAlias": "yl",
- "createTime": "2023-09-02 12:08:16",
- "updateTime": "2023-09-02 12:08:16"
- },
- {
- "id": 5,
- "categoryName": "军事",
- "categoryAlias": "js",
- "createTime": "2023-09-02 12:08:33",
- "updateTime": "2023-09-02 12:08:33"
- }
- ])
- //声明一个异步的函数
- import { articleCategoryListService, articleCategoryAddService, articleCategoryUpdateService,articleCategoryDeleteService } from '@/api/article.js'
- const articleCategoryList = async () => {
- let result = await articleCategoryListService();
- categorys.value = result.data;
- }
- articleCategoryList();
- //控制添加分类弹窗
- const dialogVisible = ref(false)
- //添加分类数据模型
- const categoryModel = ref({
- categoryName: '',
- categoryAlias: ''
- })
- //添加分类表单校验
- const rules = {
- categoryName: [
- { required: true, message: '请输入分类名称', trigger: 'blur' },
- ],
- categoryAlias: [
- { required: true, message: '请输入分类别名', trigger: 'blur' },
- ]
- }
- //调用接口,添加表单
- import { ElMessage } from 'element-plus'
- const addCategory = async () => {
- //调用接口
- let result = await articleCategoryAddService(categoryModel.value);
- ElMessage.success(result.msg ? result.msg : '添加成功')
- //调用获取所有文章分类的函数
- articleCategoryList();
- dialogVisible.value = false;
- }
- //定义变量,控制标题的展示
- const title = ref('')
- //展示编辑弹窗
- const showDialog = (row) => {
- dialogVisible.value = true; title.value = '编辑分类'
- //数据拷贝
- categoryModel.value.categoryName = row.categoryName;
- categoryModel.value.categoryAlias = row.categoryAlias;
- //扩展id属性,将来需要传递给后台,完成分类的修改
- categoryModel.value.id = row.id
- }
- //编辑分类
- const updateCategory = async () => {
- //调用接口
- let result = await articleCategoryUpdateService(categoryModel.value);
- ElMessage.success(result.msg ? result.msg : '修改成功')
- //调用获取所有分类的函数
- articleCategoryList();
- //隐藏弹窗
- dialogVisible.value = false;
- }
- //清空模型的数据
- const clearData = () => {
- categoryModel.value.categoryName = '';
- categoryModel.value.categoryAlias = '';
- }
- //删除分类
- import {ElMessageBox} from 'element-plus'
- const deleteCategory = (row) => {
- //提示用户 确认框
- ElMessageBox.confirm(
- '你确认要删除该分类信息吗?',
- '温馨提示',
- {
- confirmButtonText: '确认',
- cancelButtonText: '取消',
- type: 'warning',
- }
- )
- .then(async () => {
- //调用接口
- let result = await articleCategoryDeleteService(row.id);
- ElMessage({
- type: 'success',
- message: '删除成功',
- })
- //刷新列表
- articleCategoryList();
- })
- .catch(() => {
- ElMessage({
- type: 'info',
- message: '用户取消了删除',
- })
- })
- }
- </script>
- <template>
- <el-card class="page-container">
- <template #header>
- <div class="header">
- <span>文章分类</span>
- <div class="extra">
- <el-button type="primary" @click="dialogVisible = true; title = '添加分类'; clearData()">添加分类</el-button>
- </div>
- </div>
- </template>
- <el-table :data="categorys" style="width: 100%">
- <el-table-column label="序号" width="100" type="index"> </el-table-column>
- <el-table-column label="分类名称" prop="categoryName"></el-table-column>
- <el-table-column label="分类别名" prop="categoryAlias"></el-table-column>
- <el-table-column label="操作" width="100">
- <template #default="{ row }">
- <el-button :icon="Edit" circle plain type="primary" @click="showDialog(row)"></el-button>
- <el-button :icon="Delete" circle plain type="danger" @click="deleteCategory(row)"></el-button>
- </template>
- </el-table-column>
- <template #empty>
- <el-empty description="没有数据" />
- </template>
- </el-table>
- <!-- 添加分类弹窗 -->
- <el-dialog v-model="dialogVisible" :title="title" width="30%">
- <el-form :model="categoryModel" :rules="rules" label-width="100px" style="padding-right: 30px">
- <el-form-item label="分类名称" prop="categoryName">
- <el-input v-model="categoryModel.categoryName" minlength="1" maxlength="10"></el-input>
- </el-form-item>
- <el-form-item label="分类别名" prop="categoryAlias">
- <el-input v-model="categoryModel.categoryAlias" minlength="1" maxlength="15"></el-input>
- </el-form-item>
- </el-form>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="dialogVisible = false">取消</el-button>
- <el-button type="primary" @click="title == '添加分类' ? addCategory() : updateCategory()"> 确认 </el-button>
- </span>
- </template>
- </el-dialog>
- </el-card>
- </template>
- <style lang="scss" scoped>
- .page-container {
- min-height: 100%;
- box-sizing: border-box;
- .header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- }
- </style>
复制代码 Pinia状态管理库
Pinia是Vue的专属状态管理库,它允许你跨组件或页面共享状态
安装pinia npm install pinia
在vue应用实例中使用pinia
在src/stores/token.js中定义store
在组件中使用store
token.js
Axios哀求拦截器
Pinia持久化插件-persist
Pinia默认是内存存储,当刷新浏览器的时间会丢失数据。
Persist插件可以将pinia中的数据持久化的存储
安装persist npm install pinia-persistedstate-plugin
在pinia中使用persist
定义状态Store时指定持久化配置参数
main.js
未登录同一处理
文章管理
添加文章分类
添加分类弹窗页面
- <!-- 添加分类弹窗 -->
- <el-dialog v-model="dialogVisible" title="添加弹层" width="30%">
- <el-form :model="categoryModel" :rules="rules" label-width="100px" style="padding-right: 30px">
- <el-form-item label="分类名称" prop="categoryName">
- <el-input v-model="categoryModel.categoryName" minlength="1" maxlength="10"></el-input>
- </el-form-item>
- <el-form-item label="分类别名" prop="categoryAlias">
- <el-input v-model="categoryModel.categoryAlias" minlength="1" maxlength="15"></el-input>
- </el-form-item>
- </el-form>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="dialogVisible = false">取消</el-button>
- <el-button type="primary"> 确认 </el-button>
- </span>
- </template>
- </el-dialog>
复制代码 数据模子和校验规则
- //控制添加分类弹窗
- const dialogVisible = ref(false)
-
- //添加分类数据模型
- const categoryModel = ref({
- categoryName: '',
- categoryAlias: ''
- })
- //添加分类表单校验
- const rules = {
- categoryName: [
- { required: true, message: '请输入分类名称', trigger: 'blur' },
- ],
- categoryAlias: [
- { required: true, message: '请输入分类别名', trigger: 'blur' },
- ]
- }
复制代码 添加分类按钮单击事件
- <el-button type="primary" @click="dialogVisible = true">添加分类</el-button>
复制代码 接口调用
在article.js中提供添加分类的函数
- //添加文章分类
- export const articleCategoryAddService = (categoryModel) => {
- return request.post('/category', categoryModel)
- }
复制代码 在页面中调用接口
- //访问后台,添加文章分类
- const addCategory = async ()=>{
- let result = await articleCategoryAddService(categoryModel.value);
- ElMessage.success(result.message? result.message:'添加成功')
- //隐藏弹窗
- dialogVisible.value = false
- //再次访问后台接口,查询所有分类
- getAllCategory()
- }
- <el-button type="primary" @click="addCategory"> 确认 </el-button>
复制代码 修改文章分类
修改分类弹窗页面
修改分类弹窗和新增文章分类弹窗长的一样,以是可以服用添加分类的弹窗
弹窗标题显示定义标题
- //弹窗标题
- const title=ref('')
复制代码 在弹窗上绑定标题
- <el-dialog v-model="dialogVisible" :title="title" width="30%">
复制代码 为添加分类按钮绑定事件
- <el-button type="primary" @click="title='添加分类';dialogVisible = true">添加分类</el-button>
复制代码 为修改分类按钮绑定事件
- <el-button :icon="Edit" circle plain type="primary" @click="title='修改分类';dialogVisible=true"></el-button>
复制代码 数据回显
当点击修改分类按钮时,需要把当前这一条数据的详细信息显示到修改分类的弹窗上,这个叫回显
通过插槽的方式得到被点击按钮所在行的数据
- <template #default="{ row }">
- <el-button :icon="Edit" circle plain type="primary" @click="updateCategoryEcho(row)"></el-button>
- <el-button :icon="Delete" circle plain type="danger"></el-button>
- </template>
复制代码 回显函数
- //修改分类回显
- const updateCategoryEcho = (row) => {
- title.value = '修改分类'
- dialogVisible.value = true
- //将row中的数据赋值给categoryModel
- categoryModel.value.categoryName=row.categoryName
- categoryModel.value.categoryAlias=row.categoryAlias
- //修改的时候必须传递分类的id,所以扩展一个id属性
- categoryModel.value.id=row.id
- }
复制代码 接口调用
article.js中提供修改分类的函数
- //修改分类
- export const articleCategoryUpdateService = (categoryModel)=>{
- return request.put('/category',categoryModel)
- }
复制代码 修改确定按钮的绑定事件
- <span class="dialog-footer">
- <el-button @click="dialogVisible = false">取消</el-button>
- <el-button type="primary" @click="title==='添加分类'? addCategory():updateCategory()"> 确认 </el-button>
- </span>
复制代码 调用接口完成修改的函数
- //修改分类
- const updateCategory=async ()=>{
- let result = await articleCategoryUpdateService(categoryModel.value)
- ElMessage.success(result.message? result.message:'修改成功')
- //隐藏弹窗
- dialogVisible.value=false
- //再次访问后台接口,查询所有分类
- getAllCategory()
- }
复制代码 由于现在修改和新增共用了一个数据模子,以是在点击添加分类后,有时间会显示数据,此时可以将categoryModel中的数据清空
- //清空模型数据
- const clearCategoryModel = ()=>{
- categoryModel.value.categoryName='',
- categoryModel.value.categoryAlias=''
- }
复制代码 修改添加按钮的点击事件
- <el-button type="primary" @click="title = '添加分类'; dialogVisible = true;clearCategoryModel()">添加分类</el-button>
复制代码 删除文章分类
确认框
- //删除分类 给删除按钮绑定事件
- const deleteCategory = (row) => {
- ElMessageBox.confirm(
- '你确认删除该分类信息吗?',
- '温馨提示',
- {
- confirmButtonText: '确认',
- cancelButtonText: '取消',
- type: 'warning',
- }
- )
- .then(() => {
- //用户点击了确认
- ElMessage({
- type: 'success',
- message: '删除成功',
- })
- })
- .catch(() => {
- //用户点击了取消
- ElMessage({
- type: 'info',
- message: '取消删除',
- })
- })
- }
复制代码 接口调用
article.js中提供删除分类的函数
- //删除分类
- export const articleCategoryDeleteService = (id) => {
- return request.delete('/category?id='+id)
- }
复制代码 当用户点击确认后,调用接口删除分类
- //删除分类
- const deleteCategory = (row) => {
- ElMessageBox.confirm(
- '你确认删除该分类信息吗?',
- '温馨提示',
- {
- confirmButtonText: '确认',
- cancelButtonText: '取消',
- type: 'warning',
- }
- )
- .then(async () => {
- //用户点击了确认
- let result = await articleCategoryDeleteService(row.id)
- ElMessage.success(result.message?result.message:'删除成功')
- //再次调用getAllCategory,获取所有文章分类
- getAllCategory()
- })
- .catch(() => {
- //用户点击了取消
- ElMessage({
- type: 'info',
- message: '取消删除',
- })
- })
- }
复制代码 文章列表查询
文章列表页面组件
- <script setup>
- import {
- Edit,
- Delete
- } from '@element-plus/icons-vue'
-
- import { ref } from 'vue'
-
- //文章分类数据模型
- const categorys = ref([
- {
- "id": 3,
- "categoryName": "美食",
- "categoryAlias": "my",
- "createTime": "2023-09-02 12:06:59",
- "updateTime": "2023-09-02 12:06:59"
- },
- {
- "id": 4,
- "categoryName": "娱乐",
- "categoryAlias": "yl",
- "createTime": "2023-09-02 12:08:16",
- "updateTime": "2023-09-02 12:08:16"
- },
- {
- "id": 5,
- "categoryName": "军事",
- "categoryAlias": "js",
- "createTime": "2023-09-02 12:08:33",
- "updateTime": "2023-09-02 12:08:33"
- }
- ])
-
- //用户搜索时选中的分类id
- const categoryId=ref('')
-
- //用户搜索时选中的发布状态
- const state=ref('')
-
- //文章列表数据模型
- const articles = ref([
- {
- "id": 5,
- "title": "陕西旅游攻略",
- "content": "兵马俑,华清池,法门寺,华山...爱去哪去哪...",
- "coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png",
- "state": "草稿",
- "categoryId": 2,
- "createTime": "2023-09-03 11:55:30",
- "updateTime": "2023-09-03 11:55:30"
- },
- {
- "id": 5,
- "title": "陕西旅游攻略",
- "content": "兵马俑,华清池,法门寺,华山...爱去哪去哪...",
- "coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png",
- "state": "草稿",
- "categoryId": 2,
- "createTime": "2023-09-03 11:55:30",
- "updateTime": "2023-09-03 11:55:30"
- },
- {
- "id": 5,
- "title": "陕西旅游攻略",
- "content": "兵马俑,华清池,法门寺,华山...爱去哪去哪...",
- "coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png",
- "state": "草稿",
- "categoryId": 2,
- "createTime": "2023-09-03 11:55:30",
- "updateTime": "2023-09-03 11:55:30"
- },
- ])
-
- //分页条数据模型
- const pageNum = ref(1)//当前页
- const total = ref(20)//总条数
- const pageSize = ref(3)//每页条数
-
- //当每页条数发生了变化,调用此函数
- const onSizeChange = (size) => {
- pageSize.value = size
- }
- //当前页码发生变化,调用此函数
- const onCurrentChange = (num) => {
- pageNum.value = num
- }
- </script>
- <template>
- <el-card class="page-container">
- <template #header>
- <div class="header">
- <span>文章管理</span>
- <div class="extra">
- <el-button type="primary">添加文章</el-button>
- </div>
- </div>
- </template>
- <!-- 搜索表单 -->
- <el-form inline>
- <el-form-item label="文章分类:">
- <el-select placeholder="请选择" v-model="categoryId">
- <el-option
- v-for="c in categorys"
- :key="c.id"
- :label="c.categoryName"
- :value="c.id">
- </el-option>
- </el-select>
- </el-form-item>
-
- <el-form-item label="发布状态:">
- <el-select placeholder="请选择" v-model="state">
- <el-option label="已发布" value="已发布"></el-option>
- <el-option label="草稿" value="草稿"></el-option>
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary">搜索</el-button>
- <el-button>重置</el-button>
- </el-form-item>
- </el-form>
- <!-- 文章列表 -->
- <el-table :data="articles" style="width: 100%">
- <el-table-column label="文章标题" width="400" prop="title"></el-table-column>
- <el-table-column label="分类" prop="categoryId"></el-table-column>
- <el-table-column label="发表时间" prop="createTime"> </el-table-column>
- <el-table-column label="状态" prop="state"></el-table-column>
- <el-table-column label="操作" width="100">
- <template #default="{ row }">
- <el-button :icon="Edit" circle plain type="primary"></el-button>
- <el-button :icon="Delete" circle plain type="danger"></el-button>
- </template>
- </el-table-column>
- <template #empty>
- <el-empty description="没有数据" />
- </template>
- </el-table>
- <!-- 分页条 -->
- <el-pagination v-model:current-page="pageNum" v-model:page-size="pageSize" :page-sizes="[3, 5 ,10, 15]"
- layout="jumper, total, sizes, prev, pager, next" background :total="total" @size-change="onSizeChange"
- @current-change="onCurrentChange" style="margin-top: 20px; justify-content: flex-end" />
- </el-card>
- </template>
- <style lang="scss" scoped>
- .page-container {
- min-height: 100%;
- box-sizing: border-box;
-
- .header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- }
- </style>
复制代码 使用中文语言包,解决分页条中文问题, 在main.js中完成
- import locale from 'element-plus/dist/locale/zh-cn.js'
-
- app.use(ElementPlus,{locale})
复制代码 文章分类数据回显
ArticleMange.vue
- //文章列表查询
- import { articleCategoryListService } from '@/api/article.js'
- const getArticleCategoryList = async () => {
- //获取所有分类
- let resultC = await articleCategoryListService();
- categorys.value = resultC.data
- }
- getArticleCategoryList();
复制代码 文章列表接口调用
article.js中提供获取文章列表数据的函数
- //文章列表查询
- export const articleListService = (params) => {
- return request.get('/article', { params: params })
- }
复制代码 ArticleManage.vue中,调用接口获取数据
- //文章列表查询
- import { articleListService } from '@/api/article.js'
- const getArticles = async () => {
- let params = {
- pageNum: pageNum.value,
- pageSize: pageSize.value,
- categoryId: categoryId.value ? categoryId.value : null,
- state: state.value ? state.value : null
- }
- let result = await articleListService(params);
- //渲染列表数据
- articles.value = result.data.items
- //为列表中添加categoryName属性
- for(let i=0;i<articles.value.length;i++){
- let article = articles.value[i];
- for(let j=0;j<categorys.value.length;j++){
- if(article.categoryId===categorys.value[j].id){
- article.categoryName=categorys.value[j].categoryName
- }
- }
- }
- //渲染总条数
- total.value=result.data.total
- }
- getArticles()
复制代码 当分页条的当前页和每页条数发生变化,重新发送哀求获取数据
- //当每页条数发生了变化,调用此函数
- const onSizeChange = (size) => {
- pageSize.value = size
- getArticles()
- }
- //当前页码发生变化,调用此函数
- const onCurrentChange = (num) => {
- pageNum.value = num
- getArticles()
- }
复制代码 搜刮和重置
为搜刮按钮绑定单击事件,调用getArticles函数即可
- <el-button type="primary" @click="getArticles">搜索</el-button>
复制代码 为重置按钮绑定单击事件,清除categoryId和state的之即可
- <el-button @click="categoryId='';state=''">重置</el-button>
复制代码 添加文章
添加文章抽屉组件
- import {Plus} from '@element-plus/icons-vue'
- //控制抽屉是否显示
- const visibleDrawer = ref(false)
- //添加表单数据模型
- const articleModel = ref({
- title: '',
- categoryId: '',
- coverImg: '',
- content:'',
- state:''
- })
- <!-- 抽屉 -->
- <el-drawer v-model="visibleDrawer" title="添加文章" direction="rtl" size="50%">
- <!-- 添加文章表单 -->
- <el-form :model="articleModel" label-width="100px" >
- <el-form-item label="文章标题" >
- <el-input v-model="articleModel.title" placeholder="请输入标题"></el-input>
- </el-form-item>
- <el-form-item label="文章分类">
- <el-select placeholder="请选择" v-model="articleModel.categoryId">
- <el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id">
- </el-option>
- </el-select>
- </el-form-item>
- <el-form-item label="文章封面">
- <el-upload class="avatar-uploader" :auto-upload="false" :show-file-list="false">
- <img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" />
- <el-icon v-else class="avatar-uploader-icon">
- <Plus />
- </el-icon>
- </el-upload>
- </el-form-item>
- <el-form-item label="文章内容">
- <div class="editor">富文本编辑器</div>
- </el-form-item>
- <el-form-item>
- <el-button type="primary">发布</el-button>
- <el-button type="info">草稿</el-button>
- </el-form-item>
- </el-form>
- </el-drawer>
- /* 抽屉样式 */
- .avatar-uploader {
- :deep() {
- .avatar {
- width: 178px;
- height: 178px;
- display: block;
- }
- .el-upload {
- border: 1px dashed var(--el-border-color);
- border-radius: 6px;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- transition: var(--el-transition-duration-fast);
- }
- .el-upload:hover {
- border-color: var(--el-color-primary);
- }
- .el-icon.avatar-uploader-icon {
- font-size: 28px;
- color: #8c939d;
- width: 178px;
- height: 178px;
- text-align: center;
- }
- }
- }
- .editor {
- width: 100%;
- :deep(.ql-editor) {
- min-height: 200px;
- }
- }
复制代码 为添加文章按钮添加单击事件,展示抽屉
- <el-button type="primary" @click="visibleDrawer = true">添加文章</el-button>
复制代码 富文本编辑器
文章内容需要使用到富文本编辑器,这里咱们使用一个开源的富文本编辑器 Quill
官网地址: https://vueup.github.io/vue-quill/
安装:
- npm install @vueup/vue-quill@latest --save
复制代码 导入组件和样式:
- import { QuillEditor } from '@vueup/vue-quill'
- import '@vueup/vue-quill/dist/vue-quill.snow.css'
复制代码 页面长使用quill组件:
- <quill-editor
- theme="snow"
- v-model:content="articleModel.content"
- contentType="html"
- >
- </quill-editor>
复制代码 样式美化:
- .editor {
- width: 100%;
- :deep(.ql-editor) {
- min-height: 200px;
- }
- }
复制代码 文章封面图片上传
将来当点击+图标,选择本舆图片后,el-upload这个组件会自动发送哀求,把图片上传到指定的服务器上,而不需要我们自己使用axios发送异步哀求,以是需要给el-upload标签添加一些属性,控制哀求的发送
auto-upload:是否自动上传
action: 服务器接口路径
name: 上传的文件字段名
headers: 设置上传的哀求头
on-success: 上传成功的回调函数
- import {
- Plus
- } from '@element-plus/icons-vue'
-
- <el-form-item label="文章封面">
- <el-upload class="avatar-uploader"
- :show-file-list="false"
- >
- <img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" />
- <el-icon v-else class="avatar-uploader-icon">
- <Plus />
- </el-icon>
- </el-upload>
- </el-form-item>
复制代码 注意:
- 由于这个哀求时el-upload自动发送的异步哀求,并没有使用咱们的request.js哀求工具,以是在哀求的路ing上,需要加上/api, 这个时间哀求署理才能拦截到这个哀求,转发到后台服务器上
- 要携带哀求头,还需要导入pinia状态才可以使用
- import { useTokenStore } from '@/stores/token.js'
- const tokenStore = useTokenStore();
复制代码
- 在成功的回调函数中,可以拿到服务器相应的数据,其中有一个属性为data,对应的就是图片在阿里云oss上存储的访问地址,需要把它赋值给articleModel的coverImg属性,这样img标签就能显示这张图片了,因为img标签上通过src属性绑定了articleModel.coverImg
- //上传图片成功回调
- const uploadSuccess = (img) => {
- //img就是后台响应的数据,格式为:{code:状态码,message:提示信息,data: 图片的存储地址}
- articleModel.value.coverImg=img.data
- }
-
复制代码 添加文章接口调用
article.js中提供添加文章函数
- //添加文章
- export const articleAddService = (articleModel)=>{
- return request.post('/article',articleModel)
- }
复制代码 为已发布和草稿按钮绑定事件
- <el-form-item>
- <el-button type="primary" @click="addArticle('已发布')">发布</el-button>
- <el-button type="info" @click="addArticle('草稿')">草稿</el-button>
- </el-form-item>
复制代码 ArticleManage.vue中提供addArticle函数完成添加文章接口的调用
- //添加文章
- const addArticle=async (state)=>{
- articleModel.value.state = state
- let result = await articleAddService(articleModel.value);
- ElMessage.success(result.message? result.message:'添加成功')
- //再次调用getArticles,获取文章
- getArticles()
- //隐藏抽屉
- visibleDrawer.value=false
- }
复制代码 顶部导航栏信息显示
在Layout.vue中,页面加载完就发送哀求,获取个人信息展示,并存储到pinia中,因为将来在个人中心中修改信息的时间还需要使用
user.js中提供获取个人信息的函数
- //获取个人信息
- export const userInfoGetService = ()=>{
- return request.get('/user/userInfo');
- }
复制代码 src/stores/user.js中,定义个人中心状态
- import { defineStore } from "pinia"
- import {ref} from 'vue'
-
- export const useUserInfoStore = defineStore('userInfo',()=>{
- //1.定义用户信息
- const info = ref({})
- //2.定义修改用户信息的方法
- const setInfo = (newInfo)=>{
- info.value = newInfo
- }
- //3.定义清空用户信息的方法
- const removeInfo = ()=>{
- info.value={}
- }
-
- return{info,setInfo,removeInfo}
- },{
- persist:true
- })
复制代码 Layout.vue中获取个人信息,并存储到pinia中
- //导入接口函数
- import {userInfoGetService} from '@/api/user.js'
- //导入pinia
- import {useUserInfoStore} from '@/stores/user.js'
- const userInfoStore = useUserInfoStore();
- import {ref} from 'vue'
-
- //获取个人信息
- const getUserInf = async ()=>{
- let result = await userInfoGetService();
- //存储pinia
- userInfoStore.info =result.data;
- }
- getUserInf()
复制代码 Layout.vue的顶部导航栏中,展示昵称和头像
- <div>黑马程序员:<strong>{{ userInfoStore.info.nickname ? userInfoStore.info.nickname : userInfoStore.info.usrename }}</strong></div>
- <el-avatar :src="userInfoStore.info.userPic ? userInfoStore.info.userPic : avatar" />
复制代码 下拉菜单功能
el-dropdown中功能实现
在el-dropdown中有四个子条目,分别是:
其中其三个起到路由功能,跟左侧菜单中【个人中心】下面的二级菜单是同样的功能,退出登录需要删除本地pinia中存储的token以及userInfo
路由实现:
在el-dropdown-item标签上添加command属性,属性值和路由表中/user/xxx保持同等
- <el-dropdown-menu>
- <el-dropdown-item command="info" :icon="User">基本资料</el-dropdown-item>
- <el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item>
- <el-dropdown-item command="password" :icon="EditPen">重置密码</el-dropdown-item>
- <el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item>
- </el-dropdown-menu>
复制代码 在el-dropdown标签上绑定command事件,当有条目被点击后,会触发这个事件
- <el-dropdown placement="bottom-end" @command="handleCommand">
复制代码 提供handleCommand函数,参数为点击条目标command属性值
- //dropDown条目被点击后,回调的函数
- import {useRouter} from 'vue-router'
- const router = useRouter()
- const handleCommand = (command)=>{
- if(command==='logout'){
- //退出登录
- alert('退出登录')
- }else{
- //路由
- router.push('/user/'+command)
- }
- }
复制代码 退出登录实现:
- import {ElMessage,ElMessageBox} from 'element-plus'
- import { useTokenStore } from '@/stores/token.js'
- const tokenStore = useTokenStore()
- const handleCommand = (command) => {
- if (command === 'logout') {
- //退出登录
- ElMessageBox.confirm(
- '你确认退出登录码?',
- '温馨提示',
- {
- confirmButtonText: '确认',
- cancelButtonText: '取消',
- type: 'warning',
- }
- )
- .then(async () => {
- //用户点击了确认
- //清空pinia中的token和个人信息
- userInfoStore.info={}
- tokenStore.token=''
- //跳转到登录页
- router.push('/login')
- })
- .catch(() => {
- //用户点击了取消
- ElMessage({
- type: 'info',
- message: '取消退出',
- })
- })
- } else {
- //路由
- router.push('/user/' + command)
- }
- }
复制代码 个人中心
基本资料修改
基本资料页面组件
- <script setup>
- import { ref } from 'vue'
- const userInfo = ref({
- id: 0,
- username: 'zhangsan',
- nickname: 'zs',
- email: 'zs@163.com',
- })
- const rules = {
- nickname: [
- { required: true, message: '请输入用户昵称', trigger: 'blur' },
- {
- pattern: /^\S{2,10}$/,
- message: '昵称必须是2-10位的非空字符串',
- trigger: 'blur'
- }
- ],
- email: [
- { required: true, message: '请输入用户邮箱', trigger: 'blur' },
- { type: 'email', message: '邮箱格式不正确', trigger: 'blur' }
- ]
- }
- </script>
- <template>
- <el-card class="page-container">
- <template #header>
- <div class="header">
- <span>基本资料</span>
- </div>
- </template>
- <el-row>
- <el-col :span="12">
- <el-form :model="userInfo" :rules="rules" label-width="100px" size="large">
- <el-form-item label="登录名称">
- <el-input v-model="userInfo.username" disabled></el-input>
- </el-form-item>
- <el-form-item label="用户昵称" prop="nickname">
- <el-input v-model="userInfo.nickname"></el-input>
- </el-form-item>
- <el-form-item label="用户邮箱" prop="email">
- <el-input v-model="userInfo.email"></el-input>
- </el-form-item>
- <el-form-item>
- <el-button type="primary">提交修改</el-button>
- </el-form-item>
- </el-form>
- </el-col>
- </el-row>
- </el-card>
- </template>
复制代码 表单数据回显
个人信息之前已经存储到了pinia中,只需要从pinia中获取个人信息,替换模板数据即可
- import { useUserInfoStore } from '@/stores/user.js';
- const userInfoStore = useUserInfoStore()
- const userInfo = ref({...userInfoStore.info})
复制代码 接口调用
在src/api/user.js中提供修改基本资料的函数
- //修改个人信息
- export const userInfoUpdateService = (userInfo)=>{
- return request.put('/user/update',userInfo)
- }
复制代码 为修改按钮绑定单击事件
- <el-button type="primary" @click="updateUserInfo">提交修改</el-button>
复制代码 提供updateUserInfo函数
- //修改用户信息
- import {userInfoUpdateService} from '@/api/user.js'
- import { ElMessage } from 'element-plus';
- const updateUserInfo = async ()=>{
- let result = await userInfoUpdateService(userInfo.value)
- ElMessage.success(result.message? result.message:'修改成功')
- //更新pinia中的数据
- userInfoStore.info.nickname=userInfo.value.nickname
- userInfoStore.info.email = userInfo.value.email
- }
复制代码 用户头像修改

修改头像页面组件
- <script setup>
- import { Plus, Upload } from '@element-plus/icons-vue'
- import {ref} from 'vue'
- import avatar from '@/assets/default.png'
- const uploadRef = ref()
- //用户头像地址
- const imgUrl= avatar
- </script>
- <template>
- <el-card class="page-container">
- <template #header>
- <div class="header">
- <span>更换头像</span>
- </div>
- </template>
- <el-row>
- <el-col :span="12">
- <el-upload
- ref="uploadRef"
- class="avatar-uploader"
- :show-file-list="false"
- >
- <img v-if="imgUrl" :src="imgUrl" class="avatar" />
- <img v-else src="avatar" width="278" />
- </el-upload>
- <br />
- <el-button type="primary" :icon="Plus" size="large" @click="uploadRef.$el.querySelector('input').click()">
- 选择图片
- </el-button>
- <el-button type="success" :icon="Upload" size="large">
- 上传头像
- </el-button>
- </el-col>
- </el-row>
- </el-card>
- </template>
- <style lang="scss" scoped>
- .avatar-uploader {
- :deep() {
- .avatar {
- width: 278px;
- height: 278px;
- display: block;
- }
- .el-upload {
- border: 1px dashed var(--el-border-color);
- border-radius: 6px;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- transition: var(--el-transition-duration-fast);
- }
- .el-upload:hover {
- border-color: var(--el-color-primary);
- }
- .el-icon.avatar-uploader-icon {
- font-size: 28px;
- color: #8c939d;
- width: 278px;
- height: 278px;
- text-align: center;
- }
- }
- }
- </style>
复制代码 头像回显
从pinia中读取用户的头像数据
- //读取用户信息
- import {ref} from 'vue'
- import {useUserInfoStore} from '@/stores/user.js'
- const userInfoStore = useUserInfoStore()
- const imgUrl=ref(userInfoStore.info.userPic)
复制代码 img标签上绑定图片地址
- <img v-if="imgUrl" :src="imgUrl" class="avatar" />
- <img v-else src="@/assets/avatar.jpg" width="278" />
复制代码 头像上传
为el-upload指定属性值,分别有:
- action: 服务器接口路径
- headers: 设置哀求头,需要携带token
- on-success: 上传成功的回调函数
- name: 上传图片的字段名称
- <el-upload
- class="avatar-uploader"
- :show-file-list="false"
- :auto-upload="true"
- action="/api/upload"
- name="file"
- :headers="{'Authorization':tokenStore.token}"
- :on-success="uploadSuccess"
- >
- <img v-if="imgUrl" :src="imgUrl" class="avatar" />
- <img v-else src="@/assets/avatar.jpg" width="278" />
- </el-upload>
复制代码 提供上传成功的回调函数
- //读取token信息
- import {useTokenStore} from '@/stores/token.js'
- const tokenStore = useTokenStore()
-
- //图片上传成功的回调
- const uploadSuccess = (result)=>{
- //回显图片
- imgUrl.value = result.data
- }
复制代码 外部触发图片选择
需要获取到el-upload组件,然后再通过$el.querySelector(‘input’)获取到el-upload对应的元素,触发click事件
- //获取el-upload元素
- const uploadRef = ref()
-
-
- <el-button type="primary" :icon="Plus" size="large" @click="uploadRef.$el.querySelector('input').click()">
- 选择图片
- </el-button>
复制代码 接口调用
在user.js中提供修改头像的函数
- //修改头像
- export const userAvatarUpdateService=(avatarUrl)=>{
- let params = new URLSearchParams();
- params.append('avatarUrl',avatarUrl)
- return request.patch('/user/updateAvatar',params)
- }
复制代码 为【上传头像】按钮绑定单击事件
- <el-button type="success" :icon="Upload" size="large" @click="updateAvatar">
- 上传头像
- </el-button>
复制代码 提供updateAvatar函数,完成头像更新
- //调用接口,更新头像url
- import {userAvatarUpdateService} from '@/api/user.js'
- import {ElMessage} from 'element-plus'
- const updateAvatar = async ()=>{
- let result = await userAvatarUpdateService(imgUrl.value)
- ElMessage.success(result.message? result.message:'修改成功')
- //更新pinia中的数据
- userInfoStore.info.userPic=imgUrl.value
- }
-
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |