森蓝小程序
This commit is contained in:
158
components/customTabBar/customTabBar.vue
Normal file
158
components/customTabBar/customTabBar.vue
Normal file
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<view class="tabBar">
|
||||
<view class="cont">
|
||||
<view class="image-text_1 flex-col" @tap="switchTab(list[0].pagePath)">
|
||||
<image
|
||||
class="thumbnail_1"
|
||||
referrerpolicy="no-referrer"
|
||||
:src="selected == 0? list[0].selectedIconPath:list[0].iconPath"
|
||||
/>
|
||||
<text :class="selected == 0?'text-group_1':'text-group_2'">
|
||||
{{list[0].text}}
|
||||
</text>
|
||||
</view>
|
||||
<!-- <image
|
||||
class="label_1"
|
||||
referrerpolicy="no-referrer"
|
||||
src="https://humeng-res.oss-cn-beijing.aliyuncs.com/hm_aigc/applet/icon_camera%403x.png"
|
||||
@tap="jumpVlog"
|
||||
/> -->
|
||||
<view class="image-text_2 flex-col" @tap="switchTab(list[1].pagePath)">
|
||||
<image
|
||||
class="thumbnail_2"
|
||||
referrerpolicy="no-referrer"
|
||||
:src="selected == 1? list[1].selectedIconPath:list[1].iconPath"
|
||||
/>
|
||||
<text :class="selected == 1?'text-group_3':'text-group_4'">
|
||||
{{list[1].text}}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
selected: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
list: [
|
||||
{
|
||||
pagePath: "/pages/home/home",
|
||||
text: "首页",
|
||||
iconPath: "/static/icon/icon_Home_n@3x_da.png",
|
||||
selectedIconPath: "/static/icon/icon_Home_n@3x_a.png",
|
||||
},
|
||||
{
|
||||
pagePath: "/pages/mine/mine",
|
||||
text: "我的",
|
||||
iconPath: "/static/icon/icon_我的_n@3x_da.png",
|
||||
selectedIconPath: "/static/icon/icon_我的_n@3x_a.png",
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
switchTab(url) {
|
||||
uni.switchTab({
|
||||
url
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.tabBar {
|
||||
z-index: 100;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
font-size: 28rpx;
|
||||
background-color: #fff;
|
||||
color: #636363;
|
||||
left: 0;
|
||||
height: 150rpx;
|
||||
background: none;
|
||||
}
|
||||
.cont {
|
||||
z-index: 0;
|
||||
height: 130rpx;
|
||||
margin-top: 30rpx;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
.image-text_1 {
|
||||
margin-top: 5px;
|
||||
margin-right: 50px;
|
||||
}
|
||||
|
||||
.thumbnail_1 {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.text-group_1 {
|
||||
overflow-wrap: break-word;
|
||||
color: rgba(22, 140, 248, 1);
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
line-height: 12px;
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
.text-group_2 {
|
||||
overflow-wrap: break-word;
|
||||
color: rgba(80, 92, 115, 1);
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
line-height: 12px;
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
.label_1 {
|
||||
width: 43px;
|
||||
height: 42px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.image-text_2 {
|
||||
margin-top: 5px;
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
.thumbnail_2 {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.text-group_3 {
|
||||
overflow-wrap: break-word;
|
||||
color: rgba(22, 140, 248, 1);
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
line-height: 12px;
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
.text-group_4 {
|
||||
overflow-wrap: break-word;
|
||||
color: rgba(80, 92, 115, 1);
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
line-height: 12px;
|
||||
margin-top: 9px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
188
components/helang-compress/helang-compress.vue
Normal file
188
components/helang-compress/helang-compress.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<view class="compress" v-if="canvasId">
|
||||
<canvas :canvas-id="canvasId" :style="{ width: canvasSize.width,height: canvasSize.height}"></canvas>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
pic:'',
|
||||
canvasSize: {
|
||||
width: 0,
|
||||
height: 0
|
||||
},
|
||||
canvasId:""
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if(!uni || !uni._helang_compress_canvas){
|
||||
uni._helang_compress_canvas = 1;
|
||||
}else{
|
||||
uni._helang_compress_canvas++;
|
||||
}
|
||||
this.canvasId = `compress-canvas${uni._helang_compress_canvas}`;
|
||||
},
|
||||
methods: {
|
||||
// 压缩
|
||||
compressFun(params) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// 等待图片信息
|
||||
let info = await this.getImageInfo(params.src).then(info=>info).catch(()=>null);
|
||||
|
||||
if(!info){
|
||||
reject('获取图片信息异常');
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置最大 & 最小 尺寸
|
||||
const maxSize = params.maxSize || 1080;
|
||||
const minSize = params.minSize || 540;
|
||||
|
||||
// 当前图片尺寸
|
||||
let {width,height} = info;
|
||||
let oldWidth = width;
|
||||
|
||||
// 最小尺寸校验
|
||||
if(width == maxSize && height == minSize){
|
||||
resolve(params.src);
|
||||
return;
|
||||
}
|
||||
|
||||
//新尺寸
|
||||
width = maxSize;
|
||||
height = minSize;
|
||||
|
||||
// 设置画布尺寸
|
||||
this.$set(this,"canvasSize",{
|
||||
width: `${width}px`,
|
||||
height: `${height}px`
|
||||
});
|
||||
|
||||
|
||||
// Vue.nextTick 回调在 App 有异常,则使用 setTimeout 等待DOM更新
|
||||
setTimeout(() => {
|
||||
const ctx = uni.createCanvasContext(this.canvasId, this);
|
||||
ctx.clearRect(0,0,width, height)
|
||||
ctx.drawImage(info.path, 0, 0, width, height);
|
||||
ctx.draw(false, () => {
|
||||
uni.canvasToTempFilePath({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: width,
|
||||
height: height,
|
||||
destWidth: width,
|
||||
destHeight: height,
|
||||
canvasId: this.canvasId,
|
||||
fileType: params.fileType || 'png',
|
||||
quality: params.quality || 0.9,
|
||||
success: (res) => {
|
||||
resolve(res.tempFilePath);
|
||||
},
|
||||
fail:(err)=>{
|
||||
reject(null);
|
||||
}
|
||||
},this);
|
||||
});
|
||||
}, 300);
|
||||
});
|
||||
},
|
||||
// 获取图片信息
|
||||
getImageInfo(src){
|
||||
return new Promise((resolve, reject)=>{
|
||||
uni.getImageInfo({
|
||||
src,
|
||||
success: (info)=> {
|
||||
resolve(info);
|
||||
},
|
||||
fail: () => {
|
||||
reject(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
// 批量压缩
|
||||
compress(params){
|
||||
// index:进度,done:成功,fail:失败
|
||||
let [index,done,fail] = [0,0,0];
|
||||
// 压缩完成的路径集合
|
||||
let paths = [];
|
||||
// 待压缩的图片
|
||||
let waitList = [];
|
||||
if(typeof params.src == 'string'){
|
||||
waitList = [params.src];
|
||||
}else{
|
||||
waitList = params.src;
|
||||
}
|
||||
// 批量压缩方法
|
||||
let batch = ()=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
// 开始
|
||||
let start = async ()=>{
|
||||
// 等待图片压缩方法返回
|
||||
let path = await next().catch(()=>null);
|
||||
if(path){
|
||||
done++;
|
||||
paths.push(path);
|
||||
}else{
|
||||
fail++;
|
||||
}
|
||||
|
||||
params.progress && params.progress({
|
||||
done,
|
||||
fail,
|
||||
count:waitList.length
|
||||
});
|
||||
|
||||
index++;
|
||||
// 压缩完成
|
||||
if(index >= waitList.length){
|
||||
resolve(true);
|
||||
}else{
|
||||
start();
|
||||
}
|
||||
}
|
||||
start();
|
||||
});
|
||||
}
|
||||
// 依次调用压缩方法
|
||||
let next = ()=>{
|
||||
return this.compressFun({
|
||||
src:waitList[index],
|
||||
maxSize:params.maxSize,
|
||||
fileType:params.fileType,
|
||||
quality:params.quality,
|
||||
minSize:params.minSize
|
||||
})
|
||||
}
|
||||
|
||||
// 全部压缩完成后调用
|
||||
return new Promise(async (resolve, reject)=>{
|
||||
// 批量压缩方法回调
|
||||
let res = await batch();
|
||||
if(res){
|
||||
if(typeof params.src == 'string'){
|
||||
resolve(paths[0]);
|
||||
}else{
|
||||
resolve(paths);
|
||||
}
|
||||
}else{
|
||||
reject(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.compress{
|
||||
position: fixed;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
overflow: hidden;
|
||||
top: -99999px;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
249
components/ren-dropdown-filter/ren-dropdown-filter.vue
Normal file
249
components/ren-dropdown-filter/ren-dropdown-filter.vue
Normal file
@@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<view class="filter-wrapper" :style="{ height: height + 'rpx', top: top,'border-top':border?'1rpx solid #f2f2f2':'none' }" @touchmove.stop.prevent="discard">
|
||||
<view class="inner-wrapper">
|
||||
<view class="mask" :class="showMask ? 'show' : 'hide'" @tap="tapMask"></view>
|
||||
<view class="navs">
|
||||
<view class="c-flex-align" :class="{ 'c-flex-center': index > 0, actNav: index === actNav }" v-for="(item, index) in navData" :key="index" @click="navClick(index)">
|
||||
<view v-for="(child, childx) in item" :key="childx" v-if="child.select">{{ child.text }}</view>
|
||||
<image src="https://i.loli.net/2020/07/15/QsHxlr1gbSImvWt.png" mode="" class="icon-triangle" v-if="index === actNav"></image>
|
||||
<image src="https://i.loli.net/2020/07/15/xjVSvzWcH9NO7al.png" mode="" class="icon-triangle" v-else></image>
|
||||
</view>
|
||||
|
||||
<!-- <view class="date-wrapper">
|
||||
<picker mode="date" @change="handleDate">
|
||||
<view class="date c-flex-align" :style="{ height: height + 'rpx' }" @click="dateClick">
|
||||
<view>{{ selDate }}</view>
|
||||
<image src="https://i.loli.net/2020/07/15/xjVSvzWcH9NO7al.png" mode="" class="icon-triangle"></image>
|
||||
</view>
|
||||
</picker>
|
||||
</view> -->
|
||||
</view>
|
||||
<scroll-view scroll-y="true" class="popup" :class="popupShow ? 'popupShow' : ''">
|
||||
<view class="item-opt c-flex-align" :class="item.select ? 'actOpt' : ''" v-for="(item, index) in navData[actNav]" :key="index" @click="handleOpt(index)">
|
||||
{{ item.text }}
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import { getCurDateTime } from '@/libs/utils.js';
|
||||
export default {
|
||||
props: {
|
||||
height: {
|
||||
type: Number,
|
||||
default: 108
|
||||
},
|
||||
top: {
|
||||
type: String,
|
||||
default: 'calc(var(--window-statsu-bar) + 44px)'
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
filterData: {
|
||||
//必填
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
}
|
||||
// default: () => {
|
||||
// return [
|
||||
// [{ text: '全部状态', value: '' }, { text: '状态1', value: 1 }, { text: '状态2', value: 2 }, { text: '状态3', value: 3 }],
|
||||
// [{ text: '全部类型', value: '' }, { text: '类型1', value: 1 }, { text: '类型2', value: 2 }, { text: '类型3', value: 3 }]
|
||||
// ];
|
||||
// }
|
||||
},
|
||||
defaultIndex: {
|
||||
//默认选中条件索引,超出一类时必填
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [0];
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
navData: [],
|
||||
popupShow: false,
|
||||
showMask: false,
|
||||
actNav: null,
|
||||
selDate: '选择日期',
|
||||
selIndex: [] //选中条件索引
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.navData = this.filterData;
|
||||
this.selIndex = this.defaultIndex;
|
||||
this.keepStatus();
|
||||
},
|
||||
mounted() {
|
||||
// this.selDate = getCurDateTime().formatDate;
|
||||
},
|
||||
methods: {
|
||||
updateFilterData(data) {
|
||||
this.navData = data;
|
||||
this.keepStatus();
|
||||
},
|
||||
keepStatus() {
|
||||
this.navData.forEach(itemnavData => {
|
||||
itemnavData.map(child => {
|
||||
child.select = false;
|
||||
});
|
||||
return itemnavData;
|
||||
});
|
||||
for (let i = 0; i < this.selIndex.length; i++) {
|
||||
let selindex = this.selIndex[i];
|
||||
this.navData[i][selindex].select = true;
|
||||
}
|
||||
},
|
||||
navClick(index) {
|
||||
if (index === this.actNav) {
|
||||
this.tapMask();
|
||||
return;
|
||||
}
|
||||
this.popupShow = true;
|
||||
this.showMask = true;
|
||||
this.actNav = index;
|
||||
},
|
||||
handleOpt(index) {
|
||||
this.selIndex[this.actNav] = index;
|
||||
this.keepStatus();
|
||||
setTimeout(() => {
|
||||
this.tapMask();
|
||||
}, 100);
|
||||
let data = [];
|
||||
let res = this.navData.forEach(item => {
|
||||
let sel = item.filter(child => child.select);
|
||||
data.push(sel);
|
||||
});
|
||||
// console.log(data);
|
||||
this.$emit('onSelected', data);
|
||||
},
|
||||
dateClick() {
|
||||
this.tapMask();
|
||||
},
|
||||
tapMask() {
|
||||
this.showMask = false;
|
||||
this.popupShow = false;
|
||||
this.actNav = null;
|
||||
},
|
||||
handleDate(e) {
|
||||
let d = e.detail.value;
|
||||
this.selDate = d;
|
||||
this.$emit('dateChange', d);
|
||||
},
|
||||
discard() {}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
page {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.c-flex-align {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.c-flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
.filter-wrapper {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
width: 750rpx;
|
||||
z-index: 999;
|
||||
.inner-wrapper {
|
||||
// position: relative;
|
||||
.navs {
|
||||
position: relative;
|
||||
height: 90rpx;
|
||||
padding: 0 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
border-bottom: 2rpx solid #f5f6f9;
|
||||
color: #8b9aae;
|
||||
z-index: 999;
|
||||
box-sizing: border-box;
|
||||
& > view {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
flex-direction: row;
|
||||
z-index: 999;
|
||||
}
|
||||
.date {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.actNav {
|
||||
color: #4d7df9;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.mask {
|
||||
z-index: 666;
|
||||
position: fixed;
|
||||
// top: calc(var(--status-bar-height) + 44px);
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
transition: background-color 0.15s linear;
|
||||
&.show {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
&.hide {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.popup {
|
||||
position: relative;
|
||||
max-height: 700rpx;
|
||||
background-color: #fff;
|
||||
border-bottom-left-radius: 20rpx;
|
||||
border-bottom-right-radius: 20rpx;
|
||||
overflow: scroll;
|
||||
z-index: 999;
|
||||
transition: all 1s linear;
|
||||
opacity: 0;
|
||||
display: none;
|
||||
.item-opt {
|
||||
height: 100rpx;
|
||||
padding: 0 40rpx;
|
||||
color: #8b9aae;
|
||||
border-bottom: 2rpx solid #f5f6f9;
|
||||
}
|
||||
.actOpt {
|
||||
color: #4d7df9;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
&::after {
|
||||
content: '✓';
|
||||
font-weight: bold;
|
||||
font-size: 36rpx;
|
||||
position: absolute;
|
||||
right: 40rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.popupShow {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-triangle {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
627
components/yankai-cropper/cropper.vue
Normal file
627
components/yankai-cropper/cropper.vue
Normal file
@@ -0,0 +1,627 @@
|
||||
<template>
|
||||
<view class="cropper" id="cropper" :class="{ show: show }">
|
||||
<view class="cropper-head">
|
||||
<!-- <view class="cropper-btn cropper-reset" @tap="resetCrop">重做</view> -->
|
||||
</view>
|
||||
<view class="cropper-body">
|
||||
<image id="image" class="cropper-image" :src="imagePath" mode="aspectFit"></image>
|
||||
<view :style="{ width: stageWidth + 'px', height: stageHeight + 'px', left: stageLeft + 'px', top: stageTop + 'px' }" class="cropper-stage" @touchstart.stop.prevent="touchStart" @touchmove.stop.prevent="touchMove">
|
||||
<view id="box" class="cropper-box" :style="{ width: boxWidth + 'px', height: boxHeight + 'px', left: boxLeft + 'px', top: boxTop + 'px' }">
|
||||
<view id="lt" class="lt"></view>
|
||||
<view id="lb" class="lb"></view>
|
||||
<view id="rt" class="rt"></view>
|
||||
<view id="rb" class="rb"></view>
|
||||
|
||||
<view class="line-v" style="left:33.3%;"></view>
|
||||
<view class="line-v" style="left:66.6%;"></view>
|
||||
<view class="line-h" style="top:33.3%;"></view>
|
||||
<view class="line-h" style="top:66.6%;"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<canvas class="cropper-canvas" canvas-id="canvas" :style="{ height: canvasHeight + 'px', width: canvasWidth + 'px' }"></canvas>
|
||||
</view>
|
||||
<view class="cropper-bottom">
|
||||
<view class="cropper-btn cropper-cancel" @tap="cancelCrop">取消</view>
|
||||
<view class="cropper-btn cropper-ok" @tap="completeCrop">裁剪</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
//无须渲染的变量
|
||||
|
||||
let layoutLeft = 0;
|
||||
let layoutTop = 0;
|
||||
let layoutWidth = 0;
|
||||
let layoutHeight = 0;
|
||||
|
||||
let stageLeft = 0;
|
||||
let stageTop = 0;
|
||||
let stageWidth = 0;
|
||||
let stageHeight = 0;
|
||||
|
||||
let imageWidth = 0;
|
||||
let imageHeight = 0;
|
||||
|
||||
let pixelRatio = 1; //todo设备像素密度//暂不使用//
|
||||
|
||||
let imageStageRatio = 1; //图片实际尺寸与剪裁舞台大小的比值,用于尺寸换算。
|
||||
|
||||
let minBoxWidth = 0;
|
||||
let minBoxHeight = 0;
|
||||
|
||||
let touchStartBoxLeft = 0;
|
||||
let touchStartBoxTop = 0;
|
||||
let touchStartBoxWidth = 0;
|
||||
let touchStartBoxHeight = 0;
|
||||
|
||||
let touchStartX = 0;
|
||||
let touchStartY = 0;
|
||||
|
||||
export default {
|
||||
name: 'cropper',
|
||||
props: {
|
||||
quality: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
|
||||
//目标文件的类型。默认值为jpg,jpg:输出jpg格式图片;png:输出png格式图片
|
||||
outputFileType: {
|
||||
type: String,
|
||||
default: 'jpg'
|
||||
},
|
||||
//目标图片的宽高比,默认null,即不限制剪裁宽高比。aspectRatio需大于0
|
||||
aspectRatio: {
|
||||
type: [Number, null],
|
||||
default: null
|
||||
},
|
||||
//最小剪裁尺寸与原图尺寸的比率,默认0.15,即宽度最小剪裁到原图的0.15宽。
|
||||
minBoxWidthRatio: {
|
||||
type: Number,
|
||||
default: 0.15
|
||||
},
|
||||
//同minBoxWidthRatio,当设置aspectRatio时,minBoxHeight值设置无效。minBoxHeight值由minBoxWidth 和 aspectRatio自动计算得到。
|
||||
minBoxHeightRatio: {
|
||||
type: Number,
|
||||
default: 0.15
|
||||
},
|
||||
//剪裁框初始大小比率。默认值0.8,即剪裁框默认宽度为图片宽度的0.8倍。
|
||||
initialBoxWidthRatio: {
|
||||
type: Number,
|
||||
default: 0.8
|
||||
},
|
||||
//同initialBoxWidthRatio,当设置aspectRatio时,initialBoxHeightRatio值设置无效。initialBoxHeightRatio值由initialBoxWidthRatio 和 aspectRatio自动计算得到。
|
||||
initialBoxHeightRatio: {
|
||||
type: Number,
|
||||
default: 0.8
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
//data
|
||||
stageLeft: 0,
|
||||
stageTop: 0,
|
||||
stageWidth: 0,
|
||||
stageHeight: 0,
|
||||
|
||||
boxWidth: 0,
|
||||
boxHeight: 0,
|
||||
boxLeft: 0,
|
||||
boxTop: 0,
|
||||
|
||||
canvasWidth: 0,
|
||||
canvasHeight: 0,
|
||||
show: false,
|
||||
imagePath: ''
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
// setTimeout(() => {
|
||||
// this.init();
|
||||
// }, 150);
|
||||
},
|
||||
methods: {
|
||||
resetCrop() {
|
||||
this.$emit('reset');
|
||||
this.init(this.imagePath);
|
||||
},
|
||||
cancelCrop() {
|
||||
this.$emit('cancel');
|
||||
},
|
||||
completeCrop() {
|
||||
uni.showLoading({
|
||||
mask: true,
|
||||
title: '图片处理中'
|
||||
});
|
||||
let imagePath = this.imagePath;
|
||||
let canvasContext = uni.createCanvasContext('canvas', this);
|
||||
|
||||
let boxLeft = this.boxLeft;
|
||||
let boxTop = this.boxTop;
|
||||
let boxWidth = this.boxWidth;
|
||||
let boxHeight = this.boxHeight;
|
||||
|
||||
let sx = Math.ceil(boxLeft * imageStageRatio);
|
||||
let sy = Math.ceil(boxTop * imageStageRatio);
|
||||
|
||||
let sWidth = Math.ceil(boxWidth * imageStageRatio);
|
||||
let sHeight = Math.ceil(boxHeight * imageStageRatio);
|
||||
let dx = 0;
|
||||
let dy = 0;
|
||||
|
||||
let dWidth = Math.ceil(sWidth * pixelRatio);
|
||||
let dHeight = Math.ceil(sHeight * pixelRatio);
|
||||
const param = {
|
||||
x: sx,
|
||||
y: sy,
|
||||
width: dWidth,
|
||||
height: dHeight,
|
||||
rotate: 0,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
};
|
||||
|
||||
canvasContext.drawImage(imagePath, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
|
||||
canvasContext.draw(false, () => {
|
||||
uni.canvasToTempFilePath(
|
||||
{
|
||||
x: dx,
|
||||
y: dy,
|
||||
width: dWidth,
|
||||
height: dHeight,
|
||||
destWidth: sWidth,
|
||||
destHeight: sHeight,
|
||||
canvasId: 'canvas',
|
||||
fileType: this.outputFileType,
|
||||
quality: this.quality,
|
||||
success: res => {
|
||||
this.$emit('complete', { param, path: res.tempFilePath,source:this.imagePath });
|
||||
}
|
||||
},
|
||||
this
|
||||
);
|
||||
});
|
||||
},
|
||||
touchMove(e) {
|
||||
let targetId = e.target.id;
|
||||
let touch = e.touches[0];
|
||||
let pageX = touch.pageX;
|
||||
let pageY = touch.pageY;
|
||||
|
||||
let offsetX = pageX - touchStartX;
|
||||
let offsetY = pageY - touchStartY;
|
||||
|
||||
if (targetId == 'box') {
|
||||
let newBoxLeft = touchStartBoxLeft + offsetX;
|
||||
let newBoxTop = touchStartBoxTop + offsetY;
|
||||
|
||||
if (newBoxLeft < 0) {
|
||||
newBoxLeft = 0;
|
||||
}
|
||||
if (newBoxTop < 0) {
|
||||
newBoxTop = 0;
|
||||
}
|
||||
if (newBoxLeft + touchStartBoxWidth > stageWidth) {
|
||||
newBoxLeft = stageWidth - touchStartBoxWidth;
|
||||
}
|
||||
if (newBoxTop + touchStartBoxHeight > stageHeight) {
|
||||
newBoxTop = stageHeight - touchStartBoxHeight;
|
||||
}
|
||||
this.boxLeft = newBoxLeft;
|
||||
this.boxTop = newBoxTop;
|
||||
} else if (targetId == 'lt') {
|
||||
if (this.aspectRatio) {
|
||||
offsetY = offsetX / this.aspectRatio;
|
||||
}
|
||||
|
||||
let newBoxLeft = touchStartBoxLeft + offsetX;
|
||||
let newBoxTop = touchStartBoxTop + offsetY;
|
||||
|
||||
if (newBoxLeft < 0) {
|
||||
newBoxLeft = 0;
|
||||
}
|
||||
if (newBoxTop < 0) {
|
||||
newBoxTop = 0;
|
||||
}
|
||||
|
||||
if (touchStartBoxLeft + touchStartBoxWidth - newBoxLeft < minBoxWidth) {
|
||||
newBoxLeft = touchStartBoxLeft + touchStartBoxWidth - minBoxWidth;
|
||||
}
|
||||
if (touchStartBoxTop + touchStartBoxHeight - newBoxTop < minBoxHeight) {
|
||||
newBoxTop = touchStartBoxTop + touchStartBoxHeight - minBoxHeight;
|
||||
}
|
||||
|
||||
let newBoxWidth = touchStartBoxWidth - (newBoxLeft - touchStartBoxLeft);
|
||||
let newBoxHeight = touchStartBoxHeight - (newBoxTop - touchStartBoxTop);
|
||||
|
||||
//约束比例
|
||||
if (newBoxTop == 0 && this.aspectRatio && newBoxLeft != 0) {
|
||||
newBoxWidth = newBoxHeight * this.aspectRatio;
|
||||
newBoxLeft = touchStartBoxWidth - newBoxWidth + touchStartBoxLeft;
|
||||
}
|
||||
if (newBoxLeft == 0 && this.aspectRatio) {
|
||||
newBoxHeight = newBoxWidth / this.aspectRatio;
|
||||
newBoxTop = touchStartBoxHeight - newBoxHeight + touchStartBoxTop;
|
||||
}
|
||||
|
||||
if (newBoxWidth == minBoxWidth && this.aspectRatio) {
|
||||
newBoxHeight = newBoxWidth / this.aspectRatio;
|
||||
newBoxTop = touchStartBoxHeight - newBoxHeight + touchStartBoxTop;
|
||||
}
|
||||
this.boxTop = newBoxTop;
|
||||
this.boxLeft = newBoxLeft;
|
||||
this.boxWidth = newBoxWidth;
|
||||
this.boxHeight = newBoxHeight;
|
||||
} else if (targetId == 'rt') {
|
||||
if (this.aspectRatio) {
|
||||
offsetY = -offsetX / this.aspectRatio;
|
||||
}
|
||||
|
||||
let newBoxWidth = touchStartBoxWidth + offsetX;
|
||||
if (newBoxWidth < minBoxWidth) {
|
||||
newBoxWidth = minBoxWidth;
|
||||
}
|
||||
if (touchStartBoxLeft + newBoxWidth > stageWidth) {
|
||||
newBoxWidth = stageWidth - touchStartBoxLeft;
|
||||
}
|
||||
|
||||
let newBoxTop = touchStartBoxTop + offsetY;
|
||||
|
||||
if (newBoxTop < 0) {
|
||||
newBoxTop = 0;
|
||||
}
|
||||
|
||||
if (touchStartBoxTop + touchStartBoxHeight - newBoxTop < minBoxHeight) {
|
||||
newBoxTop = touchStartBoxTop + touchStartBoxHeight - minBoxHeight;
|
||||
}
|
||||
let newBoxHeight = touchStartBoxHeight - (newBoxTop - touchStartBoxTop);
|
||||
|
||||
//约束比例
|
||||
if (newBoxTop == 0 && this.aspectRatio && newBoxWidth != stageWidth - touchStartBoxLeft) {
|
||||
newBoxWidth = newBoxHeight * this.aspectRatio;
|
||||
}
|
||||
|
||||
if (newBoxWidth == stageWidth - touchStartBoxLeft && this.aspectRatio) {
|
||||
newBoxHeight = newBoxWidth / this.aspectRatio;
|
||||
newBoxTop = touchStartBoxHeight - newBoxHeight + touchStartBoxTop;
|
||||
}
|
||||
|
||||
if (newBoxWidth == minBoxWidth && this.aspectRatio) {
|
||||
newBoxHeight = newBoxWidth / this.aspectRatio;
|
||||
newBoxTop = touchStartBoxHeight - newBoxHeight + touchStartBoxTop;
|
||||
}
|
||||
|
||||
this.boxTop = newBoxTop;
|
||||
this.boxHeight = newBoxHeight;
|
||||
this.boxWidth = newBoxWidth;
|
||||
} else if (targetId == 'lb') {
|
||||
if (this.aspectRatio) {
|
||||
offsetY = -offsetX / this.aspectRatio;
|
||||
}
|
||||
let newBoxLeft = touchStartBoxLeft + offsetX;
|
||||
|
||||
if (newBoxLeft < 0) {
|
||||
newBoxLeft = 0;
|
||||
}
|
||||
if (touchStartBoxLeft + touchStartBoxWidth - newBoxLeft < minBoxWidth) {
|
||||
newBoxLeft = touchStartBoxLeft + touchStartBoxWidth - minBoxWidth;
|
||||
}
|
||||
|
||||
let newBoxWidth = touchStartBoxWidth - (newBoxLeft - touchStartBoxLeft);
|
||||
|
||||
let newBoxHeight = touchStartBoxHeight + offsetY;
|
||||
if (newBoxHeight < minBoxHeight) {
|
||||
newBoxHeight = minBoxHeight;
|
||||
}
|
||||
if (touchStartBoxTop + newBoxHeight > stageHeight) {
|
||||
newBoxHeight = stageHeight - touchStartBoxTop;
|
||||
}
|
||||
|
||||
//约束比例
|
||||
if (newBoxHeight == stageHeight - touchStartBoxTop && this.aspectRatio && newBoxLeft != 0) {
|
||||
newBoxWidth = newBoxHeight * this.aspectRatio;
|
||||
newBoxLeft = touchStartBoxWidth - newBoxWidth + touchStartBoxLeft;
|
||||
}
|
||||
if (newBoxLeft == 0 && this.aspectRatio) {
|
||||
newBoxHeight = newBoxWidth / this.aspectRatio;
|
||||
}
|
||||
|
||||
if (newBoxWidth == minBoxWidth && this.aspectRatio) {
|
||||
newBoxHeight = newBoxWidth / this.aspectRatio;
|
||||
}
|
||||
|
||||
this.boxLeft = newBoxLeft;
|
||||
this.boxWidth = newBoxWidth;
|
||||
this.boxHeight = newBoxHeight;
|
||||
} else if (targetId == 'rb') {
|
||||
if (this.aspectRatio) {
|
||||
offsetY = offsetX / this.aspectRatio;
|
||||
}
|
||||
let newBoxWidth = touchStartBoxWidth + offsetX;
|
||||
if (newBoxWidth < minBoxWidth) {
|
||||
newBoxWidth = minBoxWidth;
|
||||
}
|
||||
if (touchStartBoxLeft + newBoxWidth > stageWidth) {
|
||||
newBoxWidth = stageWidth - touchStartBoxLeft;
|
||||
}
|
||||
|
||||
let newBoxHeight = touchStartBoxHeight + offsetY;
|
||||
if (newBoxHeight < minBoxHeight) {
|
||||
newBoxHeight = minBoxHeight;
|
||||
}
|
||||
if (touchStartBoxTop + newBoxHeight > stageHeight) {
|
||||
newBoxHeight = stageHeight - touchStartBoxTop;
|
||||
}
|
||||
|
||||
//约束比例
|
||||
if (newBoxHeight == stageHeight - touchStartBoxTop && this.aspectRatio && newBoxWidth != stageWidth - touchStartBoxLeft) {
|
||||
newBoxWidth = newBoxHeight * this.aspectRatio;
|
||||
}
|
||||
|
||||
if (newBoxWidth == stageWidth - touchStartBoxLeft && this.aspectRatio) {
|
||||
newBoxHeight = newBoxWidth / this.aspectRatio;
|
||||
}
|
||||
|
||||
if (newBoxWidth == minBoxWidth && this.aspectRatio) {
|
||||
newBoxHeight = newBoxWidth / this.aspectRatio;
|
||||
}
|
||||
|
||||
this.boxWidth = newBoxWidth;
|
||||
this.boxHeight = newBoxHeight;
|
||||
}
|
||||
},
|
||||
touchStart(e) {
|
||||
let touch = e.touches[0];
|
||||
let pageX = touch.pageX;
|
||||
let pageY = touch.pageY;
|
||||
|
||||
touchStartX = pageX;
|
||||
touchStartY = pageY;
|
||||
|
||||
touchStartBoxLeft = this.boxLeft;
|
||||
touchStartBoxTop = this.boxTop;
|
||||
touchStartBoxWidth = this.boxWidth;
|
||||
touchStartBoxHeight = this.boxHeight;
|
||||
},
|
||||
close(force=true) {
|
||||
this.show = false;
|
||||
if(force){
|
||||
this.imagePath = ''
|
||||
}
|
||||
},
|
||||
init(src) {
|
||||
if (!src) {
|
||||
return '';
|
||||
}
|
||||
this.imagePath = src;
|
||||
uni.showLoading({
|
||||
mask: true,
|
||||
title: '载入图片中'
|
||||
});
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select('.cropper-body')
|
||||
.boundingClientRect(rect => {
|
||||
layoutLeft = rect.left;
|
||||
layoutTop = rect.top;
|
||||
layoutWidth = rect.width;
|
||||
layoutHeight = rect.height;
|
||||
|
||||
uni.getImageInfo({
|
||||
src: this.imagePath,
|
||||
success: imageInfo => {
|
||||
imageWidth = imageInfo.width;
|
||||
imageHeight = imageInfo.height;
|
||||
let imageWH = imageWidth / imageHeight;
|
||||
let layoutWH = layoutWidth / layoutHeight;
|
||||
if (imageWH >= layoutWH) {
|
||||
stageWidth = layoutWidth;
|
||||
stageHeight = stageWidth / imageWH;
|
||||
imageStageRatio = imageHeight / stageHeight;
|
||||
} else {
|
||||
stageHeight = layoutHeight;
|
||||
stageWidth = layoutHeight * imageWH;
|
||||
imageStageRatio = imageWidth / stageWidth;
|
||||
}
|
||||
stageLeft = (layoutWidth - stageWidth) / 2;
|
||||
stageTop = (layoutHeight - stageHeight) / 2;
|
||||
|
||||
minBoxWidth = stageWidth * this.minBoxWidthRatio;
|
||||
minBoxHeight = stageHeight * this.minBoxHeightRatio;
|
||||
|
||||
let boxWidth = stageWidth * this.initialBoxWidthRatio;
|
||||
let boxHeight = stageHeight * this.initialBoxHeightRatio;
|
||||
|
||||
if (this.aspectRatio) {
|
||||
boxHeight = boxWidth / this.aspectRatio;
|
||||
}
|
||||
if (boxHeight > stageHeight) {
|
||||
boxHeight = stageHeight;
|
||||
boxWidth = boxHeight * this.aspectRatio;
|
||||
}
|
||||
|
||||
let boxLeft = (stageWidth - boxWidth) / 2;
|
||||
let boxTop = (stageHeight - boxHeight) / 2;
|
||||
|
||||
this.canvasWidth = imageWidth * pixelRatio;
|
||||
this.canvasHeight = imageHeight * pixelRatio;
|
||||
|
||||
this.stageLeft = stageLeft;
|
||||
this.stageTop = stageTop;
|
||||
this.stageWidth = stageWidth;
|
||||
this.stageHeight = stageHeight;
|
||||
|
||||
this.boxWidth = boxWidth;
|
||||
this.boxHeight = boxHeight;
|
||||
this.boxLeft = boxLeft;
|
||||
this.boxTop = boxTop;
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
this.show = true;
|
||||
}, 100);
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: '图片载入失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.exec();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.cropper {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: #000;
|
||||
z-index: -1000000;
|
||||
opacity: 0;
|
||||
&.show {
|
||||
z-index: 999;
|
||||
opacity: 1;
|
||||
}
|
||||
.cropper-head {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 750rpx;
|
||||
z-index: 6;
|
||||
height: calc(var(--status-bar-height) + 88rpx);
|
||||
padding-top: var(--status-bar-height);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
.cropper-btn {
|
||||
height: 64rpx;
|
||||
margin: 0 20rpx;
|
||||
padding: 0 30rpx;
|
||||
line-height: 64rpx;
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.cropper-body {
|
||||
margin: calc(var(--status-bar-height) + 88rpx) 30rpx 0 30rpx;
|
||||
height: calc(100vh - var(--status-bar-height) - 88rpx - 100rpx - var(--safe-area-inset-bottom));
|
||||
position: relative;
|
||||
}
|
||||
.cropper-bottom {
|
||||
height: calc(var(--safe-area-inset-bottom) + 100rpx);
|
||||
padding-top: var(--safe-area-inset-bottom);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: fixed;
|
||||
z-index: 6;
|
||||
width: 750rpx;
|
||||
bottom: 0;
|
||||
}
|
||||
.cropper-ok {
|
||||
color: #39f;
|
||||
}
|
||||
.cropper-image {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.cropper-stage {
|
||||
position: absolute;
|
||||
.cropper-box {
|
||||
position: absolute;
|
||||
border: 4rpx solid #ddd;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 0 0 2000rpx rgba(0, 0, 0, 0.5);
|
||||
.lt {
|
||||
position: absolute;
|
||||
height: 48rpx;
|
||||
width: 48rpx;
|
||||
left: -6rpx;
|
||||
top: -6rpx;
|
||||
border-left: 12rpx solid #ffffff;
|
||||
border-top: 12rpx solid #ffffff;
|
||||
}
|
||||
|
||||
.lb {
|
||||
position: absolute;
|
||||
height: 48rpx;
|
||||
width: 48rpx;
|
||||
left: -6rpx;
|
||||
bottom: -6rpx;
|
||||
border-left: 12rpx solid #ffffff;
|
||||
border-bottom: 12rpx solid #ffffff;
|
||||
}
|
||||
|
||||
.rt {
|
||||
position: absolute;
|
||||
height: 48rpx;
|
||||
width: 48rpx;
|
||||
right: -6rpx;
|
||||
top: -6rpx;
|
||||
border-right: 12rpx solid #ffffff;
|
||||
border-top: 12rpx solid #ffffff;
|
||||
}
|
||||
|
||||
.rb {
|
||||
position: absolute;
|
||||
height: 48rpx;
|
||||
width: 48rpx;
|
||||
right: -6rpx;
|
||||
bottom: -6rpx;
|
||||
border-right: 12rpx solid #ffffff;
|
||||
border-bottom: 12rpx solid #ffffff;
|
||||
}
|
||||
.line-v,
|
||||
.line-h {
|
||||
position: absolute;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.line-v {
|
||||
width: 2rpx;
|
||||
border-left: 2rpx dashed #fff;
|
||||
height: 100%;
|
||||
}
|
||||
.line-h {
|
||||
height: 2rpx;
|
||||
border-bottom: 2rpx dashed #fff;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cropper-canvas {
|
||||
position: fixed;
|
||||
background-color: red;
|
||||
left: 5000rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// 安全域兼容样式
|
||||
// page {
|
||||
// --safe-area-inset-top: 0px;
|
||||
// --safe-area-inset-right: 0px;
|
||||
// --safe-area-inset-bottom: 0px;
|
||||
// --safe-area-inset-left: 0px;
|
||||
|
||||
// @supports (top: constant(safe-area-inset-top)) {
|
||||
// --safe-area-inset-top: constant(safe-area-inset-top);
|
||||
// --safe-area-inset-right: constant(safe-area-inset-right);
|
||||
// --safe-area-inset-bottom: constant(safe-area-inset-bottom);
|
||||
// --safe-area-inset-left: constant(safe-area-inset-left);
|
||||
// }
|
||||
|
||||
// @supports (top: env(safe-area-inset-top)) {
|
||||
// --safe-area-inset-top: env(safe-area-inset-top);
|
||||
// --safe-area-inset-right: env(safe-area-inset-right);
|
||||
// //--safe-area-inset-bottom: 12px;
|
||||
// --safe-area-inset-bottom: env(safe-area-inset-bottom);
|
||||
// --safe-area-inset-left: env(safe-area-inset-left);
|
||||
// }
|
||||
// }
|
||||
</style>
|
||||
Reference in New Issue
Block a user