森蓝小程序

This commit is contained in:
2025-04-23 19:50:30 +08:00
parent 7776447ce3
commit 5205361ac0
111 changed files with 20504 additions and 0 deletions

View 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>

View 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>

View 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>

View 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
},
//目标文件的类型。默认值为jpgjpg输出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>