技能学习:学习使用Node.js + Vue.js,开发前端全栈网站-9.图片上传

简介: 之前我们做出了分类功能模块,同时在分类中实现了无限级分类关联;又制作文章功能模块,利用文章做出了多个分类的关联。本篇文章我们再建立一个技能模块,用来讲解图片的上传功能实现。
1.根据文章模块实现流程制作出技能模块

在这里插入图片描述
SkillSet.vue:

<template>
    <div>
        <h1>{{id ? '编辑' : '创建'}}技能</h1>
        <el-form label-width="80px" style="margin-top:20px;" @submit.native.prevent="save">
            <el-form-item label="所属分类">
                <el-select v-model="model.categories" multiple>
                    <el-option v-for="item in categories" :key="item._id" :label="item.name" :value="item._id"></el-option>
                </el-select>
            </el-form-item>
            <el-form-item label="技能名称">
                <el-input v-model="model.name"></el-input>
            </el-form-item>
            <el-form-item label="技能介绍">
                <el-input v-model="model.introduce"></el-input>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" native-type="submit">保存</el-button>
            </el-form-item>
        </el-form>
    </div>
</template>
<script>
export default {
    props: {
        id: {}
    },
    data(){
        return {
            model: {},
            categories: [],
        }
    },
    methods: {
        async save(){
            let res
            if(this.id){
                res = await this.$http.put('rest/skills/' + this.id, this.model)
            }else{
                res = await this.$http.post('rest/skills', this.model)
            }
            console.log("en?",res)
            this.$router.push('/skills/list')
            this.$message({
                type: 'success',
                message: '保存成功'
            })
        },
        async fetch(){
            const res = await this.$http.get('rest/skills/' + this.id)
            this.model = res.data
        },
        async fetchCategories(){
            const res = await this.$http.get('rest/categories')
            this.categories = res.data
        }
    },
    created(){
        this.id && this.fetch()
        this.fetchCategories()
    }
}
</script>

SkillList.vue:

<template>
    <div>
        <h1>技能列表</h1>
        <el-table :data="items">
            <el-table-column prop="_id" label="ID" width="220">
            </el-table-column>
            <!-- <el-table-column prop="categories[0].name,categories[1].name" label="所属分类">
                <template slot-scope="scope"> {{scope.row.categories[0].name}},{{scope.row.categories[1].name}} </template>
            </el-table-column> -->
            <el-table-column prop="name" label="文章标题">
            </el-table-column>
            <el-table-column
            fixed="right"
            label="操作"
            width="100">
                <template slot-scope="scope">
                    <el-button type="text" size="small" @click="$router.push('/skills/edit/' + scope.row._id)">编辑</el-button>
                    <el-button @click="remove(scope.row)" type="text" size="small">删除</el-button>
                </template>
            </el-table-column>
        </el-table>
    </div>
</template>
<script>
export default {
    data() {
        return {
            items: []
        }
    },
    methods: {
        async fetch(){
            const res = await this.$http.get('rest/skills')
            this.items = res.data
        },
        remove(row){
            this.$confirm('是否确定要删除文章"' + row.name + '"?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(async () => {
                // 要想使用await,函数必须使用async
                // await异步执行,待调用接口获取数据完成后再将值传给res,进行下一步操作
                const res = await this.$http.delete('rest/skills/' + row._id)
                this.$message({
                    type: 'success',
                    message: '删除成功!'
                });
                if(res.status == 200){
                    // 接口调用成功后,刷新页面
                    this.fetch()
                }
            }).catch(() => {
                this.$message({
                    type: 'info',
                    message: '已取消删除'
                });          
            });
        }
    },
    created() {
        this.fetch()
    }
}
</script>

在这里插入图片描述
在这里插入图片描述

2.使用elementUI放入图片上传组件

在这里插入图片描述
在技能介绍放置修改后的图片上传模块:

<el-form-item label="图标上传">
    <!-- 动态获取接口地址,所以action加冒号 -->
    <el-upload
        class="avatar-uploader"
        :action="$http.defaults.baseURL + '/upload'"
        :show-file-list="false"
        :on-success="handleAvatarSuccess"
        :before-upload="beforeAvatarUpload">
        <img v-if="model.icon" :src="model.icon" class="avatar">
        <i v-else class="el-icon-plus avatar-uploader-icon"></i>
    </el-upload>
</el-form-item>

下方引入效果样式style:

<style>
  .avatar-uploader .el-upload {
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
  }
  .avatar-uploader .el-upload:hover {
    border-color: #409EFF;
  }
  .avatar-uploader-icon {
    font-size: 28px;
    color: #8c939d;
    width: 178px;
    height: 178px;
    line-height: 178px;
    text-align: center;
  }
  .avatar {
    width: 178px;
    height: 178px;
    display: block;
  }
</style>

在methods中添加图片的上传之前和成功之后两个事件:

// 图片上传成功之后
handleAvatarSuccess(res) {
    console.log(res)
},
// 图片上传之前的验证
beforeAvatarUpload(file) {
    console.log(file)
    const isJPG = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg' || file.type === 'image/gif' ;
    const isLt2M = file.size / 1024 / 1024 < 2;

    if (!isJPG) {
    this.$message.error('上传格式必须为常用图片格式,如png,jpg,gif等');
    }
    if (!isLt2M) {
    this.$message.error('上传图标图片大小不能超过 2MB!');
    }
    return isJPG && isLt2M;
}

测试,现在没有编写upload接口,binary表示二进制文件,file是图片数据名:
在这里插入图片描述

3.编写上传接口upload

server/routes/admin/index.js:
上传图片接口不属于之前做的通用CRUD接口rest,在最下方另开一个app.post用于新建图片上传接口地址。
在这里插入图片描述
由于express不能获取到文件上传的数据,所以我们要使用获取文件数据的类包multer,定义在中间件中使用:

cd server
npm i multer

在这里插入图片描述
使用multer,并定义中间件:

// 引入获取文件数据的multer包
const multer = require('multer')
// 定义中间件upload
// dest目标地址,__dirname表示当前文件,要以当前文件为准找到我们想要把图片保存到的文件夹,
// 我把uploads文件夹新建到了server/uploads
const upload = multer({dest: __dirname + '/../../uploads'})
// 图片上传接口
// file是前台传入调用图片上传接口upload时formdata里边的数据名
app.post('/admin/api/upload', upload.single('file'), async(req, res) => {

})

完善图片上传接口:

app.post('/admin/api/upload', upload.single('file'), async(req, res) => {
    // 使用中间件后,multer将数据赋值到req中,否则req不能调取file
    const file = req.file
    res.send(file)
})

此时,上传图片就可以把图片文件保存到指定的uploads文件夹中了,测试:
在这里插入图片描述
在这里插入图片描述
图片上传成功,没问题。

4.将文件路径返回给前台数据。

看一下接口成功后的信息,这些信息就是利用multer将值传到req的file数据:
在这里插入图片描述
其中filename就是我们保存下来文件的文件名,所以我们要访问到图片的话,就要通过接口地址再加上这个filename,也就是给图片添加路由地址。因为在我们学习路由的时候就发现,在node.js中与其他后端不同,这里的文件地址都是由我们自己定义的,而不是真实路由,这也就是node.js路由的弊端吧。
(1)在server端index.js定义路由,找到uploads文件夹的静态真实地址,定义路由地址
在这里插入图片描述
(2)定义图片的路由地址,将路由地址放入准备输出的file中,从而方便前台调用查询
在这里插入图片描述
再测试一下,查看url能否正常生成:
在这里插入图片描述
没问题。
在这里插入图片描述
打印出的res也没问题.
(3)把url在前端页面显示
在这里插入图片描述
再次进行测试:
在这里插入图片描述
没问题。检查一下数据的绑定:
在这里插入图片描述
也没问题。
(4)完善保存按钮接口
由于我们使用了通用CRUD接口,所以只完善skill模型即可,将绑定的数据在server/models/Skill.js都定义好字段和类型。

const mongoose = require('mongoose')

const schema = new mongoose.Schema({
    name: { type: String },
    categories: [{ type: mongoose.SchemaTypes.ObjectId, ref: 'Category' }],
    introduce: { type: String },
    icon: { type: String },
})

module.exports = mongoose.model('Skill', schema)

测试:
在这里插入图片描述
在这里插入图片描述
没问题。
(5)技能列表页面显示图标。
同样使用elsmentUI中的效果,发现一个有意思的:
在这里插入图片描述
改动技能列表SkillList.vue:

<el-table-column prop="name" label="技能名称" width="120">
    <template slot-scope="scope">
        <el-popover trigger="hover" placement="top">
            <img :src="scope.row.icon" width="140">
            <div slot="reference" class="name-wrapper">
                <el-tag size="medium">{{ scope.row.name }}</el-tag>
            </div>
        </el-popover>
    </template>
</el-table-column>

在这里插入图片描述

相关文章
|
10月前
|
JSON 自然语言处理 前端开发
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
571 72
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
|
10月前
|
资源调度 JavaScript 前端开发
前端开发必备!Node.js 18.x LTS保姆级安装教程(附国内镜像源配置)
本文详细介绍了Node.js的安装与配置流程,涵盖环境准备、版本选择(推荐LTS版v18.x)、安装步骤(路径设置、组件选择)、环境验证(命令测试、镜像加速)及常见问题解决方法。同时推荐开发工具链,如VS Code、Yarn等,并提供常用全局包安装指南,帮助开发者快速搭建高效稳定的JavaScript开发环境。内容基于官方正版软件,确保合规性与安全性。
10243 24
|
JavaScript 前端开发 jenkins
抛弃node和vscode,如何用记事本开发出一个完整的vue前端项目
本文探讨了在不依赖Node和VSCode的情况下,仅使用记事本和浏览器开发一个完整的Vue3前端项目的方法。通过CDN引入Vue、Vue Router、Element-UI等库,直接编写HTML文件实现页面功能,展示了前端开发的本质是生成HTML。虽然日常开发离不开现代工具,但掌握这种基础方法有助于快速实现想法或应对特殊环境限制。文章还介绍了如何用Node简单部署HTML文件到服务器,提供了一种高效、轻量的开发思路。
301 10
|
存储 设计模式 JavaScript
Vue 组件化开发:构建高质量应用的核心
本文深入探讨了 Vue.js 组件化开发的核心概念与最佳实践。
970 1
|
前端开发 JavaScript 关系型数据库
基于 Vue2.0 + Nest.js 全栈开发的后台应用
Vue2 Admin 是一个基于 Vue2 和 Ant Design Pro 开发的前端项目,配合 Nest.js 构建的后端,提供了一个完整的全栈后台应用解决方案。该项目支持动态国际化、用户权限管理、操作日志记录等功能,适合全栈开发者学习参考。线上预览地址:https://vue2.baiwumm.com/,用户名:Admin,密码:abc123456。
240 0
基于 Vue2.0 + Nest.js 全栈开发的后台应用
|
JavaScript
Vue基础知识总结 4:vue组件化开发
Vue基础知识总结 4:vue组件化开发
|
JavaScript 前端开发 持续交付
构建现代Web应用:Vue.js与Node.js的完美结合
【10月更文挑战第22天】随着互联网技术的快速发展,Web应用已经成为了人们日常生活和工作的重要组成部分。前端技术和后端技术的不断创新,为Web应用的构建提供了更多可能。在本篇文章中,我们将探讨Vue.js和Node.js这两大热门技术如何完美结合,构建现代Web应用。
838 4
|
JavaScript 前端开发 测试技术
组件化开发:创建可重用的Vue组件
【10月更文挑战第21天】组件化开发:创建可重用的Vue组件
170 1
|
JavaScript 前端开发
vue全局公共组件自动引入并注册,开发效率直接起飞!
【10月更文挑战第14天】vue全局公共组件自动引入并注册,开发效率直接起飞!
426 1
|
存储 前端开发 中间件
vue3之vite配置vite-plugin-mock使用mock轻松创建模拟数据提高开发效率
vue3之vite配置vite-plugin-mock使用mock轻松创建模拟数据提高开发效率
2189 0