<template>
<section class="vm-feedback feedback__inputBox">
<!--textarea-->
<div class="feedback__inputBox--textareaBox">
<vm-textarea :placeholder="placeholder"
ref="textarea"
v-model="value.text"
:name="name"
:rows="rows"
:readonly="readonly"
:disabled="disabled"
:autofocus="autofocus"
:maxlength="maxlength"
@onInput="inputHandler"></vm-textarea>
</div>
<p class="feedback__text">{{textCount}}/{{maxlength}}</p>
<!--image 1~3| && word count-->
<div class="feedback__inputBox--images">
<div class="image"
v-for="(item,index) in value.images" :key="index"
:style="{backgroundImage:'url('+item.code+')'}">
<div class="preview" @click="previewImage(index)"></div>
<div class="delete" @click="removeImage(index)"></div>
</div>
<!--input-->
<div class="image empty" v-show="value.images.length < maximage" @click="addFile($event)">
<input @change="onChangeHandler" class="file" type="file">
</div>
</div>
</section>
</template>
<script type="text/javascript">
/**
* @component Feedback
* @description
*
* ## 反馈图文框组件 / Feedback
*
* 用于反馈/意见信息/评价等场景. 如果在Alipay环境,则使用alipay的组件上传。
*
* ### 如何引入
* ```
* import { Feedback } from 'vimo'
* Vue.component(Feedback.name, Feedback)
* // 或者
* export default{
* components: {
* Feedback
* }
* }
* ```
*
* @props {Number} [rows=3] - rows
* @props {String} [name] - name
* @props {Boolean} [readonly=false] - readonly
* @props {Boolean} [disabled=false] - disabled
* @props {Boolean} [autofocus=false] - autofocus
* @props {String} [placeholder='请输入您的反馈...'] - placeholder
* @props {Number} [maxlength=300] - 文本输入的最大长度
* @props {Number} [maximage=300] - 图片上传的最大个数
* @props {Boolean} [isH5=false] - 是否强制使用H5模式
*
* @props {Object} value - 传入传出数据
* @props {String} value.text - 传入传出文本
* @props {Array} value.images - 传入传出图片信息
* @props {Array} value.images.code - base64格式的图片
* @props {Array} value.images.blob - 修正方向及大小的二进制blob
* @props {Array} value.images.file - 图片file句柄
*
* @demo #/feedback
* @usage
* <Feedback v-model="feedbackInfo" :maxlength="100" :maximage="4"></Feedback>
* */
import canvasResize from './fixImage'
import { isArray, isString } from '../../util/type'
import Textarea from '../textarea'
import PreviewImage from '../preview-image'
export default {
name: 'Feedback',
components: {'vm-textarea': Textarea},
data () {
return {}
},
props: {
rows: {
type: Number,
default: 3
},
name: String,
readonly: Boolean,
disabled: Boolean,
autofocus: Boolean,
placeholder: {
type: String,
default: '请输入您的反馈...'
},
maxlength: {
type: Number,
default: 300
},
maximage: {
type: Number,
default: 3
},
value: {
type: Object,
required: true,
validator (value) {
return isString(value.text) && isArray(value.images)
}
},
isH5: Boolean
},
computed: {
textareaComponent () {
return this.$refs.textarea
},
// 计算输入数
textCount () {
return this.value.text.length
}
},
methods: {
/**
* @function update
* @description
* 更新textarea组件
* */
update () {
this.textareaComponent.update()
},
/**
* @function destroy
* @description
* 销毁textarea组件
* */
destroy () {
this.textareaComponent.destroy()
},
// -------- private -------
/**
* 图片预览
* ps: alipay 的图片预览组件 无法预览 自己的图片选择器选择的文件, 因此这里使用的是H5组件
* @private
* */
previewImage (index) {
let images = []
this.value.images.forEach((image) => {
images.push(image.code)
})
PreviewImage.present({
isH5: true,
current: index || 0,
urls: images || []
})
},
/**
* @private
* */
addFile ($event) {
let isAlipayReady = window.AlipayJSBridge && !this.isH5
if (isAlipayReady) {
// 阻止h5的input的触发
$event.preventDefault()
$event.stopPropagation()
console.info('Feedback 组件使用Alipay模式, 单次上传一张.')
window.ap && window.ap.chooseImage(1, (res) => {
window.ap && window.ap.compressImage({
apFilePaths: res.apFilePaths,
level: 0
}, (result) => {
// Cannot read property '0' of undefined
if (result && result.apFilePaths && result.apFilePaths.length > 0 && result.apFilePaths[0]) {
let imgData = {
code: result.apFilePaths[0],
blob: result.apFilePaths[0], // 转化的二进制图片文件
file: result.apFilePaths[0] // 源文件 file
}
this.pushImageData(imgData)
}
})
})
} else {
console.info('Feedback 组件使用H5模式!')
}
},
/**
* @private
* */
inputHandler () {
if (this.value.text.length > this.maxlength) {
this.value.text = this.value.text.substr(0, this.maxlength)
} else {
this.$emit('input', this.value)
}
},
/**
* 图片数据推入
* @private
* */
pushImageData (imgData) {
this.value.images.push(imgData)
this.$emit('input', this.value)
},
/**
* 插入图片时
* @private
* */
onChangeHandler (event) {
let input = event.target
let fixImage = (file, callback) => {
// 加载资源
canvasResize(file, {
width: 640, // 最大的尺寸,如果比这小是不会出现放大的情况的,文章宽度为710px
height: 0,
crop: false,
quality: 80,
// rotate: 90,
callback (data, width, height) {
// 将图片改为二进制文件,准备上传
let _blob = canvasResize('dataURLtoBlob', data)
!!callback && callback(_blob)
}
})
}
if (input.files && input.files[0]) {
let file = input.files[0]
if (!input.files[0].type.match('image.*')) {
return null
}
fixImage(file, (_blob) => {
let reader = new FileReader()
reader.onload = (e) => {
let imgData = {
code: e.target.result,
blob: _blob, // 转化的二进制图片文件
file: file // 源文件 file
}
this.pushImageData(imgData)
}
reader.readAsDataURL(_blob)
})
}
},
/**
* 点击移除图片
* @private
* */
removeImage (index) {
this.value.images.splice(index, 1)
}
}
}
</script>
<style lang="scss">
.vm-feedback.feedback__inputBox {
border-bottom: 1px solid #f2f2f2;
width: 100%;
background: #fff;
box-sizing: border-box;
margin-bottom: 10px;
.feedback__inputBox--textareaBox {
height: auto;
width: 100%;
padding: 10px;
background: #fff;
}
.feedback__text {
color: #8b8b8b;
font-size: 14px;
margin: 0;
text-align: right;
padding: 0 10px;
}
.feedback__inputBox--images {
display: flex;
justify-content: flex-start;
align-content: flex-start;
flex-wrap: wrap;
padding: 10px;
.image {
width: 60px;
height: 60px;
margin-right: 10px;
margin-bottom: 10px;
box-sizing: border-box;
/*overflow: hidden;*/
position: relative;
display: flex;
flex: 0 0 auto;
justify-content: center;
align-items: center;
border: 1px solid #eee;
background-repeat: no-repeat;
background-color: #eee;
background-position: center center;
background-size: cover;
.file {
display: none;
}
img {
width: 100%;
min-height: 100%;
}
}
.preview {
height: 100%;
width: 100%;
position: absolute;
z-index: 1;
left: 0;
top: 0;
}
.delete {
position: absolute;
height: 25px;
width: 25px;
top: -5px;
right: -5px;
z-index: 2;
&:before {
content: '';
position: absolute;
height: 16px;
width: 16px;
background: red;
border-radius: 100%;
top: 0;
right: 0;
z-index: 3;
}
&::after {
content: '';
height: 1px;
width: 10px;
position: absolute;
background: #fff;
top: 7px;
right: 3px;
z-index: 4;
}
}
.empty {
border: 1px dashed #8c8c8c;
background: #f3f3f3;
position: relative;
.file {
display: block !important;
width: 100%;
height: 100%;
opacity: 0;
}
&::before,
&::after {
background: #dadada;
left: 50%;
top: 50%;
position: absolute;
content: '';
}
&::after {
margin-left: -1px;
margin-top: -15px;
height: 30px;
width: 2px;
}
&::before {
margin-left: -15px;
margin-top: -1px;
width: 30px;
height: 2px;
}
}
}
}
</style>