电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術(shù)文章
文章詳情頁

基于vue-simple-uploader封裝文件分片上傳、秒傳及斷點(diǎn)續(xù)傳的全局上傳插件功能

瀏覽:3日期:2022-10-05 08:50:08
1. 前言

之前公司要在管理系統(tǒng)中做一個(gè)全局上傳插件,即切換各個(gè)頁面的時(shí)候,上傳界面還在并且上傳不會受到影響,這在vue這種spa框架面前并不是什么難題。然而后端大佬說我們要實(shí)現(xiàn)分片上傳、秒傳以及斷點(diǎn)續(xù)傳的功能,聽起來頭都大了。

很久之前我寫了一篇webuploader的文章,結(jié)果使用起來發(fā)現(xiàn)問題很多,且官方團(tuán)隊(duì)不再維護(hù)這個(gè)插件了, 經(jīng)過多天調(diào)研及踩雷,最終決定基于vue-simple-uploader插件實(shí)現(xiàn)該功能,在項(xiàng)目中使用起來無痛且穩(wěn)定。

如果你只是想實(shí)現(xiàn)基本的(非定制化的)上傳功能,直接使用vue-simple-uploader,多讀一下它的文檔,不需要更多的二次封裝。如果你只是想實(shí)現(xiàn)全局上傳插件,也可以參照一下我的實(shí)現(xiàn)。如果你用到了分片上傳、秒傳及斷點(diǎn)續(xù)傳這些復(fù)雜的功能,恭喜你,這篇文章的重點(diǎn)就在于此。

本文源碼在此:https://github.com/shady-xia/Blog/tree/master/vue-simple-uploader

基于vue-simple-uploader封裝文件分片上傳、秒傳及斷點(diǎn)續(xù)傳的全局上傳插件功能

2. 關(guān)于vue-simple-uploader

vue-simple-uploader是基于 simple-uploader.js 封裝的vue上傳插件。它的優(yōu)點(diǎn)包括且不限于以下幾種:

支持文件、多文件、文件夾上傳;支持拖拽文件、文件夾上傳 可暫停、繼續(xù)上傳 錯(cuò)誤處理 支持“秒傳”,通過文件判斷服務(wù)端是否已存在從而實(shí)現(xiàn)“秒傳” 分塊上傳 支持進(jìn)度、預(yù)估剩余時(shí)間、出錯(cuò)自動重試、重傳等操作

讀這篇文章之前,建議先讀一遍simple-uploader.js的文檔,然后再讀一下vue-simple-uploader的文檔,了解一下各個(gè)參數(shù)的作用是什么,我在這里假定大家已經(jīng)比較熟悉了。。vue-simple-uploader文檔

simple-uploader.js文檔

安裝:npm install vue-simple-uploader --save使用:在main.js中:

import uploader from ’vue-simple-uploader’Vue.use(uploader)3. 基于vue-simple-uploader封裝全局上傳組件

引入vue-simple-uploader后,我們開始封裝全局的上傳組件globalUploader.vue,代碼比較長,就不整個(gè)放出來了,源碼放到github上了,這里一步一步地講解。

template部分如下,本人自定義了模板和樣式,所以html部分比較長,css部分暫時(shí)不列出,大家可以根據(jù)自己的ui去更改,主要關(guān)注一下uploader這個(gè)組件的options參數(shù)及文件added、success、progress、error幾個(gè)事件:

<template> <div id='global-uploader'> <!-- 上傳 --> <uploader ref='uploader' :options='options' :autoStart='false' @file-added='onFileAdded' @file-success='onFileSuccess' @file-progress='onFileProgress' @file-error='onFileError' class='uploader-app'> <uploader-unsupport></uploader-unsupport> <uploader-btn :attrs='attrs' ref='uploadBtn'>選擇文件</uploader-btn> <uploader-list v-show='panelShow'> <div slot-scope='props' :class='{’collapse’: collapse}'> <div class='file-title'> <h2>文件列表</h2> <div class='operate'> <el-button @click='fileListShow' type='text' :title='collapse ? ’展開’:’折疊’ '> <i :class='collapse ? ’icon-fullscreen’: ’icon-minus-round’'></i> </el-button> <el-button @click='close' type='text'> <i class='iconfont icon-close'></i> </el-button> </div> </div> <ul class='file-list'> <li v-for='file in props.fileList' :key='file.id'> <uploader-file : ref='files' :file='file' :list='true'></uploader-file> </li> <div v-if='!props.fileList.length'><i class='nucfont inuc-empty-file'></i> 暫無待上傳文件</div> </ul> </div> </uploader-list> </uploader> </div></template>

組件中的data部分:

data() { return { options: { target: ’http://xxxxx/xx’, // 目標(biāo)上傳 URL chunkSize: ’2048000’, //分塊大小 fileParameterName: ’file’, //上傳文件時(shí)文件的參數(shù)名,默認(rèn)file maxChunkRetries: 3, //最大自動失敗重試上傳次數(shù) testChunks: true, //是否開啟服務(wù)器分片校驗(yàn) // 服務(wù)器分片校驗(yàn)函數(shù),秒傳及斷點(diǎn)續(xù)傳基礎(chǔ) checkChunkUploadedByResponse: function (chunk, message) { let objMessage = JSON.parse(message); if (objMessage.skipUpload) { return true; } return (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0 }, headers: { // 在header中添加的驗(yàn)證,請根據(jù)實(shí)際業(yè)務(wù)來 Authorization: 'Bearer ' + Ticket.get().access_token }, }, attrs: { // 接受的文件類型,形如[’.png’, ’.jpg’, ’.jpeg’, ’.gif’, ’.bmp’...] 這里我封裝了一下 accept: ACCEPT_CONFIG.getAll() }, panelShow: false, //選擇文件后,展示上傳panel }},

全局引用:在app.vue中引用,即作為全局的組件一直存在,只不過在不使用的時(shí)候把上傳界面隱藏了

<global-uploader></global-uploader>4. 文件上傳流程概覽

1. 點(diǎn)擊按鈕,觸發(fā)文件上傳操作:

(如果你做的不是全局上傳的功能,而是直接點(diǎn)擊上傳,忽略這一步。)

因?yàn)槲易龅氖侨稚蟼鞯牟寮劝焉蟼鞯拇翱陔[藏起來,在點(diǎn)擊某個(gè)上傳按鈕的時(shí)候,用Bus發(fā)送一個(gè)openUploader的事件,在globalUploader.vue中接收該事件,trigger我們uploader-btn的click事件。

在某個(gè)頁面中,點(diǎn)擊上傳按鈕,同時(shí)把要給后臺的參數(shù)帶過來(如果有的話),這里組件之間傳值我用的event bus,當(dāng)然用vuex會更好:

Bus.$emit(’openUploader’, { superiorID: this.superiorID})

在globalUploader.vue中接收該事件:

Bus.$on(’openUploader’, query => { this.params = query || {}; if (this.$refs.uploadBtn) { // 這樣就打開了選擇文件的操作窗口 $(’#global-uploader-btn’).click(); }});

2. 選擇文件后,將上傳的窗口展示出來,開始md5的計(jì)算工作

onFileAdded(file) { this.panelShow = true;// 計(jì)算MD5,下文會提到 this.computeMD5(file);},

這里有個(gè)前提,我在uploader中將autoStart設(shè)為了false,為什么要這么做?

在選擇文件之后,我要計(jì)算MD5,以此來實(shí)現(xiàn)斷點(diǎn)續(xù)傳及秒傳的功能,所以選擇文件后直接開始上傳肯定不行,要等MD5計(jì)算完畢之后,再開始文件上傳的操作。

具體的MD5計(jì)算方法,會在下面講,這里先簡單引出。

上傳過程中,會不斷觸發(fā)file-progress上傳進(jìn)度的回調(diào)

// 文件進(jìn)度的回調(diào)onFileProgress(rootFile, file, chunk) { console.log(`上傳中 ${file.name},chunk:${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`)},3. 文件上傳成功后

文件上傳成功后,在“上傳完成”的回調(diào)中,通過服務(wù)端返回的needMerge字段,來判斷是否需要再發(fā)送合并分片的請求,如果這個(gè)字段為true,則需要給后臺發(fā)一個(gè)請求合并的ajax請求,否則直接上傳成功。

注意:這里的needMerge是我和后臺商議決定的字段名

onFileSuccess(rootFile, file, response, chunk) { let res = JSON.parse(response); // 服務(wù)器自定義的錯(cuò)誤,這種錯(cuò)誤是Uploader無法攔截的 if (!res.result) { this.$message({ message: res.message, type: ’error’ }); return }// 如果服務(wù)端返回需要合并 if (res.needMerge) { api.mergeSimpleUpload({ tempName: res.tempName, fileName: file.name, ...this.params, }).then(data => { // 文件合并成功 Bus.$emit(’fileSuccess’, data); }).catch(e => {}); // 不需要合并 } else { Bus.$emit(’fileSuccess’, res); console.log(’上傳成功’); }},onFileError(rootFile, file, response, chunk) {console.log(error)},5. 文件分片

vue-simple-uploader自動將文件進(jìn)行分片,在options的chunkSize中可以設(shè)置每個(gè)分片的大小。

如圖:對于大文件來說,會發(fā)送多個(gè)請求,在設(shè)置testChunks為true后(在插件中默認(rèn)就是true),會發(fā)送與服務(wù)器進(jìn)行分片校驗(yàn)的請求,下面的第一個(gè)get請求就是該請求;后面的每一個(gè)post請求都是上傳分片的請求

基于vue-simple-uploader封裝文件分片上傳、秒傳及斷點(diǎn)續(xù)傳的全局上傳插件功能

看一下發(fā)送給服務(wù)端的參數(shù),其中chunkNumber表示當(dāng)前是第幾個(gè)分片,totalChunks代表所有的分片數(shù),這兩個(gè)參數(shù)都是都是插件根據(jù)你設(shè)置的chunkSize來計(jì)算的。

基于vue-simple-uploader封裝文件分片上傳、秒傳及斷點(diǎn)續(xù)傳的全局上傳插件功能

需要注意的就是在最后文件上傳成功的事件中,通過后臺返回的字段,來判斷是否要再給后臺發(fā)送一個(gè)文件合并的請求。

6. MD5的計(jì)算過程

斷點(diǎn)續(xù)傳及秒傳的基礎(chǔ)是要計(jì)算文件的MD5,這是文件的唯一標(biāo)識,然后服務(wù)器根據(jù)MD5進(jìn)行判斷,是進(jìn)行秒傳還是斷點(diǎn)續(xù)傳。

在file-added事件之后,就計(jì)算MD5,我們最終的目的是將計(jì)算出來的MD5加到參數(shù)里傳給后臺,然后繼續(xù)文件上傳的操作,詳細(xì)的思路步驟是:

把uploader組件的autoStart設(shè)為false,即選擇文件后不會自動開始上傳 先通過 file.pause()暫停文件,然后通過H5的FileReader接口讀取文件 將異步讀取文件的結(jié)果進(jìn)行MD5,這里我用的加密工具是spark-md5,你可以通過npm install spark-md5 --save來安裝,也可以使用其他MD5加密工具。 file有個(gè)屬性是uniqueIdentifier,代表文件唯一標(biāo)示,我們把計(jì)算出來的MD5賦值給這個(gè)屬性 file.uniqueIdentifier = md5,這就實(shí)現(xiàn)了我們最終的目的。 通過file.resume()開始/繼續(xù)文件上傳。

/*** 計(jì)算md5,實(shí)現(xiàn)斷點(diǎn)續(xù)傳及秒傳* @param file*//*** 計(jì)算md5,實(shí)現(xiàn)斷點(diǎn)續(xù)傳及秒傳* @param file*/ computeMD5(file) { let fileReader = new FileReader(); let time = new Date().getTime(); let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; let currentChunk = 0; const chunkSize = 10 * 1024 * 1000; let chunks = Math.ceil(file.size / chunkSize); let spark = new SparkMD5.ArrayBuffer(); // 文件狀態(tài)設(shè)為'計(jì)算MD5' this.statusSet(file.id, ’md5’); file.pause(); loadNext(); fileReader.onload = (e => { spark.append(e.target.result); if (currentChunk < chunks) { currentChunk++; loadNext(); // 實(shí)時(shí)展示MD5的計(jì)算進(jìn)度 this.$nextTick(() => { $(`.myStatus_${file.id}`).text(’校驗(yàn)MD5 ’+ ((currentChunk/chunks)*100).toFixed(0)+’%’) }) } else { let md5 = spark.end(); this.computeMD5Success(md5, file); console.log(`MD5計(jì)算完畢:${file.name} nMD5:${md5} n分片:${chunks} 大小:${file.size} 用時(shí):${new Date().getTime() - time} ms`); } }); fileReader.onerror = function () { this.error(`文件${file.name}讀取出錯(cuò),請檢查該文件`) file.cancel(); }; function loadNext() { let start = currentChunk * chunkSize; let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize; fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end)); }},computeMD5Success(md5, file) { // 將自定義參數(shù)直接加載uploader實(shí)例的opts上 Object.assign(this.uploader.opts, { query: { ...this.params, } }) file.uniqueIdentifier = md5; file.resume(); this.statusRemove(file.id);},

給file的uniqueIdentifier 屬性賦值后,請求中的identifier即是我們計(jì)算出來的MD5

基于vue-simple-uploader封裝文件分片上傳、秒傳及斷點(diǎn)續(xù)傳的全局上傳插件功能

7. 秒傳及斷點(diǎn)續(xù)傳

在計(jì)算完MD5后,我們就能談斷點(diǎn)續(xù)傳及秒傳的概念了。

服務(wù)器根據(jù)前端傳過來的MD5去判斷是否可以進(jìn)行秒傳或斷點(diǎn)續(xù)傳:

a. 服務(wù)器發(fā)現(xiàn)文件已經(jīng)完全上傳成功,則直接返回秒傳的標(biāo)識。 b. 服務(wù)器發(fā)現(xiàn)文件上傳過分片信息,則返回這些分片信息,告訴前端繼續(xù)上傳,即斷點(diǎn)續(xù)傳。7.1 對于前端來說

在每次上傳過程的最開始,vue-simple-uploader會發(fā)送一個(gè)get請求,來問服務(wù)器我哪些分片已經(jīng)上傳過了,

基于vue-simple-uploader封裝文件分片上傳、秒傳及斷點(diǎn)續(xù)傳的全局上傳插件功能

這個(gè)請求返回的結(jié)果也有幾種可能:

a. 如果是秒傳,在請求結(jié)果中會有相應(yīng)的標(biāo)識,比如我這里是skipUpload為true,且返回了url,代表服務(wù)器告訴我們這個(gè)文件已經(jīng)有了,我直接把url給你,你不用再傳了,這就是秒傳。

圖a1:秒傳情況下后臺返回值

基于vue-simple-uploader封裝文件分片上傳、秒傳及斷點(diǎn)續(xù)傳的全局上傳插件功能

圖a2:秒傳gif

基于vue-simple-uploader封裝文件分片上傳、秒傳及斷點(diǎn)續(xù)傳的全局上傳插件功能

b. 如果后臺返回了分片信息,這是斷點(diǎn)續(xù)傳。如圖,返回的數(shù)據(jù)中有個(gè)uploaded的字段,代表這些分片是已經(jīng)上傳過的了,插件會自動跳過這些分片的上傳。

圖b1:斷點(diǎn)續(xù)傳情況下后臺返回值

基于vue-simple-uploader封裝文件分片上傳、秒傳及斷點(diǎn)續(xù)傳的全局上傳插件功能

圖b2:斷點(diǎn)續(xù)傳gif

基于vue-simple-uploader封裝文件分片上傳、秒傳及斷點(diǎn)續(xù)傳的全局上傳插件功能

c. 可能什么都不會返回,那這就是個(gè)全新的文件了,走完整的分片上傳邏輯

7.2 前端做分片檢驗(yàn):checkChunkUploadedByResponse

前面講的是概念,現(xiàn)在說一說前端在拿到這些返回值之后怎么處理。插件自己是不會判斷哪個(gè)需要跳過的,在代碼中由options中的checkChunkUploadedByResponse控制,它會根據(jù) XHR 響應(yīng)內(nèi)容檢測每個(gè)塊是否上傳成功了,成功的分片直接跳過上傳你要在這個(gè)函數(shù)中進(jìn)行處理,可以跳過的情況下返回true即可。

checkChunkUploadedByResponse: function (chunk, message) { let objMessage = JSON.parse(message); if (objMessage.skipUpload) { return true; } return (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0},

注:skipUpload 和 uploaded 是我和后臺商議的字段,你要按照后臺實(shí)際返回的字段名來。

8. 源碼及后記

總共幾個(gè)文件,app.vue,封裝的全局上傳組件globalUploader.vue,調(diào)用組件的demo.vue,源碼放到github上了:https://github.com/shady-xia/Blog/tree/master/vue-simple-uploader。

globalUploader源碼中的ticket和api都是自己用的, 一個(gè)是accesstoken,一個(gè)是基于axios封裝的請求庫,請根據(jù)你的業(yè)務(wù)需求替代之。另外上傳界面的展開和收起用到了jquery,通知用到了Element的組件,請忽略之。

本人水平有限,更多的是提供一個(gè)思路,供大家參考。

封裝完這個(gè)插件后,再加上開發(fā)文件資源庫,我發(fā)現(xiàn)已經(jīng)基本實(shí)現(xiàn)了一個(gè)簡易的百度網(wǎng)盤了,一個(gè)管理系統(tǒng),功能搞的這么復(fù)雜,坑爹??!

8.1 關(guān)于第一個(gè)分片丟失問題

關(guān)于開啟了testChunk后服務(wù)器收不到第一個(gè)分片的問題:simpleUploader文檔上是這么寫的:

基于vue-simple-uploader封裝文件分片上傳、秒傳及斷點(diǎn)續(xù)傳的全局上傳插件功能

testChunk的那個(gè)get請求,默認(rèn)帶了第一個(gè)分片給服務(wù)端,如果服務(wù)端返回的是200狀態(tài),則假定當(dāng)前塊已經(jīng)上傳過了,不會再上傳了;所以這里服務(wù)器要改成其他http狀態(tài)碼,比如204,這樣就不在“ 200, 201, 202”這個(gè)集合里了,代表服務(wù)端還沒有這個(gè)塊,需要按照標(biāo)準(zhǔn)模式上傳,這樣第一個(gè)分片就會再次被上傳了

2019/8/6更新

1、優(yōu)化了計(jì)算文件MD5的方式,展示MD5的計(jì)算進(jìn)度

之前文章中計(jì)算MD5的方式為對整個(gè)文件直接計(jì)算MD5,很吃內(nèi)存,容易導(dǎo)致瀏覽器崩潰我改成了通過分片讀取文件的方式計(jì)算MD5,防止直接讀取大文件時(shí)因內(nèi)存占用過大導(dǎo)致的網(wǎng)頁卡頓、崩潰

2、新增了的自定義的狀態(tài)

(之前我就封裝了幾種自定義狀態(tài),最近總有小伙伴問怎么沒有“校驗(yàn)MD5”,“合并中”這些狀態(tài),我就把我的方法寫出來了,方式很笨,但是能實(shí)現(xiàn)效果)

插件原本只支持了success、error、uploading、paused、waiting這幾種狀態(tài),

由于業(yè)務(wù)需求,我額外增加了“校驗(yàn)MD5”、“合并中”、“轉(zhuǎn)碼中”、“上傳失敗”這幾種自定義的狀態(tài)

由于前幾種狀態(tài)是插件已經(jīng)封裝好的,我不能改源碼,只能用比較hack的方式:當(dāng)自定義狀態(tài)開始時(shí),要手動調(diào)一下statusSet方法,生成一個(gè)p標(biāo)簽蓋在原本的狀態(tài)上面;當(dāng)自定義狀態(tài)結(jié)束時(shí),還要手動調(diào)用statusRemove移除該標(biāo)簽。

this.statusSet(file.id, ’merging’);this.statusRemove(file.id);

具體使用可以參考源碼,同時(shí)希望simple-uploader的插件作者后面能夠支持自定義狀態(tài)的配置。

到此這篇關(guān)于基于vue-simple-uploader封裝文件分片上傳、秒傳及斷點(diǎn)續(xù)傳的全局上傳插件功能的文章就介紹到這了,更多相關(guān)vue simple uploader封裝內(nèi)容請搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!

標(biāo)簽: Vue
相關(guān)文章:
主站蜘蛛池模板: 有机废气处理-rto焚烧炉-催化燃烧设备-VOC冷凝回收装置-三梯环境 | 欧美日韩国产一区二区三区不_久久久久国产精品无码不卡_亚洲欧洲美洲无码精品AV_精品一区美女视频_日韩黄色性爱一级视频_日本五十路人妻斩_国产99视频免费精品是看4_亚洲中文字幕无码一二三四区_国产小萍萍挤奶喷奶水_亚洲另类精品无码在线一区 | 湖南长沙商标注册专利申请,长沙公司注册代理记账首选美创! | HYDAC过滤器,HYDAC滤芯,现货ATOS油泵,ATOS比例阀-东莞市广联自动化科技有限公司 | 亿立分板机_曲线_锯片式_走刀_在线式全自动_铣刀_在线V槽分板机-杭州亿协智能装备有限公司 | 飞飞影视_热门电影在线观看_影视大全| 蜘蛛车-登高车-高空作业平台-高空作业车-曲臂剪叉式升降机租赁-重庆海克斯公司 | 驾驶式洗地机/扫地机_全自动洗地机_工业洗地机_荣事达工厂官网 | 不锈钢复合板|钛复合板|金属复合板|南钢集团安徽金元素复合材料有限公司-官网 | 高楼航空障碍灯厂家哪家好_航空障碍灯厂家_广州北斗星障碍灯有限公司 | 深圳公司注册-工商注册代理-注册公司流程和费用_护航财税 | 蜜蜂职场文库_职场求职面试实用的范文资料大全 | 烘干设备-热泵烘干机_广东雄贵能源设备有限公司 | 济南律师,济南法律咨询,山东法律顾问-山东沃德律师事务所 | 土壤肥料养分速测仪_测土配方施肥仪_土壤养分检测仪-杭州鸣辉科技有限公司 | LNG鹤管_内浮盘价格,上装鹤管,装车撬厂家-连云港赛威特机械 | 滤芯,过滤器,滤油机,贺德克滤芯,精密滤芯_新乡市宇清流体净化技术有限公司 | 青岛侦探调查_青岛侦探事务所_青岛调查事务所_青岛婚外情取证-青岛狄仁杰国际侦探公司 | 石磨面粉机|石磨面粉机械|石磨面粉机组|石磨面粉成套设备-河南成立粮油机械有限公司 | 12cr1mov无缝钢管切割-15crmog无缝钢管切割-40cr无缝钢管切割-42crmo无缝钢管切割-Q345B无缝钢管切割-45#无缝钢管切割 - 聊城宽达钢管有限公司 | 螺纹三通快插接头-弯通快插接头-宁波舜驰气动科技有限公司 | 湖州织里童装_女童男童中大童装_款式多尺码全_织里儿童网【官网】-嘉兴嘉乐网络科技有限公司 | 上海软件开发-上海软件公司-软件外包-企业软件定制开发公司-咏熠科技 | 烟气换热器_GGH烟气换热器_空气预热器_高温气气换热器-青岛康景辉 | 北京网站建设公司_北京网站制作公司_北京网站设计公司-北京爱品特网站建站公司 | 恒温恒湿试验箱_高低温试验箱_恒温恒湿箱-东莞市高天试验设备有限公司 | 深圳美安可自动化设备有限公司,喷码机,定制喷码机,二维码喷码机,深圳喷码机,纸箱喷码机,东莞喷码机 UV喷码机,日期喷码机,鸡蛋喷码机,管芯喷码机,管内壁喷码机,喷码机厂家 | 高低温试验房-深圳高低温湿热箱-小型高低温冲击试验箱-爱佩试验设备 | Eiafans.com_环评爱好者 环评网|环评论坛|环评报告公示网|竣工环保验收公示网|环保验收报告公示网|环保自主验收公示|环评公示网|环保公示网|注册环评工程师|环境影响评价|环评师|规划环评|环评报告|环评考试网|环评论坛 - Powered by Discuz! | 台式恒温摇床价格_大容量恒温摇床厂家-上海量壹科学仪器有限公司 | 福兰德PVC地板|PVC塑胶地板|PVC运动地板|PVC商用地板-中国弹性地板系统专业解决方案领先供应商! 福建成考网-福建成人高考网 | 电动球阀_不锈钢电动球阀_电动三通球阀_电动调节球阀_上海湖泉阀门有限公司 | 南京精锋制刀有限公司-纵剪机刀片_滚剪机刀片_合金刀片厂家 | 东莞市踏板石餐饮管理有限公司_正宗桂林米粉_正宗桂林米粉加盟_桂林米粉加盟费-东莞市棒子桂林米粉 | 卓能JOINTLEAN端子连接器厂家-专业提供PCB接线端子|轨道式端子|重载连接器|欧式连接器等电气连接产品和服务 | 仿古瓦,仿古金属瓦,铝瓦,铜瓦,铝合金瓦-西安东申景观艺术工程有限公司 | 中空玻璃生产线,玻璃加工设备,全自动封胶线,铝条折弯机,双组份打胶机,丁基胶/卧式/立式全自动涂布机,玻璃设备-山东昌盛数控设备有限公司 | 金属管浮子流量计_金属转子流量计厂家-淮安润中仪表科技有限公司 | 东莞注册公司-代办营业执照-东莞公司注册代理记账-极刻财税 | 学习安徽网 | 挤出机_橡胶挤出机_塑料挤出机_胶片冷却机-河北伟源橡塑设备有限公司 |