特性:
- 支持自定义瓦片图尺寸
- 支持显示预览最小尺寸100x100像素大小,切换为实际切割尺寸
- 支持获取切割后的文件Files数组
sgUploadTileImage源码
<template>
<div :class="$options.name">
<div class="sg-ctrl">
<div class="px">
<span>瓦片图边长</span>
<el-input-number style="width: 130px;" v-model.trim="tileSize" :precision="0" :step="100" :min="100"
:max="500" :controls-position="`left`" /><span>像素</span>
</div>
<div class="btns">
<el-switch v-model="view100px" inactive-color="#ccc" active-color="#409EFF" inactive-text=""
active-text="固定100宽高显示预览" :inactive-value="false" :active-value="true" />
<el-button :loading="loading" type="primary" icon="el-icon-upload2"
@click="d => $refs.sgUpload.triggerUploadFile()">上传大图</el-button>
<el-button :loading="loading" type="success" icon="el-icon-s-promotion" @click="uploadTiles"
v-if="loadingPercent === 100">上传瓦片图</el-button>
</div>
<div class="tip-text">
<p>{{ loadingText }}</p>
<p style="color: #67C23A;" v-if="!(loadingPercent === 0 || loadingPercent === 100)">{{ loadingPercent }}%
</p>
</div>
</div>
<div class="sg-tiles" :view100px="view100px">
<div :style="{ width: `${(view100px ? 100 : tileSize) * colCount}px` }">
<img v-for="(a, i) in tiles" :key="i" :src="a" :width="view100px ? 100 : tileSize"
:height="view100px ? 100 : tileSize">
</div>
</div>
<!-- 上传组件 -->
<sgUpload drag ref="sgUpload" :data="{
maxSize: 1000,
accept: `*`,
}" @resultBase64Image="resultBase64Image" @success="uploadSuccess" @error="uploadError" hideUploadTray
@showFakeLoading="showFakeLoading" @hideFakeLoading="hideFakeLoading" />
</div>
</template>
<script>
import sgUpload from "@/vue/components/admin/sgUpload";
export default {
name: 'sgUploadTileImage',
components: {
sgUpload,
},
data() {
return {
loading: false,
view100px: true,
loadingText: '',
colCount: 0,
rowCount: 0,
loadingPercent: 0,
src: '',
fileFormat: '',
tileSize: 500,
tiles: [],//瓦片图数组
loadingInterval: null,//虚假加载动画
dur: 100,
}
},
methods: {
uploadTiles(d) {
let r = [];
let format = this.fileFormat.toLocaleLowerCase().split('/')[1];
this.tiles.forEach((base64, i) => {
let fileName = `${i}.${format}`;
let file = this.$g.image.getFileFromBase64(base64, fileName);
r.push(file);
});
this.$emit(`uploadTiles`, r);
},
getRowColIndex(itemIndex = 0, colCount = 3) {
//必选参数:itemIndex是当前元素的索引值,从0开始计算(如果是从1开始,则需将传入值-1,即itemIndex--)
//必选参数:colCount是每一行显示多少个元素
return {
colIndex: itemIndex % colCount,//计算列索引(从0开始)
rowIndex: Math.floor(itemIndex / colCount),//计算行索引(从0开始)
}
},
// 获取瓦片图
getTiles({ img, format = "image/png", cb } = {}) {
this.fileFormat = format;
let canvas = document.createElement('canvas'), ctx = canvas.getContext('2d');
canvas.width = this.tileSize, canvas.height = this.tileSize;
// let imgArea = img.width * img.height;//图片的面积
// let tileArea = this.tileSize * this.tileSize;//瓦片图的面积
this.loadingText = `已经为您生成${len}个瓦片图(尺寸:${this.tileSize}像素×${this.tileSize}像素)。`;
let tiles = [];
let colCount = Math.ceil(img.width / this.tileSize);//列数量
let rowCount = Math.ceil(img.height / this.tileSize);//行数量
let len = colCount * rowCount;//瓦片图总数
this.colCount = colCount;
this.rowCount = rowCount;
for (let i = 0; i < len; i++) {
let { colIndex, rowIndex } = this.getRowColIndex(i, colCount);
let drawImageWidth = colIndex === colCount - 1 ? (img.width % this.tileSize) : this.tileSize;
let drawImageHeight = rowIndex === rowCount - 1 ? (img.height % this.tileSize) : this.tileSize;
canvas.width = drawImageWidth, canvas.height = drawImageHeight;
ctx.drawImage(img,
this.tileSize * colIndex, this.tileSize * rowIndex,//绘制图片起始点的横纵坐标
drawImageWidth, drawImageHeight,//切割图片的宽高
0, 0,//绘制canvas的起始点横纵坐标
drawImageWidth, drawImageHeight);//绘制canvas的宽高
tiles.push(canvas.toDataURL(format));
}
cb && cb(tiles);
},
/* 将图片(路径)转换为Base64 */
resultBase64Image(url, f) {
this.tiles = [];
this.$el.style.setProperty("--last-img-scale", (100 - 1) / (this.tileSize - 1)); //js往css传递局部参数
this.loadingText = '正在为您分解图片,请稍候!';
this.__showFakeLoading();
let img = new Image(); img.crossOrigin = 'Anonymous';
img.onload = d => {
this.getTiles({
img,
cb: tiles => {
this.tiles = tiles;
this.loading = false;
this.__hideFakeLoading();
this.loadingPercent = 100;
}
})
}
img.src = url;
},
uploadSuccess(d, f) { }, uploadError(d, f) { },
showFakeLoading(d) {
this.loadingText = '图片上传中,请稍候!';
this.loading = true;
this.__showFakeLoading();
},
__showFakeLoading() {
clearInterval(this.loadingInterval);
this.loadingInterval = setInterval(() => {
this.loadingPercent >= 99 ? this.__hideFakeLoading() : this.loadingPercent++;
}, this.dur);
},
hideFakeLoading(d) {
},
__hideFakeLoading() {
clearInterval(this.loadingInterval);
this.loadingPercent = 0;
},
}
};
</script>
<style lang="scss" scoped>
.sgUploadTileImage {
display: flex;
flex-direction: column;
.sg-ctrl {
display: flex;
flex-shrink: 0;
flex-wrap: nowrap;
align-items: center;
.px {
margin-right: 10px;
span {
margin-left: 5px;
}
}
.tip-text {
margin-left: 10px;
display: flex;
align-items: center;
}
}
.sg-tiles {
overflow: auto;
// max-height: calc(100vh - 100px);
flex-grow: 1;
div {
display: flex;
flex-wrap: wrap;
img {
box-sizing: border-box;
border: 0.5px solid white;
object-position: top left;
object-fit: scale-down;
}
}
&[view100px] {
div {
img {
&:last-of-type {
width: revert;
height: revert;
transform-origin: left top;
transform: scale(var(--last-img-scale));
}
}
}
}
}
}
</style>
用例
<template>
<div>
<el-button type="primary" @click="dialogVisible = true">上传超大文件</el-button>
<el-dialog :custom-class="'sgUploadTileImage-el-dialog'" :append-to-body="true" :close-on-click-modal="true"
:close-on-press-escape="true" :destroy-on-close="true" :fullscreen="true" :show-close="true" :title="`瓦片图上传`"
:width="'100%'" :visible.sync="dialogVisible">
<div style="width: 100%;height: 100%;">
<sgUploadTileImage @uploadTiles="uploadTiles" />
</div>
</el-dialog>
</div>
</template>
<script>
import sgUploadTileImage from "@/vue/components/admin/sgUploadTileImage";
export default {
components: { sgUploadTileImage, },
data() {
return { dialogVisible: false, }
},
methods: {
uploadTiles(files) {
console.log(`瓦片图files:`, files);
},
},
};
</script>
<style lang="scss">
.sgUploadTileImage-el-dialog {
.el-dialog__body {
padding: 0;
.sg-tiles {
max-height: calc(100vh - 100px);
}
}
}
</style>