天天看點

技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

1.根據文章子產品實作流程制作出技能子產品

技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳
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>           
技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳
技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

2.使用elementUI放入圖檔上傳元件

技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

在技能介紹放置修改後的圖檔上傳子產品:

<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是圖檔資料名:

技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

3.編寫上傳接口upload

server/routes/admin/index.js:

上傳圖檔接口不屬于之前做的通用CRUD接口rest,在最下方另開一個app.post用于建立圖檔上傳接口位址。

技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

由于express不能擷取到檔案上傳的資料,是以我們要使用擷取檔案資料的類包multer,定義在中間件中使用:

cd server           
npm i multer           
技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

使用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檔案夾中了,測試:

技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳
技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

圖檔上傳成功,沒問題。

4.将檔案路徑傳回給前台資料。

看一下接口成功後的資訊,這些資訊就是利用multer将值傳到req的file資料:

技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

其中filename就是我們儲存下來檔案的檔案名,是以我們要通路到圖檔的話,就要通過接口位址再加上這個filename,也就是給圖檔添加路由位址。因為在我們學習路由的時候就發現,在node.js中與其他後端不同,這裡的檔案位址都是由我們自己定義的,而不是真實路由,這也就是node.js路由的弊端吧。

(1)在server端index.js定義路由,找到uploads檔案夾的靜态真實位址,定義路由位址

技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

(2)定義圖檔的路由位址,将路由位址放入準備輸出的file中,進而友善前台調用查詢

技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

再測試一下,檢視url能否正常生成:

技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

沒問題。

技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

列印出的res也沒問題.

(3)把url在前端頁面顯示

技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

再次進行測試:

技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

沒問題。檢查一下資料的綁定:

技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

也沒問題。

(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)           

測試:

技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳
技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

(5)技能清單頁面顯示圖示。

同樣使用elsmentUI中的效果,發現一個有意思的:

技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

改動技能清單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>           
技能學習:學習使用Node.js + Vue.js,開發前端全棧網站-9.圖檔上傳

繼續閱讀