select forth give up third

This commit is contained in:
2026-04-19 00:53:38 +08:00
commit e3f024525b
370 changed files with 47362 additions and 0 deletions

View File

@@ -0,0 +1,186 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
const ERR_MSG_OK = 'chooseAndUploadFile:ok';
const ERR_MSG_FAIL = 'chooseAndUploadFile:fail';
function chooseImage(opts) {
const { count, sizeType, sourceType, extension } = opts;
return new Promise((resolve, reject) => {
uni.chooseImage({
count,
sizeType,
sourceType,
extension,
success(res) {
resolve(normalizeChooseAndUploadFileRes(res, 'image'));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseImage:fail', ERR_MSG_FAIL),
});
},
});
});
}
function chooseVideo(opts) {
const { camera, compressed, maxDuration, sourceType, extension } = opts;
return new Promise((resolve, reject) => {
uni.chooseVideo({
camera,
compressed,
maxDuration,
sourceType,
extension,
success(res) {
const { tempFilePath, duration, size, height, width } = res;
resolve(normalizeChooseAndUploadFileRes({
errMsg: 'chooseVideo:ok',
tempFilePaths: [tempFilePath],
tempFiles: [
{
name: (res.tempFile && res.tempFile.name) || '',
path: tempFilePath,
size,
type: (res.tempFile && res.tempFile.type) || '',
width,
height,
duration,
fileType: 'video',
cloudPath: '',
},
],
}, 'video'));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseVideo:fail', ERR_MSG_FAIL),
});
},
});
});
}
function chooseAll(opts) {
const { count, extension } = opts;
return new Promise((resolve, reject) => {
let chooseFile = uni.chooseFile;
if (typeof wx !== 'undefined' &&
typeof wx.chooseMessageFile === 'function') {
chooseFile = wx.chooseMessageFile;
}
if (typeof chooseFile !== 'function') {
return reject({
errMsg: ERR_MSG_FAIL + ' 请指定 type 类型,该平台仅支持选择 image 或 video。',
});
}
chooseFile({
type: 'all',
count,
extension,
success(res) {
resolve(normalizeChooseAndUploadFileRes(res));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseFile:fail', ERR_MSG_FAIL),
});
},
});
});
}
function normalizeChooseAndUploadFileRes(res, fileType) {
res.tempFiles.forEach((item, index) => {
if (!item.name) {
item.name = item.path.substring(item.path.lastIndexOf('/') + 1);
}
if (fileType) {
item.fileType = fileType;
}
item.cloudPath =
Date.now() + '_' + index + item.name.substring(item.name.lastIndexOf('.'));
});
// wx.chooseMessageFile
if (!res.tempFilePaths) {
res.tempFilePaths = res.tempFiles.map((file) => file.path);
}
return res;
}
function uploadCloudFiles(res, max = 5, onUploadProgress) {
res = Object.assign({}, res);
res.errMsg = ERR_MSG_OK;
const files = res.tempFiles;
const len = files.length;
let count = 0;
return new Promise((resolve) => {
while (count < max) {
next();
}
function next() {
let cur = count++;
if (cur >= len) {
!files.find((item) => !item.url && !item.errMsg) && resolve(res);
return;
}
const fileItem = files[cur];
uniCloud
.uploadFile({
filePath: fileItem.path,
cloudPath: fileItem.cloudPath,
fileType: fileItem.fileType,
onUploadProgress(res) {
res.index = cur;
res.tempFile = fileItem;
res.tempFilePath = fileItem.path;
onUploadProgress &&
onUploadProgress(res);
},
})
.then((res) => {
fileItem.url = res.fileID;
if (cur < len) {
next();
}
})
.catch((res) => {
// fileItem.errMsg = res.message;
fileItem.errMsg = res.errMsg || res.message;
if (cur < len) {
next();
}
});
}
});
}
function uploadFiles(choosePromise, { onChooseFile, onUploadProgress }) {
return choosePromise
.then((res) => {
if (onChooseFile) {
const customChooseRes = onChooseFile(res);
if (typeof customChooseRes !== 'undefined') {
return Promise.resolve(customChooseRes).then((chooseRes) => typeof chooseRes === 'undefined' ? res : chooseRes);
}
}
return res;
})
.then((res) => {
if (res === false) {
return {
errMsg: ERR_MSG_OK,
tempFilePaths: [],
tempFiles: [],
};
}
return uploadCloudFiles(res, 5, onUploadProgress);
});
}
function chooseAndUploadFile(opts = { type: 'all' }) {
if (opts.type === 'image') {
return uploadFiles(chooseImage(opts), opts);
}
else if (opts.type === 'video') {
return uploadFiles(chooseVideo(opts), opts);
}
return uploadFiles(chooseAll(opts), opts);
}
exports.chooseAndUploadFile = chooseAndUploadFile;

View File

@@ -0,0 +1,668 @@
<template>
<view class="uni-file-picker">
<view v-if="title" class="uni-file-picker__header">
<text class="file-title">{{ title }}</text>
<text class="file-count">{{ filesList.length }}/{{ limitLength }}</text>
</view>
<upload-image
v-if="fileMediatype === 'image' && showType === 'grid'"
:readonly="readonly"
:image-styles="imageStyles"
:files-list="filesList"
:limit="limitLength"
:disablePreview="disablePreview"
:delIcon="delIcon"
@uploadFiles="uploadFiles"
@choose="choose"
@delFile="delFile"
>
<slot>
<view class="is-add">
<view class="icon-add"></view>
<view class="icon-add rotate"></view>
</view>
</slot>
</upload-image>
<upload-file
v-if="fileMediatype !== 'image' || showType !== 'grid'"
:readonly="readonly"
:list-styles="listStyles"
:files-list="filesList"
:showType="showType"
:delIcon="delIcon"
@uploadFiles="uploadFiles"
@choose="choose"
@delFile="delFile"
>
<slot><button type="primary" size="mini">选择文件</button></slot>
</upload-file>
</view>
</template>
<script>
import { chooseAndUploadFile } from './choose-and-upload-file.js'
import uploadImage from './upload-image.vue'
import uploadFile from './upload-file.vue'
let fileInput = null
/**
* FilePicker 文件选择上传
* @description 文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间
* @tutorial https://ext.dcloud.net.cn/plugin?id=4079
* @property {Object|Array} value 组件数据,通常用来回显 ,类型由return-type属性决定
* @property {Boolean} disabled=[true|false] 组件禁用
* @value true 禁用
* @value false 取消禁用
* @property {Boolean} readonly=[true|false] 组件只读,不可选择,不显示进度,不显示删除按钮
* @value true 只读
* @value false 取消只读
* @property {String} return-type=[array|object] 限制 value 格式,当为 object 时 ,组件只能单选,且会覆盖
* @value array 规定 value 属性的类型为数组
* @value object 规定 value 属性的类型为对象
* @property {Boolean} disable-preview=[true|false] 禁用图片预览,仅 mode:grid 时生效
* @value true 禁用图片预览
* @value false 取消禁用图片预览
* @property {Boolean} del-icon=[true|false] 是否显示删除按钮
* @value true 显示删除按钮
* @value false 不显示删除按钮
* @property {Boolean} auto-upload=[true|false] 是否自动上传值为true则只触发@select,可自行上传
* @value true 自动上传
* @value false 取消自动上传
* @property {Number|String} limit 最大选择个数 h5 会自动忽略多选的部分
* @property {String} title 组件标题,右侧显示上传计数
* @property {String} mode=[list|grid] 选择文件后的文件列表样式
* @value list 列表显示
* @value grid 宫格显示
* @property {String} file-mediatype=[image|video|all] 选择文件类型
* @value image 只选择图片
* @value video 只选择视频
* @value all 选择所有文件
* @property {Array} file-extname 选择文件后缀,根据 file-mediatype 属性而不同
* @property {Object} list-style mode:list 时的样式
* @property {Object} image-styles 选择文件后缀,根据 file-mediatype 属性而不同
* @event {Function} select 选择文件后触发
* @event {Function} progress 文件上传时触发
* @event {Function} success 上传成功触发
* @event {Function} fail 上传失败触发
* @event {Function} delete 文件从列表移除时触发
*/
export default {
name: 'uniFilePicker',
components: {
uploadImage,
uploadFile
},
props: {
value: {
type: [Array, Object],
default() {
return []
}
},
disabled: {
type: Boolean,
default: false
},
disablePreview: {
type: Boolean,
default: false
},
delIcon: {
type: Boolean,
default: true
},
// 自动上传
autoUpload: {
type: Boolean,
default: true
},
// 最大选择个数 h5只能限制单选或是多选
limit: {
type: [Number, String],
default: 9
},
// 列表样式 grid | list | list-card
mode: {
type: String,
default: 'grid'
},
// inputUrl: {
// type: Boolean,
// default: false
// },
// 选择文件类型 image/video/all
fileMediatype: {
type: String,
default: 'image'
},
// 文件类型筛选
fileExtname: {
type: [Array, String],
default() {
return []
}
},
title: {
type: String,
default: ''
},
listStyles: {
type: Object,
default() {
return {
// 是否显示边框
border: true,
// 是否显示分隔线
dividline: true,
// 线条样式
borderStyle: {}
}
}
},
imageStyles: {
type: Object,
default() {
return {
width: 'auto',
height: 'auto'
}
}
},
readonly: {
type: Boolean,
default: false
},
returnType: {
type: String,
default: 'array'
}
},
watch: {
value: {
handler(newVal) {
let newFils = []
let newData = [].concat(newVal || [])
newData.forEach(v => {
const files = this.files.find(i => i.url === v.url)
const reg = /cloud:\/\/([\w.]+\/?)\S*/
if (!v.path) {
v.path = v.url
}
if (reg.test(v.url)) {
this.getTempFileURL(v, v.url)
}
newFils.push(files ? files : v)
})
this.files = newFils
},
immediate: true
}
},
data() {
return {
files: [],
}
},
computed: {
filesList() {
let files = []
this.files.forEach(v => {
files.push(v)
})
return files
},
showType() {
if (this.fileMediatype === 'image') {
return this.mode
}
return 'list'
},
limitLength() {
if (this.returnType === 'object') {
return 1
}
if (!this.limit) {
return 1
}
if (this.limit >= 9) {
return 9
}
return this.limit
},
extname(){
if (!Array.isArray(this.fileExtname)) {
let extname = this.fileExtname.replace(/(\[|\])/g,'')
return extname.split(',')
} else {
return this.fileExtname
}
return []
}
},
created() {
// this.files = Object.assign([], this.value)
this.tempData = {}
},
methods: {
/**
* 继续上传
*/
upload() {
// TODO 先放弃这个实现 ,不能全部上传
// if (this.$uploadFiles) {
// this.$uploadFiles()
// } else {
// uni.showToast({
// title: '请先选择文件',
// icon: 'none'
// })
// }
let files = []
this.files.forEach((v, index) => {
if (v.status === 'ready' || v.status === 'error') {
files.push(Object.assign({}, v))
}
})
this.uploadFiles(files)
},
/**
* 选择文件
*/
choose() {
if (this.disabled) return
if (this.files.length >= Number(this.limitLength) && this.showType !== 'grid' && this.returnType === 'array') {
uni.showToast({
title: `您最多选择 ${this.limitLength} 个文件`,
icon: 'none'
})
return
}
// uni.showActionSheet({
// itemList: ['填写 url 地址', '选择文件'],
// success: (res) => {
// if (res.tapIndex === 1) {
// this.chooseFiles()
// }
// },
// fail: function(res) {}
// });
this.chooseFiles()
},
/**
* 选择文件并上传
*/
chooseFiles() {
// API 正式发布前使用本地API上传函数
if (!uniCloud.chooseAndUploadFile) {
uniCloud.chooseAndUploadFile = chooseAndUploadFile
}
uniCloud
.chooseAndUploadFile({
type: this.fileMediatype,
compressed: false,
// TODO 如果为空video 有问题
extension: this.extname.length > 0 ? this.extname : undefined,
count: this.limitLength - this.files.length, //默认9
onChooseFile: async res => {
if ((Number(this.limitLength) === 1 && this.disablePreview && !this.disabled) || this.returnType === 'object') {
this.files = []
}
let filePaths = []
let files = []
if (this.extname && this.extname.length > 0) {
res.tempFiles.forEach(v => {
let fileFullName = this.getFileExt(v.name)
const extname = fileFullName.ext.toLowerCase()
if (this.extname.indexOf(extname) !== -1) {
files.push(v)
filePaths.push(v.path)
}
})
if (files.length !== res.tempFiles.length) {
uni.showToast({
title: `当前选择了${res.tempFiles.length}个文件 ${res.tempFiles.length - files.length} 个文件格式不正确`,
icon: 'none',
duration: 5000
})
}
} else {
filePaths = res.tempFilePaths
files = res.tempFiles
}
let currentData = []
for (let i = 0; i < files.length; i++) {
if (this.limitLength - this.files.length <= 0) break
files[i].uuid = Date.now()
let filedata = await this.getFileData(files[i], this.fileMediatype)
filedata.progress = 0
filedata.status = 'ready'
this.files.push(filedata)
currentData.push(filedata)
}
this.$emit('select', {
tempFiles: currentData,
tempFilePaths: filePaths
})
res.tempFiles = files
// 停止自动上传
if (!this.autoUpload) {
res.tempFiles = []
// TODO 先放弃这个实现 ,不能全部上传
// return new Promise((resolve) => {
// this.$uploadFiles = () => {
// // this._is_uploading = true
// resolve(res)
// }
// })
}
},
onUploadProgress: progressEvent => {
this.setProgress(progressEvent, progressEvent.index)
}
})
.then(result => {
this.setSuccessAndError(result.tempFiles)
})
.catch(err => {
console.log('选择失败', err)
})
},
/**
* 批传
* @param {Object} e
*/
uploadFiles(files) {
files = [].concat(files)
this.uploadCloudFiles(files, 5, res => {
this.setProgress(res, res.index, true)
})
.then(result => {
this.setSuccessAndError(result)
})
.catch(err => {
console.log('err', err)
})
},
/**
* 成功或失败
*/
async setSuccessAndError(res, fn) {
let successData = []
let errorData = []
let tempFilePath = []
let errorTempFilePath = []
for (let i = 0; i < res.length; i++) {
// const index = item.index
const item = res[i]
const index = item.uuid ? this.files.findIndex(p => p.uuid === item.uuid) : item.index
if (index === -1 || !this.files) break
if (item.errMsg === 'request:fail') {
this.files[index].url = item.path
this.files[index].status = 'error'
this.files[index].errMsg = item.errMsg
this.files[index].progress = -1
errorData.push(this.files[index])
errorTempFilePath.push(this.files[index].url)
} else {
this.files[index].errMsg = ''
this.files[index].url = item.url
this.files[index].status = 'success'
successData.push(this.files[index])
tempFilePath.push(this.files[index].url)
}
}
if (successData.length > 0) {
this.setEmit()
// 状态改变返回
this.$emit('success', {
tempFiles: this.backObject(successData),
tempFilePaths: tempFilePath
})
}
if (errorData.length > 0) {
this.$emit('fail', {
tempFiles: this.backObject(errorData),
tempFilePaths: errorTempFilePath
})
}
},
/**
* 获取进度
* @param {Object} progressEvent
* @param {Object} index
* @param {Object} type
*/
setProgress(progressEvent, index, type) {
const fileLenth = this.files.length
const percentNum = (index / fileLenth) * 100
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
let idx = index
if (!type) {
idx = this.files.findIndex(p => p.uuid === progressEvent.tempFile.uuid)
}
if (idx === -1 || !this.files[idx]) return
this.files[idx].progress = percentCompleted
// 上传中
this.$emit('progress', {
index: idx,
progress: parseInt(percentCompleted),
tempFile: this.files[idx]
})
},
/**
* 删除
* @param {Object} index
*/
delFile(index) {
this.$emit('delete', {
tempFile: this.files[index],
tempFilePath: this.files[index].url
})
this.files.splice(index, 1)
},
/**
* 获取文件名和后缀
* @param {Object} name
*/
getFileExt(name) {
const last_len = name.lastIndexOf('.')
const len = name.length
return {
name: name.substring(0, last_len),
ext: name.substring(last_len + 1, len)
}
},
/**
* 获取图片信息
* @param {Object} filepath
*/
getFileInfo(filepath) {
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: filepath,
success(res) {
resolve(res)
},
fail(err) {
reject(err)
}
})
})
},
/**
* 获取封装数据
*/
async getFileData(files, type = 'image') {
// 最终需要上传数据库的数据
let fileFullName = this.getFileExt(files.name)
const extname = fileFullName.ext.toLowerCase()
let filedata = {
name: files.name,
uuid: files.uuid,
extname: extname || '',
cloudPath: files.cloudPath,
fileType: files.fileType,
url: files.path || files.path,
size: files.size, //单位是字节
image: {},
path: files.path,
video: {}
}
if (type === 'image') {
const imageinfo = await this.getFileInfo(files.path)
filedata.image.width = imageinfo.width
filedata.image.height = imageinfo.height
filedata.image.location = imageinfo.path
}
return filedata
},
/**
* 批量上传
*/
uploadCloudFiles(files, max = 5, onUploadProgress) {
files = JSON.parse(JSON.stringify(files))
const len = files.length
let count = 0
let self = this
return new Promise(resolve => {
while (count < max) {
next()
}
function next() {
let cur = count++
if (cur >= len) {
!files.find(item => !item.url && !item.errMsg) && resolve(files)
return
}
const fileItem = files[cur]
const index = self.files.findIndex(v => v.uuid === fileItem.uuid)
fileItem.url = ''
delete fileItem.errMsg
uniCloud
.uploadFile({
filePath: fileItem.path,
cloudPath: fileItem.cloudPath,
fileType: fileItem.fileType,
onUploadProgress: res => {
res.index = index
onUploadProgress && onUploadProgress(res)
}
})
.then(res => {
fileItem.url = res.fileID
fileItem.index = index
if (cur < len) {
next()
}
})
.catch(res => {
fileItem.errMsg = res.errMsg || res.message
fileItem.index = index
if (cur < len) {
next()
}
})
}
})
},
setEmit() {
let data = []
if (this.returnType === 'object') {
data = this.backObject(this.files)[0]
} else {
data = this.backObject(this.files)
}
this.$emit('input', data)
},
backObject(files) {
let newFilesData = JSON.parse(JSON.stringify(files))
newFilesData.map(v => {
delete v.path
delete v.uuid
delete v.video
delete v.progress
delete v.errMsg
delete v.status
delete v.cloudPath
return v
})
return newFilesData
},
async getTempFileURL(file, fileList) {
fileList = {
fileList: [].concat(fileList)
}
const urls = await uniCloud.getTempFileURL(fileList)
file.path = urls.fileList[0].tempFileURL || ''
const index = this.files.findIndex(v => v.path === file.path)
if (index !== -1) {
this.$set(this.files, index, file)
}
}
}
}
</script>
<style>
.uni-file-picker {
/* #ifndef APP-NVUE */
box-sizing: border-box;
overflow: hidden;
/* #endif */
}
.uni-file-picker__header {
padding-top: 5px;
padding-bottom: 10px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: space-between;
}
.file-title {
font-size: 14px;
color: #333;
}
.file-count {
font-size: 14px;
color: #999;
}
.is-add {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.icon-add {
width: 50px;
height: 5px;
background-color: #f1f1f1;
border-radius: 2px;
}
.rotate {
position: absolute;
transform: rotate(90deg);
}
</style>

View File

@@ -0,0 +1,324 @@
<template>
<view class="uni-file-picker__files">
<view v-if="!readonly" class="files-button" @click="choose">
<slot></slot>
</view>
<!-- :class="{'is-text-box':showType === 'list'}" -->
<view v-if="list.length > 0" class="uni-file-picker__lists is-text-box" :style="borderStyle">
<!-- ,'is-list-card':showType === 'list-card' -->
<view class="uni-file-picker__lists-box" v-for="(item ,index) in list" :key="index" :class="{
'files-border':index !== 0 && styles.dividline}"
:style="index !== 0 && styles.dividline &&borderLineStyle">
<view class="uni-file-picker__item">
<!-- :class="{'is-text-image':showType === 'list'}" -->
<!-- <view class="files__image is-text-image">
<image class="header-image" :src="item.logo" mode="aspectFit"></image>
</view> -->
<view class="files__name">{{item.name}}</view>
<view v-if="delIcon&&!readonly" class="icon-del-box icon-files" @click="delFile(index)">
<view class="icon-del icon-files"></view>
<view class="icon-del rotate"></view>
</view>
</view>
<view v-if="(item.progress && item.progress !== 100) ||item.progress===0 " class="file-picker__progress">
<progress class="file-picker__progress-item" :percent="item.progress === -1?0:item.progress" stroke-width="4"
:backgroundColor="item.errMsg?'#ff5a5f':'#EBEBEB'" />
</view>
<view v-if="item.status === 'error'" class="file-picker__mask" @click.stop="uploadFiles(item,index)">
点击重试
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: "uploadFile",
props: {
filesList: {
type: Array,
default () {
return []
}
},
delIcon: {
type: Boolean,
default: true
},
limit: {
type: [Number, String],
default: 9
},
showType: {
type: String,
default: ''
},
listStyles: {
type: Object,
default () {
return {
// 是否显示边框
border: true,
// 是否显示分隔线
dividline: true,
// 线条样式
borderStyle: {}
}
}
},
readonly:{
type:Boolean,
default:false
}
},
computed: {
list() {
let files = []
this.filesList.forEach(v => {
files.push(v)
})
return files
},
styles() {
let styles = {
border: true,
dividline: true,
'border-style': {}
}
return Object.assign(styles, this.listStyles)
},
borderStyle() {
let {
borderStyle,
border
} = this.styles
let obj = {}
if (!border) {
obj.border = 'none'
} else {
let width = (borderStyle && borderStyle.width) || 1
width = this.value2px(width)
let radius = (borderStyle && borderStyle.radius) || 5
radius = this.value2px(radius)
obj = {
'border-width': width,
'border-style': (borderStyle && borderStyle.style) || 'solid',
'border-color': (borderStyle && borderStyle.color) || '#eee',
'border-radius': radius
}
}
let classles = ''
for (let i in obj) {
classles += `${i}:${obj[i]};`
}
return classles
},
borderLineStyle() {
let obj = {}
let {
borderStyle
} = this.styles
if (borderStyle && borderStyle.color) {
obj['border-color'] = borderStyle.color
}
if (borderStyle && borderStyle.width) {
let width = borderStyle && borderStyle.width || 1
let style = borderStyle && borderStyle.style || 0
if (typeof width === 'number') {
width += 'px'
} else {
width = width.indexOf('px') ? width : width + 'px'
}
obj['border-width'] = width
if (typeof style === 'number') {
style += 'px'
} else {
style = style.indexOf('px') ? style : style + 'px'
}
obj['border-top-style'] = style
}
let classles = ''
for (let i in obj) {
classles += `${i}:${obj[i]};`
}
return classles
}
},
methods: {
uploadFiles(item, index) {
this.$emit("uploadFiles", {
item,
index
})
},
choose() {
this.$emit("choose")
},
delFile(index) {
this.$emit('delFile', index)
},
value2px(value) {
if (typeof value === 'number') {
value += 'px'
} else {
value = value.indexOf('px') !== -1 ? value : value + 'px'
}
return value
}
}
}
</script>
<style lang="scss">
.uni-file-picker__files {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: flex-start;
}
.files-button {
// border: 1px red solid;
}
.uni-file-picker__lists {
position: relative;
margin-top: 5px;
overflow: hidden;
}
.file-picker__mask {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
position: absolute;
right: 0;
top: 0;
bottom: 0;
left: 0;
color: #fff;
font-size: 14px;
background-color: rgba(0, 0, 0, 0.4);
}
.uni-file-picker__lists-box {
position: relative;
}
.uni-file-picker__item {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
padding: 8px 10px;
padding-right: 5px;
padding-left: 10px;
}
.files-border {
border-top: 1px #eee solid;
}
.files__name {
flex: 1;
font-size: 14px;
color: #666;
margin-right: 25px;
/* #ifndef APP-NVUE */
word-break: break-all;
word-wrap: break-word;
/* #endif */
}
.icon-files {
/* #ifndef APP-NVUE */
position: static;
background-color: initial;
/* #endif */
}
// .icon-files .icon-del {
// background-color: #333;
// width: 12px;
// height: 1px;
// }
.is-list-card {
border: 1px #eee solid;
margin-bottom: 5px;
border-radius: 5px;
box-shadow: 0 0 2px 0px rgba(0, 0, 0, 0.1);
padding: 5px;
}
.files__image {
width: 40px;
height: 40px;
margin-right: 10px;
}
.header-image {
width: 100%;
height: 100%;
}
.is-text-box {
border: 1px #eee solid;
border-radius: 5px;
}
.is-text-image {
width: 25px;
height: 25px;
margin-left: 5px;
}
.rotate {
position: absolute;
transform: rotate(90deg);
}
.icon-del-box {
/* #ifndef APP-NVUE */
display: flex;
margin: auto 0;
/* #endif */
align-items: center;
justify-content: center;
position: absolute;
top: 0px;
bottom: 0;
right: 5px;
height: 26px;
width: 26px;
// border-radius: 50%;
// background-color: rgba(0, 0, 0, 0.5);
z-index: 2;
transform: rotate(-45deg);
}
.icon-del {
width: 15px;
height: 1px;
background-color: #333;
// border-radius: 1px;
}
/* #ifdef H5 */
@media all and (min-width: 768px) {
.uni-file-picker__files {
max-width: 375px;
}
}
/* #endif */
</style>

View File

@@ -0,0 +1,289 @@
<template>
<view class="uni-file-picker__container">
<view class="file-picker__box" v-for="(item,index) in filesList" :key="index" :style="boxStyle">
<view class="file-picker__box-content" :style="borderStyle">
<image class="file-image" :src="item.path" mode="aspectFill" @click.stop="prviewImage(item,index)"></image>
<view v-if="delIcon && !readonly" class="icon-del-box" @click.stop="delFile(index)">
<view class="icon-del"></view>
<view class="icon-del rotate"></view>
</view>
<view v-if="(item.progress && item.progress !== 100) ||item.progress===0 " class="file-picker__progress">
<progress class="file-picker__progress-item" :percent="item.progress === -1?0:item.progress" stroke-width="4"
:backgroundColor="item.errMsg?'#ff5a5f':'#EBEBEB'" />
</view>
<view v-if="item.errMsg" class="file-picker__mask" @click.stop="uploadFiles(item,index)">
点击重试
</view>
</view>
</view>
<view v-if="filesList.length < limit && !readonly" class="file-picker__box" :style="boxStyle">
<view class="file-picker__box-content is-add" :style="borderStyle" @click="choose">
<slot>
<view class="icon-add"></view>
<view class="icon-add rotate"></view>
</slot>
</view>
</view>
</view>
</template>
<script>
export default {
name: "uploadImage",
props: {
filesList: {
type: Array,
default () {
return []
}
},
disabled:{
type: Boolean,
default: false
},
disablePreview: {
type: Boolean,
default: false
},
limit: {
type: [Number, String],
default: 9
},
imageStyles: {
type: Object,
default () {
return {
width: 'auto',
height: 'auto',
border: {}
}
}
},
delIcon: {
type: Boolean,
default: true
},
readonly:{
type:Boolean,
default:false
}
},
computed: {
styles() {
let styles = {
width: 'auto',
height: 'auto',
border: {}
}
return Object.assign(styles, this.imageStyles)
},
boxStyle() {
const {
width = 'auto',
height = 'auto'
} = this.styles
let obj = {}
if (height === 'auto') {
if (width !== 'auto') {
obj.height = this.value2px(width)
obj['padding-top'] = 0
} else {
obj.height = 0
}
} else {
obj.height = this.value2px(height)
obj['padding-top'] = 0
}
if (width === 'auto') {
if (height !== 'auto') {
obj.width = this.value2px(height)
} else {
obj.width = '33.3%'
}
} else {
obj.width = this.value2px(width)
}
let classles = ''
for(let i in obj){
classles+= `${i}:${obj[i]};`
}
return classles
},
borderStyle() {
let {
border
} = this.styles
let obj = {}
if (typeof border === 'boolean') {
obj.border = border ? '1px #eee solid' : 'none'
} else {
let width = (border && border.width) || 1
width = this.value2px(width)
let radius = (border && border.radius) || 5
radius = this.value2px(radius)
obj = {
'border-width': width,
'border-style': (border && border.style) || 'solid',
'border-color': (border && border.color) || '#eee',
'border-radius': radius
}
}
let classles = ''
for(let i in obj){
classles+= `${i}:${obj[i]};`
}
return classles
}
},
methods: {
uploadFiles(item, index) {
this.$emit("uploadFiles", item)
},
choose() {
this.$emit("choose")
},
delFile(index) {
this.$emit('delFile', index)
},
prviewImage(img, index) {
let urls = []
if(Number(this.limit) === 1&&this.disablePreview&&!this.disabled){
this.$emit("choose")
}
if(this.disablePreview) return
this.filesList.forEach(i => {
urls.push(i.path)
})
uni.previewImage({
urls: urls,
current: index
});
},
value2px(value) {
if (typeof value === 'number') {
value += 'px'
} else {
if (value.indexOf('%') === -1) {
value = value.indexOf('px') !== -1 ? value : value + 'px'
}
}
return value
}
}
}
</script>
<style lang="scss">
.uni-file-picker__container {
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
/* #endif */
flex-wrap: wrap;
margin: -5px;
}
.file-picker__box {
position: relative;
// flex: 0 0 33.3%;
width: 33.3%;
height: 0;
padding-top: 33.33%;
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
}
.file-picker__box-content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: 5px;
border: 1px #eee solid;
border-radius: 8px;
overflow: hidden;
}
.file-picker__progress {
position: absolute;
bottom: 0;
left: 0;
right: 0;
/* border: 1px red solid; */
z-index: 2;
}
.file-picker__progress-item {
width: 100%;
}
.file-picker__mask {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
position: absolute;
right: 0;
top: 0;
bottom: 0;
left: 0;
color: #fff;
font-size: 12px;
background-color: rgba(0, 0, 0, 0.4);
}
.file-image {
width: 100%;
height: 100%;
}
.is-add {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.icon-add {
width: 50px;
height: 5px;
background-color: #f1f1f1;
border-radius: 2px;
}
.rotate {
position: absolute;
transform: rotate(90deg);
}
.icon-del-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
position: absolute;
top: 5px;
right: 5px;
height: 26px;
width: 26px;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 2;
transform: rotate(-45deg);
}
.icon-del {
width: 15px;
height: 2px;
background-color: #fff;
border-radius: 2px;
}
</style>