iT邦幫忙

2023 iThome 鐵人賽

DAY 22
0

Hi,大家好,又到了連續假期了,總算可以脫離幾天上班的生活了XD,昨天我們完成了附件上傳的機制,使用 dataURL 的機制,傳完之後,當然就是要可以在畫面上看到附件了,今天就讓我們來完成這項功能吧。

前置作業說明

在這裡我們要先進行檔案架構的說明,因為是非正式性的專案,我們的附件是先放在程式碼的存放位置,並以 caseID 做為資料夾名稱,格式為 png 檔,另外因為我們會進行自動縮圖,因此必需安裝好縮圖用的程式,安裝方式如下

  1. 連接到imagemagick官網 ,下載imagemagick並安裝套件,這是一個很常用且功能強大的圖檔轉換的元件
  2. 打開vscode,點擊檢視->終端機,輸入 npm i imagemagick save ,下載 node.js的 imagemagick 程式庫

取得附件清單

取得附件的方式是直接去掃指定資料夾中的 png 檔,因為我們之前已經完成了抓取詳細資料的功能,我們這次只要針對該功能加入抓取附件清單的機制即可,修正之程式碼如下

    /**
     * 取得客服案件詳細資料
     * @param {string} guid 客服紀錄編號
     */
    getSingleCase: async (guid) => {
        //擴充原先的取得詳細資料的功能,加入取出圖檔清單之機制
        let data = await cases.findOne({
            where: {
                caseid: guid
            },
            include: [circuit, casefile]
        });
        let result = {}
        if (data) {
            data.dataValues.casestatus_desc = getStatusDesc(data.dataValues.casestatus)
            result = data.dataValues
            for (let i = 0; i < result.circuits.length; i++) {
                let obj = result.circuits[i].dataValues
                obj.status_desc = getStatusDesc(obj.status)
            }
            //搜尋資料夾中的檔案
            result.casefiles = []
            let files = fs.readdirSync(path.join(__dirname, guid))
            for (let i = 0; i < files.length; i++) {
                //排除掉非png檔與檔名為 xxx.png.png 之檔案
                if (path.extname(files[i]) != ".png" || path.basename(files[i]).indexOf(".png.png") >= 0) {
                    continue;
                }
                let obj = {}
                //將檔名轉為 base64格式,做為搜尋網址列參數與檔案之依據
                obj.fileid = new Buffer.from(path.join(guid, files[i])).toString("base64")
                //縮圖網址
                obj.thomb = "/saf/thomb/" + obj.fileid
                //全圖大小網址
                obj.url = "/saf/viewpic/" + obj.fileid
                result.casefiles.push(obj)
            }
        }
        return result
    },

說明:

  1. 我們會產出縮圖,其檔名規則為原圖檔名 + .png,亦即原圖為 XXX.png,縮圖為 XXX.png.png,所以掃資料夾的圖檔時要排除掉非png 與縮圖
  2. 將附件檔名與資料夾名稱組合後,做為 fileid 回傳給前端,該編號是提共給瀏覽器呼叫圖檔之用

實際讀取圖檔之功能製作

接下來要配合剛剛訂定的縮圖網址與原圖網址製作對應的 router,打開 routers/saf.js 後,加入下列程式

/**
 * 顯示原圖
 */
router.get("/viewpic/:guid", async (req, res, next) => {
    try {
        let guid = req.params.guid
        let file = await casedao.showfile(guid, false)
        res.setHeader("Content-Type", mime.getType(file.filename))
        res.send(file.buf)
    } catch(err) {
        res.status(500).send(err.message || err)
    }
})

/**
 * 顯示縮圖
 */
router.get("/thomb/:guid", async (req, res, next) => {
    try {
        let guid = req.params.guid
        let file = await casedao.showfile(guid, true)
        res.setHeader("Content-Type", "image/png")
        res.send(file.buf)
    } catch(err) {
        res.status(500).send(err.message || err)
    }
})

接下來撰寫讀檔與縮圖功能,打開 casedao.js 後,加上讀圖與縮圖之機制,在這邊我們需要先安裝好縮圖用的套件

const im = require('imagemagick');
/**
 * 產生縮圖
 * @param {string} src 
 */
async function createThomb(src) {
    return new Promise((resolve, reject) => {
        im.resize({
            srcPath: src + "[0]",
            dstPath: src + ".png",
            width: 150
        }, function (err, stdout, stderr) {
            if (err) {
                reject(err);
            } else {
                resolve();
            }
        });
    })
}

    /**
     * 讀取圖檔
     * @param {string} guid 檔嬣編號
     * @param {boolean} isthomb 是否為縮圖
     */
    showfile: async (guid, isthomb) => {
        let src = new Buffer.from(guid, "base64").toString("utf-8")
        src = path.join(__dirname, src)
        let result = {}
        result.filename = path.basename(src)
        if (!isthomb) {
            result.buf = fs.readFileSync(src)
        } else {
            let thombfile = src + ".png"
            try {
                fs.accessSync(thombfile)
            } catch (err) {
                await createThomb(src)
            }
            result.buf = fs.readFileSync(thombfile)
        }
        return result
    },

說明:

  1. 原圖與縮圖其實做法相同,都是去讀取檔案後,將讀好的檔案透過 res.send 的方式丟給瀏覽器,在這邊是分開2個不同的 router,實際上也可以設定成同一個 router,以網址列參數進行區隔
  2. 取得圖檔的動作,主要就是接收網址列傳入的 guid 參數,並將其進行 base64 解碼後,組合成為檔名後,透過 fs.readFileSync 將檔案內容抓出來
  3. 若要找的是縮圖,在組合檔名時,會組合成為縮圖檔名,此時會先透過 fs.accessSync 進行檔案是否存在的檢查,若是出現錯誤,表示檔案不存在,此時呼叫 imagemagick 產好縮圖後,再按照程序讀檔後回傳

顯示頁面的調整

接下來打開之前的詳細資料頁面「case.ejs」,加上圖檔顯示之功能,程式碼如下

<div id="app" class="container">
  <h3>問題描述</h3>
  <div class="row dr tr">
    <div class="col-2 title txtr">問題名稱:</div>
    <div class="col-10 info">{{data.cases.casedesc}}</div>
  </div>
  <div class="row dr">
    <div class="col-2 title txtr">問題描述:</div>
    <div class="col-10 info">{{data.cases.caseinfo}}</div>
  </div>
  <div class="row dr">
    <div class="col-12 info tc" v-if="data.cases.casefiles.length >= 1">附件</div>
  </div>
  <div class="row dr info" v-if="data.cases.casefiles.length >= 1">
  <div class="col-2 tc" style="margin-top: 10px; margin-bottom: 10px;"
    v-for="(file, index) in data.cases.casefiles">
    <label>附件 {{index + 1}}</label><br />
    <a v-bind:href="file.url" target="_blank">
      <img v-bind:src="file.thomb" />
    </a>
  </div>
  </div>
  <div class="row dr">
    <div class="col-2 title txtr">聯絡人:</div>
    <div class="col-6 info">{{data.cases.fromper}} <a
        href="mailto://{{data.cases.frommail}}">{{data.cases.frommail}}</a></div>
    <div class="col-2 title txtr">聯絡電話:</div>
    <div class="col-2 info">{{data.cases.fromtel}}</div>
  </div>
  <div class="row dr">
    <div class="col-2 title txtr">登錄時間:</div>
    <div class="col-8 info">{{data.cases.logdate}}</div>
    <div class="col-2" v-bind:class="[data.casestatus == '10'? 'info': 'inpcc']">{{data.cases.casestatus_desc}}</div>
  </div>
  <hr />
  以下省略…

說明:

  1. 在剛剛的資料取得功能中,我們將檔案資訊取出後,放在 casefiles 陣列中,陣列元素包含了縮圖網址與原圖網址,因此我們會在頁面上的預定顯示圖檔的地方,加上顯示用的 div,並利用「v-if="data.cases.casefiles.length >= 1"」判斷若是 casefiles 有元素時才顯示
  2. 利用 「v-for="(file, index) in data.cases.casefiles"」將檔案清單列出在 div 中,並製作縮圖的影像元件與原圖的超連結

完成畫面

結語

今天完成了檔案檢視的功能,為了方便使用者快速瀏覽,我們還加上了縮圖的機制,透過 imagemagick,除了一般圖檔,還可以做出 pdf 的縮圖,是一個很強大的軟體。目前完成了資料的登錄、附件的上傳、瀏覽,明天要處理的是處理流程的輸入與通知,那麼,我們明天再繼續吧


上一篇
加入檔案上傳功能
下一篇
加入流程控管機制
系列文
以vue.js + node.js 搭建一個客服填單系統30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言