vue实现照片选择或者拍照功能
照片格式校验,
图片质量压缩,
图片尺寸压缩,
图片离线保存,
图片base64编码互转
<template>
<div class="photo-list">
<hips-view
ref="photoList"
header-fixed
sub-header-fixed
header-height="48"
:sub-header-height="subHeaderHeight"
footer-height="48"
<div
slot="header"
style="width:100%"
<hips-nav-bar
:title="headerTitle"
<div slot="left">
<hips-button
style="margin-left:10px"
@click="goBack"
返回
</hips-button>
</div>
<div slot="right">
<hips-button
v-if="orderStatus===1"
style="margin-right:10px"
@click="uploadImg"
上传
</hips-button>
<hips-button
v-if="orderStatus===0"
style="margin-right:10px"
@click="saveImg"
保存
</hips-button>
</div>
</hips-nav-bar>
</div>
<div
slot="sub-header"
class="sub-header"
<div class="content-item">
<div
v-if="orderStatus===0"
class="content-item-checked"
<hips-checkbox
v-model="checked"
:border="false"
class="content-item-components"
已确认录入完整信息
</hips-checkbox>
</div>
<div class="content-title">
<span v-if="orderStatus===1|orderStatus===0">
数量({{ photoDataList.length }}/30)
</span>
<span v-if="orderStatus===2">
数量({{ photoDataList.length }})
</span>
</div>
<hips-button
v-if="deleteFileSelected.length < 1&&orderStatus!==2"
class="read-only-delete"
size="small"
@click="deleteNull"
删除
</hips-button>
<hips-button
v-if="deleteFileSelected.length>0&&orderStatus!==2"
size="small"
type="warning"
@click="deleteFile"
删除
</hips-button>
<hips-button
v-if="orderStatus===2"
class="back-to-top"
size="small"
@click="backToTop"
回到顶部
</hips-button>
</div>
</div>
<div
class="content"
:class="[orderStatus!==0?'content-12':'content-0']"
<hips-scroll
ref="scroll"
<div
v-if="photoDataList.length"
class="list"
<ol class="list-ol">
<li
v-for="(item, index) in photoDataList"
:key="index"
class="list-li"
<label
v-if="item.status.canSelect"
class="checkbox-label"
<span class="checkbox-lists">
<input
v-model="item.status.selectValue"
class="checkbox-list-input"
type="checkbox"
@change="checkChange"
@click="select(item)"
<span class="checkbox-list-span" />
</span>
</label>
<img
v-if="item.status.read"
ref="showImg"
:src="changeUrl(item.file)"
class="show-img"
@click="showPreviewer(index)"
<img
v-if="item.status.unUpload"
:src="upload"
alt="图片未上传"
class="upload"
</li>
</ol>
</div>
<img
:src="yasuohou"
alt=""
</hips-scroll>
</div>
<div
slot="footer"
class="footer"
<div class="left">
<label
v-if="photoDataList.length < 30"
for="picture"
class="button"
- 相册照片
</label>
<label
v-else
class="only-read-button"
- 相册照片
</label>
<input
id="picture"
type="file"
@change="change"
</div>
<div class="right">
<label
v-if="photoDataList.length < 30"
id="camera"
class="button"
- 新拍照片
</label>
<label
v-else
class="only-read-button"
- 新拍照片
</label>
<input
id="camera"
type="file"
accept="image/*"
capture="camera"
</div>
</div>
</hips-view>
</div>
</template></pre>
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="javascript" cid="n8" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><script>
import { NavBar, Button, Tabs, Tab, View, Scroll, Input, Group, Cell, Checkbox , CheckboxGroup, Previewer } from "@hips/vue-ui";
import edit from "../../../assets/editor.png"
import add from "../../../assets/add-fill.png"
import upload from "../../../assets/upload.png"
import DB from "@/indexDB"
// import { uuid } from "@/utils"
import Vue from "vue"
Vue.use(Previewer);
export default {
name: "PhotoList",
components: {
[View.name]: View,
[Tabs.name]: Tabs,
[Tab.name]: Tab,
[NavBar.name]: NavBar,
[Button.name]: Button,
[Scroll.name]: Scroll,
[Input.name]: Input,
[Group.name]: Group,
[Cell.name]: Cell,
[Checkbox.name]: Checkbox,
[CheckboxGroup.name]: CheckboxGroup,
[Previewer.name]: Previewer,
},
data(){
return{
subHeaderHeight: 100,
headerTitle: "照片一览",
checked: false,
edit: edit,
add: add,
upload: upload,
mimeTypes: [ 'image/png', 'image/jpeg' ], // 图片类型
selectValue: false,
photoDataList: [], // 数据列表数组
imgUrl: "",
canSelect: false,
unUpload: false,
orderStatus: 0, // 0 工单还未提交, 1 工单已经提交,但还未点击完成派工时 2 完成派工后
imageSet: [], // 展示的照片源
deleteFileSelected: [], // 已经勾选删除的照片数组
yasuohou: "",
files: [],
}
},
beforeRouteEnter(to,from,next){
next((vm) => {
if(vm.orderStatus !== 0) {
vm.subHeaderHeight = 38
vm.resizePage();
}
vm.photoDataList = [];
let PhotoAlbum = {
name: "album",
store: "photo1",
id: "timeStamp",
db: null,
}
DB.openDB(PhotoAlbum, PhotoAlbum.name, 1, PhotoAlbum.store, PhotoAlbum.id).then((DBres) => { // 将当前角色的menu存在indexedDB中
DB.readAll(DBres.db, PhotoAlbum.store).then((res)=> {
for(let i = 0; i < res.length; i ++){
let status = {};
if(vm.orderStatus === 0) {
status = {
canSelect: true,
unUpload: true,
selectValue: false,
read: true, // 是否是从本地文件夹读取的图片
}
}
if(vm.orderStatus === 1) {
status = {
canSelect: false,
unUpload: true,
selectValue: false,
read: true, // 是否是从本地文件夹读取的图片
}
}
if(vm.orderStatus === 2) {
status = {
canSelect: false,
unUpload: false,
selectValue: false,
read: true, // 是否是从本地文件夹读取的图片
}
}
res[i].status = status;
}
vm.photoDataList = res;
})
})
})
},
watch: {
photoDataList() {
this.deleteFileSelected = this.photoDataList.filter((item) => {
return item.status.selectValue === true;
})
},
files() {
console.log("files", this.files)
},
},
methods: {
showPreviewer (index) { // 图片预览
this.imageSet = [];
for(let i = 0; i < this.photoDataList.length; i ++) {
this.imageSet.push({
msrc: "",
src: window.URL.createObjectURL(this.photoDataList[i].file),
desc: this.photoDataList[i].file.name,
})
}
this.{index}) }, }) }, saveImg() { // 保存图片 let PhotoAlbum = { name: "album", store: "photo1", id: "timeStamp", db: null, } DB.openDB(PhotoAlbum, PhotoAlbum.name, 1, PhotoAlbum.store, PhotoAlbum.id).then((DBres) => { // 将当前角色的menu存在indexedDB中 DB.update(DBres.db, PhotoAlbum.store, this.photoDataList); }) }, uploadImg() { // 上传图片 }, typeCheck(file) { // 图片类型校验 let flag = false; let fileTypes = ".jpg"; let filePath = file.value; //当括号里面的值为0、空字符、false 、null 、undefined的时候就相当于false if(filePath){ let isNext = false; let fileEnd = filePath.substring(filePath.indexOf(".")); if (fileTypes === fileEnd) { isNext = true; flag= true; } if (!isNext){ alert('不接受此文件类型,请上传jpg格式文件'); file.value = ""; flag = false; } } return flag; }, sizeCheck(file) { let filePath = file.value; let that = this; that.files = []; let fileTypes = ".jpg"; if(filePath) { let isNext = false; let fileEnd = filePath.substring(filePath.indexOf(".")); if (fileTypes === fileEnd) { isNext = true; } if (!isNext){ // 类型检测 that.$hips.dialog({ title: "提示", content: "不接受此文件类型,请上传jpg格式文", okText: '确定', cancelText: '取消', closable: false, }) file.value = ""; return; } let filePic = file.files[0]; //读取图片数据 let reader = new FileReader(); reader.onload = function (e) { let data = e.target.result; let image = new Image(); //加载图片获取图片真实宽度和高度 image.src = data; setTimeout(() => { if(image.width < 640 | image.height < 480) { // 分辨率不可小于640*480 // alert("照片添加失败,分辨率不可小于640*480"); let back = () => { that.files = []; file.value = ""; return; } that.$hips.dialog.confirm({ title: "提示", content: "照片添加失败,分辨率不可小于640*480", okText: '确定', cancelText: '取消', closable: false, onOk: back, }); } if(image.width >= 640 && image.height >= 480&& image.width <= 1920 && image.height <= 1080) { // 分辨率不可大于1920*1080 if(file.files[0].size/1024 > 150) { // 压缩大小 let canvasWidth = image.width; let canvasHeight = image.height; let canvas = document.createElement('canvas');//生成canvas let ctx = canvas.getContext('2d'); canvas.width = canvasWidth; // 创建节点属性 canvas.height = canvasHeight; let anw = document.createAttribute('width'); anw.nodeValue = canvasWidth; let anh = document.createAttribute('height'); anh.nodeValue = canvasHeight; canvas.setAttributeNode(anw); canvas.setAttributeNode(anh); ctx.fillRect(0, 0, canvasWidth, canvasHeight); ctx.drawImage(image, 0, 0, canvasWidth, canvasHeight); //图像质量,值越小,所绘制出的图像越模糊 let quality = 0.05; let size = 0; let lastObj; while(size < 150) { if(obj) { lastObj = obj; } let base64String = canvas.toDataURL('image/jpeg', quality); let obj = that.base64ToImg(base64String, file.files[0].name); size = Number((obj.size / 1024).toFixed(2)); quality = Number((quality + 0.05).toFixed(2)); } that.files.push(lastObj); file.value = ""; } else { that.files.push(file.files[0]); file.value = ""; } } if(image.width > 1920 | image.height > 1080) { // 压缩分辨率 let scale = Number((image.width / image.height).toFixed(2)); let standard = Number((1920 / 1080).toFixed(2)); let canvasWidth, canvasHeight; if( scale > standard ) { canvasWidth = image.width; canvasHeight = Number((image.width / scale).toFixed(2)); } else { canvasHeight = image.height; canvasWidth = Number((image.height * scale).toFixed(2)); } let canvas = document.createElement('canvas');//生成canvas let ctx = canvas.getContext('2d'); canvas.width = canvasWidth; // 创建节点属性 canvas.height = canvasHeight; let anw = document.createAttribute('width'); anw.nodeValue = canvasWidth; let anh = document.createAttribute('height'); anh.nodeValue = canvasHeight; canvas.setAttributeNode(anw); canvas.setAttributeNode(anh); ctx.fillRect(0, 0, canvasWidth, canvasHeight); ctx.drawImage(image, 0, 0, canvasWidth, canvasHeight); //图像质量,值越小,所绘制出的图像越模糊 let quality = 0.95; let base64String = canvas.toDataURL('image/jpeg', quality); let obj = that.base64ToImg(base64String, file.files[0].name); if((obj.size / 1024) < 150) { that.files.push(obj); file.value = ""; } else { // 压缩大小 let quality = 0.05; let size = 0; let lastObj; while(size < 150) { if(obj) { lastObj = obj; } let base64String = canvas.toDataURL('image/jpeg', quality); let obj = that.base64ToImg(base64String, file.files[0].name); size = Number((obj.size / 1024).toFixed(2)); quality = Number((quality + 0.05).toFixed(2)); } that.files.push(lastObj); file.value = ""; return; } } let arr = []; for(let i = 0; i < that.files.length; i ++) { let pictureObj = {}; let status = { canSelect: true, unUpload: true, selectValue: false, read: true, // 是否是从本地文件夹读取的图片 } pictureObj.status = status; pictureObj.file = that.files[i]; pictureObj.timeStamp = (new Date()).valueOf(); arr.push(pictureObj); } that.photoDataList = [ ...that.photoDataList, ...arr ] ; }, 1) }; reader.readAsDataURL(filePic); } else { this.$hips.dialog } }, change(e) { this.sizeCheck(e.target); // this.imgToBase64(this.photoDataList); // indexedDB不能直接存储base64 }, imgToBase64(imgList) { // 图片base64编码 for(let i = 0; i < imgList.length; i ++) { let reader = new FileReader(); reader.readAsDataURL(imgList[i].file); reader.onload = (e) => { imgList[i].status.base64 = e.target.result; } } }, base64ToImg(base64, filename) { // 图片base64转码 let dataURLtoFile = (dataurl, filename = 'file') => { if(!dataurl) return; let arr = dataurl.split(',') let mime = arr[0].match(/:(.*?);/)[1] let suffix = mime.split('/')[1] let bstr = atob(arr[1]) let n = bstr.length let u8arr = new Uint8Array(n) while (n--) { u8arr[n] = bstr.charCodeAt(n) } return new File([ u8arr ],
{suffix}, { type: mime, }) } let imgFile = dataURLtoFile(base64, filename); if(!imgFile) return; // return window.URL.createObjectURL(imgFile); return imgFile; }, changeUrl(item) { // 获取图片地址 let a = window.URL.createObjectURL(item); return a; }, deleteFile() { // 删除图片 let back = ()=> { console.log("要删除的图片", this.photoDataList); let PhotoAlbum = { name: "album", store: "photo1", id: "name", db: null, } for(let i = 0; i < this.photoDataList.length; i ++) { if(this.photoDataList[i].status.selectValue) { let mainKey = this.photoDataList[i].timeStamp; DB.openDB(PhotoAlbum, PhotoAlbum.name, 1, PhotoAlbum.store, PhotoAlbum.id).then((DBres) => { // 将当前角色的menu存在indexedDB中 DB.remove(DBres.db, PhotoAlbum.store, mainKey); }) } } let arr = []; for(let i = 0; i < this.photoDataList.length; i ++) { if(!this.photoDataList[i].status.selectValue) { arr.push(this.photoDataList[i]); } } this.photoDataList = []; for(let i = 0; i < arr.length; i ++) { this.$set(this.photoDataList, i, arr[i]); } } this.$hips.dialog.confirm({ title: '提示', content:
请确认是否删除这forceUpdate();
// this.resizePage();
},
deleteNull() { // 未勾选删除
this.refs.scroll.scrollTo(0, 0, 700, "easing");
},
goBack() { // 返回上页面
if(this.orderStatus === 1) {
let back = ()=> {
this.hips.dialog.confirm({
title: '提示',
content: '数据未被保存,是否继续返回?',
okText: '确定',
cancelText: '取消',
closable: false,
onOk: back,
onCancel() {},
});
}
if(this.orderStatus === 0 | this.orderStatus === 2) {
this.forceUpdate(); // 数据改变更新视图
},
resizePage () { // 刷新页面(未使用)
this.emit('hips-view:resize');
},
},
}
</script></pre>
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="css" cid="n9" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><style lang="stylus" scoped>
// 选择框样式 start
.checkbox-list-input {
display: none;
}
.checkbox-list-input:checked + .checkbox-list-span {
background-color: #c30;
border-color: #c30;
}
.checkbox-list-input:checked + .checkbox-list-span::after {
border-color: #fff;
transform: rotate(45deg) scale(1);
}
.checkbox-list-input[disabled] + .checkbox-list-span {
background-color: #d9d9d9;
border-color: #ccc;
}
.checkbox-list-span {
display: inline-block;
background-color: #fff;
border-radius: 100%;
border: 1px solid #ccc;
position: relative;
width: 26px;
height: 26px;
vertical-align: middle;
}
.checkbox-list-span::after {
border: 2px solid transparent;
border-left: 0;
border-top: 0;
content: "";
top: 4px;
left: 9px;
position: absolute;
width: 6px;
height: 16px;
transform: rotate(45deg) scale(0);
transition: transform .2s;
}
// 选择框样式 end
.photo-list {
.hips-view__sub-header {
border-bottom none
}
.sub-header {
.content-item {
margin 10px
.content-item-checked {
margin-top 10px
margin-bottom 10px
text-align center
background #fff
/deep/.hips-checkbox {
color #009999
}
/deep/.hips-checkbox__checked--active {
background #c30
}
.content-item-components {
display inline-block
}
}
.content-title {
display inline-block
height 30px
width 100%
line-height 30px
text-align center
margin-right -70px
}
.delete, .back-to-top {
background #ff6600
color #ffffff
}
.read-only-delete {
background #e4e4e4
color #000
}
}
}
.content-0 {
height: calc( 100vh - 218px);
}
.content-12 {
height: calc( 100vh - 166px);
}
.content {
margin: 10px;
border: 1px solid #ccc;
border-radius: 10px;
background: #fff;
.hips-row:after {
display: none;
}
.list {
.list-ol {
display flex
flex-flow row wrap
// justify-content space-around
align-items flex-start
}
.list-li {
position relative
margin-top 10px
margin-left 8px
.checkbox-lists {
position absolute
right -4px
top -6px
}
.show-img {
width 100px
height 100px
padding 2px
border 2px dashed #cccccc
}
.upload {
position absolute
width 30px
height 30px
bottom -2px
right -4px
background #fff
border-radius 50% 50%
}
}
}
}
.footer {
display flex
flex 1
height 100%
.hips-view__sub-header {
border-bottom none
}
.sub-header {
.content-item {
margin 10px
.content-item-checked {
margin-top 10px
margin-bottom 10px
text-align center
background #fff
/deep/.hips-checkbox {
color #009999
}
/deep/.hips-checkbox__checked--active {
background #c30
}
.content-item-components {
display inline-block
}
}
.content-title {
display inline-block
height 30px
width 100%
line-height 30px
text-align center
margin-right -70px
}
.delete, .back-to-top {
background #ff6600
color #ffffff
}
.read-only-delete {
background #e4e4e4
color #000
}
}
}
.content-0 {
height: calc( 100vh - 218px);
}
.content-12 {
height: calc( 100vh - 166px);
}
.content {
margin: 10px;
border: 1px solid #ccc;
border-radius: 10px;
background: #fff;
>>>.hips-row:after {
display: none;
}
.list {
.list-ol {
display flex
flex-flow row wrap
// justify-content space-around
align-items flex-start
}
.list-li {
position relative
margin-top 10px
margin-left 8px
.checkbox-lists {
position absolute
right -4px
top -6px
}
.show-img {
width 100px
height 100px
padding 2px
border 2px dashed #cccccc
}
.upload {
position absolute
width 30px
height 30px
bottom -2px
right -4px
background #fff
border-radius 50% 50%
}
}
}
}
.footer {
display flex
flex 1
height 100%
line-height 48px
input {
display inline-block
width 0
height 0
}
.left {
width 50%
text-align center
.button {
display inline-block
height 40px
width: 90%
line-height 40px
background #ff6600
color #ffffff
}
.only-read-button {
background #e4e4e4
color #000
height 40px
width: 90%
line-height 40px
display inline-block
}
}
.right {
width 50%
text-align center
.button {
display inline-block
height 40px
line-height 40px
width: 90%
background #cc3300
color #ffffff
}
.only-read-button {
background #e4e4e4
color #000
height 40px
width: 90%
line-height 40px
display inline-block
}
}
}
}
</style></pre>
效果展示
vue父子组件传参
// dom部分
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n14" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><div id="app">
<my-header :l="list"></my-header>
</div></pre>
// vue父子组件如何传参
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="javascript" cid="n16" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">var oApp = new Vue({
el: "#app",
data: {
list: ["第1项", "第2项", "第3项"]
},
components: {
'my-header': {
template: <div> <h2 ref>{{nowMessage}}</h2> <ul> <li v-for="item in l">{{item}}</li> <my-nav @changEvent="getChildContent"></my-nav> </ul> </div>
,
data: function() {
return {
message: "hello world!!!",
nowMessage: this.message //单向数据流,数据操做,refDOM操作 ,.native
}
},
methods: {
getChildContent: function(arg) {
console.log(arg);
this.nowMessage = arg;
}
},
props: ['l'], //父组件向子组件进行通信操作
components: {
'my-nav': { //子组件向父组件传值
template: <ul> <li @click="getContent">"yes, I'm fine"</li> </ul>
,
methods: {
getContent: function(ev) {
this.$emit("changEvent", ev.target.innerText);
}
}
}
}
}
}
})</pre>