Vue案例

目录

项目介绍

用户功能

注册功能

跨域问题

登录功能

Api封装

路由

路由介绍

使用路由

添加页面

安装路由

登录跳转

子路由

提供组件

配置子路由

添加子路由锚点

添加跳转链接

文章分类功能

查询列表

添加分类

回显分类

删除分类

文章管理功能

文章列表

文章分类选项

分页查询

添加文章

富文本编辑器

图片上传

保存文章实现


项目介绍

演示网站:

黑马程序员-大事件

实现

1)创建项目

 npm init vue@latest

2)安装项目需要的依赖

 npm install element-plus --save
 npm install axios
 npm install sass -D

3)在main.js中加入Element的内容

 import { createApp } from 'vue'
 import ElementPlus from 'element-plus'
 import 'element-plus/dist/index.css'
 import App from './App.vue'
 ​
 //引入中文文件
 import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
 import * as ElementPlusIconsVue from '@element-plus/icons-vue'
 ​
 const app = createApp(App)
 for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
     app.component(key, component)
 }
 app.use(ElementPlus, {
     locale: zhCn,
 })
 app.mount('#app')

4)复制准备好的资料

  1. 删除asserts、components下面自动生成的内容

  2. 将资料中的静态资源拷贝到assets目录下

用户功能

查询性质:(如果查询有条件, 页面绑定,收集条件)声明变量 --> 发送请求,获取数据--->绑定页面

增删改性质:页面绑定 --> 发送请求--> 处理返回结果

注册功能

将Login.vue复制到views目录下,然后从App.Vue中引入此组件

实现注册功能

 <script setup>
 import { User, Lock } from '@element-plus/icons-vue'
 import axios from 'axios';
 import { ElMessage } from 'element-plus';
 import { ref } from 'vue'
 //控制注册与登录表单的显示, 默认显示注册
 const isRegister = ref(false)
 ​
 //用户注册
 const registerData = ref({
     username: '',
     password: '',
     password2: ''
 })
 const register = function () {
     axios.post('http://localhost:8080/user/register', registerData.value).then(resp => {
         if (resp.data.code == 0) {
             ElMessage.success('注册成功!')
             isRegister.value = false
         } else {
             ElMessage.error(resp.data.msg)
         }
     }).catch(err => {
         console.error(err)
     })
 }
 </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">
                 <el-form-item>
                     <h1>注册</h1>
                 </el-form-item>
                 <el-form-item>
                     <el-input :prefix-icon="User" v-model="registerData.username" placeholder="请输入用户名"></el-input>
                 </el-form-item>
                 <el-form-item>
                     <el-input :prefix-icon="Lock" type="password" v-model="registerData.password"
                         placeholder="请输入密码"></el-input>
                 </el-form-item>
                 <el-form-item>
                     <el-input :prefix-icon="Lock" type="password" v-model="registerData.password2"
                         placeholder="请输入再次密码"></el-input>
                 </el-form-item>
                 <!-- 注册按钮 -->
                 <el-form-item>
                     <el-button class="button" type="primary" @click="register()" auto-insert-space>
                         注册
                     </el-button>
                 </el-form-item>
                 <el-form-item class="flex">
                     <el-link type="info" :underline="false" @click="isRegister = false">
                         ← 返回
                     </el-link>
                 </el-form-item>
             </el-form>
             <!-- 登录表单 -->
             <el-form ref="form" size="large" autocomplete="off" v-else>
                 <el-form-item>
                     <h1>登录</h1>
                 </el-form-item>
                 <el-form-item>
                     <el-input :prefix-icon="User" placeholder="请输入用户名"></el-input>
                 </el-form-item>
                 <el-form-item>
                     <el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码"></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>登录</el-button>
                 </el-form-item>
                 <el-form-item class="flex">
                     <el-link type="info" :underline="false" @click="isRegister = true">
                         注册 →
                     </el-link>
                 </el-form-item>
             </el-form>
         </el-col>
     </el-row>
 </template>

跨域问题

当我们点击注册按钮,发送请求的时候,会出现下面的跨域问题

跨域问题是由于浏览器的同源策略引起的,当浏览器从一个域名的网页去请求另一个域名的资源时,出现域名、端口、协议任一不同,都属于跨域

跨域问题可以在前后任意一端进行解决,目前大部分企业项目会选择在前端代码中添加代理进行解决

vie.config.js中配置代理

 import { fileURLToPath, URL } from 'node:url'
 ​
 import { defineConfig } from 'vite'
 import vue from '@vitejs/plugin-vue'
 ​
 export default defineConfig({
     plugins: [
         vue(),
     ],
     resolve: {
         alias: {
             '@': fileURLToPath(new URL('./src', import.meta.url))
         }
     },
     //配置代理
     server: {
         proxy: {
             '/api': {
                 target: 'http://localhost:8080', // 后端服务器地址
                 changeOrigin: true, // 是否改变请求域名
                 rewrite: (path) => path.replace(/^\/api/, '')//将原有请求路径中的api替换为''
             }
         }
     }
 })
修改发送请求的代码

 //用户注册
 const register = function(){
     // axios.post('http://localhost:8080/user/register',registerData.value).then(resp=>{
     //     console.log(resp.data);
     // })
     
     axios.post('/api/user/register',registerData.value).then(resp=>{
         console.log(resp.data);
         if(resp.data.code == 0){
              ElMessage.success('注册成功!')
             isRegister.value = false
         }else{
             ElMessage.error(resp.data.message)
         }
     }).catch(err => {
         console.log(err)
     })
 }

登录功能

定义数据和方法

 //用户登录
 const loginData = ref({
     username: '',
     password: ''
 })
 const login = function () {
     axios.post('/api/user/login', loginData.value).then(resp => {
         if (resp.data.code == 0) {
             ElMessage.success('登录成功!')
             //跳转首页
         } else {
             ElMessage.error(resp.data.message);
         }
     }).catch(err => {
         console.error(err)
     })
 }

页面绑定

           
  <!-- 登录表单 -->
             <el-form ref="form" size="large" autocomplete="off" :model="loginData" v-else>
                 <el-form-item>
                     <h1>登录</h1>
                 </el-form-item>
                 <el-form-item>
                     <el-input :prefix-icon="User" placeholder="请输入用户名" v-model="loginData.username"></el-input>
                 </el-form-item>
                 <el-form-item>
                     <el-input name="password" :prefix-icon="Lock" type="password" v-model="loginData.password" placeholder="请输入密码"></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">
                         注册 →
                     </el-link>
                 </el-form-item>
             </el-form>

Api封装

在src目录下创建util/request.js

 //导入axios
 import axios from 'axios';
 import { ElMessage } from 'element-plus';
 ​
 //复制axios实例
 const request = axios.create({
     baseURL: "/api"
 })
 ​
 //为request实例添加响应拦截器
 request.interceptors.response.use(
     result => {
         if (result.data.code === 0) {
             return result.data;
         }
         //报错
         ElMessage.error(result.data.message ? result.data.message : "服务器异常");
         return Promise.reject(result.data);
     },
     err => {
         console.log(err);
         return Promise.reject(err);
     }
 )
 ​
 //导出request对象
 export default request;
在src目录下创建/api/user.js

 //导入request.js
 import request from "@/util/request";
 ​
 //用户注册
 export function registerAPI(params) {
     return request.post('/user/register', params.value);
 }
 ​
 //用户登录
 export function loginAPI(params) {
     return request.post('/user/login', params.value);
 }
修改Login.vue中的方法

 import {registerApi,loginApi} from  '@/api/user.js'
 ​
 //用户注册
 const register = async function () {
     let data = await registerApi(registerData);
     ElMessage.success('注册成功!');
 }
 ​
 //用户登录
 const login = async function () {
     let result = await loginAPI(loginData);
     ElMessage.success('登录成功!');
 }

路由

路由介绍

在App.vue中,不能同时展示Login.vue和Layout.vue的原因是vue属于单页面应用程序

也就是说一个网站中显示一个页面,所有的功能与交互都在这唯一的一个页面内完成

实现上面内容就要用到路由,它就是根据浏览器的访问路径不同,将不同的组件渲染到唯一的一个页面上

而路由的核心就是配置页面路径和访问组件之间的映射关系

使用路由

添加页面

复制资料中的Home.vue到views目录中

安装路由

1) 添加依赖

 npm install vue-router@4

2) 在src/router目录下,定义一个index.js文件,内容如下

 
//导入vue-router
 import { createRouter, createWebHistory } from 'vue-router'
 ​
 //导入组件
 import Login from '@/views/Login.vue'
 import Home from '@/views/Home.vue'
 ​
 //定义路由关系
 const routes = [
     { path: '/login', component: Login },
     { path: '/', component: Home }
 ]
 ​
 //创建路由器
 const router = createRouter({
     history: createWebHistory(),
     routes: routes
 });
 ​
 export default router

3) 在main.js中导入创建应用实例的js文件,并调用实例的use方法使用路由器

// 导入
import router from '@/router'

// 使用
app.use(router)

4) 在App.vue文件的template标签中,定义router-view标签

5) 在浏览器地址栏分别访问:http://localhost:5173/http://localhost:5173/login

登录跳转

在登录成功后,需要通过代码的方式将页面切换到首页,此时就需要调用路由器相关的API

//用户登录
const login = async function () {
    let result = await loginAPI(loginData);
    ElMessage.success('登录成功!');
    router.push("/")
}

子路由

在咱们的主页面中,当用户点击左侧的菜单时,右侧主区域的内容需要发生变化,

将来每切换一个菜单,右侧需要加载对应组件的内容进行展示,像这样的场景咱们也需要使用路由来完成

由于这些组件都需要在Layout.vue中展示, 而Layout.vue本身已经参与了路由,因此我们需要在Layout.vue中通过子路由的方式来实现

提供组件

将提供好的页面复制到项目中

配置子路由

在src/router/index.js中配置子路由

//导入vue-router
import { createRouter, createWebHistory } from 'vue-router'

//导入组件
import Login from '@/views/Login.vue'
import Layout from '@/views/Layout.vue'
import ArticleCategory from '@/views/article/ArticleCategory.vue'
import ArticleManage from '@/views/article/ArticleManage.vue'
import UserInfo from '@/views/user/UserInfo.vue'
import UserAvatar from '@/views/user/UserAvatar.vue'
import UserResetPassword from '@/views/user/UserResetPassword.vue'

//定义路由关系
const routes = [
    { path: '/login', component: Login },
    {
        path: '/',
        component: Layout,
        //重定向
        redirect: '/article/manage',
        //子路由
        children: [
            { path: '/article/category', component: ArticleCategory },
            { path: '/article/manage', component: ArticleManage },
            { path: '/user/info', component: UserInfo },
            { path: '/user/avatar', component: UserAvatar },
            { path: '/user/password', component: UserResetPassword },
        ]
    }
]

//创建路由器
const router = createRouter({
    history: createWebHistory(),
    routes: routes
});

export default router

添加子路由锚点

在Layout.vue组件的右侧中间区域,添加router-view标签

<!-- 中间区域 -->
<el-main>
    <div style="width: 1290px; height: 570px;border: 1px solid red;">
        <router-view></router-view>
    </div>
</el-main>

添加跳转链接

为左侧按钮添加路由链接

        <!-- 左侧菜单 -->
        <el-aside width="200px">
            <div class="el-aside__logo"></div>
            <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/password">
                        <el-icon>
                            <EditPen />
                        </el-icon>
                        <span>重置密码</span>
                    </el-menu-item>
                </el-sub-menu>
            </el-menu>
        </el-aside>

文章分类功能

查询列表

创建src/api/category.js文件编写查询分类的功能

//导入请求工具类
import request from '@/util/request.js'

//文章分类列表查询
export function findArticleCategoryListAPI(){
    return request.get('/category')
}
在ArticleCategory.vue中定义查询方法

import { findArticleCategoryListAPI } from '@/api/category';

//获取所有文章分类数据
const categorys = ref([])
const findAllCategory = async function () {
    let result = await findArticleCategoryListAPI();
    categorys.value = result.data;
}
findAllCategory();

但是上述的代码并不能真正的获取到所有文章分类数据,服务器响应状态码为401,因为目前请求头中并没有携带token

我们后面所发送的请求都需要在请求头中携带token,这可以借助请求拦截器实现

而这个token是在登录成功返回来的,所有我们还要修改登录成功的代码,将返回的token存储在LocalStorage中

① 修改登录代码

//用户登录
const login = async function () {
    let result = await loginAPI(loginData);
    //将token报错到localStorage中
    localStorage.setItem("BIG_EVENT_TOKEN", result.data);
    ElMessage.success('登录成功!');
    router.push("/")
}

② 修改request工具类

//导入axios
import router from '@/router';
import axios from 'axios';
import { ElMessage } from 'element-plus';

//复制axios实例
const request = axios.create({
    baseURL:"/api"
})

//为request添加请求拦截器
request.interceptors.request.use(
    (config)=>{
        //在发送请求之前做什么
        let token = localStorage.getItem("BIG_EVENT_TOKEN");
        //如果token中有值,在携带
        if(token){
            config.headers.Authorization=token
        }
        return config
    },
    (err)=>{
        //如果请求错误做什么
        Promise.reject(err)
    }
)

//为request实例添加拦截器
request.interceptors.response.use(
    result => {
        if(result.data.code ===0){
            return result.data;
        }
        //报错
        ElMessage.error(result.data.msg ? result.data.msg : "服务器异常");
        return Promise.reject(result.data);
    },
    err=>{
        console.log(err);
        //如果响应状态码时401,代表未登录,给出对应的提示,并跳转到登录页
        if(err.response.status===401){
            ElMessage.error('请先登录!')
            router.push('/login')
        }else{
            ElMessage.error('服务异常');
        }
        return Promise.reject(err);
    }
)

//导出request对象
export default request;

添加分类

category.js

//新增文章分类
export function saveCategoryAPI(params){
    return request.post('/category',params.value)
}

在ArticleCategory.vue中js部分

const dialogVisible = ref(false)//控制添加弹窗显示
const categoryForAdd = ref({})//添加分类对象
const saveCategory = async function () {
    let result = await saveCategoryAPI(categoryForAdd);
    //清空表单
    categoryForAdd.value = {}
    //关闭弹框
    dialogVisible.value = false
    //重新查询
    findAllCategory()
}

在ArticleCategory.vue中html部分

    <!-- 添加分类弹窗 -->
    <el-dialog v-model="dialogVisible" title="添加弹层" width="30%">
        <el-form label-width="100px" style="padding-right: 30px">
            <el-form-item label="分类名称">
                <el-input v-model="categoryForAdd.categoryName" minlength="1" maxlength="10"></el-input>
            </el-form-item>
            <el-form-item label="分类别名">
                <el-input v-model="categoryForAdd.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="saveCategory()"> 确认 </el-button>
            </span>
        </template>
    </el-dialog>

回显分类

在actegory.js新增主键查询方法

//根据id查询
export function findCategoryByIdAPI(id){
    return request.get('/category/'+id)}

点击修改,触发查询回显方法

<el-button :icon="Edit" circle plain type="primary" @click="openUpdateDialog(row.id)"></el-button>

发送请求,获取分类

//打开修改框
const dialogVisible2 = ref(false)//控制修改弹窗显示
const categoryForUpdate = ref({})//添加修改分类对象
const openUpdateDialog = async function(id){
    //弹层
    dialogVisible2.value = true
    //查询
    let result = await findCategoryByIdAPI(id);
    //回显
    categoryForUpdate.value = result.data;}

回显到页面上

    <!-- 修改分类弹窗 -->
    <el-dialog v-model="dialogVisible2" title="修改弹层" width="30%">
        <el-form label-width="100px" style="padding-right: 30px">
            <el-form-item label="分类名称">
                <el-input v-model="categoryForUpdate.categoryName" minlength="1" maxlength="10"></el-input>
            </el-form-item>
            <el-form-item label="分类别名">
                <el-input v-model="categoryForUpdate.categoryAlias" minlength="1" maxlength="15"></el-input>
            </el-form-item>
        </el-form>
        <template #footer>
            <span class="dialog-footer">
                <el-button @click="dialogVisible2 = false">取消</el-button>
                <el-button type="primary" @click="updateCategory()"> 确认 </el-button>
            </span>
        </template>
    </el-dialog>

category.js

//修改文章分类
export function updateCategoryAPI(params){
    return request.put('/category',params.value)}

修改分类

//修改分类
const updateCategory = async function () {
    let result = await updateCategoryAPI(categoryForUpdate);
    //关闭弹框
    dialogVisible2.value = false
    //重新查询
    findAllCategory()}

删除分类

触发删除方法

<el-button :icon="Delete" circle plain type="danger" @click="deleteCategory(row.id)"></el-button>

category.js

//根据id删除
export function deleteCategoryByIdAPI(id){
    return request.delete('/category/'+id)}

删除方法

//删除
const deleteCategory = async function (id) {
    ElMessageBox.confirm('你确认删除该分类信息吗?', '温馨提示',
        {
            confirmButtonText: '确认',
            cancelButtonText: '取消',
            type: 'warning',
        }
    ).then(async () => {
        //用户点击了确认
        let result = await deleteCategoryByIdAPI(id);
        findAllCategory()
        ElMessage({
            type: 'success',
            message: '删除成功',
        })
    }).catch(() => {
        //用户点击了取消
        ElMessage({
            type: 'info',
            message: '取消删除',
        })
    })
}

文章管理功能

文章列表

文章分类选项

//文章分类数据模型
const categorys = ref([])
const findAllCategory = async function () {
    let result = await findArticleCategoryListAPI();
    categorys.value = result.data;
}
findAllCategory();

分页查询

article.js

//导入请求工具类
import request from '@/util/request.js'

//分页列表查询
export function findArticleListByPageAPI(categoryId, state, pageNum, pageSize) {
    return request.get('/article', {
        params: {
            "categoryId": categoryId.value,
            "state": state.value,
            "pageNum": pageNum.value,
            "pageSize": pageSize.value,
        }
    });
}
页面渲染

<script setup>
import { findArticleListByPageAPI } from '@/api/article';
import { findArticleCategoryListAPI } from '@/api/category';
import {
    Edit,
    Delete, Plus
} from '@element-plus/icons-vue'
import { ref } from 'vue'
//import func from '../../../vue-temp/vue-editor-bridge';
//控制抽屉是否显示
const visibleDrawer = ref(false)
//添加表单数据模型
const articleModel = ref({
    title: '',
    categoryId: '',
    coverImg: '',
    content: '',
    state: ''
})


//文章分类数据模型
const categorys = ref([])
const categoryMap = new Map()
const findArticleCategoryList = async function(){
    let result = await findArticleCategoryListAPI()
    categorys.value = result.data

    for(let category of categorys.value){
        categoryMap.set(category.id,category.categoryName);
    }
}
findArticleCategoryList()


//用户搜索时选中的分类id
const categoryId = ref('')
//用户搜索时选中的发布状态
const state = ref('')



//分页条数据模型
const pageNum = ref(1)//当前页
const total = ref(20)//总条数
const pageSize = ref(3)//每页条数
const onSizeChange = (size) => {//当每页条数发生了变化,调用此函数
    //pageSize.value = size
    findArticleListByPage()
}
const onCurrentChange = (num) => {//当前页码发生变化,调用此函数
    //pageNum.value = num
    findArticleListByPage()
}

//文章列表数据模型
const articles = ref([])
const findArticleListByPage = async function(){
      let result =await findArticleListByPageAPI(categoryId,state,pageNum,pageSize)
      total.value = result.data.total
      for(let item of result.data.items){
        item.categoryName = categoryMap.get(item.categoryId)
      }
      articles.value = result.data.items
}
findArticleListByPage();

</script>
<template>
    <el-card class="page-container">
        <template #header>
            <div class="header">
                <span>文章管理</span>
                <div class="extra">
                    <el-button type="primary" @click="visibleDrawer = true">添加文章</el-button>
                </div>
            </div>
        </template>
        <!-- 搜索表单 -->
        <el-form inline>
            <el-form-item label="文章分类:" style="width: 250px;">
                <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="发布状态:" style="width: 250px;">
                <el-select placeholder="请选择" v-model="state">
                    <el-option label="已发布" value="1"></el-option>
                    <el-option label="草稿" value="0"></el-option>
                </el-select>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" @click="findArticleListByPage">搜索</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="categoryName"></el-table-column>
            <el-table-column label="发表时间" prop="createTime"> </el-table-column>
            <el-table-column label="状态" prop="state">
                <template #default="{ row }">
                    {{row.state == 1 ? '已发布' : '草稿'}}
                </template>
            </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>

    <!-- 抽屉 -->
    <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>
</template>
<style lang="scss" scoped>
.page-container {
    min-height: 100%;
    box-sizing: border-box;

    .header {
        display: flex;
        align-items: center;
        justify-content: space-between;
    }
}

/* 抽屉样式 */
.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;
    }
}
</style>

添加文章

富文本编辑器

文章内容需要使用到富文本编辑器,这里咱们使用一个开源的富文本编辑器 Quill

官网地址: VueQuill | Rich Text Editor Component for Vue 3

安装:

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>

图片上传

将来当点击+图标,选择本地图片后,el-upload这个组件会自动发送请求,把图片上传到指定的服务器上,而不需要我们自己使用axios发送异步请求,所以需要给el-upload标签添加一些属性,控制请求的发送

auto-upload:是否自动上传

action: 服务器接口路径

name: 上传的文件字段名

headers: 设置上传的请求头

on-success: 上传成功的回调函数

//获取token
const getToken = function(){
    return localStorage.getItem("BIG_EVENT_TOKEN")
}

//上传图片成功回调
const uploadSuccess = (img) => {
    //img就是后台响应的数据,格式为:{code:状态码,message:提示信息,data: 图片的存储地址}
    articleModel.value.coverImg=img.data
}
<el-form-item label="文章封面">
    <el-upload class="avatar-uploader" :show-file-list="false"
               :auto-upload="true"  action="/api/upload"
               :name="file" :headers="{'Authorization':getToken()}"
               :on-success = "uploadSuccess">
        <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>

保存文章实现

article.js中提供添加文章函数

//添加文章
export function saveArticleAPI(params){
    return request.post('/article',params.value)
}

为已发布和草稿按钮绑定事件

<el-form-item>
    <el-button type="primary" @click="addArticle('1')">发布</el-button>
    <el-button type="info" @click="addArticle('0')">草稿</el-button>
</el-form-item>

ArticleManage.vue中提供addArticle函数完成添加文章接口的调用

//添加文章
const addArticle = async function (state) {
    articleModel.value.state = state
    let result = await saveArticleAPI(articleModel);
    ElMessage.success(result.message ? result.message : '添加成功')
    //再次调用getArticles,获取文章
    findByPage()
    //隐藏抽屉
    visibleDrawer.value = false
}

作为AI助手,我需要先声明一下:WebRTC技术是一种实时通信的技术,用于浏览器之间的音视频通话、屏幕共享等场景。而Vue是一种前端框架,用于构建用户界面。 因此,WebRTC和Vue并不是一种直接相关的技术,但是可以通过Vue来构建WebRTC应用的用户界面。 下面是一个简单的WebRTC Vue案例: 1. 安装Vue和WebRTC相关的库 ``` npm install vue webrtc-adapter vue-webrtc ``` 2. 在Vue组件中引入WebRTC组件 ```html <template> <div> <video ref="localVideo" autoplay muted></video> <video ref="remoteVideo" autoplay></video> <button @click="startCall">Start Call</button> <button @click="endCall">End Call</button> </div> </template> <script> import VueWebRTC from 'vue-webrtc' export default { name: 'WebRTCComponent', components: { VueWebRTC }, data () { return { localStream: null, remoteStream: null, isCalling: false } }, methods: { async startCall () { this.isCalling = true this.localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }) this.$refs.localVideo.srcObject = this.localStream const rtcPeerConnection = new RTCPeerConnection({ iceServers: [ { urls: 'stun:stun.stunprotocol.org' } ] }) this.localStream.getTracks().forEach(track => rtcPeerConnection.addTrack(track, this.localStream)) rtcPeerConnection.addEventListener('icecandidate', event => { if (event.candidate) { // send ice candidate to remote peer } }) rtcPeerConnection.addEventListener('track', event => { this.remoteStream = event.streams[0] this.$refs.remoteVideo.srcObject = this.remoteStream }) // create offer and set local description const offer = await rtcPeerConnection.createOffer() await rtcPeerConnection.setLocalDescription(offer) // send offer to remote peer }, endCall () { // close peer connection and release stream resources this.isCalling = false this.localStream.getTracks().forEach(track => track.stop()) this.remoteStream.getTracks().forEach(track => track.stop()) const rtcPeerConnection = this.$refs.vueWebRTC.rtcPeerConnection if (rtcPeerConnection) { rtcPeerConnection.close() } } } } </script> ``` 3. 在Vue实例中使用WebRTC组件 ```js import Vue from 'vue' import WebRTCComponent from './WebRTCComponent.vue' new Vue({ el: '#app', components: { WebRTCComponent }, template: '<WebRTCComponent/>' }) ``` 在这个案例中,我们使用了vue-webrtc组件来简化WebRTC的使用。具体来说,我们在startCall方法中使用getUserMedia获取本地音视频流,创建RTCPeerConnection实例,并将本地流添加到peer connection中。然后,我们使用createOffer方法创建一个offer并将其设置为本地的SDP(Session Description Protocol)。最后,我们将offer发送给远程peer,并等待远程peer的answer。在answer到达之后,我们将其设置为远程SDP,并完成peer connection的建立。 需要注意的是,在实际应用中,我们需要处理各种事件,例如网络中断、peer connection失败等等。同时,我们还需要考虑如何安全地传输音视频流以及如何支持多人通话等场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啵啵薯条

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值