mirror of
https://github.com/kuaifan/dootask.git
synced 2026-08-13 16:28:34 +00:00
491 lines
19 KiB
Vue
Executable File
491 lines
19 KiB
Vue
Executable File
<template>
|
||
<div class="common-img-update">
|
||
<div v-if="type !== 'callback'" class="imgcomp-upload-list" v-for="item in uploadList">
|
||
<template v-if="item.status === 'finished'">
|
||
<div @click="handleTouch($event, item)" class="imgcomp-upload-img" v-bind:style="{ 'background-image': 'url(' + backgroundImage(item.thumb) + ')' }"></div>
|
||
<div class="imgcomp-upload-list-cover">
|
||
<Icon type="ios-eye-outline" @click.native="handleView(item)"></Icon>
|
||
<Icon type="ios-trash-outline" @click.native="handleRemove(item)"></Icon>
|
||
</div>
|
||
</template>
|
||
<template v-else>
|
||
<Progress v-if="item.showProgress" :percent="item.percentage" hide-info></Progress>
|
||
</template>
|
||
</div>
|
||
<div class="add-box" v-bind:class="{ 'callback-add-box': type === 'callback' }">
|
||
<div @click="handleTouch($event, null)" class="add-box-icon">
|
||
<Icon type="md-add" size="32"></Icon>
|
||
</div>
|
||
<div class="add-box-upload">
|
||
<div class="add-box-item" @click="browsePicture">
|
||
<span>{{$L('浏览')}}<em v-if="type === 'callback'">{{$L('图片')}}</em></span>
|
||
</div>
|
||
<div class="add-box-item">
|
||
<Upload
|
||
name="image"
|
||
ref="upload"
|
||
accept="image/*"
|
||
:action="actionUrl"
|
||
:headers="uploadHeaders"
|
||
:data="uploadParams"
|
||
:show-upload-list="false"
|
||
:max-size="maxImageSize"
|
||
:format="['jpg', 'jpeg', 'webp', 'gif', 'png']"
|
||
:default-file-list="defaultList"
|
||
:on-progress="handleProgress"
|
||
:on-success="handleSuccess"
|
||
:on-error="handleError"
|
||
:on-format-error="handleFormatError"
|
||
:on-exceeded-size="handleMaxSize"
|
||
:before-upload="handleBeforeUpload"
|
||
:multiple=multiple>
|
||
<span>{{$L('上传')}}<em v-if="type === 'callback'">{{$L('图片')}}</em></span>
|
||
</Upload>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<Modal :title="$L('浏览图片空间')" v-model="browseVisible" class="img-upload-modal" width="710">
|
||
<div class="browse-load" v-if="isLoading">{{$L('加载中...')}}</div>
|
||
<div class="browse-list" v-else :class="httpType==='input'?'browse-list-disabled':''" ref="browselistbox">
|
||
<div v-if="browseList.length <= 0">{{$L('无内容')}}</div>
|
||
<div v-else class="browse-item" v-for="item in browseList" @click="browseItem(item)">
|
||
<Icon v-if="item.active" class="browse-icon" type="ios-checkmark-circle"></Icon>
|
||
<div class="browse-img" v-bind:style="browseStyle(item.thumb)"></div>
|
||
<div class="browse-title">{{item.title}}</div>
|
||
</div>
|
||
</div>
|
||
<div slot="footer" class="img-upload-foot">
|
||
<div v-if="type !== 'callback' && http && httpType===''" class="img-upload-foot-input" @click="httpType='input'">
|
||
<Icon type="ios-image" size="22"/>
|
||
<div class="img-upload-foot-httptitle">{{$L('自定义图片地址')}}</div>
|
||
</div>
|
||
<div v-if="type !== 'callback' && http && httpType==='input'" class="img-upload-foot-input">
|
||
<Input v-model="httpValue" :placeholder="$L('以 http:// 或 https:// 开头')" @on-search="httpEnter" search :enter-button="$L('确定')">
|
||
<span slot="prepend" @click="httpType=''" style="cursor:pointer">{{$L('自定义地址')}}: </span>
|
||
</Input>
|
||
</div>
|
||
<Button v-if="httpType===''" @click="browseVisible=false">{{$L('关闭')}}</Button>
|
||
<Button v-if="httpType===''" type="primary" @click="handleCallback(true)">{{$L('完成')}}</Button>
|
||
</div>
|
||
</Modal>
|
||
<Modal :title="$L('查看图片')" v-model="visible" class="img-upload-modal" draggable>
|
||
<div style="max-height:480px;overflow:auto;">
|
||
<a :href="imgVisible" target="_blank"><img :src="imgVisible" v-if="visible" style="max-width:100%;max-height:900px;display:block;margin:0 auto"></a>
|
||
</div>
|
||
</Modal>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import {chunkedUpload, CHUNK_THRESHOLD} from "../store/chunkedUpload";
|
||
|
||
export default {
|
||
name: 'ImgUpload',
|
||
props: {
|
||
value: {},
|
||
num: {},
|
||
width: {},
|
||
height: {},
|
||
whcut: {},
|
||
type: {},
|
||
http: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
otherParams: {
|
||
type: Object,
|
||
default: () => {
|
||
return {};
|
||
}
|
||
},
|
||
uploadIng: {
|
||
type: Number,
|
||
default: 0
|
||
},
|
||
maxSize: {
|
||
type: Number,
|
||
default: 1024 * 10 // 10M
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
actionUrl: $A.apiUrl('system/imgupload'),
|
||
multiple: this.num > 1,
|
||
visible: false,
|
||
browseVisible: false,
|
||
isLoading: false,
|
||
browseList: [],
|
||
browseListNext: [],
|
||
imgVisible: '',
|
||
defaultList: this.initItems(this.value),
|
||
uploadList: [],
|
||
maxNum: Math.min(Math.max($A.runNum(this.num), 1), 99),
|
||
httpValue: '',
|
||
httpType: '',
|
||
maxImageSize: this.maxSize
|
||
}
|
||
},
|
||
mounted() {
|
||
this.uploadList = this.$refs.upload.fileList;
|
||
this.$emit('input', this.uploadList);
|
||
//
|
||
let browseBox = $A(this.$refs.browselistbox);
|
||
browseBox.scroll(() => {
|
||
let nHight = browseBox[0].scrollHeight;
|
||
let nTop = browseBox[0].scrollTop;
|
||
let boxHight = browseBox.height();
|
||
if (nTop + boxHight >= nHight) {
|
||
//到底了
|
||
if (this.browseListNext.length > 0) {
|
||
let tmpNext = this.browseListNext;
|
||
this.browseListNext = [];
|
||
this.browsePictureFor(tmpNext);
|
||
}
|
||
}
|
||
});
|
||
},
|
||
watch: {
|
||
value(val) {
|
||
if (typeof val === 'string') {
|
||
this.$emit('input', this.initItems(val));
|
||
return;
|
||
}
|
||
if (val === this.$refs.upload.fileList) {
|
||
return;
|
||
}
|
||
this.$refs.upload.fileList = this.initItems(val);
|
||
this.uploadList = this.$refs.upload.fileList;
|
||
},
|
||
browseVisible() {
|
||
this.httpType = '';
|
||
this.httpValue = '';
|
||
}
|
||
},
|
||
computed: {
|
||
uploadHeaders() {
|
||
return {
|
||
fd: $A.getSessionStorageString("userWsFd"),
|
||
token: this.userToken,
|
||
}
|
||
},
|
||
|
||
uploadParams() {
|
||
let params = {
|
||
width: this.width,
|
||
height: this.height,
|
||
whcut: this.whcut,
|
||
};
|
||
if (Object.keys(this.otherParams).length > 0) {
|
||
return Object.assign(params, this.otherParams);
|
||
} else {
|
||
return params;
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
handleCallback(file) {
|
||
if (this.type === 'callback') {
|
||
if (file === true) {
|
||
this.$emit('on-callback', this.uploadList);
|
||
this.$refs.upload.fileList = [];
|
||
this.uploadList = this.$refs.upload.fileList;
|
||
} else if (typeof file === "object") {
|
||
this.$emit('on-callback', [file]);
|
||
}
|
||
}
|
||
this.browseVisible = false;
|
||
},
|
||
initItems(items) {
|
||
//数据初始化
|
||
if (typeof items === 'string') {
|
||
items = [{'url': items}];
|
||
}
|
||
let list = [];
|
||
$A.each(items, (index, item) => {
|
||
if (typeof item === 'string') item = {'url': item};
|
||
if (item.url) {
|
||
item.active = true;
|
||
item.status = 'finished';
|
||
if (typeof item.path === 'undefined') item.path = item.url;
|
||
if (typeof item.thumb === 'undefined') item.thumb = item.url;
|
||
list.push(item);
|
||
}
|
||
});
|
||
return list;
|
||
},
|
||
handleTouch(event, item) {
|
||
if (!this.windowTouch) {
|
||
return;
|
||
}
|
||
const list = [];
|
||
if (item === null) {
|
||
const subfix = this.type === 'callback' ? '图片' : '';
|
||
list.push(...[
|
||
{
|
||
label: '浏览' + subfix,
|
||
value: 'browse'
|
||
},
|
||
{
|
||
label: '上传' + subfix,
|
||
value: 'upload'
|
||
}
|
||
])
|
||
} else {
|
||
list.push(...[
|
||
{
|
||
label: '查看',
|
||
value: 'view'
|
||
},
|
||
{
|
||
label: '删除',
|
||
value: 'trash'
|
||
}
|
||
])
|
||
}
|
||
this.$store.commit('menu/operation', {
|
||
event,
|
||
list,
|
||
onUpdate: async (act) => {
|
||
switch (act) {
|
||
case 'browse':
|
||
this.browsePicture();
|
||
break;
|
||
case 'upload':
|
||
this.$refs.upload?.handleClick()
|
||
break;
|
||
case 'view':
|
||
this.handleView(item);
|
||
break;
|
||
case 'trash':
|
||
this.handleRemove(item);
|
||
break;
|
||
}
|
||
}
|
||
})
|
||
},
|
||
handleView(item) {
|
||
//查看
|
||
this.$store.dispatch("previewImage", item.url)
|
||
// this.visible = true;
|
||
// this.imgVisible = item.url;
|
||
},
|
||
handleRemove(item) {
|
||
//删除
|
||
let fileList = this.$refs.upload.fileList;
|
||
this.$refs.upload.fileList.splice(fileList.indexOf(item), 1);
|
||
this.$emit('input', this.$refs.upload.fileList);
|
||
},
|
||
handleProgress(event, file) {
|
||
//开始上传
|
||
if (file._uploadIng === undefined) {
|
||
file._uploadIng = true;
|
||
this.$emit('update:uploadIng', this.uploadIng + 1);
|
||
}
|
||
},
|
||
handleSuccess(res, file) {
|
||
//上传完成
|
||
this.$emit('update:uploadIng', this.uploadIng - 1);
|
||
if (res.ret === 1) {
|
||
file.url = res.data.url;
|
||
file.path = res.data.path;
|
||
file.thumb = res.data.thumb;
|
||
this.handleCallback(file);
|
||
} else {
|
||
$A.noticeWarning({
|
||
title: this.$L('上传失败'),
|
||
desc: this.$L('文件 (*) 上传失败 (*)', file.name, res.msg),
|
||
});
|
||
this.$refs.upload.fileList.pop();
|
||
}
|
||
this.$emit('input', this.$refs.upload.fileList);
|
||
},
|
||
handleError() {
|
||
//上传错误
|
||
this.$emit('update:uploadIng', this.uploadIng - 1);
|
||
},
|
||
handleFormatError(file) {
|
||
//上传类型错误
|
||
$A.noticeWarning({
|
||
title: this.$L('文件格式不正确'),
|
||
desc: this.$L('文件 (*) 格式不正确,请上传 jpg、jpeg、webp、gif、png 格式的图片。', file.name)
|
||
});
|
||
},
|
||
handleMaxSize(file) {
|
||
//上传大小错误
|
||
$A.noticeWarning({
|
||
title: this.$L('超出文件大小限制'),
|
||
desc: this.$L('文件 (*) 太大,不能超过:(*)', file.name, $A.bytesToSize(this.maxImageSize * 1024))
|
||
});
|
||
},
|
||
handleBeforeUpload(file) {
|
||
//上传前判断
|
||
let check = this.uploadList.length < this.maxNum;
|
||
if (!check && this.uploadList.length == 1) {
|
||
this.handleRemove(this.uploadList[0]);
|
||
check = this.uploadList.length < this.maxNum;
|
||
}
|
||
if (!check) {
|
||
$A.noticeWarning(this.$L('最多只能上传 (*) 张图片。', this.maxNum));
|
||
return false;
|
||
}
|
||
// ≥ 10MB 走分片(iview max-size 拦在前,需调用方放大 maxSize 才能进到这里)
|
||
if (file && file.size >= CHUNK_THRESHOLD) {
|
||
this.handleChunkedUpload(file);
|
||
return false;
|
||
}
|
||
return check;
|
||
},
|
||
|
||
async handleChunkedUpload(rawFile) {
|
||
// 与原 iview 路径同效果:成功后构造 fileList item 触发 handleCallback
|
||
this.$emit('update:uploadIng', this.uploadIng + 1);
|
||
const item = {
|
||
uid: 'chunked-' + Date.now() + '-' + Math.random().toString(36).slice(2),
|
||
name: rawFile.name,
|
||
size: rawFile.size,
|
||
status: 'uploading',
|
||
showProgress: true,
|
||
percentage: 0,
|
||
};
|
||
this.$refs.upload.fileList.push(item);
|
||
this.uploadList = this.$refs.upload.fileList;
|
||
try {
|
||
const data = await chunkedUpload({
|
||
file: rawFile,
|
||
scene: 'image',
|
||
sceneParams: {
|
||
width: this.width,
|
||
height: this.height,
|
||
whcut: this.whcut,
|
||
},
|
||
onProgress: percent => { item.percentage = percent; },
|
||
});
|
||
item.status = 'finished';
|
||
item.percentage = 100;
|
||
item.url = data.url;
|
||
item.path = data.path;
|
||
item.thumb = data.thumb;
|
||
this.handleCallback(item);
|
||
this.$emit('input', this.$refs.upload.fileList);
|
||
} catch (err) {
|
||
$A.noticeWarning({
|
||
title: this.$L('上传失败'),
|
||
desc: this.$L('文件 (*) 上传失败 (*)', rawFile.name, (err && err.message) || ''),
|
||
});
|
||
const idx = this.$refs.upload.fileList.indexOf(item);
|
||
if (idx > -1) this.$refs.upload.fileList.splice(idx, 1);
|
||
this.$emit('input', this.$refs.upload.fileList);
|
||
} finally {
|
||
this.$emit('update:uploadIng', this.uploadIng - 1);
|
||
}
|
||
},
|
||
handleClick() {
|
||
//手动上传
|
||
if (this.handleBeforeUpload()) {
|
||
this.$refs.upload.handleClick()
|
||
}
|
||
},
|
||
handleManual(file) {
|
||
//手动传file
|
||
if (this.handleBeforeUpload()) {
|
||
this.$refs.upload.upload(file);
|
||
}
|
||
},
|
||
browsePicture(path) {
|
||
//获取图片空间
|
||
this.browseVisible = true;
|
||
this.browseList = [];
|
||
this.browseListNext = [];
|
||
this.isLoading = true;
|
||
this.$store.dispatch("call", {
|
||
url: 'system/imgview',
|
||
data: {path: path ? path : ''},
|
||
}).then(({data}) => {
|
||
let dirs = data['dirs'];
|
||
for (let i = 0; i < dirs.length; i++) {
|
||
this.browseList.push(dirs[i]);
|
||
}
|
||
this.browsePictureFor(data['files']);
|
||
}).catch(({msg}) => {
|
||
this.browseVisible = false;
|
||
$A.noticeWarning(msg);
|
||
}).finally(_ => {
|
||
this.isLoading = false;
|
||
});
|
||
},
|
||
|
||
browsePictureFor(files) {
|
||
for (let o = 0; o < files.length; o++) {
|
||
for (let j = 0; j < this.uploadList.length; j++) {
|
||
if (this.uploadList[j]['url'] === files[o]['url']
|
||
|| this.uploadList[j]['url'] === files[o]['path']) {
|
||
files[o]['active'] = true;
|
||
break;
|
||
}
|
||
}
|
||
if (o < 100) {
|
||
this.browseList.push(files[o]);
|
||
} else {
|
||
this.browseListNext.push(files[o]);
|
||
}
|
||
}
|
||
},
|
||
|
||
browseItem(item) {
|
||
//点击选择图片
|
||
if (item.type === 'dir') {
|
||
//文件夹
|
||
this.browsePicture(item.path);
|
||
} else if (item.type === 'file') {
|
||
//文件
|
||
if (item.active) {
|
||
let fileList = this.$refs.upload.fileList;
|
||
this.$refs.upload.fileList.splice(fileList.indexOf(item), 1);
|
||
item.active = false;
|
||
} else {
|
||
if (this.maxNum === 1) {
|
||
for (let i = 0; i < this.browseList.length; i++) {
|
||
this.browseList[i].active = false;
|
||
}
|
||
this.$refs.upload.fileList = [];
|
||
this.uploadList = this.$refs.upload.fileList;
|
||
}
|
||
let check = this.uploadList.length < this.maxNum;
|
||
if (!check) {
|
||
$A.noticeWarning(this.$L('最多只能选择 (*) 张图片。', this.maxNum));
|
||
return;
|
||
}
|
||
item.active = true;
|
||
item.status = 'finished';
|
||
this.$refs.upload.fileList.push(item);
|
||
this.uploadList = this.$refs.upload.fileList;
|
||
}
|
||
this.$emit('input', this.$refs.upload.fileList);
|
||
}
|
||
},
|
||
|
||
browseStyle(thumb) {
|
||
if (!/https?:\/\//.test(thumb) && !/^\//.test(thumb)) {
|
||
thumb = $A.mainUrl(thumb);
|
||
}
|
||
return {
|
||
'background-image': `url("${thumb}")`
|
||
}
|
||
},
|
||
|
||
backgroundImage(url) {
|
||
if ($A.strExists(url, "?", false)) {
|
||
return $A.mainUrl(url) + "&__thumb=true";
|
||
} else {
|
||
return $A.mainUrl(url) + "?__thumb=true";
|
||
}
|
||
},
|
||
|
||
httpEnter() {
|
||
this.$emit('input', this.initItems(this.httpValue));
|
||
this.browseVisible = false;
|
||
}
|
||
}
|
||
}
|
||
</script>
|