This commit is contained in:
Teng 2025-06-20 17:32:20 +08:00
parent cd3970af23
commit 53abf9b335
85 changed files with 4873 additions and 1450 deletions

View File

@ -0,0 +1,37 @@
<template>
<view>
<!-- https://ext.dcloud.net.cn/plugin?id=10333 -->
<qf-image-cropper :src="src" :width="width" :height="height" :radius="0" @crop="handleCrop" />
</view>
</template>
<script setup>
import {
ref
} from 'vue'
import {
onLoad
} from '@dcloudio/uni-app';
import QfImageCropper from '@/uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.vue'
//
const src = ref('') //
const type = ref(0);
const width = ref(600);
const height = ref(400);
//
const handleCrop = (e) => {
// console.log("")
uni.setStorageSync(`imgkey${type.value}`, e.tempFilePath);
uni.navigateBack()
}
onLoad((options) => {
src.value = options.url
//type
type.value = options.type
if(options.size){
width.value = 900;
height.value = 600;
}
});
</script>

View File

@ -1,248 +0,0 @@
<template>
<view
class="carousel"
@touchstart="onTouchStart"
@touchmove.prevent="onTouchMove"
@touchend="onTouchEnd"
>
<view
v-for="(img, i) in visibleImages"
:key="img"
class="carousel-item"
:style="itemStyle(i)"
>
<image :src="img" mode="aspectFill" class="carousel-image" />
<view class="font" :style="i==1?{fontWeight:600}:{}" >{{img.includes('0') ? imageName[0] : (img.includes('1') ? imageName[1] : imageName[2])}} </view>
</view>
<view
class="carousel-item"
:style="leftCopyStyle"
key="left-copy"
>
<image :src="leftCopyImage" mode="aspectFill" class="carousel-image" />
</view>
<view
class="carousel-item"
:style="rightCopyStyle"
key="right-copy"
>
<image :src="rightCopyImage" mode="aspectFill" class="carousel-image" />
</view>
</view>
</template>
<script setup>
import { ref, computed,watch } from 'vue';
const emit = defineEmits(['updateCenterIndex'])
const images = ref([
'https://www.focusnu.com/media/directive/index/three/0.png',
'https://www.focusnu.com/media/directive/index/three/1.png',
'https://www.focusnu.com/media/directive/index/three/2.png',
])
const imageName = ref([
"长者入住",
"机构加盟",
"员工入驻",
])
const len = images.value.length
const startX = ref(0)
const position = ref(0)
const isDragging = ref(false)
const enableTransition = ref(false)
const dragDirection = ref(0)
const imageSpacing = 220
const centerIndex = computed(() => {
return mod(Math.floor(position.value), len)
})
const leftIndex = computed(() => mod(centerIndex.value - 1, len))
const rightIndex = computed(() => mod(centerIndex.value + 1, len))
const visibleImages = computed(() => [
images.value[leftIndex.value],
images.value[centerIndex.value],
images.value[rightIndex.value],
])
const leftCopyImage = computed(() => images.value[leftIndex.value])
const rightCopyImage = computed(() => images.value[rightIndex.value])
function mod(n, m) {
return ((n % m) + m) % m
}
function onTouchStart(e) {
if (enableTransition.value) enableTransition.value = false
isDragging.value = true
startX.value = e.touches[0].clientX
dragDirection.value = 0
}
function onTouchMove(e) {
if (!isDragging.value) return
const currentX = e.touches[0].clientX
const delta = currentX - startX.value
dragDirection.value = delta > 0 ? 1 : -1
position.value -= delta / imageSpacing
startX.value = currentX
}
function onTouchEnd() {
if (!isDragging.value) return
isDragging.value = false
enableTransition.value = true
position.value = Math.round(position.value)
}
function itemStyle(i) {
const floorPos = Math.floor(position.value)
const offset = position.value - floorPos // [-1, 1)
const absOff = Math.abs(offset) // 0 1
//
const baseX = (i - 1) * imageSpacing
const translateX = baseX - offset * imageSpacing
// zIndex
let opacity = 0.5
let zIndex = 1
if (i === 1) {
opacity = 1 - 0.5 * absOff
zIndex = 3
} else if ((i === 2 && offset >= 0) || (i === 0 && offset < 0)) {
opacity = 0.5 + 0.5 * absOff
zIndex = 2
}
// absOff scale
let scale = 0.8
if (i === 1) {
// offset 0 1.1offset 1 0.8
scale = 1.1 - 0.3 * absOff
} else if ((i === 2 && offset >= 0) || (i === 0 && offset < 0)) {
// offset 0 0.8offset 1 1.1
scale = 0.8 + 0.3 * absOff
}
return {
transform: `translate(-50%, -50%) translateX(${translateX}rpx) scale(${scale})`,
opacity,
zIndex,
transition: enableTransition.value
? 'transform 0.3s ease, opacity 0.3s ease'
: 'none',
}
}
const leftCopyStyle = computed(() => {
const show = dragDirection.value === 1
const floorPos = Math.floor(position.value)
const offset = position.value - floorPos
const translateX = 2.2 * imageSpacing - offset * imageSpacing
let scale = 0.8
let opacity = 0.5
let zIndex = 2
if (offset < 0) {
const posOffset = -offset
scale = 0.8 + 0.5 * posOffset
opacity = 0.5 + 0.5 * posOffset
}
return {
transform: `translate(-50%, -50%) translateX(${translateX}rpx) scale(${scale})`,
opacity,
zIndex,
pointerEvents: 'none',
position: 'absolute',
top: '40%',
left: '50%',
opacity: show ? 0.5 : 0,
willChange: 'transform, opacity',
transition: enableTransition.value
? 'transform 0.3s ease, opacity 0.3s ease'
: 'none',
}
})
const rightCopyStyle = computed(() => {
const show = dragDirection.value === 1
const floorPos = Math.floor(position.value)
const offset = position.value - floorPos
const translateX = -2.2 * imageSpacing - offset * imageSpacing
let scale = 0.8
let opacity = 0.5
let zIndex = 2
if (offset >= 0) {
scale = 0.8 + 0.5 * offset
opacity = 0.5 + 0.5 * offset
}
return {
transform: `translate(-50%, -50%) translateX(${translateX}rpx) scale(${scale})`,
opacity,
zIndex,
pointerEvents: 'none',
position: 'absolute',
top: '40%',
left: '50%',
opacity: show ? 0.5 : 0,
willChange: 'transform, opacity',
transition: enableTransition.value
? 'transform 0.3s ease, opacity 0.3s ease'
: 'none',
}
})
// 3. centerIndex emit
watch(centerIndex, (newIdx) => {
// console.log("???",newIdx)
emit('updateCenterIndex', newIdx)
})
</script>
<style scoped>
.carousel {
position: relative;
width: 90%;
height: 650rpx;
display: flex;
justify-content: center;
overflow: hidden;
}
.carousel-item {
position: absolute;
width: 300rpx;
height: 450rpx;
top: 40%;
left: 50%;
margin: 0;
backface-visibility: hidden;
will-change: transform, opacity;
}
.carousel-image {
width: 100%;
height: 100%;
border-radius: 12rpx;
user-select: none;
touch-action: pan-y;
pointer-events: none;
}
.font{
width: 100%;
display: flex;
justify-content: center;
margin-top: 20rpx;
}
</style>

View File

@ -1,5 +1,10 @@
{
"pages": [
"pages": [{
"path": "pages/login/callback",
"style": {
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/login/index",
"style": {
@ -21,7 +26,8 @@
{
"path": "pages/login/workjoin",
"style": {
"navigationBarTitleText": "员工入驻"
"navigationBarTitleText": "员工入驻",
"enablePullDownRefresh": true
}
},
{
@ -30,18 +36,19 @@
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/login/callback",
"style": {
"navigationBarTitleText": "登录"
}
},
"path": "pages/login/protocol",
"style": {
"navigationBarTitleText": "护理单元使用条款"
}
},
{
"path": "pages/login/protocol",
"style": {
"navigationBarTitleText": "护理单元使用条款"
}
},
"path": "pages/login/special",
"style": {
"navigationBarTitleText": "绑定成功"
}
},
{
"path": "pages/index/index",
"style": {
@ -103,16 +110,12 @@
}
},
{
"path": "pages/addoldman/camera",
"path": "compontent/public/camera",
"style": {
"navigationBarTitleText": "照相机"
"navigationBarTitleText": "图像识别",
"enablePullDownRefresh": false,
"disableScroll": true
}
},
{
"path": "pages/camera/CustomCamera",
"style": {
"navigationBarTitleText": "图像识别"
}
}
],

View File

@ -52,7 +52,6 @@
<view v-for="(item,index) in nameArray" :key="index" class="one"
@click="openLook(textArray[index])">
<view class="one-left">{{item}}</view>
<!-- <view class="one-right">{{textArray[index] ? textArray[index] : "自动获取" }}</view> -->
<view class="one-right">{{textArray[index] ? textArray[index] : "自动获取" }}</view>
</view>
</view>
@ -87,7 +86,6 @@
<view v-for="(item,index) in nameArray0" :key="index" class="one"
@click="openLook(textArray0[index])">
<view class="one-left">{{item}}</view>
<!-- <view class="one-right">{{textArray[index] ? textArray[index] : "自动获取" }}</view> -->
<view class="one-right">{{textArray0[index] ? textArray0[index] : "自动获取" }}</view>
</view>
</view>
@ -99,16 +97,12 @@
<view v-for="(item,index) in nameArray1" :key="index" class="one"
@click="openLook(textArray1[index])">
<view class="one-left">{{item}}</view>
<!-- <view class="one-right">{{textArray[index] ? textArray[index] : "自动获取" }}</view> -->
<view class="one-right">{{textArray1[index] ? textArray1[index] : "自动获取" }}</view>
</view>
</view>
</view>
</view>
<view style="display: flex;width: 100%;margin-top: 40rpx;">
<!-- <view class="finish-button" @click="goBack">
上一步
</view> -->
<view class="finish-button" @click="next" v-if="alldata.status==3 ||alldata.status==0">
重新提交
</view>
@ -229,9 +223,6 @@
if(data.orgBuildingArea){
textArray1[5] = data.orgBuildingArea + '平方米';
}
// headImge0.value = `${base_url}/sys/common/static/${data.comBusinessLicense}`;
})
</script>
@ -390,6 +381,7 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 180rpx;
}
.left-father {

View File

@ -66,7 +66,8 @@
reactive
} from 'vue'
import {
onLoad
onLoad,
onShow
} from '@dcloudio/uni-app';
import {
base_url
@ -93,7 +94,9 @@
success: chooseRes => {
tempImagePath.value = chooseRes.tempFilePaths[0]
//
uploadImage(tempImagePath.value)
//
uni.navigateTo({ url: `/compontent/public/camera?url=${chooseRes.tempFilePaths[0]}&type=0&size=1` });
// uploadImage(tempImagePath.value)
},
fail: err => {
console.error('拍照失败:', err)
@ -254,6 +257,13 @@
const goBack = () => {
uni.navigateBack()
}
onShow(() => {
const img = uni.getStorageSync('imgkey0')
if (img) {
uploadImage(img)
uni.removeStorageSync('imgkey0')
}
})
</script>
<style lang="scss" scoped>
@ -269,7 +279,7 @@
.white-content {
width: 90%;
margin-left: 5%;
margin-top: 30rpx;
margin-top: 180rpx;
// height: 1200rpx;
border-radius: 35rpx;
background-color: rgb(245, 251, 254);

View File

@ -81,7 +81,8 @@
reactive
} from 'vue'
import {
onLoad
onLoad,
onShow
} from '@dcloudio/uni-app';
import {
base_url
@ -114,17 +115,6 @@
//
function getMessage() {
// wx.ready(() => {
// wx.chooseAddress({
// success(res) {
// console.log('address', res)
// },
// fail(err) {
// console.error('fail', err)
// }
// })
// })
// 使 UniApp API
uni.chooseImage({
count: 1,
@ -132,7 +122,7 @@
success: chooseRes => {
tempImagePath.value = chooseRes.tempFilePaths[0]
//
uploadImage(tempImagePath.value)
uni.navigateTo({ url: `/compontent/public/camera?url=${chooseRes.tempFilePaths[0]}&type=0` });
},
fail: err => {
console.error('拍照失败:', err)
@ -309,55 +299,6 @@
})
}
}
// 1. JSSDK
// async function loadWxJSSDK() {
// // if (window.wx) return
// await new Promise(resolve => {
// const script = document.createElement('script')
// script.src = 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js'
// script.onload = resolve
// document.head.appendChild(script)
// getapi()
// })
// }
// const getapi = () => {
// const post = `${uni.getStorageSync('serverUrl')}/weiXinPay/getJsApiInfo`;
// const pay = {
// url: location.href.split('#')[0],
// };
// console.log("????",pay)
// fetch(post, {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json'
// },
// body: JSON.stringify(pay)
// })
// .then(res => res.json())
// .then(data => {
// // secondArray.value = [...data.result];
// // console.log("???", data)
// wx.config({
// debug: false, // alert
// appId: `wx8fc3e4305d2fbf0b`, //
// timestamp: data.timestamp, //
// nonceStr: data.nonceStr, //
// signature: data.signature, //
// jsApiList: [ // 使 JS
// 'chooseAddress',
// 'getLocation',
// 'openLocation',
// /* */
// ]
// })
// })
// .catch(err => {
// console.error(':', err);
// });
// }
const goBack = () => {
uni.navigateBack()
}
@ -379,6 +320,13 @@
}
})
onShow(() => {
const img = uni.getStorageSync('imgkey0')
if (img) {
uploadImage(img)
uni.removeStorageSync('imgkey0')
}
})
</script>
<style lang="scss" scoped>
@ -535,6 +483,7 @@
}
.title-back {
margin-top: 100rpx;
width: 100%;
height: 100rpx;
display: flex;

View File

@ -186,12 +186,12 @@
if (res.message == `保存成功!`) {
uni.setStorageSync('specicalid', "");
uni.reLaunch({
url: `/pages/login/workjoin?type=1`
url: `/pages/login/threeselectone`
});
} else {
uni.setStorageSync('specicalid', res.result.id);
uni.reLaunch({
url: `/pages/login/workjoin?type=1`
url: `/pages/login/threeselectone`
});
}
@ -336,7 +336,7 @@
.white-content {
width: 90%;
margin-left: 5%;
margin-top: 30rpx;
margin-top: 180rpx;
// height: 1200rpx;
border-radius: 35rpx;
background-color: rgb(245, 251, 254);

View File

@ -73,20 +73,22 @@
//
function getMessage() {
uni.navigateTo({ url: '/pages/addoldman/camera' });
// uni.navigateTo({ url: '/pages/addoldman/camera' });
// 使 UniApp API
// uni.chooseImage({
// count: 1,
// sourceType: ['camera'],
// success: chooseRes => {
// tempImagePath.value = chooseRes.tempFilePaths[0]
// //
// uploadImage(tempImagePath.value)
// },
// fail: err => {
// console.error('', err)
// }
// })
uni.chooseImage({
count: 1,
sourceType: ['camera'],
success: chooseRes => {
tempImagePath.value = chooseRes.tempFilePaths[0]
//
uploadImage(tempImagePath.value)
// uni.navigateTo({ url: `/pages/addoldman/camera?url=${chooseRes.tempFilePaths[0]}` });
},
fail: err => {
console.error('拍照失败:', err)
}
})
}
const headImge = ref("");

View File

@ -1,195 +0,0 @@
<template>
<view class="container">
<!-- 原生相机预览 -->
<camera id="idCamera" class="camera-preview" device-position="back" flash="off" binderror="onCameraError" />
<!-- 隐藏的裁剪 Canvas -->
<canvas canvas-id="cropCanvas"
style="width:100%;height:100%;position:absolute;top:0;left:0;display:none;"></canvas>
<!-- 遮罩层 -->
<view class="mask">
<view class="mask-block top" />
<view class="mask-block middle">
<view class="side" />
<view class="cutout">
<text class="hint">请将身份证放入此框内</text>
</view>
<view class="side" />
</view>
<view class="mask-block bottom" />
</view>
<!-- 底部拍照按钮 -->
<view class="controls">
<button class="shutter" @tap="takePhoto"> </button>
</view>
</view>
</template>
<script setup>
import {
onMounted,
ref
} from 'vue';
let cameraContext;
const sysInfo = uni.getSystemInfoSync(); //
onMounted(() => {
cameraContext = uni.createCameraContext();
});
function onCameraError(e) {
console.error('Camera Error:', e);
}
function takePhoto() {
cameraContext.takePhoto({
quality: 'high',
success: async (res) => {
const src = res.tempImagePath;
// 2.1 cutout px
const screenW = sysInfo.windowWidth;
const screenH = sysInfo.windowHeight;
const topMaskH = screenH * 0.25; // mask-block.top
const middleH = screenH * 0.50; // mask-block.middle
const cutoutH = middleH * 0.60; // cutout
const cutoutW = cutoutH * 1.586; // aspect-ratio:1.586
const cutoutX = (screenW - cutoutW) / 2; //
const cutoutY = topMaskH + (middleH - cutoutH) / 2;
// 2.2
const info = await uni.getImageInfo({
src
});
const origW = info.width;
const origH = info.height;
// 2.3
const ratioW = origW / screenW;
const ratioH = origH / screenH;
const sx = cutoutX * ratioW;
const sy = cutoutY * ratioH;
const sWidth = cutoutW * ratioW;
const sHeight = cutoutH * ratioH;
// uni.authorize({
// scope: 'scope.camera',
// success: () => console.log(''),
// fail: () => uni.showModal({ title: '', content: '' })
// });
// 3. Canvas
const ctx = uni.createCanvasContext('cropCanvas', {
enableScroll: false
});
//
ctx.drawImage(src, sx, sy, sWidth, sHeight, 0, 0, sWidth, sHeight);
ctx.draw(false, () => {
uni.canvasToTempFilePath({
canvasId: 'cropCanvas',
x: 0,
y: 0,
width: sWidth,
height: sHeight,
destWidth: sWidth,
destHeight: sHeight,
success: (cropRes) => {
//
uni.setStorageSync('idcardPhoto', cropRes.tempFilePath);
uni.navigateBack({
delta: 1
});
},
fail: (err) => {
console.error('Canvas to TempFile Fail:', err);
}
});
});
},
fail: (err) => {
console.error('Take Photo Fail:', err);
}
});
}
</script>
<style scoped>
.container {
position: relative;
width: 100%;
height: 100vh;
background: #000;
}
.camera-preview {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.mask-block {
width: 100%;
background: rgba(0, 0, 0, 0.5);
}
.mask-block.top,
.mask-block.bottom {
height: 25%;
}
.mask-block.middle {
display: flex;
flex-direction: row;
height: 50%;
}
.side {
flex: 1;
background: rgba(0, 0, 0, 0.5);
}
.cutout {
width: 100%;
height: 60%;
aspect-ratio: 1.586;
border: 2rpx dashed #fff;
position: relative;
}
.hint {
position: absolute;
bottom: 20rpx;
width: 100%;
text-align: center;
color: #fff;
font-size: 24rpx;
}
.controls {
position: absolute;
bottom: 50rpx;
width: 100%;
display: flex;
justify-content: center;
}
.shutter {
width: 300rpx;
height: 120rpx;
border-radius: 60rpx;
background: rgba(255, 255, 255, 0.7);
display: flex;
justify-content: center;
align-items: center;
}
</style>

View File

@ -1,138 +0,0 @@
<template>
<view class="page">
<!-- 视频预览层拿到 MediaStream 后才显示 -->
<video
ref="video"
class="video"
playsinline webkit-playsinline x5-playsinline
muted
v-if="started"
/>
<!-- Canvas实时把 video 画上来同时用于拍照 -->
<canvas ref="canvas" class="canvas" v-if="started"></canvas>
<!-- 中间遮罩 -->
<image
class="overlay"
src="https://www.focusnu.com/media/directive/index/nu.png"
:style="{ opacity: started ? 1 : 0 }"
/>
<!-- 按钮区 -->
<view class="btn-bar" v-if="started">
<button class="btn close" @click="close">关闭</button>
<button class="btn shoot" @click="shoot"></button>
</view>
<!-- 首次进入的 开始拍照 按钮触发权限弹窗 -->
<view class="starter" v-if="!started">
<button class="btn start" @click="startCamera">开始拍照</button>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onBeforeUnmount } from 'vue'
import { useRouter } from 'vue-router'
// ----------------- -----------------
const video = ref<HTMLVideoElement>()
const canvas = ref<HTMLCanvasElement>()
// ----------------- -----------------
const started = ref(false)
let stream: MediaStream | null = null
let rafID: number
// ----------------- -----------------
async function startCamera() {
// 1.
if (!navigator.mediaDevices?.getUserMedia) {
uni.showToast({ title: '当前浏览器不支持实时相机', icon: 'none' })
return
}
try {
stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: { ideal: 'environment' } },
audio: false
})
// 2. <video>
const v = video.value!
v.srcObject = stream
await v.play()
// 3. real-time Canvas
drawFrame()
started.value = true
} catch (err) {
console.error(err)
uni.showToast({ title: '无法获取摄像头权限', icon: 'none' })
}
}
function drawFrame() {
if (!started.value) return
const v = video.value!, c = canvas.value!
const ctx = c.getContext('2d')!
if (v.videoWidth && v.videoHeight) {
// canvas
if (c.width !== window.innerWidth) {
c.width = window.innerWidth
c.height = window.innerHeight
}
ctx.drawImage(v, 0, 0, c.width, c.height)
}
rafID = requestAnimationFrame(drawFrame)
}
function shoot() {
const dataURL = canvas.value!.toDataURL('image/jpeg', 0.9)
// / oss / emit
uni.navigateBack()
uni.$emit('photoTaken', dataURL)
}
function close() {
cleanup()
uni.navigateBack()
}
function cleanup() {
cancelAnimationFrame(rafID)
if (stream) stream.getTracks().forEach(t => t.stop())
}
onBeforeUnmount(cleanup)
</script>
<style scoped>
.page { position:fixed; inset:0; background:#000; overflow:hidden; }
.video { position:absolute; inset:0; object-fit:cover; z-index:1; }
.canvas { position:absolute; inset:0; z-index:1; }
.overlay{
position:absolute; top:50%; left:50%;
width:220px; height:220px;
transform:translate(-50%,-50%); z-index:2;
pointer-events:none;
}
.btn-bar{
position:absolute; bottom:60px; inset-inline:0;
display:flex; justify-content:space-around; z-index:3;
}
.btn{
border:none; font-size:16px; color:#fff;
}
.close{ width:100px; height:40px; border-radius:20px; background:rgba(0,0,0,.5); }
.shoot{ width:80px; height:80px; border-radius:40px; background:#fff; }
.starter{
position:absolute; inset:0; display:flex;
justify-content:center; align-items:center; z-index:3;
background:#000;
}
.start{ width:140px; height:46px; border-radius:23px; background:#1aad19; }
</style>

View File

@ -407,7 +407,7 @@
justify-content: flex-end;
// margin-top: 35rpx;
position: fixed;
top: 35rpx;
top: 120rpx;
left: 0;
z-index: 999;
margin-top: 120rpx;
@ -867,7 +867,7 @@
}
.back-imge{
position: absolute;
top: 100rpx;
top: 180rpx;
left: 30rpx;
width: 50rpx;
height: 50rpx;

View File

@ -46,190 +46,246 @@
import {
onLoad
} from '@dcloudio/uni-app';
import { base_url } from '@/request/index.js'
import request from '@/request/index.js';
// import { base_url } from '@/request/index.js'
// import request from '@/request/index.js';
import {
reactive,
ref
} from 'vue';
import {getList} from "@/api/loginApi.js"
import {
getOpenid,
getMessage
} from '@/api/loginApi.js'
// import {getList} from "@/api/loginApi.js"
const itemArray = ["NU", "动态", "我的"];
const itemTarget = ref(0);
// const {
// fetchUserInfo,
// openid,
// userInfo
// } = useWeChatAuth();
// const itemArray = ["NU", "", ""];
// const itemTarget = ref(0);
// // const {
// // fetchUserInfo,
// // openid,
// // userInfo
// // } = useWeChatAuth();
const ceshi = reactive({
name: "",
openid: "",
accessToken: ""
})
// const jumpto = () => {
// console.log("???")
// uni.navigateTo({
// url: "/pages/pay/index"
// });
// const ceshi = reactive({
// name: "",
// openid: "",
// accessToken: ""
// })
// // const jumpto = () => {
// // console.log("???")
// // uni.navigateTo({
// // url: "/pages/pay/index"
// // });
// // }
// const getOpenId = (code) => {
// const url = `${base_url}/weixin/wechat/callback?code=${encodeURIComponent(code)}`;
// fetch(url)
// .then(res => res.json())
// .then(data => {
// ceshi.name = data.data.nickname
// ceshi.openid = data.data.openid
// ceshi.accessToken = data.accessToken
// uni.setStorage({
// key: 'openid',
// data: {
// openid: data.data.openid,
// accessToken: data.accessToken,
// }
// });
// getUserMessage()
// })
// .catch(err => {
// console.error(" :", err);
// });
// }
const getOpenId = (code) => {
const url = `${base_url}/weixin/wechat/callback?code=${encodeURIComponent(code)}`;
fetch(url)
.then(res => res.json())
.then(data => {
ceshi.name = data.data.nickname
ceshi.openid = data.data.openid
ceshi.accessToken = data.accessToken
uni.setStorage({
key: 'openid',
data: {
openid: data.data.openid,
accessToken: data.accessToken,
}
});
getUserMessage()
})
.catch(err => {
console.error("❌ 获取用户信息失败:", err);
});
}
// const serve = ref("")
const look = ref("")
const getUserMessage = () => {
const url =
`${base_url}/h5Api/nuBizAdvisoryInfo/queryWeixinInfo?openId=${encodeURIComponent(ceshi.openid)}`;
fetch(url)
.then(res => res.json())
.then(data => {
console.log("个人信息打印", data)
uni.setStorageSync('token', data.result.token);
uni.setStorageSync('serverUrl', data.result.serverUrl);
console.log("???token存储",data.result.token)
// const post = `${uni.getStorageSync('serverUrl')}/weiXinPay/getJsApiInfo`;
// const pay = {
// access_token: ceshi.accessToken,
// };
// fetch(post, {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json'
// },
// body: JSON.stringify(pay)
// })
// .then(res => res.json())
// .then(data => {
// // secondArray.value = [...data.result];
// console.log("???", data)
// // const serve = ref("")
// const look = ref("")
// const getUserMessage = () => {
// const url =
// `${base_url}/h5Api/nuBizAdvisoryInfo/queryWeixinInfo?openId=${encodeURIComponent(ceshi.openid)}`;
// fetch(url)
// .then(res => res.json())
// .then(data => {
// console.log("", data)
// uni.setStorageSync('token', data.result.token);
// uni.setStorageSync('serverUrl', data.result.serverUrl);
// console.log("???token",data.result.token)
// // const post = `${uni.getStorageSync('serverUrl')}/weiXinPay/getJsApiInfo`;
// // const pay = {
// // access_token: ceshi.accessToken,
// // };
// // fetch(post, {
// // method: 'POST',
// // headers: {
// // 'Content-Type': 'application/json'
// // },
// // body: JSON.stringify(pay)
// // })
// // .then(res => res.json())
// // .then(data => {
// // // secondArray.value = [...data.result];
// // console.log("???", data)
// })
// .catch(err => {
// console.error(':', err);
// });
// const urlpost = `${data.result.serverUrl}/weiXinPay/getUserInfo`;
// const payload = {
// openid: ceshi.openid,
// access_token: ceshi.accessToken,
// // serverUrl: serve.value
// };
// console.log("???/", payload)
// fetch(urlpost, {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json'
// },
// body: JSON.stringify(payload)
// })
// .then(res => res.json())
// .then(data => {
// // secondArray.value = [...data.result];
// look.value = data
// console.log("", data)
// })
// .catch(err => {
// console.error(':', err);
// });
// // })
// // .catch(err => {
// // console.error(':', err);
// // });
// // const urlpost = `${data.result.serverUrl}/weiXinPay/getUserInfo`;
// // const payload = {
// // openid: ceshi.openid,
// // access_token: ceshi.accessToken,
// // // serverUrl: serve.value
// // };
// // console.log("???/", payload)
// // fetch(urlpost, {
// // method: 'POST',
// // headers: {
// // 'Content-Type': 'application/json'
// // },
// // body: JSON.stringify(payload)
// // })
// // .then(res => res.json())
// // .then(data => {
// // // secondArray.value = [...data.result];
// // look.value = data
// // console.log("", data)
// // })
// // .catch(err => {
// // console.error(':', err);
// // });
if(!data.result.tel){
uni.redirectTo({
url: `/pages/login/phonebumber`
});
}else{
uni.redirectTo({
url: `/pages/login/threeselectone`
});
uni.setStorageSync('tel', data.result.tel);
}
getjigou()
})
// if(!data.result.tel){
// uni.redirectTo({
// url: `/pages/login/phonebumber`
// });
// }else{
// uni.redirectTo({
// url: `/pages/login/threeselectone`
// });
// uni.setStorageSync('tel', data.result.tel);
// }
// getjigou()
// })
}
const jigouArray = ref([]);
const getjigou = () => {
const url = `${base_url}/sys/sysDepart/queryInstitutionsList`;
fetch(url)
.then(res => res.json())
.then(data => {
jigouArray.value = [...data]
console.log("机构打印", jigouArray.value)
})
}
const secondArray = ref([]);
const jigouClick = (element) => {
const url = `${element.serverUrl}/h5Api/nuBaseInfo/list`;
fetch(url)
.then(res => res.json())
.then(data => {
secondArray.value = [...data.result]
})
uni.setStorage({
key: 'serverUrl',
data: {
url: element.serverUrl,
// }
// const jigouArray = ref([]);
// const getjigou = () => {
// const url = `${base_url}/sys/sysDepart/queryInstitutionsList`;
// fetch(url)
// .then(res => res.json())
// .then(data => {
// jigouArray.value = [...data]
// console.log("", jigouArray.value)
// })
// }
// const secondArray = ref([]);
// const jigouClick = (element) => {
// const url = `${element.serverUrl}/h5Api/nuBaseInfo/list`;
// fetch(url)
// .then(res => res.json())
// .then(data => {
// secondArray.value = [...data.result]
// })
// uni.setStorage({
// key: 'serverUrl',
// data: {
// url: element.serverUrl,
// }
// });
// const urlpost = `${base_url}/h5Api/nuBizAdvisoryInfo/editNuBizAdvisoryInfo`;
// const payload = {
// openId: ceshi.openid,
// serverUrl: element.serverUrl
// };
// console.log("???/", payload)
// fetch(urlpost, {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json'
// },
// body: JSON.stringify(payload)
// })
// .then(res => res.json())
// .then(data => {
// // secondArray.value = [...data.result];
// console.log("???", data)
// })
// .catch(err => {
// console.error(':', err);
// });
// }
const superLogin = () => {
uni.login({
provider: 'weixin',
success(res) {
getOpenid(res.code).then(res => {
let openid = res.data.openid
uni.setStorageSync("openid", openid)
getMessage(openid).then(res => {
// uni.navigateTo({
// url: `/pages/login/phonebumber`
// });
if(!res.result.tel){
uni.redirectTo({
url: `/pages/login/index`
});
}else{
// uni.setStorageSync('tel', res.result.tel);
// uni.setStorageSync('token', res.result.token);
// uni.setStorageSync('serverUrl', res.result.serverUrl);
// uni.redirectTo({
// url: `/pages/login/threeselectone`
// });
if(uni.getStorageSync('special')){
uni.setStorageSync('tel', res.result.tel);
uni.setStorageSync('token', res.result.token);
uni.setStorageSync('serverUrl', res.result.serverUrl);
uni.redirectTo({
url: `/pages/login/special`
});
}else{
uni.setStorageSync('tel', res.result.tel);
uni.setStorageSync('token', res.result.token);
uni.setStorageSync('serverUrl', res.result.serverUrl);
uni.redirectTo({
url: `/pages/login/threeselectone`
});
}
}
})
})
},
fail(err) {
console.error('获取 code 失败:', err);
}
});
const urlpost = `${base_url}/h5Api/nuBizAdvisoryInfo/editNuBizAdvisoryInfo`;
const payload = {
openId: ceshi.openid,
serverUrl: element.serverUrl
};
console.log("???/", payload)
fetch(urlpost, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
.then(res => res.json())
.then(data => {
// secondArray.value = [...data.result];
console.log("???", data)
})
.catch(err => {
console.error('请求失败:', err);
});
}
onLoad(() => {
const href = window.location.href;
const queryString = href.split('?')[1]?.split('#')[0]; // ? #
const query = {};
if (queryString) {
queryString.split('&').forEach(pair => {
const [key, value] = pair.split('=');
query[key] = decodeURIComponent(value);
});
onLoad((options) => {
superLogin();
if(options.type){
uni.setStorageSync('special', true);
}else{
uni.setStorageSync('special', false);
}
// const href = window.location.href;
// const queryString = href.split('?')[1]?.split('#')[0]; // ? #
// const query = {};
console.log('解析到的 query 参数:', query);
// if (queryString) {
// queryString.split('&').forEach(pair => {
// const [key, value] = pair.split('=');
// query[key] = decodeURIComponent(value);
// });
// }
if (query.code) {
getOpenId(query.code)
// fetchUserInfo(query.code)
}
// console.log(' query :', query);
// if (query.code) {
// getOpenId(query.code)
// // fetchUserInfo(query.code)
// }
});
</script>

View File

@ -1,17 +1,7 @@
<template>
<view class="login-container">
<!-- <view class="title">
<image class="title-imge" src="https://www.focusnu.com/media/directive/index/nu.png" />
<view class="title-font">
<view class="">您好</view>
<view class="">欢迎使用护理单元~</view>
</view>
</view>
<image class="photo-imge" src="https://www.focusnu.com/media/directive/index/bgc.png" />
<image class="old-imge" src="https://www.focusnu.com/media/directive/index/old.png" /> -->
<image class="photo-imge" src="https://www.focusnu.com/media/directive/login/bgc.png" />
<image class="back-imge" src="https://www.focusnu.com/media/directive/login/back.png" @click="goback" />
<!-- <view class="under-container"> -->
<view class="under-container-title">
<view class="code-title">
请输入验证码
@ -21,17 +11,10 @@
<view class="code-font">
{{ mobile }}
</view>
<!-- <text class="code-font"></text> -->
</view>
</view>
<view class="captcha-container">
<u-message-input active-color="#333333" inactive-color="rgb(175,179,189)" width="150" :focus="true" mode="bottomLine" @finish="finshinput" ></u-message-input>
<!-- <view class="captcha-box">
<view v-for="(digit, index) in captcha" :key="index" class="captcha-item">
<input :value="captcha[index]" class="captcha-input" type="number" maxlength="4"
@input="handleInput(index, $event)" :focus="focusedIndex === index" />
</view>
</view> -->
</view>
<view class="under-view" style="z-index: 1;">
@ -128,7 +111,7 @@
if (captcha.value[index]) {
if (index < 3) {
focusedIndex.value = index + 1; //
console.log("cccccccccc")
// console.log("cccccccccc")
}
}
let isFour = true;
@ -187,13 +170,29 @@
}).then(res => {
if (res.success) {
getMessage(openid).then(res => {
uni.setStorageSync('tel', res.result.tel);
uni.setStorageSync('token', res.result.token);
uni.setStorageSync('serverUrl', res.result.serverUrl);
uni.redirectTo({
url: `/pages/login/threeselectone`
});
// uni.setStorageSync('tel', res.result.tel);
// uni.setStorageSync('token', res.result.token);
// uni.setStorageSync('serverUrl', res.result.serverUrl);
// uni.redirectTo({
// url: `/pages/login/threeselectone`
// });
if(uni.getStorageSync('special')){
uni.setStorageSync('tel', res.result.tel);
uni.setStorageSync('token', res.result.token);
uni.setStorageSync('serverUrl', res.result.serverUrl);
uni.redirectTo({
url: `/pages/login/special`
});
}else{
uni.setStorageSync('tel', res.result.tel);
uni.setStorageSync('token', res.result.token);
uni.setStorageSync('serverUrl', res.result.serverUrl);
uni.redirectTo({
url: `/pages/login/threeselectone`
});
}
})
} else {
uni.showToast({
title: '验证码错误',

View File

@ -67,35 +67,7 @@
isFadingOut.value = false
}
const superLogin = () => {
uni.login({
provider: 'weixin',
success(res) {
getOpenid(res.code).then(res => {
let openid = res.data.openid
uni.setStorageSync("openid", openid)
getMessage(openid).then(res => {
// uni.navigateTo({
// url: `/pages/login/phonebumber`
// });
if(!res.result.tel){
}else{
uni.setStorageSync('tel', res.result.tel);
uni.setStorageSync('token', res.result.token);
uni.setStorageSync('serverUrl', res.result.serverUrl);
uni.redirectTo({
url: `/pages/login/threeselectone`
});
}
})
})
},
fail(err) {
console.error('获取 code 失败:', err);
}
});
}
const getCode = () => {
@ -120,6 +92,21 @@
uni.redirectTo({
url: `/pages/login/threeselectone`
});
// if(uni.getStorageSync('special')){
// uni.setStorageSync('tel', res.result.tel);
// uni.setStorageSync('token', res.result.token);
// uni.setStorageSync('serverUrl', res.result.serverUrl);
// uni.redirectTo({
// url: `/pages/login/threeselectone`
// });
// }else{
// uni.setStorageSync('tel', res.result.tel);
// uni.setStorageSync('token', res.result.token);
// uni.setStorageSync('serverUrl', res.result.serverUrl);
// uni.redirectTo({
// url: `/pages/login/threeselectone`
// });
// }
}
})
})
@ -142,8 +129,13 @@
url: "/pages/login/protocol"
});
}
onLoad(()=>{
onLoad((options)=>{
superLogin()
// if(options.type){
// uni.setStorageSync('special', true);
// }else{
// uni.setStorageSync('special', false);
// }
})
</script>

64
pages/login/special.vue Normal file
View File

@ -0,0 +1,64 @@
<template>
<view class="font-father">
<view class="font-title">
尊敬的用户你的手机<text>{{ phonenumber }}</text>已成功绑定欢迎加入护理单元大家庭!
</view>
<view class="bottom-button" @click="jumpto">
我的机构
</view>
</view>
</template>
<script setup>
import {
onLoad
} from '@dcloudio/uni-app'
import {
reactive,
ref
} from 'vue';
const phonenumber = ref("");
const jumpto = () => {
uni.redirectTo({
url: `/pages/login/threeselectone`
});
}
onLoad(()=>{
phonenumber.value = uni.getStorageSync('tel')
})
</script>
<style lang="scss" scoped>
.font-father {
width: 100%;
height: 100vh;
padding: 0 50rpx;
display: flex;
justify-content: center;
position: relative;
// flex-direction: column;
.font-title{
margin-top: 200rpx;
}
.bottom-button{
position: fixed;
bottom: 150rpx;
left: 50%;
transform: translateX(-50%);
width: 400rpx;
height: 100rpx;
// border-radius: 30rpx;
border-radius: 50rpx;
background: linear-gradient(to right, #00C9FF, #0076FF);
color: #fff;
font-size: 33rpx;
display: flex;
justify-content: center;
align-items: center;
}
}
</style>

View File

@ -68,7 +68,7 @@
护理员严格按标准流程定时为失能老人开展床旁照护用专业与温情守护老人生活与健康
</view>
<view style="display: flex;">
<!-- <view class="white-button" style="margin-right: 30rpx;">
<!-- <view class="white-button" style="margin-right: 30rpx;">
审核列表
</view> -->
<view class="white-button">
@ -96,10 +96,10 @@
申请加盟
</view>
</view>
</view>
<view class="card-right">
<image class="right-imge" src="https://www.focusnu.com/media/directive/login/gongsi.png" />
<image class="right-imge" src="https://www.focusnu.com/media/directive/login/gongsi.png" @click="ceshi" />
</view>
</view>
<view class="blue-button" @click="close">
@ -126,9 +126,14 @@
const changePhoto = (index) => {
itemTarget.value = index;
}
const ceshi = () => {
uni.navigateTo({
url: `/pages/login/workjoin?type=1`
});
}
const jumpToindex = () => {
uni.redirectTo({
uni.navigateTo({
url: `/pages/index/index`
});
}

View File

@ -1,52 +1,44 @@
<template>
<view class="login-container">
<image class="photo-imge" src="https://www.focusnu.com/media/directive/index/workjoin/bgc.png" />
<image class="old-imge" src="https://www.focusnu.com/media/directive/index/workjoin/ren.png" />
<view class="under-container" @touchstart.stop @touchmove.stop @touchend.stop>
<view class="white-card">
<image class="left-img"
:src="type=== `1` ? `https://www.focusnu.com/media/directive/index/workjoin/man.png` : `https://www.focusnu.com/media/directive/index/workjoin/bgcren.png`" />
<view class="card-font">
<view style="font-size: 30rpx;font-weight: 600;margin: 20rpx 0 30rpx 0;">
{{type=== `1` ? `机构加盟`:`员工入驻`}}
</view>
<view style="color: #666666;font-size: 25rpx;">
护理院日常护理涵盖生活照料健康监测康复护理及心理关怀为老人提供贴心照护
</view>
<view class="white-card">
<image class="left-img"
:src="type=== `1` ? `https://www.focusnu.com/media/directive/index/workjoin/man.png` : `https://www.focusnu.com/media/directive/index/workjoin/bgcren.png`" />
<view class="card-font">
<view style="font-size: 30rpx;font-weight: 600;margin: 20rpx 0 30rpx 0;">
{{type=== `1` ? `机构加盟`:`员工入驻`}}
</view>
<view style="color: #666666;font-size: 25rpx;">
护理院日常护理涵盖生活照料健康监测康复护理及心理关怀为老人提供贴心照护
</view>
</view>
<view class="white-ball" @click="goback">
<image class="ball-imge" src="https://www.focusnu.com/media/directive/index/workjoin/x.png" />
</view>
<view class="shu-father">
<view class="shu"></view>
<view class="shu-font">{{ type==="1" ? `机构加盟审核列表` : `员工入驻审核列表` }}</view>
</view>
<view class="white-ball" @click="goback">
<image class="ball-imge" src="https://www.focusnu.com/media/directive/index/workjoin/x.png" />
</view>
<view class="shu-father">
<view class="shu"></view>
<view class="shu-font">{{ type==="1" ? `机构加盟审核列表` : `员工入驻审核列表` }}</view>
</view>
<view class="under-scroll">
<scroll-view scroll-y refresher-enabled :refresher-triggered="isRefreshing"
@refresherrefresh="onRefresh" style="height: 100%;width: 100%;">
<view v-for="(item,index) in workArray" :key="index">
<view class="white-small">
<view style="width: 100%;margin-bottom: 80rpx;font-size: 25rpx;">
{{item.comName}}申请入驻加盟护理单元提交时间:{{item.updateTime}}审核结果:{{item.status==1 ? "审核中" :item.status==2?`审核完成`:item.status==3?`驳回`:`待提交` }}
{{item.status==3?`,驳回备注:${item.content}`:``}}
</view>
<view class="button-heng">
<view class="blue-button" v-if="item.status==3||item.status==0" @click="again(item)">
修改申请
</view>
<view class="white-button" @click="jumpToAll(item)">
查看详情
</view>
</view>
</view>
<view class="under-scroll">
<view v-for="(item,index) in workArray" :key="index">
<view class="white-small">
<view style="width: 100%;margin-bottom: 80rpx;font-size: 25rpx;">
{{item.comName}}申请入驻加盟护理单元提交时间:{{item.updateTime}}审核结果:{{item.status==1 ? "审核中" :item.status==2?`审核完成`:item.status==3?`驳回`:`待提交` }}
{{item.status==3?`,驳回备注:${item.content}`:``}}
</view>
<view class="button-heng">
<view class="blue-button" v-if="item.status==3||item.status==0" @click="again(item)">
修改申请
</view>
<view class="white-button" @click="jumpToAll(item)">
查看详情
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</view>
</template>
@ -65,16 +57,7 @@
} from '@/pages/addjigou/api/addjigou.js'
const type = ref(0)
const workArray = ref([])
const isRefreshing = ref(false)
const onRefresh = () => {
console.log("下拉刷新被触发")
isRefreshing.value = true
getMessageList(uni.getStorageSync('tel')).then(res => {
workArray.value = [];
workArray.value = res.result;
isRefreshing.value = false
})
}
// URL
onLoad((options) => {
// options.type URL number
@ -86,13 +69,20 @@
}
})
onPullDownRefresh(()=>{
getMessageList(uni.getStorageSync('tel')).then(res => {
workArray.value = res.result
uni.stopPullDownRefresh()
})
})
const goback = () => {
uni.navigateBack()
}
const again = (item) => {
console.log("????", item)
uni.setStorageSync("baddata", item)
uni.setStorageSync('specicalid', item.id);
uni.navigateTo({
@ -151,23 +141,24 @@
height: 750rpx;
}
.under-container {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: 100%;
background-color: rgb(236, 238, 244);
box-shadow: 10rpx 10rpx 20rpx rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
align-items: center;
z-index: 1;
}
// .under-container {
// position: fixed;
// left: 0;
// bottom: 0;
// width: 100%;
// height: 100%;
// background-color: rgb(236, 238, 244);
// box-shadow: 10rpx 10rpx 20rpx rgba(0, 0, 0, 0.1);
// display: flex;
// flex-direction: column;
// align-items: center;
// z-index: 1;
// }
}
.white-card {
margin-top: 30rpx;
margin-top: 200rpx;
margin-left: 3%;
width: 94%;
background-color: #fff;
height: 320rpx;
@ -194,7 +185,7 @@
.white-ball {
position: absolute;
right: 60rpx;
top: 60rpx;
top: 220rpx;
width: 75rpx;
height: 75rpx;
border-radius: 50%;
@ -275,6 +266,7 @@
margin-right: 20rpx;
border-radius: 30rpx;
color: #fff;
}
}
</style>

View File

@ -61,7 +61,6 @@ export default (params) => {
}
},
fail(err) {
console.log(err)
if (err.errMsg.indexOf('request:fail') !== -1) {
uni.showToast({
title: '网络异常',
@ -76,11 +75,6 @@ export default (params) => {
}
reject(err);
},
complete() {
// 不管成功还是失败都会执行
// uni.hideLoading();
// uni.hideToast();
}
});
}).catch(() => {});

View File

@ -0,0 +1,72 @@
## 2.2.52024-07-30
* 修复 当 checkRange=true 时,拖动四个伸缩角放大图片时还可能会超出或未到边界的问题
* 修复 当 checkRange=false 时,图片旋转时会放大图片适应裁剪尺寸的问题
* 修复 当 checkRange=true 时,图片旋转 90° 或 270° 进行缩放可能会无法拖动图片的问题
## 2.2.42024-06-21
* 新增 reverseRotatable 属性,是否支持逆向翻转
* 修复 `2.1.7` 版本导致旋转后图片没有自动适配裁剪框的问题
## 2.2.32024-06-21
* 新增 gpu 属性,是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题
* 修复 组件使用 `v-if` 并设置 `src` 属性时可能会出现图片渲染位置存在偏差的问题
## 2.2.22024-06-21
* 优化 组件实例 chooseImage 方法支持传参
* 修复 组件使用 `v-if` 时组件无非正常渲染的问题
## 2.2.12024-06-15
* 修复 H5平台不支持手势拖动图片的问题
## 2.2.02024-05-31
* 修复 APP平台 `vue2` 项目因 `2.1.9` 版本修复 `vue3` 项目bug而引发的问题
## 2.1.92024-05-29
* 修复 APP平台 `vue3` 项目因 uniapp `renderjs` 中未支持条件编译导致运行了H5平台代码报错的问题
## 2.1.82024-05-29
* 新增 zIndex 属性,调整组件层级
* 新增 组件内容插槽
* 优化 微信小程序平台动态修改元素style时的多余内容
## 2.1.72024-05-28
* 新增 checkRange 属性,当 checkRange=false 时允许图片位置超出裁剪边界
* 新增 minScale 属性,图片最小缩放倍数,当 minScale<0 时可使图片宽高不再受裁剪区域宽高限制
* 新增 backgroundColor 属性,生成图片背景色,如果裁剪区域没有完全包含在图片中时,不设置该属性生成图片存在一定的透明块
* 优化 动态修改图片宽高但没有传入src时尺寸适应问题
* 修复 APP平台通过 `this.$ownerInstance` 获取组件实例时机过早,其值为 `undefined` 导致报错界面没有正常渲染的问题
## 2.1.62023-04-16
* 修复 组件使用 v-show 指令会导致选择图片后初始位置严重偏位的问题
## 2.1.52023-04-15
* 新增 兼容APP平台
## 2.1.42023-03-13
* 新增 fileType 属性,用于指定生成文件的类型,只支持 'jpg' 或 'png',默认为 'png'
* 新增 delay 属性,微信小程序平台使用 `Canvas 2D` 绘制时控制图片从绘制到生成所需时间
* 优化 当生成图片的尺寸宽/高超过 Canvas 2D 最大限制1365*1365则将画布尺寸缩放在限制范围内绘制完成后输出目标尺寸
* 优化 旋转图标指示方向与实际旋转方向不符
## 2.1.32023-02-06
* 优化 vue3支持
## 2.1.22023-02-03
* 新增 navigation 属性H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 "navigationStyle": "custom" 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差
* 修复 H5平台部分设备已知iPhone11以下机型拍照的图片缩放时会闪动的问题
## 2.1.12022-12-06
* 修复 横屏适配问题
## 2.1.02022-12-06
* 新增 兼容H5平台使用 renderjs 响应手势事件
## 2.0.02022-12-05
* 重构 插件,使用 WXS 响应手势事件
* 新增 图片翻转
* 新增 拉伸裁剪框放大图片
* 新增 监听PC鼠标滚轮触发缩放
* 新增 圆形、圆角矩形的图片裁剪
* 优化 图片缩放移动端以双指触摸中心点为缩放中心点PC端以鼠标所在点为缩放中心点
* 优化 裁剪框样式
* 优化 图片位置拖动 支持边界回弹效果(滑动时可滑出边界,释放时回弹到边界)
* 优化 生成图片使用新版 Canvas 2D 接口

View File

@ -0,0 +1,855 @@
/**
* 图片编辑器-手势监听
* 1. 支持编译到app-vueuni-app 2.5.5及以上版本H5上
*/
/** 图片偏移量 */
var offset = { x: 0, y: 0 };
/** 图片缩放比例 */
var scale = 1;
/** 图片最小缩放比例 */
var minScale = 1;
/** 图片旋转角度 */
var rotate = 0;
/** 触摸点 */
var touches = [];
/** 图片布局信息 */
var img = {};
/** 系统信息 */
var sys = {};
/** 裁剪区域布局信息 */
var area = {};
/** 触摸行为类型 */
var touchType = '';
/** 操作角的位置 */
var activeAngle = 0;
/** 裁剪区域布局信息偏移量 */
var areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
/** 元素ID */
var elIds = {
'imageStyles': 'crop-image',
'maskStylesList': 'crop-mask-block',
'borderStyles': 'crop-border',
'circleBoxStyles': 'crop-circle-box',
'circleStyles': 'crop-circle',
'gridStylesList': 'crop-grid',
'angleStylesList': 'crop-angle',
}
/** 记录上次初始化时间戳排除APP重复更新 */
var timestamp = 0;
/** vue3 renderjs 条件编译无效,以此方式区别 APP 和 H5 */
// #ifdef H5
var platform = 'H5';
// #endif
// #ifdef APP
var platform = 'APP';
// #endif
/** 容错值 */
var fault = 0.000001;
/**
* 获取ab两数中的最小正数
* @param a
* @param b
*/
function minimum(a, b) {
if (a > 0 && b < 0) return a;
if (a < 0 && b > 0) return b;
if (a > 0 && b > 0) return Math.min(a, b);
return 0;
}
/**
* 在容错访问内获取n近似值
* @param n
*/
function num(n) {
var m = parseFloat((n).toFixed(6));
return m === fault || m === -fault ? 0 : m;
}
/**
* 比较a值在容错值范围内是否等于b值
* @param a
* @param b
*/
function equalsByFault(a, b) {
return Math.abs(a - b) <= fault;
}
/**
* 比较a值在容错值范围内是否小于b值
* @param a
* @param b
*/
function lessThanByFault(a, b) {
var c = a - b;
return c < 0 ? c < -fault : c < fault;
}
/**
* 验证并获取有效最大值
* @param v
* @param max
* @param isInclude
* @param x
* @param y
* @param rate
* @returns
*/
function validMax(v, max, isInclude, x, y, rate) {
if(typeof max === 'number') {
if(isInclude && equalsByFault(max, y)) { // 宽高不等时x轴用y轴值要做等比例转换
var n = num(max * rate);
if (n <= x) return n; // 转化后值在x轴最大值范围内
return x; // 转化后值超出x轴最大值范围则用最大值
}
return max;
}
return v;
}
/**
* 样式对象转字符串
* @param {Object} style 样式对象
*/
function styleToString(style) {
if(typeof style === 'string') return style;
var str = '';
for (let k in style) {
str += k + ':' + style[k] + ';';
}
return str;
}
/**
*
* @param {Object} instance 页面实例对象
* @param {Object} key 要修改样式的key
* @param {Object|Array} style 样式
*/
function setStyle(instance, key, style) {
// console.log('setStyle', instance, key, JSON.stringify(style))
// #ifdef APP-PLUS
if(platform === 'APP') {
if(Object.prototype.toString.call(style) === '[object Array]') {
for (var i = 0, len = style.length; i < len; i++) {
var el = window.document.getElementById(elIds[key] + '-' + (i + 1));
el && (el.style = styleToString(style[i]));
}
} else {
var el = window.document.getElementById(elIds[key]);
el && (el.style = styleToString(style));
}
}
// #endif
// #ifdef H5
if(platform === 'H5') instance[key] = style;
// #endif
}
/**
* 触发页面实例指定方法
* @param {Object} instance 页面实例对象
* @param {Object} name 方法名称
* @param {Object} obj 传递参数
*/
function callMethod(instance, name, obj) {
// #ifdef APP-PLUS
if(platform === 'APP') instance.callMethod(name, obj);
// #endif
// #ifdef H5
if(platform === 'H5') instance[name](obj);
// #endif
}
/**
* 计算两点间距
* @param {Object} touches 触摸点信息
*/
function getDistanceByTouches(touches) {
// 根据勾股定理求两点间距离
var a = touches[1].pageX - touches[0].pageX;
var b = touches[1].pageY - touches[0].pageY;
var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
// 求两点间的中点坐标
// 1. a、b可能为负值
// 2. 在求a、b时如用touches[1]减touches[0]则求中点坐标也得用touches[1]减a/2、b/2
// 3. 同理在求a、b时也可用touches[0]减touches[1]则求中点坐标也得用touches[0]减a/2、b/2
var x = touches[1].pageX - a / 2;
var y = touches[1].pageY - b / 2;
return { c, x, y };
};
/**
* 修正取值
* @param {Object} a
* @param {Object} b
* @param {Object} c
* @param {Object} reverse 是否反向
*/
function correctValue(a, b, c, reverse) {
return num(reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c));
}
/**
* 旋转90°或270°时检查边界限制 xy 拖动范围禁止滑出边界
* @param {Object} e 点坐标
* @param {Object} xReverse x是否反向
* @param {Object} yReverse y是否反向
*/
function checkRotateRange(e, xReverse, yReverse) {
var o = num((img.height - img.width) / 2); // 宽高差值一半
return {
x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, xReverse),
y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, yReverse)
};
}
/**
* 检查边界限制 xy 拖动范围禁止滑出边界
* @param {Object} e 点坐标
*/
function checkRange(e) {
var r = rotate / 90 % 2;
if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移
if (area.width === area.height) {
return checkRotateRange(e, img.height < area.height, img.width < area.width);
}
var isInclude = img.height < area.width && img.width < area.height; // 图片是否包含在裁剪区域内
if (img.width < area.height || img.height < area.width) {
if (area.width < area.height && img.width < img.height) {
return isInclude
? checkRotateRange(e, area.width < area.height, area.width < area.height)
: checkRotateRange(e, false, true);
}
if (area.height < area.width && img.height < img.width) {
return isInclude
? checkRotateRange(e, area.height < area.width, area.height < area.width)
: checkRotateRange(e, true, false);
}
}
if (img.height >= area.width && img.width >= area.height) {
return checkRotateRange(e, false, false);
}
if (isInclude) {
return area.height < area.width
? checkRotateRange(e, true, true)
: checkRotateRange(e, area.width < area.height, area.width < area.height);
}
if (img.height < area.width && !img.width < area.height) {
return checkRotateRange(e, true, false);
}
if (!img.height < area.width && img.width < area.height) {
return checkRotateRange(e, false, true);
}
return checkRotateRange(e, img.height < area.height, img.width < area.width);
}
return {
x: correctValue(e.x, -img.width + area.width + area.left, area.left, img.width < area.width),
y: correctValue(e.y, -img.height + area.height + area.top, area.top, img.height < area.height)
}
};
/**
* 变更图片布局信息
* @param {Object} e 布局信息
*/
function changeImageRect(e) {
// console.log('changeImageRect', e)
offset.x += e.x || 0;
offset.y += e.y || 0;
if(e.check && area.checkRange) { // 检查边界
var point = checkRange(offset);
if(offset.x !== point.x || offset.y !== point.y) {
offset = point;
}
}
// 因频繁修改 width/height 会造成大量的内存消耗改为scale
// e.instance.imageStyles = {
// width: img.width + 'px',
// height: img.height + 'px',
// transform: 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + ox) + 'px) rotate(' + rotate +'deg)'
// };
var ox = (img.width - img.oldWidth) / 2;
var oy = (img.height - img.oldHeight) / 2;
// e.instance.imageStyles = {
// width: img.oldWidth + 'px',
// height: img.oldHeight + 'px',
// transform: 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px) rotate(' + rotate +'deg) scale(' + scale + ')'
// };
setStyle(e.instance, 'imageStyles', {
width: img.oldWidth + 'px',
height: img.oldHeight + 'px',
transform: (img.gpu ? 'translateZ(0) ' : '') + 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px' + ') rotate(' + rotate +'deg) scale(' + scale + ')'
});
callMethod(e.instance, 'dataChange', {
width: img.width,
height: img.height,
x: offset.x,
y: offset.y,
rotate: rotate
});
};
/**
* 变更裁剪区域布局信息
* @param {Object} e 布局信息
*/
function changeAreaRect(e) {
// console.log('changeAreaRect', e)
// 变更蒙版样式
setStyle(e.instance, 'maskStylesList', [
{
left: 0,
width: (area.left + areaOffset.left) + 'px',
top: 0,
bottom: 0,
'z-index': area.zIndex + 2
},
{
left: (area.right + areaOffset.right) + 'px',
right: 0,
top: 0,
bottom: 0,
'z-index': area.zIndex + 2
},
{
left: (area.left + areaOffset.left) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
top: 0,
height: (area.top + areaOffset.top) + 'px',
'z-index': area.zIndex + 2
},
{
left: (area.left + areaOffset.left) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
top: (area.bottom + areaOffset.bottom) + 'px',
// height: (area.top - areaOffset.bottom + sys.offsetBottom) + 'px',
bottom: 0,
'z-index': area.zIndex + 2
}
]);
// 变更边框样式
if(area.showBorder) {
setStyle(e.instance, 'borderStyles', {
left: (area.left + areaOffset.left) + 'px',
top: (area.top + areaOffset.top) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 3
});
}
// 变更参考线样式
if(area.showGrid) {
setStyle(e.instance, 'gridStylesList', [
{
'border-width': '1px 0 0 0',
left: (area.left + areaOffset.left) + 'px',
right: (area.right + areaOffset.right) + 'px',
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) / 3 - 0.5) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '1px 0 0 0',
left: (area.left + areaOffset.left) + 'px',
right: (area.right + areaOffset.right) + 'px',
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) * 2 / 3 - 0.5) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 1px 0 0',
top: (area.top + areaOffset.top) + 'px',
bottom: (area.bottom + areaOffset.bottom) + 'px',
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) / 3 - 0.5) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 1px 0 0',
top: (area.top + areaOffset.top) + 'px',
bottom: (area.bottom + areaOffset.bottom) + 'px',
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) * 2 / 3 - 0.5) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 3
}
]);
}
// 变更四个伸缩角样式
if(area.showAngle) {
setStyle(e.instance, 'angleStylesList', [
{
'border-width': area.angleBorderWidth + 'px 0 0 ' + area.angleBorderWidth + 'px',
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0 0',
left: (area.right + areaOffset.right - area.angleSize) + 'px',
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px',
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0',
left: (area.right + areaOffset.right - area.angleSize) + 'px',
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
'z-index': area.zIndex + 3
}
]);
}
// 变更圆角样式
if(area.radius > 0) {
var radius = area.radius;
if(area.width === area.height && area.radius >= area.width / 2) { // 圆形
radius = (area.width / 2);
} else { // 圆角矩形
if(area.width !== area.height) { // 限制圆角半径不能超过短边的一半
radius = Math.min(area.width / 2, area.height / 2, radius);
}
}
setStyle(e.instance, 'circleBoxStyles', {
left: (area.left + areaOffset.left) + 'px',
top: (area.top + areaOffset.top) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 2
});
setStyle(e.instance, 'circleStyles', {
'box-shadow': '0 0 0 ' + Math.max(area.width, area.height) + 'px rgba(51, 51, 51, 0.8)',
'border-radius': radius + 'px'
});
}
};
/**
* 缩放图片
* @param {Object} e 布局信息
*/
function scaleImage(e) {
// console.log('scaleImage', e)
var last = scale;
scale = Math.min(Math.max(e.scale + scale, minScale), img.maxScale);
if(last !== scale) {
img.width = num(img.oldWidth * scale);
img.height = num(img.oldHeight * scale);
// 参考问题有一个长4000px、宽4000px的四方形ABCDA点的坐标固定在(-2000,-2000)
// 该四边形上有一个点E坐标为(-100,-300)将该四方形复制一份并缩小到90%后,
// 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合
// 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变
// 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可
e.x = num((e.x - offset.x) * (1 - scale / last));
e.y = num((e.y - offset.y) * (1 - scale / last));
changeImageRect(e);
return true;
}
return false;
};
/**
* 获取触摸点在哪个角
* @param {number} x 触摸点x轴坐标
* @param {number} y 触摸点y轴坐标
* @return {number} 角的位置0=1=左上2=右上3=左下4=右下
*/
function getToucheAngle(x, y) {
// console.log('getToucheAngle', x, y, JSON.stringify(area))
var o = area.angleBorderWidth; // 需扩大触发范围则把 o 值加大即可
var oy = sys.navigation ? 0 : sys.windowTop;
if(y >= area.top - o + oy && y <= area.top + area.angleSize + o + oy) {
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
return 1; // 左上角
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
return 2; // 右上角
}
} else if(y >= area.bottom - area.angleSize - o + oy && y <= area.bottom + o + oy) {
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
return 3; // 左下角
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
return 4; // 右下角
}
}
return 0; // 无触摸到角
};
/**
* 重置数据
*/
function resetData() {
offset = { x: 0, y: 0 };
scale = 1;
minScale = img.minScale;
rotate = 0;
};
function getTouchs(touches) {
var result = [];
var len = touches ? touches.length : 0
for (var i = 0; i < len; i++) {
result[i] = {
pageX: touches[i].pageX,
// h5无标题栏时窗口顶部距离仍为标题栏高度且触摸点y轴坐标还是有标题栏的值即减去标题栏高度的值
pageY: touches[i].pageY + sys.windowTop
};
}
return result;
};
var mouseEvent = false;
export default {
data() {
return {
imageStyles: {},
maskStylesList: [{}, {}, {}, {}],
borderStyles: {},
gridStylesList: [{}, {}, {}, {}],
angleStylesList: [{}, {}, {}, {}],
circleBoxStyles: {},
circleStyles: {}
}
},
created() {
// 监听 PC 端鼠标滚轮
// #ifdef H5
platform === 'H5' && window.addEventListener('mousewheel', async (e) => {
var touchs = getTouchs([e])
img.src && scaleImage({
instance: await this.getInstance(),
check: true,
// 鼠标向上滚动时deltaY 固定 -100鼠标向下滚动时deltaY 固定 100
scale: e.deltaY > 0 ? -0.05 : 0.05,
x: touchs[0].pageX,
y: touchs[0].pageY
});
});
// #endif
},
// #ifdef H5
mounted() {
platform === 'H5' && this.initH5Events();
},
// #endif
setPlatform(p) {
platform = p;
},
methods: {
// #ifdef H5
getTouchEvent(e) {
e.touches = [
{ pageX: e.pageX, pageY: e.pageY }
];
return e;
},
initH5Events() {
const preview = document.getElementById('pic-preview');
preview?.addEventListener('mousedown', (e, ev) => {
mouseEvent = true;
this.touchstart(this.getTouchEvent(e));
});
preview?.addEventListener('mousemove', (e) => {
if (!mouseEvent) return;
this.touchmove(this.getTouchEvent(e));
});
preview?.addEventListener('mouseup', (e) => {
mouseEvent = false;
this.touchend(this.getTouchEvent(e))
});
preview?.addEventListener('mouseleave', (e) => {
mouseEvent = false;
this.touchend(this.getTouchEvent(e))
});
},
// #endif
async getInstance() {
// #ifdef APP-PLUS
if(platform === 'APP')
return this.$ownerInstance
? Promise.resolve(this.$ownerInstance)
: new Promise((resolve) => {
setTimeout(async () => {
resolve(await this.getInstance());
});
});
// #endif
// #ifdef H5
if(platform === 'H5')
return Promise.resolve(this);
// #endif
},
/**
* 初始化观察数据变更
* @param {Object} newVal 新数据
* @param {Object} oldVal 旧数据
* @param {Object} o 组件实例对象
*/
initObserver: async function(newVal, oldVal, o, i) {
// console.log('initObserver', newVal, oldVal, o, i)
if(newVal && (!img.src || timestamp !== newVal.timestamp)) {
timestamp = newVal.timestamp;
img = newVal.img;
sys = newVal.sys;
area = newVal.area;
minScale = img.minScale;
resetData();
const instance = await this.getInstance()
img.src && changeImageRect({
instance,
x: (sys.windowWidth - img.width) / 2,
y: (sys.windowHeight + sys.windowTop - sys.offsetBottom - img.height) / 2
});
changeAreaRect({
instance
});
}
},
/**
* 鼠标滚轮滚动
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
mousewheel: function(e, o) {
// h5平台 wheel 事件无法判断滚轮滑动方向,需使用 mousewheel
},
/**
* 触摸开始
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
touchstart: function(e, o) {
if(!img.src) return;
touches = getTouchs(e.touches);
activeAngle = area.showAngle ? getToucheAngle(touches[0].pageX, touches[0].pageY) : 0;
if(touches.length === 1 && activeAngle !== 0) {
touchType = 'stretch'; // 伸缩裁剪区域
} else {
touchType = '';
}
// console.log('touchstart', e, activeAngle)
},
/**
* 触摸移动
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
touchmove: async function(e, o) {
if(!img.src) return;
// console.log('touchmove', e, o)
e.touches = getTouchs(e.touches);
if(touchType === 'stretch') { // 触摸四个角进行拉伸
var point = e.touches[0];
var start = touches[0];
var x = point.pageX - start.pageX;
var y = point.pageY - start.pageY;
if(x !== 0 || y !== 0) {
var maxX = num(area.width * (1 - area.minScale));
var maxY = num(area.height * (1 - area.minScale));
// console.log(x, y, maxX, maxY, offset, area)
touches[0] = point;
var r = rotate / 90 % 2;
var m = r === 1 ? num((img.height - img.width) / 2) : 0; // 宽高差值一半
var xCompare = r === 1 ? lessThanByFault(img.height, area.width) : lessThanByFault(img.width, area.width);
var yCompare = r === 1 ? lessThanByFault(img.width, area.height) : lessThanByFault(img.height, area.height)
var isInclude = xCompare && yCompare;
var isIntersect = area.checkRange && (xCompare || yCompare); // 图片是否包含在裁剪区域内
var isReverse = !isInclude || num((offset.x - area.left) / area.width) <= num((offset.y - area.top) / area.height) || (area.width > area.height && img.width < img.height && r === 1);
switch(activeAngle) {
case 1: // 左上角
x = num(x + areaOffset.left);
y = num(y + areaOffset.top);
if(x >= 0 && y >= 0) { // 有效滑动
var t = num(offset.y + m - area.top);
var l = num(offset.x - m - area.left);
// && (offset.x + img.width < area.right || offset.y + img.height < area.bottom)
var max = isIntersect && ((l >= 0) || (t >= 0))
? minimum(t, l)
: false;
if(x > y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(x > maxX) x = maxX;
y = num(x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(y > maxY) y = maxY;
x = num(y * area.width / area.height);
}
areaOffset.left = x;
areaOffset.top = y;
}
break;
case 2: // 右上角
x = num(x + areaOffset.right);
y = num(y + areaOffset.top);
if(x <= 0 && y >= 0) { // 有效滑动
var w = (r === 1 ? img.height : img.width);
var t = num(offset.y + m - area.top);
var l = num(area.right + m - offset.x - w);
var max = isIntersect && ((t >= 0) || (l >= 0))
? minimum(t, l)
: false;
if(-x > y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(-x > maxX) x = -maxX;
y = num(-x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(y > maxY) y = maxY;
x = num(-y * area.width / area.height);
}
areaOffset.right = x;
areaOffset.top = y;
}
break;
case 3: // 左下角
x += num(x + areaOffset.left);
y += num(y + areaOffset.bottom);
if(x >= 0 && y <= 0) { // 有效滑动
var w = (r === 1 ? img.width : img.height);
var t = num(area.bottom - m - offset.y - w);
var l = num(offset.x - m - area.left);
var max = isIntersect && ((l >= 0) || (t >= 0))
? minimum(t, l)
: false;
if(x > -y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(x > maxX) x = maxX;
y = num(-x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(-y > maxY) y = -maxY;
x = num(-y * area.width / area.height);
}
areaOffset.left = x;
areaOffset.bottom = y;
}
break;
case 4: // 右下角
x = num(x + areaOffset.right);
y = num(y + areaOffset.bottom);
if(x <= 0 && y <= 0) { // 有效滑动
var w = (r === 1 ? img.height : img.width);
var h = (r === 1 ? img.width : img.height);
var t = num(area.bottom - offset.y - h - m);
var l = num(area.right + m - offset.x - w);
var max = isIntersect && ((l >= 0) || (t >= 0))
? minimum(t, l)
: false;
if(-x > -y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(-x > maxX) x = -maxX;
y = num(x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(-y > maxY) y = -maxY;
x = num(y * area.width / area.height);
}
areaOffset.right = x;
areaOffset.bottom = y;
}
break;
}
// console.log(x, y, JSON.stringify(areaOffset))
changeAreaRect({
instance: await this.getInstance(),
});
// this.draw();
}
} else if (e.touches.length == 2) { // 双点触摸缩放
var start = getDistanceByTouches(touches);
var end = getDistanceByTouches(e.touches);
scaleImage({
instance: await this.getInstance(),
check: !area.bounce,
scale: (end.c - start.c) / 100,
x: end.x,
y: end.y
});
touchType = 'scale';
} else if(touchType === 'scale') {// 从双点触摸变成单点触摸 / 从缩放变成拖动
touchType = 'move';
} else {
changeImageRect({
instance: await this.getInstance(),
check: !area.bounce,
x: e.touches[0].pageX - touches[0].pageX,
y: e.touches[0].pageY - touches[0].pageY
});
touchType = 'move';
}
touches = e.touches;
},
/**
* 触摸结束
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
touchend: async function(e, o) {
if(!img.src) return;
if(touchType === 'stretch') { // 拉伸裁剪区域的四个角缩放
// 裁剪区域宽度被缩放到多少
var left = areaOffset.left;
var right = areaOffset.right;
var top = areaOffset.top;
var bottom = areaOffset.bottom;
var w = area.width + right - left;
var h = area.height + bottom - top;
// 图像放大倍数
var p = scale * (area.width / w) - scale;
// 复原裁剪区域
areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
changeAreaRect({
instance: await this.getInstance(),
});
scaleImage({
instance: await this.getInstance(),
scale: p,
x: area.left + left + (1 === activeAngle || 3 === activeAngle ? w : 0),
y: area.top + top + (1 === activeAngle || 2 === activeAngle ? h : 0)
});
} else if (area.bounce) { // 检查边界并矫正,实现拖动到边界时有回弹效果
changeImageRect({
instance: await this.getInstance(),
check: true
});
}
},
/**
* 顺时针翻转图片90°
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
rotateImage: async function(r) {
rotate = (rotate + (r || 90)) % 360;
if(img.minScale >= 1 && area.checkRange) {
// 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域
minScale = 1;
if(img.width < area.height) {
minScale = area.height / img.oldWidth;
} else if(img.height < area.width) {
minScale = area.width / img.oldHeight;
}
if(minScale !== 1) {
scaleImage({
instance: await this.getInstance(),
scale: minScale - scale,
x: sys.windowWidth / 2,
y: (sys.windowHeight - sys.offsetBottom) / 2
});
}
}
// 由于拖动画布后会导致图片位置偏移,翻转时的旋转中心点需是图片区域+偏移区域的中心点
// 翻转x轴中心点 = (超出裁剪区域右侧的图片宽度 - 超出裁剪区域左侧的图片宽度) / 2
// 翻转y轴中心点 = (超出裁剪区域下方的图片宽度 - 超出裁剪区域上方的图片宽度) / 2
var ox = ((offset.x + img.width - area.right) - (area.left - offset.x)) / 2;
var oy = ((offset.y + img.height - area.bottom) - (area.top - offset.y)) / 2;
changeImageRect({
instance: await this.getInstance(),
check: true,
x: -ox - oy,
y: -oy + ox
});
},
rotateImage90: function() {
this.rotateImage(90)
},
rotateImage270: function() {
this.rotateImage(270)
},
}
}

View File

@ -0,0 +1,743 @@
<template>
<view class="image-cropper" :style="{ zIndex }" @wheel="cropper.mousewheel">
<canvas v-if="use2d" type="2d" id="imgCanvas" class="img-canvas" :style="{
width: `${canvansWidth}px`,
height: `${canvansHeight}px`
}"></canvas>
<canvas v-else id="imgCanvas" canvas-id="imgCanvas" class="img-canvas" :style="{
width: `${canvansWidth}px`,
height: `${canvansHeight}px`
}"></canvas>
<view id="pic-preview" class="pic-preview" :change:init="cropper.initObserver" :init="initData" @touchstart="cropper.touchstart" @touchmove="cropper.touchmove" @touchend="cropper.touchend">
<image v-if="imgSrc" id="crop-image" class="crop-image" :style="cropper.imageStyles" :src="imgSrc" webp></image>
<view v-for="(item, index) in maskList" :key="item.id" :id="item.id" class="crop-mask-block" :style="cropper.maskStylesList[index]"></view>
<view v-if="showBorder" id="crop-border" class="crop-border" :style="cropper.borderStyles"></view>
<view v-if="radius > 0" id="crop-circle-box" class="crop-circle-box" :style="cropper.circleBoxStyles">
<view class="crop-circle" id="crop-circle" :style="cropper.circleStyles"></view>
</view>
<block v-if="showGrid">
<view v-for="(item, index) in gridList" :key="item.id" :id="item.id" class="crop-grid" :style="cropper.gridStylesList[index]"></view>
</block>
<block v-if="showAngle">
<view v-for="(item, index) in angleList" :key="item.id" :id="item.id" class="crop-angle" :style="cropper.angleStylesList[index]">
<view :style="[{
width: `${angleSize}px`,
height: `${angleSize}px`
}]"></view>
</view>
</block>
</view>
<slot />
<view class="fixed-bottom safe-area-inset-bottom" :style="{ zIndex: initData.area.zIndex + 99 }">
<view v-if="(rotatable || reverseRotatable) && !!imgSrc" class="action-bar">
<view v-if="reverseRotatable" class="rotate-icon" @click="cropper.rotateImage270"></view>
<view v-if="rotatable" class="rotate-icon is-reverse" @click="cropper.rotateImage90"></view>
</view>
<view v-if="!choosable" class="choose-btn" @click="cropClick">确定</view>
<block v-else-if="!!imgSrc">
<view class="rechoose" @click="chooseImage">重选</view>
<button class="button" size="mini" @click="cropClick">确定</button>
</block>
<view v-else class="choose-btn" @click="chooseImage">选择图片</view>
</view>
</view>
</template>
<!-- #ifdef APP-VUE -->
<script module="cropper" lang="renderjs">
import cropper from './qf-image-cropper.render.js';
// vue3 app renderjs
cropper.setPlatform('APP');
export default {
mixins: [ cropper ]
}
</script>
<!-- #endif -->
<!-- #ifdef H5 -->
<script module="cropper" lang="renderjs">
import cropper from './qf-image-cropper.render.js';
export default {
mixins: [ cropper ]
}
</script>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN || MP-QQ -->
<script module="cropper" lang="wxs" src="./qf-image-cropper.wxs"></script>
<!-- #endif -->
<script>
/** 裁剪区域最大宽高所占屏幕宽度百分比 */
const AREA_SIZE = 75;
/** 图片默认宽高 */
const IMG_SIZE = 300;
export default {
name:"qf-image-cropper",
// #ifdef MP-WEIXIN
options: {
// 使 class
styleIsolation: "isolated"
},
// #endif
props: {
/** 图片资源地址 */
src: {
type: String,
default: ''
},
/** 裁剪宽度有些平台或设备对于canvas的尺寸有限制过大可能会导致无法正常绘制 */
width: {
type: Number,
default: IMG_SIZE
},
/** 裁剪高度有些平台或设备对于canvas的尺寸有限制过大可能会导致无法正常绘制 */
height: {
type: Number,
default: IMG_SIZE
},
/** 是否绘制裁剪区域边框 */
showBorder: {
type: Boolean,
default: true
},
/** 是否绘制裁剪区域网格参考线 */
showGrid: {
type: Boolean,
default: true
},
/** 是否展示四个支持伸缩的角 */
showAngle: {
type: Boolean,
default: true
},
/** 裁剪区域最小缩放倍数 */
areaScale: {
type: Number,
default: 0.3
},
/** 图片最小缩放倍数 */
minScale: {
type: Number,
default: 1
},
/** 图片最大缩放倍数 */
maxScale: {
type: Number,
default: 5
},
/** 检查图片位置是否超出裁剪边界,如果超出则会矫正位置 */
checkRange: {
type: Boolean,
default: true
},
/** 生成图片背景色:如果裁剪区域没有完全包含在图片中时,不设置该属性生成图片存在一定的透明块 */
backgroundColor: {
type: String
},
/** 是否有回弹效果:当 checkRange 为 true 时有效,拖动时可以拖出边界,释放时会弹回边界 */
bounce: {
type: Boolean,
default: true
},
/** 是否支持翻转 */
rotatable: {
type: Boolean,
default: true
},
/** 是否支持逆向翻转 */
reverseRotatable: {
type: Boolean,
default: false
},
/** 是否支持从本地选择素材 */
choosable: {
type: Boolean,
default: true
},
/** 是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题 */
gpu: {
type: Boolean,
default: false
},
/** 四个角尺寸单位px */
angleSize: {
type: Number,
default: 20
},
/** 四个角边框宽度单位px */
angleBorderWidth: {
type: Number,
default: 2
},
zIndex: {
type: [Number, String]
},
/** 裁剪图片圆角半径单位px */
radius: {
type: Number,
default: 0
},
/** 生成文件的类型,只支持 'jpg' 或 'png'。默认为 'png' */
fileType: {
type: String,
default: 'png'
},
/**
* 图片从绘制到生成所需时间单位ms
* 微信小程序平台使用 `Canvas 2D` 绘制时有效
* 如绘制大图或出现裁剪图片空白等情况应适当调大该值 `Canvas 2d` 采用同步绘制需自己把控绘制完成时间
*/
delay: {
type: Number,
default: 1000
},
// #ifdef H5
/**
* 页面是否是原生标题栏
* H5平台当 showAngle true 使用插件的页面在 `page.json` 中配置了 "navigationStyle": "custom" 必须将此值设为 false 否则四个可拉伸角的触发位置会有偏差
* 因H5平台的窗口高度是包含标题栏的而屏幕触摸点的坐标是不包含的
*/
navigation: {
type: Boolean,
default: true
}
// #endif
},
emits: ["crop"],
data() {
return {
// id 使 v-for key
maskList: [
{ id: 'crop-mask-block-1' },
{ id: 'crop-mask-block-2' },
{ id: 'crop-mask-block-3' },
{ id: 'crop-mask-block-4' },
],
gridList: [
{ id: 'crop-grid-1' },
{ id: 'crop-grid-2' },
{ id: 'crop-grid-3' },
{ id: 'crop-grid-4' },
],
angleList: [
{ id: 'crop-angle-1' },
{ id: 'crop-angle-2' },
{ id: 'crop-angle-3' },
{ id: 'crop-angle-4' },
],
/** 本地缓存的图片路径 */
imgSrc: '',
/** 图片的裁剪宽度 */
imgWidth: IMG_SIZE,
/** 图片的裁剪高度 */
imgHeight: IMG_SIZE,
/** 裁剪区域最大宽度所占屏幕宽度百分比 */
widthPercent: AREA_SIZE,
/** 裁剪区域最大高度所占屏幕宽度百分比 */
heightPercent: AREA_SIZE,
/** 裁剪区域布局信息 */
area: {},
/** 未被缩放过的图片宽 */
oldWidth: 0,
/** 未被缩放过的图片高 */
oldHeight: 0,
/** 系统信息 */
sys: uni.getSystemInfoSync(),
scaleWidth: 0,
scaleHeight: 0,
rotate: 0,
offsetX: 0,
offsetY: 0,
use2d: false,
canvansWidth: 0,
canvansHeight: 0,
// imageStyles: {},
// maskStylesList: [{}, {}, {}, {}],
// borderStyles: {},
// gridStylesList: [{}, {}, {}, {}],
// angleStylesList: [{}, {}, {}, {}],
// circleBoxStyles: {},
// circleStyles: {},
}
},
computed: {
initData() {
// console.log('initData')
return {
timestamp: new Date().getTime(),
area: {
...this.area,
bounce: this.bounce,
showBorder: this.showBorder,
showGrid: this.showGrid,
showAngle: this.showAngle,
angleSize: this.angleSize,
angleBorderWidth: this.angleBorderWidth,
minScale: this.areaScale,
widthPercent: this.widthPercent,
heightPercent: this.heightPercent,
radius: this.radius,
checkRange: this.checkRange,
zIndex: +this.zIndex || 0,
},
sys: this.sys,
img: {
minScale: this.minScale,
maxScale: this.maxScale,
src: this.imgSrc,
width: this.oldWidth,
height: this.oldHeight,
oldWidth: this.oldWidth,
oldHeight: this.oldHeight,
gpu: this.gpu,
}
}
},
imgProps() {
return {
width: this.width,
height: this.height,
src: this.src,
}
}
},
watch: {
imgProps: {
handler(val, oldVal) {
//
this.imgWidth = Number(val.width) || IMG_SIZE;
this.imgHeight = Number(val.height) || IMG_SIZE;
let use2d = true;
// #ifndef MP-WEIXIN
use2d = false;
// #endif
// if(use2d && (this.imgWidth > 1365 || this.imgHeight > 1365)) {
// use2d = false;
// }
let canvansWidth = this.imgWidth;
let canvansHeight = this.imgHeight;
let size = Math.max(canvansWidth, canvansHeight)
let scalc = 1;
if(size > 1365) {
scalc = 1365 / size;
}
this.canvansWidth = canvansWidth * scalc;
this.canvansHeight = canvansHeight * scalc;
this.use2d = use2d;
this.initArea();
const src = val.src || this.imgSrc;
src && this.initImage(src, oldVal === undefined);
},
immediate: true
},
},
methods: {
/** 提供给wxs调用用来接收图片变更数据 */
dataChange(e) {
// console.log('dataChange', e)
this.scaleWidth = e.width;
this.scaleHeight = e.height;
this.rotate = e.rotate;
this.offsetX = e.x;
this.offsetY = e.y;
},
/** 初始化裁剪区域布局信息 */
initArea() {
// = +
this.sys.offsetBottom = uni.upx2px(100) + this.sys.safeAreaInsets.bottom;
// #ifndef H5
this.sys.windowTop = 0;
this.sys.navigation = true;
// #endif
// #ifdef H5
// h5
this.sys.windowTop = this.sys.windowTop || 44;
this.sys.navigation = this.navigation;
// #endif
let wp = this.widthPercent;
let hp = this.heightPercent;
if (this.imgWidth > this.imgHeight) {
hp = hp * this.imgHeight / this.imgWidth;
} else if (this.imgWidth < this.imgHeight) {
wp = wp * this.imgWidth / this.imgHeight;
}
const size = this.sys.windowWidth > this.sys.windowHeight ? this.sys.windowHeight : this.sys.windowWidth;
const width = size * wp / 100;
const height = size * hp / 100;
const left = (this.sys.windowWidth - width) / 2;
const right = left + width;
const top = (this.sys.windowHeight + this.sys.windowTop - this.sys.offsetBottom - height) / 2;
const bottom = this.sys.windowHeight + this.sys.windowTop - this.sys.offsetBottom - top;
this.area = { width, height, left, right, top, bottom };
this.scaleWidth = width;
this.scaleHeight = height;
},
/** 从本地选取图片 */
chooseImage(options) {
// #ifdef MP-WEIXIN || MP-JD
if(uni.chooseMedia) {
uni.chooseMedia({
...options,
count: 1,
mediaType: ['image'],
success: (res) => {
this.resetData();
this.initImage(res.tempFiles[0].tempFilePath);
}
});
return;
}
// #endif
uni.chooseImage({
...options,
count: 1,
success: (res) => {
this.resetData();
this.initImage(res.tempFiles[0].path);
}
});
},
/** 重置数据 */
resetData() {
this.imgSrc = '';
this.rotate = 0;
this.offsetX = 0;
this.offsetY = 0;
this.initArea();
},
/**
* 初始化图片信息
* @param {String} url 图片链接
*/
initImage(url, isFirst) {
uni.getImageInfo({
src: url,
success: async (res) => {
if (isFirst && this.src === url) await (new Promise((resolve) => setTimeout(resolve, 50)));
this.imgSrc = res.path;
let scale = res.width / res.height;
let areaScale = this.area.width / this.area.height;
if (scale > 1) { //
if (scale >= areaScale) { //
this.scaleWidth = (this.scaleHeight / res.height) * this.scaleWidth * (res.width / this.scaleWidth);
} else { //
this.scaleHeight = res.height * this.scaleWidth / res.width;
}
} else { //
if (scale <= areaScale) { //
this.scaleHeight = (this.scaleWidth / res.width) * this.scaleHeight / (this.scaleHeight / res.height);
} else { //
this.scaleWidth = res.width * this.scaleHeight / res.height;
}
}
//
this.oldWidth = +this.scaleWidth.toFixed(2);
this.oldHeight = +this.scaleHeight.toFixed(2);
},
fail: (err) => {
console.error(err)
}
});
},
/**
* 剪切图片圆角
* @param {Object} ctx canvas 的绘图上下文对象
* @param {Number} radius 圆角半径
* @param {Number} scale 生成图片的实际尺寸与截取区域比
* @param {Function} drawImage 执行剪切时所调用的绘图方法入参为是否执行了剪切
*/
drawClipImage(ctx, radius, scale, drawImage) {
if(radius > 0) {
ctx.save();
ctx.beginPath();
const w = this.canvansWidth;
const h = this.canvansHeight;
if(w === h && radius >= w / 2) { //
ctx.arc(w / 2, h / 2, w / 2, 0, 2 * Math.PI);
} else { //
if(w !== h) { //
radius = Math.min(w / 2, h / 2, radius);
// radius = Math.min(Math.max(w, h) / 2, radius);
}
ctx.moveTo(radius, 0);
ctx.arcTo(w, 0, w, h, radius);
ctx.arcTo(w, h, 0, h, radius);
ctx.arcTo(0, h, 0, 0, radius);
ctx.arcTo(0, 0, w, 0, radius);
ctx.closePath();
}
ctx.clip();
drawImage && drawImage(true);
ctx.restore();
} else {
drawImage && drawImage(false);
}
},
/**
* 旋转图片
* @param {Object} ctx canvas 的绘图上下文对象
* @param {Number} rotate 旋转角度
* @param {Number} scale 生成图片的实际尺寸与截取区域比
*/
drawRotateImage(ctx, rotate, scale) {
if(rotate !== 0) {
// 1.
const x = this.scaleWidth * scale / 2;
const y = this.scaleHeight * scale / 2;
ctx.translate(x, y);
// 2.
ctx.rotate(rotate * Math.PI / 180);
// 3.
ctx.translate(-x, -y);
}
},
drawImage(ctx, image, callback) {
//
const scale = this.canvansWidth / this.area.width;
if(this.backgroundColor) {
if(ctx.setFillStyle) ctx.setFillStyle(this.backgroundColor);
else ctx.fillStyle = this.backgroundColor;
ctx.fillRect(0, 0, this.canvansWidth, this.canvansHeight);
}
this.drawClipImage(ctx, this.radius, scale, () => {
this.drawRotateImage(ctx, this.rotate, scale);
const r = this.rotate / 90;
ctx.drawImage(
image,
[
(this.offsetX - this.area.left),
(this.offsetY - this.area.top),
-(this.offsetX - this.area.left),
-(this.offsetY - this.area.top)
][r] * scale,
[
(this.offsetY - this.area.top),
-(this.offsetX - this.area.left),
-(this.offsetY - this.area.top),
(this.offsetX - this.area.left)
][r] * scale,
this.scaleWidth * scale,
this.scaleHeight * scale
);
});
},
/**
* 绘图
* @param {Object} canvas
* @param {Object} ctx canvas 的绘图上下文对象
* @param {String} src 图片路径
* @param {Function} callback 开始绘制时回调
*/
draw2DImage(canvas, ctx, src, callback) {
// console.log('draw2DImage', canvas, ctx, src, callback)
if(canvas) {
const image = canvas.createImage();
image.onload = () => {
this.drawImage(ctx, image);
// ````
callback && setTimeout(callback, this.delay);
};
image.onerror = (err) => {
console.error(err)
uni.hideLoading();
};
image.src = src;
} else {
this.drawImage(ctx, src);
setTimeout(() => {
ctx.draw(false, callback);
}, 200);
}
},
/**
* 画布转图片到本地缓存
* @param {Object} canvas
* @param {String} canvasId
*/
canvasToTempFilePath(canvas, canvasId) {
// console.log('canvasToTempFilePath', canvas, canvasId)
uni.canvasToTempFilePath({
canvas,
canvasId,
x: 0,
y: 0,
width: this.canvansWidth,
height: this.canvansHeight,
destWidth: this.imgWidth, //
destHeight: this.imgHeight, //
fileType: this.fileType, // png
success: (res) => {
//
this.handleImage(res.tempFilePath);
},
fail: (err) => {
uni.hideLoading();
uni.showToast({ title: '裁剪失败,生成图片异常!', icon: 'none' });
}
}, this);
},
/** 确认裁剪 */
cropClick() {
uni.showLoading({ title: '裁剪中...', mask: true });
if(!this.use2d) {
const ctx = uni.createCanvasContext('imgCanvas', this);
ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight);
this.draw2DImage(null, ctx, this.imgSrc, () => {
this.canvasToTempFilePath(null, 'imgCanvas');
});
return;
}
// #ifdef MP-WEIXIN
const query = uni.createSelectorQuery().in(this);
query.select('#imgCanvas')
.fields({ node: true, size: true })
.exec((res) => {
const canvas = res[0].node;
const dpr = uni.getSystemInfoSync().pixelRatio;
canvas.width = res[0].width * dpr;
canvas.height = res[0].height * dpr;
const ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr);
ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight);
this.draw2DImage(canvas, ctx, this.imgSrc, () => {
this.canvasToTempFilePath(canvas);
});
});
// #endif
},
handleImage(tempFilePath){
// H5tempFilePath base64
// console.log(tempFilePath)
uni.hideLoading();
this.$emit('crop', { tempFilePath });
}
}
}
</script>
<style lang="scss" scoped>
.image-cropper {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
overflow: hidden;
display: flex;
flex-direction: column;
background-color: #000;
.img-canvas {
position: absolute !important;
transform: translateX(-100%);
}
.pic-preview {
width: 100%;
flex: 1;
position: relative;
.crop-mask-block {
background-color: rgba(51, 51, 51, 0.8);
z-index: 2;
position: fixed;
box-sizing: border-box;
pointer-events: none;
}
.crop-circle-box {
position: fixed;
box-sizing: border-box;
z-index: 2;
pointer-events: none;
overflow: hidden;
.crop-circle {
width: 100%;
height: 100%;
}
}
.crop-image {
padding: 0 !important;
margin: 0 !important;
border-radius: 0 !important;
display: block !important;
backface-visibility: hidden;
}
.crop-border {
position: fixed;
border: 1px solid #fff;
box-sizing: border-box;
z-index: 3;
pointer-events: none;
}
.crop-grid {
position: fixed;
z-index: 3;
border-style: dashed;
border-color: #fff;
pointer-events: none;
opacity: 0.5;
}
.crop-angle {
position: fixed;
z-index: 3;
border-style: solid;
border-color: #fff;
pointer-events: none;
}
}
.fixed-bottom {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 99;
display: flex;
flex-direction: row;
background-color: $uni-bg-color-grey;
.action-bar {
position: absolute;
top: -90rpx;
left: 10rpx;
display: flex;
.rotate-icon {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAABCFJREFUaEPtml3IpVMUx3//ko/ChTIyiGFSMyhllI8bc4F85yuNC2FCqLmQC1+FZORiEkUMNW7UjKjJULgxV+NzSkxDhEkZgwsyigv119J63p7zvOc8z37OmXdOb51dz82711r7/99r7bXXXucVi3xokeNnRqCvB20fDmwAlgK/5bcD+FTSr33tHXQP2H4MeHQE0A+B5yRtLiUyDQJrgVc6AAaBpyV93kXkoBMIQLbfBS5NcK8BRwDXNcD+AdwnaVMbiWkRCPBBohpxHuK7M7865sclRdgNHVMhkF6IMIpwirFEUhzo8M7lwIvASTXEqyVtH8ZgagQSbOzsDknv18HZXpHn5IL8+94IOUm7miSmSqAttjPdbgGuTrnNktYsGgLpoYuAD2qg1zRTbG8P2D4SOC6/Q7vSHPALsE/S7wWy80RsPw/ckxMfSTq/LtRJwPbxwF3ASiCUTxwHCPAnEBfVF8AWSTtL7Ng+LfWOTfmlkn6udFsJ5K15R6a4kvX6yGyUFBvTOWzHXXFzCt4g6c1OArYj9iIGh43YgR+BvztXh1PSa4cMkd0jaVmXDduPAE+k3HpJD7cSGFKvfAc8FQUX8IOk/V2L1udtB/hTgdOBW4Aba/M7Ja1qs2f7euCNlHlZUlx4/495IWQ7Jl+qGbxX0gt9AHfJ2o6zFBVoNVrDKe+F3Sm8VdK1bQQ+A85JgXckXdkFaJx527cC9TpnVdvBtl3h2iapuhsGPdBw1b9xnUvaNw7AEh3bnwDnpuwGSfeP0rN9NvAMELXRXFkxEEK2nwQeSiOtRVQJwC4Z29cAW1Nuu6TVXTrN+SaBt4ErUug2Sa/2NdhH3vZy4NvU2S/p6D768w5xI3WOrAD7LtISFpGdIhVXKfaYvjd20wP13L9M0p4DBbaFRKToSLExVkr6qs+aIwlI6iwz+izUQqC+ab29PiMwqRcmPXczD8w8MFj1zg7xXEqbpdHCw7FgWSjafZL+KcQxtpjteCeflwYulFR/J3TabSslVkj6utPChAK2f6q9uZdLitKieLQRuExSvX9ZbLRUMFs09efpUZL+KtUfVo1GW/umNHC3pOhRLtiwfSbwZS6wV9IJfRdreuBBYH0a2STp9r4G+8jbXgc8mzoDT8VSO00ClwDv1ZR7XyylC4ec7ejaLUmdsV6Aw7oSbwFXpdFdks7qA6pU1na0aR6owgeIR/1cx63UzjAC0YXYVjMQHlkn6ZtSo21ytuPZGKFagQ/xsXZ/3iGuFrYdjafXG0DiQMeBi47c9/GV3BO247UV38n5o0UAP6xmu7jFOGxjRr66On5NPBDOCBsDTapxjHY1dyOcolNXnYlx1himE53p2PmNkxosevfavhg4Izt2k7TXPwZ2S6p6QZPin/2rwcQ7OKmBohCadJGF1P8PG6aaQBKVX/8AAAAASUVORK5CYII=');
background-size: 60% 60%;
background-repeat: no-repeat;
background-position: center;
width: 80rpx;
height: 80rpx;
&.is-reverse {
transform: rotateY(180deg);
}
}
}
.rechoose {
color: $uni-color-primary;
padding: 0 $uni-spacing-row-lg;
line-height: 100rpx;
}
.choose-btn {
color: $uni-color-primary;
text-align: center;
line-height: 100rpx;
flex: 1;
}
.button {
margin: auto $uni-spacing-row-lg auto auto;
background-color: $uni-color-primary;
color: #fff;
}
}
.safe-area-inset-bottom {
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom); // IOS<11.2
padding-bottom: env(safe-area-inset-bottom); // IOS>=11.2
}
}
</style>

View File

@ -0,0 +1,727 @@
/**
* 图片编辑器-手势监听
* 1. wxs 暂不支持 es6 语法
* 2. 支持编译到微信小程序、QQ小程序、app-vue、H5上uni-app 2.2.5及以上版本)
*/
/** 图片偏移量 */
var offset = { x: 0, y: 0 };
/** 图片缩放比例 */
var scale = 1;
/** 图片最小缩放比例 */
var minScale = 1;
/** 图片旋转角度 */
var rotate = 0;
/** 触摸点 */
var touches = [];
/** 图片布局信息 */
var img = {};
/** 系统信息 */
var sys = {};
/** 裁剪区域布局信息 */
var area = {};
/** 触摸行为类型 */
var touchType = '';
/** 操作角的位置 */
var activeAngle = 0;
/** 裁剪区域布局信息偏移量 */
var areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
/** 容错值 */
var fault = 0.000001;
/**
* 获取a、b两数中的最小正数
* @param a
* @param b
*/
function minimum(a, b) {
if (a > 0 && b < 0) return a;
if (a < 0 && b > 0) return b;
if (a > 0 && b > 0) return Math.min(a, b);
return 0;
}
/**
* 在容错访问内获取n近似值
* @param n
*/
function num(n) {
var m = parseFloat((n).toFixed(6));
return m === fault || m === -fault ? 0 : m;
}
/**
* 比较a值在容错值范围内是否等于b值
* @param a
* @param b
*/
function equalsByFault(a, b) {
return Math.abs(a - b) <= fault;
}
/**
* 比较a值在容错值范围内是否小于b值
* @param a
* @param b
*/
function lessThanByFault(a, b) {
var c = a - b;
return c < 0 ? c < -fault : c < fault;
}
/**
* 验证并获取有效最大值
* @param v
* @param max
* @param isInclude
* @param x
* @param y
* @param rate
* @returns
*/
function validMax(v, max, isInclude, x, y, rate) {
if(typeof max === 'number') {
if(isInclude && equalsByFault(max, y)) { // 宽高不等时x轴用y轴值要做等比例转换
var n = num(max * rate);
if (n <= x) return n; // 转化后值在x轴最大值范围内
return x; // 转化后值超出x轴最大值范围则用最大值
}
return max;
}
return v;
}
/**
* 计算两点间距
* @param {Object} touches 触摸点信息
*/
function getDistanceByTouches(touches) {
// 根据勾股定理求两点间距离
var a = touches[1].pageX - touches[0].pageX;
var b = touches[1].pageY - touches[0].pageY;
var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
// 求两点间的中点坐标
// 1. a、b可能为负值
// 2. 在求a、b时如用touches[1]减touches[0]则求中点坐标也得用touches[1]减a/2、b/2
// 3. 同理在求a、b时也可用touches[0]减touches[1]则求中点坐标也得用touches[0]减a/2、b/2
var x = touches[1].pageX - a / 2;
var y = touches[1].pageY - b / 2;
return { c, x, y };
};
/**
* 修正取值
* @param {Object} a
* @param {Object} b
* @param {Object} c
* @param {Object} reverse 是否反向
*/
function correctValue(a, b, c, reverse) {
return num(reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c));
}
/**
* 旋转90°或270°时检查边界限制 x、y 拖动范围,禁止滑出边界
* @param {Object} e 点坐标
* @param {Object} xReverse x是否反向
* @param {Object} yReverse y是否反向
*/
function checkRotateRange(e, xReverse, yReverse) {
var o = num((img.height - img.width) / 2); // 宽高差值一半
return {
x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, xReverse),
y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, yReverse)
};
}
/**
* 检查边界:限制 x、y 拖动范围,禁止滑出边界
* @param {Object} e 点坐标
*/
function checkRange(e) {
var r = rotate / 90 % 2;
if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移
if (area.width === area.height) {
return checkRotateRange(e, img.height < area.height, img.width < area.width);
}
var isInclude = img.height < area.width && img.width < area.height; // 图片是否包含在裁剪区域内
if (img.width < area.height || img.height < area.width) {
if (area.width < area.height && img.width < img.height) {
return isInclude
? checkRotateRange(e, area.width < area.height, area.width < area.height)
: checkRotateRange(e, false, true);
}
if (area.height < area.width && img.height < img.width) {
return isInclude
? checkRotateRange(e, area.height < area.width, area.height < area.width)
: checkRotateRange(e, true, false);
}
}
if (img.height >= area.width && img.width >= area.height) {
return checkRotateRange(e, false, false);
}
if (isInclude) {
return area.height < area.width
? checkRotateRange(e, true, true)
: checkRotateRange(e, area.width < area.height, area.width < area.height);
}
if (img.height < area.width && !img.width < area.height) {
return checkRotateRange(e, true, false);
}
if (!img.height < area.width && img.width < area.height) {
return checkRotateRange(e, false, true);
}
return checkRotateRange(e, img.height < area.height, img.width < area.width);
}
return {
x: correctValue(e.x, -img.width + area.width + area.left, area.left, img.width < area.width),
y: correctValue(e.y, -img.height + area.height + area.top, area.top, img.height < area.height)
};
};
/**
* 变更图片布局信息
* @param {Object} e 布局信息
*/
function changeImageRect(e) {
offset.x += e.x || 0;
offset.y += e.y || 0;
var image = e.instance.selectComponent('.crop-image');
if(e.check && area.checkRange) { // 检查边界
var point = checkRange(offset);
if(offset.x !== point.x || offset.y !== point.y) {
offset = point;
}
}
// image.setStyle({
// width: img.width + 'px',
// height: img.height + 'px',
// transform: 'translate(' + offset.x + 'px, ' + offset.y + 'px) rotate(' + rotate +'deg)'
// });
var ox = (img.width - img.oldWidth) / 2;
var oy = (img.height - img.oldHeight) / 2;
image.setStyle({
width: img.oldWidth + 'px',
height: img.oldHeight + 'px',
transform: (img.gpu ? 'translateZ(0) ' : '') + 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px) rotate(' + rotate +'deg) scale(' + scale + ')'
});
e.instance.callMethod('dataChange', {
width: img.width,
height: img.height,
x: offset.x,
y: offset.y,
rotate: rotate
});
};
/**
* 变更裁剪区域布局信息
* @param {Object} e 布局信息
*/
function changeAreaRect(e) {
// 变更蒙版样式
var masks = e.instance.selectAllComponents('.crop-mask-block');
var maskStyles = [
{
left: 0,
width: (area.left + areaOffset.left) + 'px',
top: 0,
bottom: 0,
'z-index': area.zIndex + 2
},
{
left: (area.right + areaOffset.right) + 'px',
right: 0,
top: 0,
bottom: 0,
'z-index': area.zIndex + 2
},
{
left: (area.left + areaOffset.left) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
top: 0,
height: (area.top + areaOffset.top) + 'px',
'z-index': area.zIndex + 2
},
{
left: (area.left + areaOffset.left) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
top: (area.bottom + areaOffset.bottom) + 'px',
// height: (area.top - areaOffset.bottom + sys.offsetBottom) + 'px',
bottom: 0,
'z-index': area.zIndex + 2
}
];
var len = masks.length;
for (var i = 0; i < len; i++) {
masks[i].setStyle(maskStyles[i]);
}
// 变更边框样式
if(area.showBorder) {
var border = e.instance.selectComponent('.crop-border');
border.setStyle({
left: (area.left + areaOffset.left) + 'px',
top: (area.top + areaOffset.top) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 3
});
}
// 变更参考线样式
if(area.showGrid) {
var grids = e.instance.selectAllComponents('.crop-grid');
var gridStyles = [
{
'border-width': '1px 0 0 0',
left: (area.left + areaOffset.left) + 'px',
right: (area.right + areaOffset.right) + 'px',
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) / 3 - 0.5) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '1px 0 0 0',
left: (area.left + areaOffset.left) + 'px',
right: (area.right + areaOffset.right) + 'px',
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) * 2 / 3 - 0.5) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 1px 0 0',
top: (area.top + areaOffset.top) + 'px',
bottom: (area.bottom + areaOffset.bottom) + 'px',
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) / 3 - 0.5) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 1px 0 0',
top: (area.top + areaOffset.top) + 'px',
bottom: (area.bottom + areaOffset.bottom) + 'px',
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) * 2 / 3 - 0.5) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 3
}
];
var len = grids.length;
for (var i = 0; i < len; i++) {
grids[i].setStyle(gridStyles[i]);
}
}
// 变更四个伸缩角样式
if(area.showAngle) {
var angles = e.instance.selectAllComponents('.crop-angle');
var angleStyles = [
{
'border-width': area.angleBorderWidth + 'px 0 0 ' + area.angleBorderWidth + 'px',
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0 0',
left: (area.right + areaOffset.right - area.angleSize) + 'px',
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px',
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0',
left: (area.right + areaOffset.right - area.angleSize) + 'px',
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
'z-index': area.zIndex + 3
}
];
var len = angles.length;
for (var i = 0; i < len; i++) {
angles[i].setStyle(angleStyles[i]);
}
}
// 变更圆角样式
if(area.radius > 0) {
var circleBox = e.instance.selectComponent('.crop-circle-box');
var circle = e.instance.selectComponent('.crop-circle');
var radius = area.radius;
if(area.width === area.height && area.radius >= area.width / 2) { // 圆形
radius = (area.width / 2);
} else { // 圆角矩形
if(area.width !== area.height) { // 限制圆角半径不能超过短边的一半
radius = Math.min(area.width / 2, area.height / 2, radius);
}
}
circleBox.setStyle({
left: (area.left + areaOffset.left) + 'px',
top: (area.top + areaOffset.top) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 2
});
circle.setStyle({
'box-shadow': '0 0 0 ' + Math.max(area.width, area.height) + 'px rgba(51, 51, 51, 0.8)',
'border-radius': radius + 'px'
});
}
};
/**
* 缩放图片
* @param {Object} e 布局信息
*/
function scaleImage(e) {
var last = scale;
scale = Math.min(Math.max(e.scale + scale, minScale), img.maxScale);
if(last !== scale) {
img.width = num(img.oldWidth * scale);
img.height = num(img.oldHeight * scale);
// 参考问题有一个长4000px、宽4000px的四方形ABCDA点的坐标固定在(-2000,-2000)
// 该四边形上有一个点E坐标为(-100,-300)将该四方形复制一份并缩小到90%后,
// 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合
// 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变
// 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可
e.x = num((e.x - offset.x) * (1 - scale / last));
e.y = num((e.y - offset.y) * (1 - scale / last));
changeImageRect(e);
return true;
}
return false;
};
/**
* 获取触摸点在哪个角
* @param {number} x 触摸点x轴坐标
* @param {number} y 触摸点y轴坐标
* @return {number} 角的位置0=无1=左上2=右上3=左下4=右下;
*/
function getToucheAngle(x, y) {
// console.log('getToucheAngle', x, y, JSON.stringify(area))
var o = area.angleBorderWidth; // 需扩大触发范围则把 o 值加大即可
if(y >= area.top - o && y <= area.top + area.angleSize + o) {
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
return 1; // 左上角
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
return 2; // 右上角
}
} else if(y >= area.bottom - area.angleSize - o && y <= area.bottom + o) {
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
return 3; // 左下角
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
return 4; // 右下角
}
}
return 0; // 无触摸到角
};
/**
* 重置数据
*/
function resetData() {
offset = { x: 0, y: 0 };
scale = 1;
minScale = img.minScale;
rotate = 0;
};
/**
* 顺时针翻转图片90°
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
function rotateImage(e, o, r) {
rotate = (rotate + r) % 360;
if(img.minScale >= 1 && area.checkRange) {
// 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域
minScale = 1;
if(img.width < area.height) {
minScale = area.height / img.oldWidth;
} else if(img.height < area.width) {
minScale = area.width / img.oldHeight;
}
if(minScale !== 1) {
scaleImage({
instance: o,
scale: minScale - scale,
x: sys.windowWidth / 2,
y: (sys.windowHeight - sys.offsetBottom) / 2
});
}
}
// 由于拖动画布后会导致图片位置偏移,翻转时的旋转中心点需是图片区域+偏移区域的中心点
// 翻转x轴中心点 = (超出裁剪区域右侧的图片宽度 - 超出裁剪区域左侧的图片宽度) / 2
// 翻转y轴中心点 = (超出裁剪区域下方的图片宽度 - 超出裁剪区域上方的图片宽度) / 2
var ox = ((offset.x + img.width - area.right) - (area.left - offset.x)) / 2;
var oy = ((offset.y + img.height - area.bottom) - (area.top - offset.y)) / 2;
changeImageRect({
instance: o,
check: true,
x: -ox - oy,
y: -oy + ox
});
};
module.exports = {
/**
* 初始化:观察数据变更
* @param {Object} newVal 新数据
* @param {Object} oldVal 旧数据
* @param {Object} o 组件实例对象
*/
initObserver: function(newVal, oldVal, o, i) {
if(newVal) {
img = newVal.img;
sys = newVal.sys;
area = newVal.area;
minScale = img.minScale;
resetData();
img.src && changeImageRect({
instance: o,
x: (sys.windowWidth - img.width) / 2,
y: (sys.windowHeight - sys.offsetBottom - img.height) / 2
});
changeAreaRect({
instance: o
});
// console.log('initRect', JSON.stringify(newVal))
}
},
/**
* 鼠标滚轮滚动
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
mousewheel: function(e, o) {
if(!img.src) return;
scaleImage({
instance: o,
check: true,
// 鼠标向上滚动时deltaY 固定 -100鼠标向下滚动时deltaY 固定 100
scale: e.detail.deltaY > 0 ? -0.05 : 0.05,
x: e.touches[0].pageX,
y: e.touches[0].pageY
});
},
/**
* 触摸开始
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
touchstart: function(e, o) {
if(!img.src) return;
touches = e.touches;
activeAngle = area.showAngle ? getToucheAngle(touches[0].pageX, touches[0].pageY) : 0;
if(touches.length === 1 && activeAngle !== 0) {
touchType = 'stretch'; // 伸缩裁剪区域
} else {
touchType = '';
}
// console.log('touchstart', JSON.stringify(e), activeAngle)
},
/**
* 触摸移动
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
touchmove: function(e, o) {
if(!img.src) return;
// console.log('touchmove', JSON.stringify(e), JSON.stringify(o))
if(touchType === 'stretch') { // 触摸四个角进行拉伸
var point = e.touches[0];
var start = touches[0];
var x = point.pageX - start.pageX;
var y = point.pageY - start.pageY;
if(x !== 0 || y !== 0) {
var maxX = num(area.width * (1 - area.minScale));
var maxY = num(area.height * (1 - area.minScale));
// console.log(x, y, maxX, maxY, offset, area)
touches[0] = point;
var r = rotate / 90 % 2;
var m = r === 1 ? num((img.height - img.width) / 2) : 0; // 宽高差值一半
var xCompare = r === 1 ? lessThanByFault(img.height, area.width) : lessThanByFault(img.width, area.width);
var yCompare = r === 1 ? lessThanByFault(img.width, area.height) : lessThanByFault(img.height, area.height)
var isInclude = xCompare && yCompare;
var isIntersect = area.checkRange && (xCompare || yCompare); // 图片是否包含在裁剪区域内
var isReverse = !isInclude || num((offset.x - area.left) / area.width) <= num((offset.y - area.top) / area.height) || (area.width > area.height && img.width < img.height && r === 1);
switch(activeAngle) {
case 1: // 左上角
x = num(x + areaOffset.left);
y = num(y + areaOffset.top);
if(x >= 0 && y >= 0) { // 有效滑动
var t = num(offset.y + m - area.top);
var l = num(offset.x - m - area.left);
// && (offset.x + img.width < area.right || offset.y + img.height < area.bottom)
var max = isIntersect && ((l >= 0) || (t >= 0))
? minimum(t, l)
: false;
if(x > y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(x > maxX) x = maxX;
y = num(x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(y > maxY) y = maxY;
x = num(y * area.width / area.height);
}
areaOffset.left = x;
areaOffset.top = y;
}
break;
case 2: // 右上角
x = num(x + areaOffset.right);
y = num(y + areaOffset.top);
if(x <= 0 && y >= 0) { // 有效滑动
var w = (r === 1 ? img.height : img.width);
var t = num(offset.y + m - area.top);
var l = num(area.right + m - offset.x - w);
var max = isIntersect && ((t >= 0) || (l >= 0))
? minimum(t, l)
: false;
// var max = isInclude && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y >= area.top))
// ? minimum(offset.y - area.top, area.right - offset.x - img.width)
// : false;
// console.log(offset.x, offset.y, img.width, img.height, area.top, area.right, m, max)
// console.log(offset.y + m - area.top, area.right + m - offset.x - w)
if(-x > y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(-x > maxX) x = -maxX;
y = num(-x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(y > maxY) y = maxY;
x = num(-y * area.width / area.height);
}
areaOffset.right = x;
areaOffset.top = y;
}
break;
case 3: // 左下角
x += num(x + areaOffset.left);
y += num(y + areaOffset.bottom);
if(x >= 0 && y <= 0) { // 有效滑动
var w = (r === 1 ? img.width : img.height);
var t = num(area.bottom - m - offset.y - w);
var l = num(offset.x - m - area.left);
var max = isIntersect && ((l >= 0) || (t >= 0))
? minimum(t, l)
: false;
if(x > -y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(x > maxX) x = maxX;
y = num(-x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(-y > maxY) y = -maxY;
x = num(-y * area.width / area.height);
}
areaOffset.left = x;
areaOffset.bottom = y;
}
break;
case 4: // 右下角
x = num(x + areaOffset.right);
y = num(y + areaOffset.bottom);
if(x <= 0 && y <= 0) { // 有效滑动
var w = (r === 1 ? img.height : img.width);
var h = (r === 1 ? img.width : img.height);
var t = num(area.bottom - offset.y - h - m);
var l = num(area.right + m - offset.x - w);
var max = isIntersect && ((l >= 0) || (t >= 0))
? minimum(t, l)
: false;
if(-x > -y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(-x > maxX) x = -maxX;
y = num(x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(-y > maxY) y = -maxY;
x = num(y * area.width / area.height);
}
areaOffset.right = x;
areaOffset.bottom = y;
}
break;
}
// console.log(x, y, JSON.stringify(areaOffset))
changeAreaRect({
instance: o,
});
// this.draw();
}
} else if (e.touches.length == 2) { // 双点触摸缩放
var start = getDistanceByTouches(touches);
var end = getDistanceByTouches(e.touches);
scaleImage({
instance: o,
check: !area.bounce,
scale: (end.c - start.c) / 100,
x: end.x,
y: end.y
});
touchType = 'scale';
} else if(touchType === 'scale') {// 从双点触摸变成单点触摸 / 从缩放变成拖动
touchType = 'move';
} else {
changeImageRect({
instance: o,
check: !area.bounce,
x: e.touches[0].pageX - touches[0].pageX,
y: e.touches[0].pageY - touches[0].pageY
});
touchType = 'move';
}
touches = e.touches;
},
/**
* 触摸结束
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
touchend: function(e, o) {
if(!img.src) return;
if(touchType === 'stretch') { // 拉伸裁剪区域的四个角缩放
// 裁剪区域宽度被缩放到多少
var left = areaOffset.left;
var right = areaOffset.right;
var top = areaOffset.top;
var bottom = areaOffset.bottom;
var w = area.width + right - left;
var h = area.height + bottom - top;
// 图像放大倍数
var p = scale * (area.width / w) - scale;
// 复原裁剪区域
areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
changeAreaRect({
instance: o,
});
scaleImage({
instance: o,
scale: p,
x: area.left + left + (1 === activeAngle || 3 === activeAngle ? w : 0),
y: area.top + top + (1 === activeAngle || 2 === activeAngle ? h : 0)
});
} else if (area.bounce) { // 检查边界并矫正,实现拖动到边界时有回弹效果
changeImageRect({
instance: o,
check: true
});
}
},
/**
* 顺时针翻转图片90°
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
rotateImage: function(e, o) {
rotateImage(e, o, 90);
},
rotateImage90: function(e, o) {
rotateImage(e, o, 90)
},
rotateImage270: function(e, o) {
rotateImage(e, o, 270)
},
// 此处只用于对齐其他平台端的样式参数,防止异常,无作用
imageStyles: '',
maskStylesList: ['', '', '', ''],
borderStyles: '',
gridStylesList: ['', '', '', ''],
angleStylesList: ['', '', '', ''],
circleBoxStyles: '',
circleStyles: '',
}

View File

@ -0,0 +1,81 @@
{
"id": "qf-image-cropper",
"displayName": "图片裁剪插件",
"version": "2.2.5",
"description": "图片裁剪插件,支持自定义尺寸、定点等比例缩放、拖动、图片翻转、剪切圆形/圆角图片、定制样式,功能多性能高体验好注释全。",
"keywords": [
"qf-image-cropper",
"图片裁剪",
"图片编辑",
"头像裁剪",
"小程序"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "y",
"阿里": "n",
"百度": "n",
"字节跳动": "n",
"QQ": "u",
"钉钉": "n",
"快手": "n",
"飞书": "n",
"京东": "n"
},
"快应用": {
"华为": "n",
"联盟": "n"
}
}
}
}
}

View File

@ -0,0 +1,97 @@
# qf-image-cropper
## 图片裁剪插件
uniapp微信小程序图片裁剪插件支持自定义尺寸、定点等比例缩放、拖动、图片翻转、剪切圆形/圆角图片、定制样式,功能多性能高体验好注释全。
### 平台支持:
1. 支持微信小程序移动端、PC端、开发者工具
2. 支持H5平台2.1.0版本起)
3. 支持APP平台2.1.5版本起Android、IOS
4. 其他平台暂未测试兼容性未知
### 支持功能:
1. 自定义裁剪尺寸
2. 定点等比例缩放移动端以双指触摸中心点为缩放中心点PC端以鼠标所在点为缩放中心点
3. 自由拖动:支持限制滑出边界,也支持回弹效果(滑动时可滑出边界,释放时回弹到边界)
4. 图片翻转:在裁剪尺寸非 1:1 的情况下,翻转时宽高无法铺满裁剪区域时,图片会自动放大到合适尺寸
5. 裁剪生成新图片
6. 本地选择图片
7. 可定制样式:可自由选择是否渲染裁剪边框、可伸缩裁剪顶角、参考线
8. 裁剪圆角图片:圆形、圆角矩形
### 属性说明
| 属性名 | 类型 | 默认值 | 说明 |
|:---|:---|:---|:---|
| src | String | | 图片资源地址 |
| width | Number | 300 | 裁剪宽度 |
| height | Number | 300 | 裁剪高度 |
| showBorder | Boolean | true | 是否绘制裁剪区域边框 |
| showGrid | Boolean | true | 是否绘制裁剪区域网格参考线 |
| showAngle | Boolean | true | 是否展示四个支持伸缩的角 |
| areaScale | Number | 0.3 | 裁剪区域最小缩放倍数 |
| minScale | Number | 1 | 图片最小缩放倍数 |
| maxScale | Number | 5 | 图片最大缩放倍数 |
| checkRange | Boolean | true | 检查图片位置是否超出裁剪边界,如果超出则会矫正位置 |
| backgroundColor | String | | 生成图片背景色:如果裁剪区域没有完全包含在图片中时,不设置该属性则生成图片存在一定的透明块 |
| bounce | Boolean | true | 是否有回弹效果:当 checkRange 为 true 时有效,拖动时可以拖出边界,释放时会弹回边界 |
| rotatable | Boolean | true | 是否支持翻转 |
| reverseRotatable | Boolean | false | 是否支持逆向翻转 |
| choosable | Boolean | true | 是否支持从本地选择素材 |
| gpu | Boolean | false | 是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题 |
| angleSize | Number | 20 | 四个角尺寸单位px |
| angleBorderWidth | Number | 2 | 四个角边框宽度单位px |
| zIndex | Number/String | | 调整组件层级 |
| radius | Number | | 裁剪图片圆角半径单位px |
| fileType | String | png | 生成文件的类型,只支持 'jpg' 或 'png'。默认为 'png' |
| delay | Number | 1000 | 图片从绘制到生成所需时间单位ms<br>微信小程序平台使用 `Canvas 2D` 绘制时有效<br>如绘制大图或出现裁剪图片空白等情况应适当调大该值,因 `Canvas 2d` 采用同步绘制,需自己把控绘制完成时间 |
| navigation | Boolean | true | 页面是否是原生标题栏:<br>H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 `"navigationStyle": "custom"` 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差。<br>因H5平台的窗口高度是包含标题栏的而屏幕触摸点的坐标是不包含的 |
| @crop | EventHandle | | 剪裁完成后触发event = { tempFilePath }。在H5平台下tempFilePath 为 base64 |
### 基本用法
```
<template>
<div>
<qf-image-cropper :width="500" :height="500" :radius="30" @crop="handleCrop"></qf-image-cropper>
</div>
</template>
<script>
import QfImageCropper from '@/components/qf-image-cropper/qf-image-cropper.vue';
export default {
components: {
QfImageCropper
},
methods: {
handleCrop(e) {
uni.previewImage({
urls: [e.tempFilePath],
current: 0
});
}
}
}
</script>
```
通过ref组件实例可在进入页面后直接打开相册选择图片
```
mounted() {
this.$refs.qfImageCropper.chooseImage({ sourceType: ['album'] });
}
```
### 使用说明
1.建议在`pages.json`中将引用插件的页面添加一下配置禁止下拉刷新和禁止页面滑动,防止出现性能或页面抖动等问题。
```
{
"enablePullDownRefresh": false,
"disableScroll": true
}
```
2.建议使用本插件不要设置过大宽高的目标图片尺寸建议1365x1365以内否则可能会导致如下问题
```
1.界面卡顿,内存占用过高
2.生成图片失真(模糊)
3.确定裁剪后一直显示 `裁剪中...`,该问题是由 `uni.canvasToTempFilePath` 无法回调导致,不同平台不同设备限制可能有所不同。
```
3.如裁剪后的图片存在偏移的问题,请检查是否受自己项目中父组件或全局样式影响。
4.src属性设置网络图片时图片资源必须是能触发 `getImageInfo` API 的 success 回调才可用于插件裁剪。因此小程序平台获取网络图片信息需先配置download域名白名单才能生效。
5.如果组件无法正常渲染且使用了 `v-if` 时,可尝试将 `v-if` 替换为 `v-show`
6.如果App端导入组件后无法正常渲染请尝试重新运行

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"file":"camera.js","sources":["compontent/public/camera.vue","../Hbuilder/HBuilderX/plugins/uniapp-cli-vite/uniPage:/Y29tcG9udGVudC9wdWJsaWMvY2FtZXJhLnZ1ZQ"],"sourcesContent":["<template>\r\n\t<view>\r\n\t\t<!-- https://ext.dcloud.net.cn/plugin?id=10333文档在这里自己看 -->\r\n\t\t<qf-image-cropper :src=\"src\" :width=\"width\" :height=\"height\" :radius=\"0\" @crop=\"handleCrop\" />\r\n\t</view>\r\n</template>\r\n\r\n<script setup>\r\n\timport {\r\n\t\tref\r\n\t} from 'vue'\r\n\timport {\r\n\t\tonLoad\r\n\t} from '@dcloudio/uni-app';\r\n\timport QfImageCropper from '@/uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.vue'\r\n\r\n\t// 图片源\r\n\tconst src = ref('') // 可以根据需要动态设置图片路径\r\n\tconst type = ref(0);\r\n\tconst width = ref(600);\r\n\tconst height = ref(400);\r\n\t// 裁剪完成的回调\r\n\tconst handleCrop = (e) => {\r\n\t\t// console.log(\"剪切完了\")\r\n\t\tuni.setStorageSync(`imgkey${type.value}`, e.tempFilePath);\r\n\t\tuni.navigateBack()\r\n\t}\r\n\tonLoad((options) => {\r\n\t\tsrc.value = options.url\r\n\t\t//type是一个页面有两个照相的时候做个类型区分\r\n\t\ttype.value = options.type\r\n\t\tif(options.size){\r\n\t\t\twidth.value = 900;\r\n\t\t\theight.value = 600;\r\n\t\t}\r\n\t});\r\n</script>","import MiniProgramPage from 'D:/officialAccount/compontent/public/camera.vue'\nwx.createPage(MiniProgramPage)"],"names":["ref","uni","onLoad","MiniProgramPage"],"mappings":";;;;;AAcC,MAAM,iBAAiB,MAAW;;;;AAGlC,UAAM,MAAMA,cAAG,IAAC,EAAE;AAClB,UAAM,OAAOA,kBAAI,CAAC;AAClB,UAAM,QAAQA,kBAAI,GAAG;AACrB,UAAM,SAASA,kBAAI,GAAG;AAEtB,UAAM,aAAa,CAAC,MAAM;AAEzBC,0BAAI,eAAe,SAAS,KAAK,KAAK,IAAI,EAAE,YAAY;AACxDA,oBAAAA,MAAI,aAAc;AAAA,IAClB;AACDC,kBAAM,OAAC,CAAC,YAAY;AACnB,UAAI,QAAQ,QAAQ;AAEpB,WAAK,QAAQ,QAAQ;AACrB,UAAG,QAAQ,MAAK;AACf,cAAM,QAAQ;AACd,eAAO,QAAQ;AAAA,MACf;AAAA,IACH,CAAE;;;;;;;;;;;;;;AClCF,GAAG,WAAWC,SAAe;"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"file":"special.js","sources":["pages/login/special.vue","../Hbuilder/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvbG9naW4vc3BlY2lhbC52dWU"],"sourcesContent":["<template>\r\n\t<view class=\"font-father\">\r\n\t\t<view class=\"font-title\">\r\n\t\t\t尊敬的用户你的手机<text>{{ phonenumber }}</text>已成功绑定,欢迎加入护理单元大家庭!\r\n\t\t</view>\r\n\t\t<view class=\"bottom-button\" @click=\"jumpto\">\r\n\t\t\t我的机构\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script setup>\r\n\timport {\r\n\t\tonLoad\r\n\t} from '@dcloudio/uni-app'\r\n\timport {\r\n\t\treactive,\r\n\t\tref\r\n\t} from 'vue';\r\n\t\r\n\t\r\n\tconst phonenumber = ref(\"\");\r\n\tconst jumpto = () => {\r\n\t\tuni.redirectTo({\r\n\t\t\turl: `/pages/login/threeselectone`\r\n\t\t});\r\n\t}\r\n\tonLoad(()=>{\r\n\t\tphonenumber.value = uni.getStorageSync('tel')\r\n\t})\r\n\t\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t.font-father {\r\n\t\twidth: 100%;\r\n\t\theight: 100vh;\r\n\t\tpadding: 0 50rpx;\r\n\t\tdisplay: flex;\r\n\t\tjustify-content: center;\r\n\t\tposition: relative;\r\n\t\t// flex-direction: column;\r\n\t\t.font-title{\r\n\t\t\tmargin-top: 200rpx;\r\n\t\t}\r\n\t\t.bottom-button{\r\n\t\t\tposition: fixed;\r\n\t\t\tbottom: 150rpx;\r\n\t\t\tleft: 50%;\r\n\t\t\ttransform: translateX(-50%);\r\n\t\t\twidth: 400rpx;\r\n\t\t\theight: 100rpx;\r\n\t\t\t// border-radius: 30rpx;\r\n\t\t\tborder-radius: 50rpx;\r\n\t\t\tbackground: linear-gradient(to right, #00C9FF, #0076FF);\r\n\t\t\tcolor: #fff;\r\n\t\t\tfont-size: 33rpx;\r\n\t\t\tdisplay: flex;\r\n\t\t\tjustify-content: center;\r\n\t\t\talign-items: center;\r\n\t\t}\r\n\t}\r\n\r\n</style>","import MiniProgramPage from 'D:/officialAccount/pages/login/special.vue'\nwx.createPage(MiniProgramPage)"],"names":["ref","uni","onLoad"],"mappings":";;;;;AAqBC,UAAM,cAAcA,kBAAI,EAAE;AAC1B,UAAM,SAAS,MAAM;AACpBC,oBAAAA,MAAI,WAAW;AAAA,QACd,KAAK;AAAA,MACR,CAAG;AAAA,IACD;AACDC,kBAAAA,OAAO,MAAI;AACV,kBAAY,QAAQD,oBAAI,eAAe,KAAK;AAAA,IAC9C,CAAE;;;;;;;;;;AC5BF,GAAG,WAAW,eAAe;"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"version":3,"file":"index.js","sources":["request/index.js"],"sourcesContent":["// 全局请求封装\nexport const base_url = 'https://www.focusnu.com/nursingunit'\n// 请求超出时间\nconst timeout = 5000\n \n// 需要修改token和根据实际修改请求头\nexport default (params) => {\n\tlet url = params.url;\n\tlet method = params.method || \"get\";\n\tlet data = params.data || {};\n\tlet header = {\n\t\t'X-Access-Token': uni.getStorageSync('token') || '',\n\t\t'Content-Type': 'application/json;charset=UTF-8',\n\t\t'Authorization': 'Basic c2FiZXI6c2FiZXJfc2VjcmV0',\r\n\t\t\n\t\t...params.header\n\t}\n\treturn new Promise((resolve, reject) => {\n\t\tuni.request({\n\t\t\turl: base_url + url,\n\t\t\tmethod: method,\n\t\t\theader: header,\n\t\t\tdata: data,\n timeout,\n\t\t\tsuccess(response) {\n\t\t\t\tconst res = response\n\t\t\t\t// 根据返回的状态码做出对应的操作\r\n\t\t\t\n\t\t\t\tif (res.statusCode == 200) {\n\t\t\t\t\tresolve(res.data);\n\t\t\t\t} else {\n\t\t\t\t\tuni.clearStorageSync()\n\t\t\t\t\tswitch (res.statusCode) {\n\t\t\t\t\t\tcase 401:\n\t\t\t\t\t\t\tuni.showModal({\n\t\t\t\t\t\t\t\ttitle: \"提示\",\n\t\t\t\t\t\t\t\tcontent: \"请登录\",\n\t\t\t\t\t\t\t\tshowCancel: false,\n\t\t\t\t\t\t\t\tsuccess() {\n\t\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\t\tuni.navigateTo({\n\t\t\t\t\t\t\t\t\t\t\turl: \"/pages/login/index\",\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t}, 1000);\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 404:\n\t\t\t\t\t\t\tuni.showToast({\n\t\t\t\t\t\t\t\ttitle: '请求地址不存在...',\n\t\t\t\t\t\t\t\tduration: 2000,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tuni.showToast({\n\t\t\t\t\t\t\t\ttitle: '请重试...',\n\t\t\t\t\t\t\t\tduration: 2000,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tfail(err) {\n\t\t\t\tconsole.log(err)\n\t\t\t\tif (err.errMsg.indexOf('request:fail') !== -1) {\n\t\t\t\t\tuni.showToast({\n\t\t\t\t\t\ttitle: '网络异常',\n\t\t\t\t\t\ticon: \"error\",\n\t\t\t\t\t\tduration: 2000\n\t\t\t\t\t})\n\t\t\t\t} else {\n\t\t\t\t\tuni.showToast({\n\t\t\t\t\t\ttitle: '未知异常',\n\t\t\t\t\t\tduration: 2000\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\treject(err);\n \n\t\t\t},\n\t\t\tcomplete() {\n\t\t\t\t// 不管成功还是失败都会执行\n\t\t\t\t// uni.hideLoading();\n\t\t\t\t// uni.hideToast();\n\t\t\t}\n\t\t});\n\t}).catch(() => {});\n};"],"names":["uni"],"mappings":";;AACY,MAAC,WAAW;AAExB,MAAM,UAAU;AAGhB,MAAe,UAAA,CAAC,WAAW;AAC1B,MAAI,MAAM,OAAO;AACjB,MAAI,SAAS,OAAO,UAAU;AAC9B,MAAI,OAAO,OAAO,QAAQ;AAC1B,MAAI,SAAS;AAAA,IACZ,kBAAkBA,cAAG,MAAC,eAAe,OAAO,KAAK;AAAA,IACjD,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IAEjB,GAAG,OAAO;AAAA,EACV;AACD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACvCA,kBAAAA,MAAI,QAAQ;AAAA,MACX,KAAK,WAAW;AAAA,MAChB;AAAA,MACA;AAAA,MACA;AAAA,MACS;AAAA,MACT,QAAQ,UAAU;AACjB,cAAM,MAAM;AAGZ,YAAI,IAAI,cAAc,KAAK;AAC1B,kBAAQ,IAAI,IAAI;AAAA,QACrB,OAAW;AACNA,wBAAAA,MAAI,iBAAkB;AACtB,kBAAQ,IAAI,YAAU;AAAA,YACrB,KAAK;AACJA,4BAAAA,MAAI,UAAU;AAAA,gBACb,OAAO;AAAA,gBACP,SAAS;AAAA,gBACT,YAAY;AAAA,gBACZ,UAAU;AACT,6BAAW,MAAM;AAChBA,kCAAAA,MAAI,WAAW;AAAA,sBACd,KAAK;AAAA,oBAChB,CAAW;AAAA,kBACD,GAAE,GAAI;AAAA,gBACP;AAAA,cACT,CAAQ;AACD;AAAA,YACD,KAAK;AACJA,4BAAAA,MAAI,UAAU;AAAA,gBACb,OAAO;AAAA,gBACP,UAAU;AAAA,cAClB,CAAQ;AACD;AAAA,YACD;AACCA,4BAAAA,MAAI,UAAU;AAAA,gBACb,OAAO;AAAA,gBACP,UAAU;AAAA,cAClB,CAAQ;AACD;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAAA,MACD,KAAK,KAAK;AACTA,sBAAAA,6CAAY,GAAG;AACf,YAAI,IAAI,OAAO,QAAQ,cAAc,MAAM,IAAI;AAC9CA,wBAAAA,MAAI,UAAU;AAAA,YACb,OAAO;AAAA,YACP,MAAM;AAAA,YACN,UAAU;AAAA,UAChB,CAAM;AAAA,QACN,OAAW;AACNA,wBAAAA,MAAI,UAAU;AAAA,YACb,OAAO;AAAA,YACP,UAAU;AAAA,UAChB,CAAM;AAAA,QACD;AACD,eAAO,GAAG;AAAA,MAEV;AAAA,MACD,WAAW;AAAA,MAIV;AAAA,IACJ,CAAG;AAAA,EACH,CAAE,EAAE,MAAM,MAAM;AAAA,EAAA,CAAE;AAClB;;;"}
{"version":3,"file":"index.js","sources":["request/index.js"],"sourcesContent":["// 全局请求封装\nexport const base_url = 'https://www.focusnu.com/nursingunit'\n// 请求超出时间\nconst timeout = 5000\n \n// 需要修改token和根据实际修改请求头\nexport default (params) => {\n\tlet url = params.url;\n\tlet method = params.method || \"get\";\n\tlet data = params.data || {};\n\tlet header = {\n\t\t'X-Access-Token': uni.getStorageSync('token') || '',\n\t\t'Content-Type': 'application/json;charset=UTF-8',\n\t\t'Authorization': 'Basic c2FiZXI6c2FiZXJfc2VjcmV0',\r\n\t\t\n\t\t...params.header\n\t}\n\treturn new Promise((resolve, reject) => {\n\t\tuni.request({\n\t\t\turl: base_url + url,\n\t\t\tmethod: method,\n\t\t\theader: header,\n\t\t\tdata: data,\n timeout,\n\t\t\tsuccess(response) {\n\t\t\t\tconst res = response\n\t\t\t\t// 根据返回的状态码做出对应的操作\r\n\t\t\t\n\t\t\t\tif (res.statusCode == 200) {\n\t\t\t\t\tresolve(res.data);\n\t\t\t\t} else {\n\t\t\t\t\tuni.clearStorageSync()\n\t\t\t\t\tswitch (res.statusCode) {\n\t\t\t\t\t\tcase 401:\n\t\t\t\t\t\t\tuni.showModal({\n\t\t\t\t\t\t\t\ttitle: \"提示\",\n\t\t\t\t\t\t\t\tcontent: \"请登录\",\n\t\t\t\t\t\t\t\tshowCancel: false,\n\t\t\t\t\t\t\t\tsuccess() {\n\t\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\t\tuni.navigateTo({\n\t\t\t\t\t\t\t\t\t\t\turl: \"/pages/login/index\",\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t}, 1000);\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 404:\n\t\t\t\t\t\t\tuni.showToast({\n\t\t\t\t\t\t\t\ttitle: '请求地址不存在...',\n\t\t\t\t\t\t\t\tduration: 2000,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tuni.showToast({\n\t\t\t\t\t\t\t\ttitle: '请重试...',\n\t\t\t\t\t\t\t\tduration: 2000,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tfail(err) {\n\t\t\t\tif (err.errMsg.indexOf('request:fail') !== -1) {\n\t\t\t\t\tuni.showToast({\n\t\t\t\t\t\ttitle: '网络异常',\n\t\t\t\t\t\ticon: \"error\",\n\t\t\t\t\t\tduration: 2000\n\t\t\t\t\t})\n\t\t\t\t} else {\n\t\t\t\t\tuni.showToast({\n\t\t\t\t\t\ttitle: '未知异常',\n\t\t\t\t\t\tduration: 2000\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\treject(err);\n \n\t\t\t}\n\t\t});\n\t}).catch(() => {});\n};"],"names":["uni"],"mappings":";;AACY,MAAC,WAAW;AAExB,MAAM,UAAU;AAGhB,MAAe,UAAA,CAAC,WAAW;AAC1B,MAAI,MAAM,OAAO;AACjB,MAAI,SAAS,OAAO,UAAU;AAC9B,MAAI,OAAO,OAAO,QAAQ;AAC1B,MAAI,SAAS;AAAA,IACZ,kBAAkBA,cAAG,MAAC,eAAe,OAAO,KAAK;AAAA,IACjD,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IAEjB,GAAG,OAAO;AAAA,EACV;AACD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACvCA,kBAAAA,MAAI,QAAQ;AAAA,MACX,KAAK,WAAW;AAAA,MAChB;AAAA,MACA;AAAA,MACA;AAAA,MACS;AAAA,MACT,QAAQ,UAAU;AACjB,cAAM,MAAM;AAGZ,YAAI,IAAI,cAAc,KAAK;AAC1B,kBAAQ,IAAI,IAAI;AAAA,QACrB,OAAW;AACNA,wBAAAA,MAAI,iBAAkB;AACtB,kBAAQ,IAAI,YAAU;AAAA,YACrB,KAAK;AACJA,4BAAAA,MAAI,UAAU;AAAA,gBACb,OAAO;AAAA,gBACP,SAAS;AAAA,gBACT,YAAY;AAAA,gBACZ,UAAU;AACT,6BAAW,MAAM;AAChBA,kCAAAA,MAAI,WAAW;AAAA,sBACd,KAAK;AAAA,oBAChB,CAAW;AAAA,kBACD,GAAE,GAAI;AAAA,gBACP;AAAA,cACT,CAAQ;AACD;AAAA,YACD,KAAK;AACJA,4BAAAA,MAAI,UAAU;AAAA,gBACb,OAAO;AAAA,gBACP,UAAU;AAAA,cAClB,CAAQ;AACD;AAAA,YACD;AACCA,4BAAAA,MAAI,UAAU;AAAA,gBACb,OAAO;AAAA,gBACP,UAAU;AAAA,cAClB,CAAQ;AACD;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAAA,MACD,KAAK,KAAK;AACT,YAAI,IAAI,OAAO,QAAQ,cAAc,MAAM,IAAI;AAC9CA,wBAAAA,MAAI,UAAU;AAAA,YACb,OAAO;AAAA,YACP,MAAM;AAAA,YACN,UAAU;AAAA,UAChB,CAAM;AAAA,QACN,OAAW;AACNA,wBAAAA,MAAI,UAAU;AAAA,YACb,OAAO;AAAA,YACP,UAAU;AAAA,UAChB,CAAM;AAAA,QACD;AACD,eAAO,GAAG;AAAA,MAEV;AAAA,IACJ,CAAG;AAAA,EACH,CAAE,EAAE,MAAM,MAAM;AAAA,EAAA,CAAE;AAClB;;;"}

File diff suppressed because one or more lines are too long

View File

@ -3,13 +3,14 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const common_vendor = require("./common/vendor.js");
const uni_modules_vkUviewUi_index = require("./uni_modules/vk-uview-ui/index.js");
if (!Math) {
"./pages/login/callback.js";
"./pages/login/index.js";
"./pages/login/phonebumber.js";
"./pages/login/threeselectone.js";
"./pages/login/workjoin.js";
"./pages/login/code.js";
"./pages/login/callback.js";
"./pages/login/protocol.js";
"./pages/login/special.js";
"./pages/index/index.js";
"./pages/addoldman/hukou.js";
"./pages/addoldman/yibao.js";
@ -20,8 +21,7 @@ if (!Math) {
"./pages/addjigou/name.js";
"./pages/addjigou/card.js";
"./pages/pay/index.js";
"./pages/addoldman/camera.js";
"./pages/camera/CustomCamera.js";
"./compontent/public/camera.js";
}
const _sfc_main = {
onLaunch: function() {

View File

@ -1,12 +1,13 @@
{
"pages": [
"pages/login/callback",
"pages/login/index",
"pages/login/phonebumber",
"pages/login/threeselectone",
"pages/login/workjoin",
"pages/login/code",
"pages/login/callback",
"pages/login/protocol",
"pages/login/special",
"pages/index/index",
"pages/addoldman/hukou",
"pages/addoldman/yibao",
@ -17,8 +18,7 @@
"pages/addjigou/name",
"pages/addjigou/card",
"pages/pay/index",
"pages/addoldman/camera",
"pages/camera/CustomCamera"
"compontent/public/camera"
],
"window": {
"navigationBarTextStyle": "black",

View File

@ -2571,15 +2571,6 @@ function inject(key, defaultValue, treatDefaultAsFactory = false) {
warn$1(`inject() can only be used inside setup() or functional components.`);
}
}
/*! #__NO_SIDE_EFFECTS__ */
// @__NO_SIDE_EFFECTS__
function defineComponent(options, extraOptions) {
return isFunction(options) ? (
// #8326: extend call and options.name access are considered side-effects
// by Rollup, so we have to wrap it in a pure-annotated IIFE.
/* @__PURE__ */ (() => extend({ name: options.name }, extraOptions, { setup: options }))()
) : options;
}
const isKeepAlive = (vnode) => vnode.type.__isKeepAlive;
function onActivated(hook, target) {
registerKeepAliveHook(hook, "a", target);
@ -7046,7 +7037,7 @@ function isConsoleWritable() {
function initRuntimeSocketService() {
const hosts = "192.168.2.27,127.0.0.1";
const port = "8090";
const id = "mp-weixin_nmi3ii";
const id = "mp-weixin_Q7WA3f";
const lazy = typeof swan !== "undefined";
let restoreError = lazy ? () => {
} : initOnError();
@ -7997,9 +7988,9 @@ const createHook = (lifecycle) => (hook, target = getCurrentInstance()) => {
};
const onShow = /* @__PURE__ */ createHook(ON_SHOW);
const onLoad = /* @__PURE__ */ createHook(ON_LOAD);
const onPullDownRefresh = /* @__PURE__ */ createHook(ON_PULL_DOWN_REFRESH);
exports._export_sfc = _export_sfc;
exports.createSSRApp = createSSRApp;
exports.defineComponent = defineComponent;
exports.e = e;
exports.f = f;
exports.getCurrentInstance = getCurrentInstance;
@ -8007,9 +7998,9 @@ exports.index = index;
exports.n = n;
exports.nextTick$1 = nextTick$1;
exports.o = o;
exports.onBeforeUnmount = onBeforeUnmount;
exports.onLoad = onLoad;
exports.onMounted = onMounted;
exports.onPullDownRefresh = onPullDownRefresh;
exports.onShow = onShow;
exports.onUnmounted = onUnmounted;
exports.p = p;

View File

@ -0,0 +1,40 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
if (!Math) {
QfImageCropper();
}
const QfImageCropper = () => "../../uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.js";
const _sfc_main = {
__name: "camera",
setup(__props) {
const src = common_vendor.ref("");
const type = common_vendor.ref(0);
const width = common_vendor.ref(600);
const height = common_vendor.ref(400);
const handleCrop = (e) => {
common_vendor.index.setStorageSync(`imgkey${type.value}`, e.tempFilePath);
common_vendor.index.navigateBack();
};
common_vendor.onLoad((options) => {
src.value = options.url;
type.value = options.type;
if (options.size) {
width.value = 900;
height.value = 600;
}
});
return (_ctx, _cache) => {
return {
a: common_vendor.o(handleCrop),
b: common_vendor.p({
src: src.value,
width: width.value,
height: height.value,
radius: 0
})
};
};
}
};
wx.createPage(_sfc_main);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/compontent/public/camera.js.map

View File

@ -0,0 +1,8 @@
{
"navigationBarTitleText": "图像识别",
"enablePullDownRefresh": false,
"disableScroll": true,
"usingComponents": {
"qf-image-cropper": "../../uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper"
}
}

View File

@ -0,0 +1 @@
<view><qf-image-cropper wx:if="{{b}}" bindcrop="{{a}}" u-i="b49cf4ae-0" bind:__l="__l" u-p="{{b}}"/></view>

View File

@ -41,10 +41,10 @@ const _sfc_main = {
longPressActions: {
itemList: ["保存图片"],
success: (data) => {
common_vendor.index.__f__("log", "at pages/addjigou/all.vue:174", "长按操作成功", data);
common_vendor.index.__f__("log", "at pages/addjigou/all.vue:168", "长按操作成功", data);
},
fail: (err) => {
common_vendor.index.__f__("error", "at pages/addjigou/all.vue:177", "长按操作失败", err);
common_vendor.index.__f__("error", "at pages/addjigou/all.vue:171", "长按操作失败", err);
}
}
});
@ -69,7 +69,7 @@ const _sfc_main = {
common_vendor.onLoad((options) => {
alldata.value = JSON.parse(options.element);
statesTarget.value = Number(alldata.value.status);
common_vendor.index.__f__("log", "at pages/addjigou/all.vue:205", "????", alldata.value, statesTarget.value);
common_vendor.index.__f__("log", "at pages/addjigou/all.vue:199", "????", alldata.value, statesTarget.value);
let data = alldata.value;
textArray[0] = data.name;
textArray[1] = data.sex;

View File

@ -148,6 +148,7 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 180rpx;
}
.left-father.data-v-9afbabf9 {
display: flex;

View File

@ -24,10 +24,10 @@ const _sfc_main = {
sourceType: ["camera"],
success: (chooseRes) => {
tempImagePath.value = chooseRes.tempFilePaths[0];
uploadImage(tempImagePath.value);
common_vendor.index.navigateTo({ url: `/compontent/public/camera?url=${chooseRes.tempFilePaths[0]}&type=0&size=1` });
},
fail: (err) => {
common_vendor.index.__f__("error", "at pages/addjigou/card.vue:99", "拍照失败:", err);
common_vendor.index.__f__("error", "at pages/addjigou/card.vue:102", "拍照失败:", err);
}
});
}
@ -56,7 +56,7 @@ const _sfc_main = {
common_vendor.index.hideLoading();
return;
}
common_vendor.index.__f__("log", "at pages/addjigou/card.vue:128", "营业执照", JSON.parse(JSON.parse(uploadRes.data).result.data).data);
common_vendor.index.__f__("log", "at pages/addjigou/card.vue:131", "营业执照", JSON.parse(JSON.parse(uploadRes.data).result.data).data);
let father = JSON.parse(JSON.parse(uploadRes.data).result.data).data;
textArray[0] = father.companyName;
textArray[1] = father.businessAddress;
@ -166,6 +166,13 @@ const _sfc_main = {
const goBack = () => {
common_vendor.index.navigateBack();
};
common_vendor.onShow(() => {
const img = common_vendor.index.getStorageSync("imgkey0");
if (img) {
uploadImage(img);
common_vendor.index.removeStorageSync("imgkey0");
}
});
return (_ctx, _cache) => {
return common_vendor.e({
a: common_vendor.o(($event) => show.value = $event),

View File

@ -35,7 +35,7 @@
.container .white-content.data-v-402780bb {
width: 90%;
margin-left: 5%;
margin-top: 30rpx;
margin-top: 180rpx;
border-radius: 35rpx;
background-color: #f5fbfe;
}

View File

@ -27,10 +27,10 @@ const _sfc_main = {
sourceType: ["camera"],
success: (chooseRes) => {
tempImagePath.value = chooseRes.tempFilePaths[0];
uploadImage(tempImagePath.value);
common_vendor.index.navigateTo({ url: `/compontent/public/camera?url=${chooseRes.tempFilePaths[0]}&type=0` });
},
fail: (err) => {
common_vendor.index.__f__("error", "at pages/addjigou/name.vue:138", "拍照失败:", err);
common_vendor.index.__f__("error", "at pages/addjigou/name.vue:128", "拍照失败:", err);
}
});
}
@ -50,7 +50,7 @@ const _sfc_main = {
},
formData: {},
success: (uploadRes) => {
common_vendor.index.__f__("log", "at pages/addjigou/name.vue:176", "token", common_vendor.index.getStorageSync("token"));
common_vendor.index.__f__("log", "at pages/addjigou/name.vue:166", "token", common_vendor.index.getStorageSync("token"));
if (!JSON.parse(uploadRes.data).success) {
common_vendor.index.showToast({
title: "识别失败",
@ -202,6 +202,13 @@ const _sfc_main = {
endphoto.value = data.cardFmPath;
}
});
common_vendor.onShow(() => {
const img = common_vendor.index.getStorageSync("imgkey0");
if (img) {
uploadImage(img);
common_vendor.index.removeStorageSync("imgkey0");
}
});
return (_ctx, _cache) => {
return common_vendor.e({
a: common_vendor.o(($event) => show.value = $event),

View File

@ -147,6 +147,7 @@
font-size: 35rpx;
}
.title-back.data-v-4363d488 {
margin-top: 100rpx;
width: 100%;
height: 100rpx;
display: flex;

View File

@ -61,12 +61,12 @@ const _sfc_main = {
if (res.message == `保存成功!`) {
common_vendor.index.setStorageSync("specicalid", "");
common_vendor.index.reLaunch({
url: `/pages/login/workjoin?type=1`
url: `/pages/login/threeselectone`
});
} else {
common_vendor.index.setStorageSync("specicalid", res.result.id);
common_vendor.index.reLaunch({
url: `/pages/login/workjoin?type=1`
url: `/pages/login/threeselectone`
});
}
} else {

View File

@ -35,7 +35,7 @@
.container .white-content.data-v-549d1cee {
width: 90%;
margin-left: 5%;
margin-top: 30rpx;
margin-top: 180rpx;
border-radius: 35rpx;
background-color: #f5fbfe;
}

View File

@ -15,12 +15,76 @@ const _sfc_main = {
const content = common_vendor.ref("");
const nameArray = ["姓名", "性别", "身份证号码", "民族", "出生日期", "住址", "签发机关", "有效期限"];
const textArray = common_vendor.reactive(["", "", "", "", "", "", "", ""]);
common_vendor.ref("");
const tempImagePath = common_vendor.ref("");
function getMessage() {
common_vendor.index.navigateTo({ url: "/pages/addoldman/camera" });
common_vendor.index.chooseImage({
count: 1,
sourceType: ["camera"],
success: (chooseRes) => {
tempImagePath.value = chooseRes.tempFilePaths[0];
uploadImage(tempImagePath.value);
},
fail: (err) => {
common_vendor.index.__f__("error", "at pages/addoldman/IDcard.vue:89", "拍照失败:", err);
}
});
}
const headImge = common_vendor.ref("");
const backImge = common_vendor.ref("");
function uploadImage(filePath) {
common_vendor.index.showLoading();
common_vendor.index.uploadFile({
url: `${common_vendor.index.getStorageSync("serverUrl")}/api/ocr/idCard`,
// 替换为你的POST接口地址
filePath,
name: "file",
// 后端接收时的字段名
header: {
"X-Access-Token": common_vendor.index.getStorageSync("token") || ""
},
formData: {},
success: (uploadRes) => {
if (!JSON.parse(uploadRes.data).success) {
common_vendor.index.showToast({
title: "识别失败",
icon: "error"
});
common_vendor.index.hideLoading();
return;
}
if (JSON.parse(JSON.parse(uploadRes.data).result.data).data.face) {
let father = JSON.parse(JSON.parse(uploadRes.data).result.data).data.face.data;
textArray[0] = father.name;
textArray[1] = father.sex;
textArray[2] = father.idNumber;
textArray[3] = father.ethnicity;
textArray[4] = father.birthDate;
textArray[5] = father.address;
common_vendor.index.showToast({
title: "识别成功"
});
headImge.value = filePath;
common_vendor.index.hideLoading();
} else {
let father = JSON.parse(JSON.parse(uploadRes.data).result.data).data.back.data;
textArray[6] = father.issueAuthority;
textArray[7] = father.validPeriod;
common_vendor.index.showToast({
title: "识别成功"
});
backImge.value = filePath;
common_vendor.index.hideLoading();
}
},
fail: (err) => {
common_vendor.index.showToast({
title: "上传出错",
icon: "error"
});
common_vendor.index.hideLoading();
}
});
}
const openLook = (res) => {
if (res) {
content.value = res;

View File

@ -1,74 +0,0 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const _sfc_main = {
__name: "camera",
setup(__props) {
let cameraContext;
const sysInfo = common_vendor.index.getSystemInfoSync();
common_vendor.onMounted(() => {
cameraContext = common_vendor.index.createCameraContext();
});
function takePhoto() {
cameraContext.takePhoto({
quality: "high",
success: async (res) => {
const src = res.tempImagePath;
const screenW = sysInfo.windowWidth;
const screenH = sysInfo.windowHeight;
const topMaskH = screenH * 0.25;
const middleH = screenH * 0.5;
const cutoutH = middleH * 0.6;
const cutoutW = cutoutH * 1.586;
const cutoutX = (screenW - cutoutW) / 2;
const cutoutY = topMaskH + (middleH - cutoutH) / 2;
const info = await common_vendor.index.getImageInfo({
src
});
const origW = info.width;
const origH = info.height;
const ratioW = origW / screenW;
const ratioH = origH / screenH;
const sx = cutoutX * ratioW;
const sy = cutoutY * ratioH;
const sWidth = cutoutW * ratioW;
const sHeight = cutoutH * ratioH;
const ctx = common_vendor.index.createCanvasContext("cropCanvas", {
enableScroll: false
});
ctx.drawImage(src, sx, sy, sWidth, sHeight, 0, 0, sWidth, sHeight);
ctx.draw(false, () => {
common_vendor.index.canvasToTempFilePath({
canvasId: "cropCanvas",
x: 0,
y: 0,
width: sWidth,
height: sHeight,
destWidth: sWidth,
destHeight: sHeight,
success: (cropRes) => {
common_vendor.index.setStorageSync("idcardPhoto", cropRes.tempFilePath);
common_vendor.index.navigateBack({
delta: 1
});
},
fail: (err) => {
common_vendor.index.__f__("error", "at pages/addoldman/camera.vue:103", "Canvas to TempFile Fail:", err);
}
});
});
},
fail: (err) => {
common_vendor.index.__f__("error", "at pages/addoldman/camera.vue:109", "Take Photo Fail:", err);
}
});
}
return (_ctx, _cache) => {
return {
a: common_vendor.o(takePhoto)
};
};
}
};
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-ac60523e"]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/addoldman/camera.js.map

View File

@ -1,4 +0,0 @@
{
"navigationBarTitleText": "照相机",
"usingComponents": {}
}

View File

@ -1 +0,0 @@
<view class="container data-v-ac60523e"><camera id="idCamera" class="camera-preview data-v-ac60523e" device-position="back" flash="off" binderror="onCameraError"/><canvas class="data-v-ac60523e" canvas-id="cropCanvas" style="width:100%;height:100%;position:absolute;top:0;left:0;display:none"></canvas><view class="mask data-v-ac60523e"><view class="mask-block top data-v-ac60523e"/><view class="mask-block middle data-v-ac60523e"><view class="side data-v-ac60523e"/><view class="cutout data-v-ac60523e"><text class="hint data-v-ac60523e">请将身份证放入此框内</text></view><view class="side data-v-ac60523e"/></view><view class="mask-block bottom data-v-ac60523e"/></view><view class="controls data-v-ac60523e"><button class="shutter data-v-ac60523e" bindtap="{{a}}">拍 照</button></view></view>

View File

@ -1,70 +0,0 @@
.container.data-v-ac60523e {
position: relative;
width: 100%;
height: 100vh;
background: #000;
}
.camera-preview.data-v-ac60523e {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.mask.data-v-ac60523e {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.mask-block.data-v-ac60523e {
width: 100%;
background: rgba(0, 0, 0, 0.5);
}
.mask-block.top.data-v-ac60523e,
.mask-block.bottom.data-v-ac60523e {
height: 25%;
}
.mask-block.middle.data-v-ac60523e {
display: flex;
flex-direction: row;
height: 50%;
}
.side.data-v-ac60523e {
flex: 1;
background: rgba(0, 0, 0, 0.5);
}
.cutout.data-v-ac60523e {
width: 100%;
height: 60%;
aspect-ratio: 1.586;
border: 2rpx dashed #fff;
position: relative;
}
.hint.data-v-ac60523e {
position: absolute;
bottom: 20rpx;
width: 100%;
text-align: center;
color: #fff;
font-size: 24rpx;
}
.controls.data-v-ac60523e {
position: absolute;
bottom: 50rpx;
width: 100%;
display: flex;
justify-content: center;
}
.shutter.data-v-ac60523e {
width: 300rpx;
height: 120rpx;
border-radius: 60rpx;
background: rgba(255, 255, 255, 0.7);
display: flex;
justify-content: center;
align-items: center;
}

View File

@ -1,82 +0,0 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const _sfc_main = /* @__PURE__ */ common_vendor.defineComponent({
__name: "CustomCamera",
setup(__props) {
const video = common_vendor.ref();
const canvas = common_vendor.ref();
const started = common_vendor.ref(false);
let stream = null;
let rafID;
async function startCamera() {
var _a;
if (!((_a = navigator.mediaDevices) == null ? void 0 : _a.getUserMedia)) {
common_vendor.index.showToast({ title: "当前浏览器不支持实时相机", icon: "none" });
return;
}
try {
stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: { ideal: "environment" } },
audio: false
});
const v = video.value;
v.srcObject = stream;
await v.play();
drawFrame();
started.value = true;
} catch (err) {
common_vendor.index.__f__("error", "at pages/camera/CustomCamera.vue:72", err);
common_vendor.index.showToast({ title: "无法获取摄像头权限", icon: "none" });
}
}
function drawFrame() {
if (!started.value)
return;
const v = video.value, c = canvas.value;
const ctx = c.getContext("2d");
if (v.videoWidth && v.videoHeight) {
if (c.width !== window.innerWidth) {
c.width = window.innerWidth;
c.height = window.innerHeight;
}
ctx.drawImage(v, 0, 0, c.width, c.height);
}
rafID = requestAnimationFrame(drawFrame);
}
function shoot() {
const dataURL = canvas.value.toDataURL("image/jpeg", 0.9);
common_vendor.index.navigateBack();
common_vendor.index.$emit("photoTaken", dataURL);
}
function close() {
cleanup();
common_vendor.index.navigateBack();
}
function cleanup() {
cancelAnimationFrame(rafID);
if (stream)
stream.getTracks().forEach((t) => t.stop());
}
common_vendor.onBeforeUnmount(cleanup);
return (_ctx, _cache) => {
return common_vendor.e({
a: started.value
}, started.value ? {} : {}, {
b: started.value
}, started.value ? {} : {}, {
c: started.value ? 1 : 0,
d: started.value
}, started.value ? {
e: common_vendor.o(close),
f: common_vendor.o(shoot)
} : {}, {
g: !started.value
}, !started.value ? {
h: common_vendor.o(startCamera)
} : {});
};
}
});
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-84a6cb98"]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/camera/CustomCamera.js.map

View File

@ -1,4 +0,0 @@
{
"navigationBarTitleText": "图像识别",
"usingComponents": {}
}

View File

@ -1 +0,0 @@
<view class="page data-v-84a6cb98"><video wx:if="{{a}}" ref="video" class="video data-v-84a6cb98" playsinline webkit-playsinline x5-playsinline muted/><canvas wx:if="{{b}}" ref="canvas" class="canvas data-v-84a6cb98"></canvas><image class="overlay data-v-84a6cb98" src="https://www.focusnu.com/media/directive/index/nu.png" style="{{'opacity:' + c}}"/><view wx:if="{{d}}" class="btn-bar data-v-84a6cb98"><button class="btn close data-v-84a6cb98" bindtap="{{e}}">关闭</button><button class="btn shoot data-v-84a6cb98" bindtap="{{f}}"></button></view><view wx:if="{{g}}" class="starter data-v-84a6cb98"><button class="btn start data-v-84a6cb98" bindtap="{{h}}">开始拍照</button></view></view>

View File

@ -1,31 +0,0 @@
.page.data-v-84a6cb98 { position:fixed; inset:0; background:#000; overflow:hidden;
}
.video.data-v-84a6cb98 { position:absolute; inset:0; object-fit:cover; z-index:1;
}
.canvas.data-v-84a6cb98 { position:absolute; inset:0; z-index:1;
}
.overlay.data-v-84a6cb98{
position:absolute; top:50%; left:50%;
width:220px; height:220px;
transform:translate(-50%,-50%); z-index:2;
pointer-events:none;
}
.btn-bar.data-v-84a6cb98{
position:absolute; bottom:60px; inset-inline:0;
display:flex; justify-content:space-around; z-index:3;
}
.btn.data-v-84a6cb98{
border:none; font-size:16px; color:#fff;
}
.close.data-v-84a6cb98{ width:100px; height:40px; border-radius:20px; background:rgba(0,0,0,.5);
}
.shoot.data-v-84a6cb98{ width:80px; height:80px; border-radius:40px; background:#fff;
}
.starter.data-v-84a6cb98{
position:absolute; inset:0; display:flex;
justify-content:center; align-items:center; z-index:3;
background:#000;
}
.start.data-v-84a6cb98{ width:140px; height:46px; border-radius:23px; background:#1aad19;
}

View File

@ -89,7 +89,7 @@
display: flex;
justify-content: flex-end;
position: fixed;
top: 35rpx;
top: 120rpx;
left: 0;
z-index: 999;
margin-top: 120rpx;
@ -474,7 +474,7 @@
}
.back-imge.data-v-1cf27b2a {
position: absolute;
top: 100rpx;
top: 180rpx;
left: 30rpx;
width: 50rpx;
height: 50rpx;

View File

@ -1,77 +1,52 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const request_index = require("../../request/index.js");
const api_loginApi = require("../../api/loginApi.js");
const _sfc_main = {
__name: "callback",
setup(__props) {
common_vendor.ref(0);
const ceshi = common_vendor.reactive({
name: "",
openid: "",
accessToken: ""
});
const getOpenId = (code) => {
const url = `${request_index.base_url}/weixin/wechat/callback?code=${encodeURIComponent(code)}`;
fetch(url).then((res) => res.json()).then((data) => {
ceshi.name = data.data.nickname;
ceshi.openid = data.data.openid;
ceshi.accessToken = data.accessToken;
common_vendor.index.setStorage({
key: "openid",
data: {
openid: data.data.openid,
accessToken: data.accessToken
}
});
getUserMessage();
}).catch((err) => {
common_vendor.index.__f__("error", "at pages/login/callback.vue:96", "❌ 获取用户信息失败:", err);
});
};
common_vendor.ref("");
const getUserMessage = () => {
const url = `${request_index.base_url}/h5Api/nuBizAdvisoryInfo/queryWeixinInfo?openId=${encodeURIComponent(ceshi.openid)}`;
fetch(url).then((res) => res.json()).then((data) => {
common_vendor.index.__f__("log", "at pages/login/callback.vue:107", "个人信息打印", data);
common_vendor.index.setStorageSync("token", data.result.token);
common_vendor.index.setStorageSync("serverUrl", data.result.serverUrl);
common_vendor.index.__f__("log", "at pages/login/callback.vue:110", "???token存储", data.result.token);
if (!data.result.tel) {
common_vendor.index.redirectTo({
url: `/pages/login/phonebumber`
const superLogin = () => {
common_vendor.index.login({
provider: "weixin",
success(res) {
api_loginApi.getOpenid(res.code).then((res2) => {
let openid = res2.data.openid;
common_vendor.index.setStorageSync("openid", openid);
api_loginApi.getMessage(openid).then((res3) => {
if (!res3.result.tel) {
common_vendor.index.redirectTo({
url: `/pages/login/index`
});
} else {
if (common_vendor.index.getStorageSync("special")) {
common_vendor.index.setStorageSync("tel", res3.result.tel);
common_vendor.index.setStorageSync("token", res3.result.token);
common_vendor.index.setStorageSync("serverUrl", res3.result.serverUrl);
common_vendor.index.redirectTo({
url: `/pages/login/special`
});
} else {
common_vendor.index.setStorageSync("tel", res3.result.tel);
common_vendor.index.setStorageSync("token", res3.result.token);
common_vendor.index.setStorageSync("serverUrl", res3.result.serverUrl);
common_vendor.index.redirectTo({
url: `/pages/login/threeselectone`
});
}
}
});
});
} else {
common_vendor.index.redirectTo({
url: `/pages/login/threeselectone`
});
common_vendor.index.setStorageSync("tel", data.result.tel);
},
fail(err) {
common_vendor.index.__f__("error", "at pages/login/callback.vue:261", "获取 code 失败:", err);
}
getjigou();
});
};
const jigouArray = common_vendor.ref([]);
const getjigou = () => {
const url = `${request_index.base_url}/sys/sysDepart/queryInstitutionsList`;
fetch(url).then((res) => res.json()).then((data) => {
jigouArray.value = [...data];
common_vendor.index.__f__("log", "at pages/login/callback.vue:176", "机构打印", jigouArray.value);
});
};
common_vendor.ref([]);
common_vendor.onLoad(() => {
var _a;
const href = window.location.href;
const queryString = (_a = href.split("?")[1]) == null ? void 0 : _a.split("#")[0];
const query = {};
if (queryString) {
queryString.split("&").forEach((pair) => {
const [key, value] = pair.split("=");
query[key] = decodeURIComponent(value);
});
}
common_vendor.index.__f__("log", "at pages/login/callback.vue:227", "解析到的 query 参数:", query);
if (query.code) {
getOpenId(query.code);
common_vendor.onLoad((options) => {
superLogin();
if (options.type) {
common_vendor.index.setStorageSync("special", true);
} else {
common_vendor.index.setStorageSync("special", false);
}
});
return (_ctx, _cache) => {

View File

@ -38,12 +38,21 @@ const _sfc_main = {
}).then((res) => {
if (res.success) {
api_loginApi.getMessage(openid).then((res2) => {
common_vendor.index.setStorageSync("tel", res2.result.tel);
common_vendor.index.setStorageSync("token", res2.result.token);
common_vendor.index.setStorageSync("serverUrl", res2.result.serverUrl);
common_vendor.index.redirectTo({
url: `/pages/login/threeselectone`
});
if (common_vendor.index.getStorageSync("special")) {
common_vendor.index.setStorageSync("tel", res2.result.tel);
common_vendor.index.setStorageSync("token", res2.result.token);
common_vendor.index.setStorageSync("serverUrl", res2.result.serverUrl);
common_vendor.index.redirectTo({
url: `/pages/login/special`
});
} else {
common_vendor.index.setStorageSync("tel", res2.result.tel);
common_vendor.index.setStorageSync("token", res2.result.token);
common_vendor.index.setStorageSync("serverUrl", res2.result.serverUrl);
common_vendor.index.redirectTo({
url: `/pages/login/threeselectone`
});
}
});
} else {
common_vendor.index.showToast({
@ -57,7 +66,7 @@ const _sfc_main = {
});
}
} else {
common_vendor.index.__f__("log", "at pages/login/code.vue:208", "验证码未输入完整");
common_vendor.index.__f__("log", "at pages/login/code.vue:207", "验证码未输入完整");
}
};
const getcode = () => {

View File

@ -14,32 +14,6 @@ const _sfc_main = {
function closeModal() {
isFadingOut.value = false;
}
const superLogin = () => {
common_vendor.index.login({
provider: "weixin",
success(res) {
api_loginApi.getOpenid(res.code).then((res2) => {
let openid = res2.data.openid;
common_vendor.index.setStorageSync("openid", openid);
api_loginApi.getMessage(openid).then((res3) => {
if (!res3.result.tel)
;
else {
common_vendor.index.setStorageSync("tel", res3.result.tel);
common_vendor.index.setStorageSync("token", res3.result.token);
common_vendor.index.setStorageSync("serverUrl", res3.result.serverUrl);
common_vendor.index.redirectTo({
url: `/pages/login/threeselectone`
});
}
});
});
},
fail(err) {
common_vendor.index.__f__("error", "at pages/login/index.vue:95", "获取 code 失败:", err);
}
});
};
const getCode = () => {
common_vendor.index.login({
provider: "weixin",
@ -64,7 +38,7 @@ const _sfc_main = {
});
},
fail(err) {
common_vendor.index.__f__("error", "at pages/login/index.vue:128", "获取 code 失败:", err);
common_vendor.index.__f__("error", "at pages/login/index.vue:115", "获取 code 失败:", err);
}
});
};
@ -80,7 +54,7 @@ const _sfc_main = {
url: "/pages/login/protocol"
});
};
common_vendor.onLoad(() => {
common_vendor.onLoad((options) => {
superLogin();
});
return (_ctx, _cache) => {

View File

@ -0,0 +1,25 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const _sfc_main = {
__name: "special",
setup(__props) {
const phonenumber = common_vendor.ref("");
const jumpto = () => {
common_vendor.index.redirectTo({
url: `/pages/login/threeselectone`
});
};
common_vendor.onLoad(() => {
phonenumber.value = common_vendor.index.getStorageSync("tel");
});
return (_ctx, _cache) => {
return {
a: common_vendor.t(phonenumber.value),
b: common_vendor.o(jumpto)
};
};
}
};
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-aaa5af2f"]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/login/special.js.map

View File

@ -0,0 +1,4 @@
{
"navigationBarTitleText": "绑定成功",
"usingComponents": {}
}

View File

@ -0,0 +1 @@
<view class="font-father data-v-aaa5af2f"><view class="font-title data-v-aaa5af2f"> 尊敬的用户,你的手机<text class="data-v-aaa5af2f">{{a}}</text>已成功绑定,欢迎加入护理单元大家庭! </view><view class="bottom-button data-v-aaa5af2f" bindtap="{{b}}"> 我的机构 </view></view>

View File

@ -0,0 +1,51 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场https://ext.dcloud.net.cn上很多三方插件均使用了这些样式变量
* 如果你是插件开发者建议你使用scss预处理并在插件代码中直接使用这些变量无需 import 这个文件方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者插件使用者你可以通过修改这些变量来定制自己的插件主题实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
/* 文字基本颜色 */
/* 背景颜色 */
/* 边框颜色 */
/* 尺寸变量 */
/* 文字尺寸 */
/* 图片尺寸 */
/* Border Radius */
/* 水平间距 */
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
.font-father.data-v-aaa5af2f {
width: 100%;
height: 100vh;
padding: 0 50rpx;
display: flex;
justify-content: center;
position: relative;
}
.font-father .font-title.data-v-aaa5af2f {
margin-top: 200rpx;
}
.font-father .bottom-button.data-v-aaa5af2f {
position: fixed;
bottom: 150rpx;
left: 50%;
transform: translateX(-50%);
width: 400rpx;
height: 100rpx;
border-radius: 50rpx;
background: linear-gradient(to right, #00C9FF, #0076FF);
color: #fff;
font-size: 33rpx;
display: flex;
justify-content: center;
align-items: center;
}

View File

@ -4,8 +4,13 @@ const _sfc_main = {
__name: "threeselectone",
setup(__props) {
common_vendor.ref(0);
const ceshi = () => {
common_vendor.index.navigateTo({
url: `/pages/login/workjoin?type=1`
});
};
const jumpToindex = () => {
common_vendor.index.redirectTo({
common_vendor.index.navigateTo({
url: `/pages/index/index`
});
};
@ -28,7 +33,8 @@ const _sfc_main = {
a: common_vendor.t(phone.value),
b: common_vendor.o(jumpToindex),
c: common_vendor.o(gotoadd),
d: common_vendor.o(close)
d: common_vendor.o(ceshi),
e: common_vendor.o(close)
};
};
}

View File

@ -1 +1 @@
<view class="login-container data-v-83beea56"><image class="photo-imge data-v-83beea56" src="https://www.focusnu.com/media/directive/index/indexgif.gif" mode="widthFix" lazy-load="false"/><view class="card-title data-v-83beea56"> 恭喜您已成功绑定手机 <text class="data-v-83beea56" style="color:#01A9FF">{{a}}</text>,现在您可以: </view><view class="card data-v-83beea56"><view class="card-left data-v-83beea56"><view class="card-weight data-v-83beea56"> 长者入住 </view><view class="card-text data-v-83beea56"> 护理单元日常护理涵盖生活照料、健康监测、康复护理及心理关怀,为老人提供贴心照护服务。 </view><view class="white-button data-v-83beea56" bindtap="{{b}}"> 申请入住 </view></view><view class="card-right data-v-83beea56"><image class="right-imge data-v-83beea56" src="https://www.focusnu.com/media/directive/login/old.png"/></view></view><view class="card data-v-83beea56"><view class="card-left data-v-83beea56"><view class="card-weight data-v-83beea56"> 员工入驻 </view><view class="card-text data-v-83beea56"> 护理员严格按标准流程,定时为失能老人开展床旁照护,用专业与温情守护老人生活与健康。 </view><view class="data-v-83beea56" style="display:flex"><view class="white-button data-v-83beea56"> 申请入驻 </view></view></view><view class="card-right data-v-83beea56"><image class="right-imge data-v-83beea56" src="https://www.focusnu.com/media/directive/login/yuangong.png"/></view></view><view class="card data-v-83beea56" style="height:330rpx"><view class="card-left data-v-83beea56"><view class="card-weight data-v-83beea56"> 机构加盟 </view><view class="card-text data-v-83beea56"> 加盟我们,共享银发经济红利!依托成熟运营体系,标准化服务流程降低人力成本及管理开支,背靠品牌资源,助力企业快速实现营收增长。 </view><view class="data-v-83beea56" style="display:flex"><view class="white-button data-v-83beea56" bindtap="{{c}}"> 申请加盟 </view></view></view><view class="card-right data-v-83beea56"><image class="right-imge data-v-83beea56" src="https://www.focusnu.com/media/directive/login/gongsi.png"/></view></view><view class="blue-button data-v-83beea56" bindtap="{{d}}"> 关闭 </view></view>
<view class="login-container data-v-83beea56"><image class="photo-imge data-v-83beea56" src="https://www.focusnu.com/media/directive/index/indexgif.gif" mode="widthFix" lazy-load="false"/><view class="card-title data-v-83beea56"> 恭喜您已成功绑定手机 <text class="data-v-83beea56" style="color:#01A9FF">{{a}}</text>,现在您可以: </view><view class="card data-v-83beea56"><view class="card-left data-v-83beea56"><view class="card-weight data-v-83beea56"> 长者入住 </view><view class="card-text data-v-83beea56"> 护理单元日常护理涵盖生活照料、健康监测、康复护理及心理关怀,为老人提供贴心照护服务。 </view><view class="white-button data-v-83beea56" bindtap="{{b}}"> 申请入住 </view></view><view class="card-right data-v-83beea56"><image class="right-imge data-v-83beea56" src="https://www.focusnu.com/media/directive/login/old.png"/></view></view><view class="card data-v-83beea56"><view class="card-left data-v-83beea56"><view class="card-weight data-v-83beea56"> 员工入驻 </view><view class="card-text data-v-83beea56"> 护理员严格按标准流程,定时为失能老人开展床旁照护,用专业与温情守护老人生活与健康。 </view><view class="data-v-83beea56" style="display:flex"><view class="white-button data-v-83beea56"> 申请入驻 </view></view></view><view class="card-right data-v-83beea56"><image class="right-imge data-v-83beea56" src="https://www.focusnu.com/media/directive/login/yuangong.png"/></view></view><view class="card data-v-83beea56" style="height:330rpx"><view class="card-left data-v-83beea56"><view class="card-weight data-v-83beea56"> 机构加盟 </view><view class="card-text data-v-83beea56"> 加盟我们,共享银发经济红利!依托成熟运营体系,标准化服务流程降低人力成本及管理开支,背靠品牌资源,助力企业快速实现营收增长。 </view><view class="data-v-83beea56" style="display:flex"><view class="white-button data-v-83beea56" bindtap="{{c}}"> 申请加盟 </view></view></view><view class="card-right data-v-83beea56"><image class="right-imge data-v-83beea56" src="https://www.focusnu.com/media/directive/login/gongsi.png" bindtap="{{d}}"/></view></view><view class="blue-button data-v-83beea56" bindtap="{{e}}"> 关闭 </view></view>

View File

@ -6,16 +6,6 @@ const _sfc_main = {
setup(__props) {
const type = common_vendor.ref(0);
const workArray = common_vendor.ref([]);
const isRefreshing = common_vendor.ref(false);
const onRefresh = () => {
common_vendor.index.__f__("log", "at pages/login/workjoin.vue:70", "下拉刷新被触发");
isRefreshing.value = true;
pages_addjigou_api_addjigou.getMessageList(common_vendor.index.getStorageSync("tel")).then((res) => {
workArray.value = [];
workArray.value = res.result;
isRefreshing.value = false;
});
};
common_vendor.onLoad((options) => {
type.value = options.type || "";
if (type.value) {
@ -24,10 +14,17 @@ const _sfc_main = {
});
}
});
common_vendor.onPullDownRefresh(() => {
pages_addjigou_api_addjigou.getMessageList(common_vendor.index.getStorageSync("tel")).then((res) => {
workArray.value = res.result;
common_vendor.index.stopPullDownRefresh();
});
});
const goback = () => {
common_vendor.index.navigateBack();
};
const again = (item) => {
common_vendor.index.__f__("log", "at pages/login/workjoin.vue:85", "????", item);
common_vendor.index.setStorageSync("baddata", item);
common_vendor.index.setStorageSync("specicalid", item.id);
common_vendor.index.navigateTo({
@ -58,14 +55,6 @@ const _sfc_main = {
g: common_vendor.o(($event) => jumpToAll(item), index),
h: index
});
}),
f: isRefreshing.value,
g: common_vendor.o(onRefresh),
h: common_vendor.o(() => {
}),
i: common_vendor.o(() => {
}),
j: common_vendor.o(() => {
})
};
};

View File

@ -1,4 +1,5 @@
{
"navigationBarTitleText": "员工入驻",
"enablePullDownRefresh": true,
"usingComponents": {}
}

View File

@ -1 +1 @@
<view class="login-container data-v-808c8183"><image class="photo-imge data-v-808c8183" src="https://www.focusnu.com/media/directive/index/workjoin/bgc.png"/><image class="old-imge data-v-808c8183" src="https://www.focusnu.com/media/directive/index/workjoin/ren.png"/><view class="under-container data-v-808c8183" catchtouchstart="{{h}}" catchtouchmove="{{i}}" catchtouchend="{{j}}"><view class="white-card data-v-808c8183"><image class="left-img data-v-808c8183" src="{{a}}"/><view class="card-font data-v-808c8183"><view class="data-v-808c8183" style="font-size:30rpx;font-weight:600;margin:20rpx 0 30rpx 0">{{b}}</view><view class="data-v-808c8183" style="color:#666666;font-size:25rpx"> 护理院日常护理涵盖生活照料、健康监测、康复护理及心理关怀,为老人提供贴心照护。 </view></view></view><view class="white-ball data-v-808c8183" bindtap="{{c}}"><image class="ball-imge data-v-808c8183" src="https://www.focusnu.com/media/directive/index/workjoin/x.png"/></view><view class="shu-father data-v-808c8183"><view class="shu data-v-808c8183"></view><view class="shu-font data-v-808c8183">{{d}}</view></view><view class="under-scroll data-v-808c8183"><scroll-view class="data-v-808c8183" scroll-y refresher-enabled refresher-triggered="{{f}}" bindrefresherrefresh="{{g}}" style="height:100%;width:100%"><view wx:for="{{e}}" wx:for-item="item" wx:key="h" class="data-v-808c8183"><view class="white-small data-v-808c8183"><view class="data-v-808c8183" style="width:100%;margin-bottom:80rpx;font-size:25rpx">{{item.a}}申请入驻加盟护理单元,提交时间:{{item.b}},审核结果:{{item.c}} {{item.d}}</view><view class="button-heng data-v-808c8183"><view wx:if="{{item.e}}" class="blue-button data-v-808c8183" bindtap="{{item.f}}"> 修改申请 </view><view class="white-button data-v-808c8183" bindtap="{{item.g}}"> 查看详情 </view></view></view></view></scroll-view></view></view></view>
<view class="login-container data-v-808c8183"><view class="white-card data-v-808c8183"><image class="left-img data-v-808c8183" src="{{a}}"/><view class="card-font data-v-808c8183"><view class="data-v-808c8183" style="font-size:30rpx;font-weight:600;margin:20rpx 0 30rpx 0">{{b}}</view><view class="data-v-808c8183" style="color:#666666;font-size:25rpx"> 护理院日常护理涵盖生活照料、健康监测、康复护理及心理关怀,为老人提供贴心照护。 </view></view></view><view class="white-ball data-v-808c8183" bindtap="{{c}}"><image class="ball-imge data-v-808c8183" src="https://www.focusnu.com/media/directive/index/workjoin/x.png"/></view><view class="shu-father data-v-808c8183"><view class="shu data-v-808c8183"></view><view class="shu-font data-v-808c8183">{{d}}</view></view><view class="under-scroll data-v-808c8183"><view wx:for="{{e}}" wx:for-item="item" wx:key="h" class="data-v-808c8183"><view class="white-small data-v-808c8183"><view class="data-v-808c8183" style="width:100%;margin-bottom:80rpx;font-size:25rpx">{{item.a}}申请入驻加盟护理单元,提交时间:{{item.b}},审核结果:{{item.c}} {{item.d}}</view><view class="button-heng data-v-808c8183"><view wx:if="{{item.e}}" class="blue-button data-v-808c8183" bindtap="{{item.f}}"> 修改申请 </view><view class="white-button data-v-808c8183" bindtap="{{item.g}}"> 查看详情 </view></view></view></view></view></view>

View File

@ -61,21 +61,9 @@
width: 550rpx;
height: 750rpx;
}
.login-container .under-container.data-v-808c8183 {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: 100%;
background-color: #eceef4;
box-shadow: 10rpx 10rpx 20rpx rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
align-items: center;
z-index: 1;
}
.white-card.data-v-808c8183 {
margin-top: 30rpx;
margin-top: 200rpx;
margin-left: 3%;
width: 94%;
background-color: #fff;
height: 320rpx;
@ -96,7 +84,7 @@
.white-ball.data-v-808c8183 {
position: absolute;
right: 60rpx;
top: 60rpx;
top: 220rpx;
width: 75rpx;
height: 75rpx;
border-radius: 50%;

View File

@ -2,7 +2,8 @@
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
"projectname": "weixin-officialaccount",
"setting": {
"compileHotReLoad": true
"compileHotReLoad": true,
"urlCheck": false
},
"libVersion": "3.8.9"
}

View File

@ -56,7 +56,6 @@ const request = (params) => {
}
},
fail(err) {
common_vendor.index.__f__("log", "at request/index.js:64", err);
if (err.errMsg.indexOf("request:fail") !== -1) {
common_vendor.index.showToast({
title: "网络异常",
@ -70,8 +69,6 @@ const request = (params) => {
});
}
reject(err);
},
complete() {
}
});
}).catch(() => {

View File

@ -0,0 +1,582 @@
"use strict";
const common_vendor = require("../../../../common/vendor.js");
const block0 = (Component2) => {
if (!Component2.wxsCallMethods) {
Component2.wxsCallMethods = [];
}
Component2.wxsCallMethods.push("dataChange");
};
const AREA_SIZE = 75;
const IMG_SIZE = 300;
const _sfc_main = {
name: "qf-image-cropper",
options: {
// 表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响
styleIsolation: "isolated"
},
props: {
/** 图片资源地址 */
src: {
type: String,
default: ""
},
/** 裁剪宽度有些平台或设备对于canvas的尺寸有限制过大可能会导致无法正常绘制 */
width: {
type: Number,
default: IMG_SIZE
},
/** 裁剪高度有些平台或设备对于canvas的尺寸有限制过大可能会导致无法正常绘制 */
height: {
type: Number,
default: IMG_SIZE
},
/** 是否绘制裁剪区域边框 */
showBorder: {
type: Boolean,
default: true
},
/** 是否绘制裁剪区域网格参考线 */
showGrid: {
type: Boolean,
default: true
},
/** 是否展示四个支持伸缩的角 */
showAngle: {
type: Boolean,
default: true
},
/** 裁剪区域最小缩放倍数 */
areaScale: {
type: Number,
default: 0.3
},
/** 图片最小缩放倍数 */
minScale: {
type: Number,
default: 1
},
/** 图片最大缩放倍数 */
maxScale: {
type: Number,
default: 5
},
/** 检查图片位置是否超出裁剪边界,如果超出则会矫正位置 */
checkRange: {
type: Boolean,
default: true
},
/** 生成图片背景色:如果裁剪区域没有完全包含在图片中时,不设置该属性生成图片存在一定的透明块 */
backgroundColor: {
type: String
},
/** 是否有回弹效果:当 checkRange 为 true 时有效,拖动时可以拖出边界,释放时会弹回边界 */
bounce: {
type: Boolean,
default: true
},
/** 是否支持翻转 */
rotatable: {
type: Boolean,
default: true
},
/** 是否支持逆向翻转 */
reverseRotatable: {
type: Boolean,
default: false
},
/** 是否支持从本地选择素材 */
choosable: {
type: Boolean,
default: true
},
/** 是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题 */
gpu: {
type: Boolean,
default: false
},
/** 四个角尺寸单位px */
angleSize: {
type: Number,
default: 20
},
/** 四个角边框宽度单位px */
angleBorderWidth: {
type: Number,
default: 2
},
zIndex: {
type: [Number, String]
},
/** 裁剪图片圆角半径单位px */
radius: {
type: Number,
default: 0
},
/** 生成文件的类型,只支持 'jpg' 或 'png'。默认为 'png' */
fileType: {
type: String,
default: "png"
},
/**
* 图片从绘制到生成所需时间单位ms
* 微信小程序平台使用 `Canvas 2D` 绘制时有效
* 如绘制大图或出现裁剪图片空白等情况应适当调大该值 `Canvas 2d` 采用同步绘制需自己把控绘制完成时间
*/
delay: {
type: Number,
default: 1e3
}
},
emits: ["crop"],
data() {
return {
// 用不同 id 使 v-for key 不重复
maskList: [
{ id: "crop-mask-block-1" },
{ id: "crop-mask-block-2" },
{ id: "crop-mask-block-3" },
{ id: "crop-mask-block-4" }
],
gridList: [
{ id: "crop-grid-1" },
{ id: "crop-grid-2" },
{ id: "crop-grid-3" },
{ id: "crop-grid-4" }
],
angleList: [
{ id: "crop-angle-1" },
{ id: "crop-angle-2" },
{ id: "crop-angle-3" },
{ id: "crop-angle-4" }
],
/** 本地缓存的图片路径 */
imgSrc: "",
/** 图片的裁剪宽度 */
imgWidth: IMG_SIZE,
/** 图片的裁剪高度 */
imgHeight: IMG_SIZE,
/** 裁剪区域最大宽度所占屏幕宽度百分比 */
widthPercent: AREA_SIZE,
/** 裁剪区域最大高度所占屏幕宽度百分比 */
heightPercent: AREA_SIZE,
/** 裁剪区域布局信息 */
area: {},
/** 未被缩放过的图片宽 */
oldWidth: 0,
/** 未被缩放过的图片高 */
oldHeight: 0,
/** 系统信息 */
sys: common_vendor.index.getSystemInfoSync(),
scaleWidth: 0,
scaleHeight: 0,
rotate: 0,
offsetX: 0,
offsetY: 0,
use2d: false,
canvansWidth: 0,
canvansHeight: 0
// imageStyles: {},
// maskStylesList: [{}, {}, {}, {}],
// borderStyles: {},
// gridStylesList: [{}, {}, {}, {}],
// angleStylesList: [{}, {}, {}, {}],
// circleBoxStyles: {},
// circleStyles: {},
};
},
computed: {
initData() {
return {
timestamp: (/* @__PURE__ */ new Date()).getTime(),
area: {
...this.area,
bounce: this.bounce,
showBorder: this.showBorder,
showGrid: this.showGrid,
showAngle: this.showAngle,
angleSize: this.angleSize,
angleBorderWidth: this.angleBorderWidth,
minScale: this.areaScale,
widthPercent: this.widthPercent,
heightPercent: this.heightPercent,
radius: this.radius,
checkRange: this.checkRange,
zIndex: +this.zIndex || 0
},
sys: this.sys,
img: {
minScale: this.minScale,
maxScale: this.maxScale,
src: this.imgSrc,
width: this.oldWidth,
height: this.oldHeight,
oldWidth: this.oldWidth,
oldHeight: this.oldHeight,
gpu: this.gpu
}
};
},
imgProps() {
return {
width: this.width,
height: this.height,
src: this.src
};
}
},
watch: {
imgProps: {
handler(val, oldVal) {
this.imgWidth = Number(val.width) || IMG_SIZE;
this.imgHeight = Number(val.height) || IMG_SIZE;
let use2d = true;
let canvansWidth = this.imgWidth;
let canvansHeight = this.imgHeight;
let size = Math.max(canvansWidth, canvansHeight);
let scalc = 1;
if (size > 1365) {
scalc = 1365 / size;
}
this.canvansWidth = canvansWidth * scalc;
this.canvansHeight = canvansHeight * scalc;
this.use2d = use2d;
this.initArea();
const src = val.src || this.imgSrc;
src && this.initImage(src, oldVal === void 0);
},
immediate: true
}
},
methods: {
/** 提供给wxs调用用来接收图片变更数据 */
dataChange(e) {
this.scaleWidth = e.width;
this.scaleHeight = e.height;
this.rotate = e.rotate;
this.offsetX = e.x;
this.offsetY = e.y;
},
/** 初始化裁剪区域布局信息 */
initArea() {
this.sys.offsetBottom = common_vendor.index.upx2px(100) + this.sys.safeAreaInsets.bottom;
this.sys.windowTop = 0;
this.sys.navigation = true;
let wp = this.widthPercent;
let hp = this.heightPercent;
if (this.imgWidth > this.imgHeight) {
hp = hp * this.imgHeight / this.imgWidth;
} else if (this.imgWidth < this.imgHeight) {
wp = wp * this.imgWidth / this.imgHeight;
}
const size = this.sys.windowWidth > this.sys.windowHeight ? this.sys.windowHeight : this.sys.windowWidth;
const width = size * wp / 100;
const height = size * hp / 100;
const left = (this.sys.windowWidth - width) / 2;
const right = left + width;
const top = (this.sys.windowHeight + this.sys.windowTop - this.sys.offsetBottom - height) / 2;
const bottom = this.sys.windowHeight + this.sys.windowTop - this.sys.offsetBottom - top;
this.area = { width, height, left, right, top, bottom };
this.scaleWidth = width;
this.scaleHeight = height;
},
/** 从本地选取图片 */
chooseImage(options) {
if (common_vendor.index.chooseMedia) {
common_vendor.index.chooseMedia({
...options,
count: 1,
mediaType: ["image"],
success: (res) => {
this.resetData();
this.initImage(res.tempFiles[0].tempFilePath);
}
});
return;
}
common_vendor.index.chooseImage({
...options,
count: 1,
success: (res) => {
this.resetData();
this.initImage(res.tempFiles[0].path);
}
});
},
/** 重置数据 */
resetData() {
this.imgSrc = "";
this.rotate = 0;
this.offsetX = 0;
this.offsetY = 0;
this.initArea();
},
/**
* 初始化图片信息
* @param {String} url 图片链接
*/
initImage(url, isFirst) {
common_vendor.index.getImageInfo({
src: url,
success: async (res) => {
if (isFirst && this.src === url)
await new Promise((resolve) => setTimeout(resolve, 50));
this.imgSrc = res.path;
let scale = res.width / res.height;
let areaScale = this.area.width / this.area.height;
if (scale > 1) {
if (scale >= areaScale) {
this.scaleWidth = this.scaleHeight / res.height * this.scaleWidth * (res.width / this.scaleWidth);
} else {
this.scaleHeight = res.height * this.scaleWidth / res.width;
}
} else {
if (scale <= areaScale) {
this.scaleHeight = this.scaleWidth / res.width * this.scaleHeight / (this.scaleHeight / res.height);
} else {
this.scaleWidth = res.width * this.scaleHeight / res.height;
}
}
this.oldWidth = +this.scaleWidth.toFixed(2);
this.oldHeight = +this.scaleHeight.toFixed(2);
},
fail: (err) => {
common_vendor.index.__f__("error", "at uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.vue:437", err);
}
});
},
/**
* 剪切图片圆角
* @param {Object} ctx canvas 的绘图上下文对象
* @param {Number} radius 圆角半径
* @param {Number} scale 生成图片的实际尺寸与截取区域比
* @param {Function} drawImage 执行剪切时所调用的绘图方法入参为是否执行了剪切
*/
drawClipImage(ctx, radius, scale, drawImage) {
if (radius > 0) {
ctx.save();
ctx.beginPath();
const w = this.canvansWidth;
const h = this.canvansHeight;
if (w === h && radius >= w / 2) {
ctx.arc(w / 2, h / 2, w / 2, 0, 2 * Math.PI);
} else {
if (w !== h) {
radius = Math.min(w / 2, h / 2, radius);
}
ctx.moveTo(radius, 0);
ctx.arcTo(w, 0, w, h, radius);
ctx.arcTo(w, h, 0, h, radius);
ctx.arcTo(0, h, 0, 0, radius);
ctx.arcTo(0, 0, w, 0, radius);
ctx.closePath();
}
ctx.clip();
drawImage && drawImage(true);
ctx.restore();
} else {
drawImage && drawImage(false);
}
},
/**
* 旋转图片
* @param {Object} ctx canvas 的绘图上下文对象
* @param {Number} rotate 旋转角度
* @param {Number} scale 生成图片的实际尺寸与截取区域比
*/
drawRotateImage(ctx, rotate, scale) {
if (rotate !== 0) {
const x = this.scaleWidth * scale / 2;
const y = this.scaleHeight * scale / 2;
ctx.translate(x, y);
ctx.rotate(rotate * Math.PI / 180);
ctx.translate(-x, -y);
}
},
drawImage(ctx, image, callback) {
const scale = this.canvansWidth / this.area.width;
if (this.backgroundColor) {
if (ctx.setFillStyle)
ctx.setFillStyle(this.backgroundColor);
else
ctx.fillStyle = this.backgroundColor;
ctx.fillRect(0, 0, this.canvansWidth, this.canvansHeight);
}
this.drawClipImage(ctx, this.radius, scale, () => {
this.drawRotateImage(ctx, this.rotate, scale);
const r = this.rotate / 90;
ctx.drawImage(
image,
[
this.offsetX - this.area.left,
this.offsetY - this.area.top,
-(this.offsetX - this.area.left),
-(this.offsetY - this.area.top)
][r] * scale,
[
this.offsetY - this.area.top,
-(this.offsetX - this.area.left),
-(this.offsetY - this.area.top),
this.offsetX - this.area.left
][r] * scale,
this.scaleWidth * scale,
this.scaleHeight * scale
);
});
},
/**
* 绘图
* @param {Object} canvas
* @param {Object} ctx canvas 的绘图上下文对象
* @param {String} src 图片路径
* @param {Function} callback 开始绘制时回调
*/
draw2DImage(canvas, ctx, src, callback) {
if (canvas) {
const image = canvas.createImage();
image.onload = () => {
this.drawImage(ctx, image);
callback && setTimeout(callback, this.delay);
};
image.onerror = (err) => {
common_vendor.index.__f__("error", "at uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.vue:540", err);
common_vendor.index.hideLoading();
};
image.src = src;
} else {
this.drawImage(ctx, src);
setTimeout(() => {
ctx.draw(false, callback);
}, 200);
}
},
/**
* 画布转图片到本地缓存
* @param {Object} canvas
* @param {String} canvasId
*/
canvasToTempFilePath(canvas, canvasId) {
common_vendor.index.canvasToTempFilePath({
canvas,
canvasId,
x: 0,
y: 0,
width: this.canvansWidth,
height: this.canvansHeight,
destWidth: this.imgWidth,
// 必要,保证生成图片宽度不受设备分辨率影响
destHeight: this.imgHeight,
// 必要,保证生成图片高度不受设备分辨率影响
fileType: this.fileType,
// 目标文件的类型默认png
success: (res) => {
this.handleImage(res.tempFilePath);
},
fail: (err) => {
common_vendor.index.hideLoading();
common_vendor.index.showToast({ title: "裁剪失败,生成图片异常!", icon: "none" });
}
}, this);
},
/** 确认裁剪 */
cropClick() {
common_vendor.index.showLoading({ title: "裁剪中...", mask: true });
if (!this.use2d) {
const ctx = common_vendor.index.createCanvasContext("imgCanvas", this);
ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight);
this.draw2DImage(null, ctx, this.imgSrc, () => {
this.canvasToTempFilePath(null, "imgCanvas");
});
return;
}
const query = common_vendor.index.createSelectorQuery().in(this);
query.select("#imgCanvas").fields({ node: true, size: true }).exec((res) => {
const canvas = res[0].node;
const dpr = common_vendor.index.getSystemInfoSync().pixelRatio;
canvas.width = res[0].width * dpr;
canvas.height = res[0].height * dpr;
const ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr);
ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight);
this.draw2DImage(canvas, ctx, this.imgSrc, () => {
this.canvasToTempFilePath(canvas);
});
});
},
handleImage(tempFilePath) {
common_vendor.index.hideLoading();
this.$emit("crop", { tempFilePath });
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: $data.use2d
}, $data.use2d ? {
b: `${$data.canvansWidth}px`,
c: `${$data.canvansHeight}px`
} : {
d: `${$data.canvansWidth}px`,
e: `${$data.canvansHeight}px`
}, {
f: $data.imgSrc
}, $data.imgSrc ? {
g: $data.imgSrc
} : {}, {
h: common_vendor.f($data.maskList, (item, index, i0) => {
return {
a: item.id,
b: item.id
};
}),
i: $props.showBorder
}, $props.showBorder ? {} : {}, {
j: $props.radius > 0
}, $props.radius > 0 ? {} : {}, {
k: $props.showGrid
}, $props.showGrid ? {
l: common_vendor.f($data.gridList, (item, index, i0) => {
return {
a: item.id,
b: item.id
};
})
} : {}, {
m: $props.showAngle
}, $props.showAngle ? {
n: common_vendor.f($data.angleList, (item, index, i0) => {
return {
a: item.id,
b: item.id
};
}),
o: common_vendor.s({
width: `${$props.angleSize}px`,
height: `${$props.angleSize}px`
})
} : {}, {
p: $options.initData,
q: ($props.rotatable || $props.reverseRotatable) && !!$data.imgSrc
}, ($props.rotatable || $props.reverseRotatable) && !!$data.imgSrc ? common_vendor.e({
r: $props.reverseRotatable
}, $props.reverseRotatable ? {} : {}, {
s: $props.rotatable
}, $props.rotatable ? {} : {}) : {}, {
t: !$props.choosable
}, !$props.choosable ? {
v: common_vendor.o((...args) => $options.cropClick && $options.cropClick(...args))
} : !!$data.imgSrc ? {
x: common_vendor.o((...args) => $options.chooseImage && $options.chooseImage(...args)),
y: common_vendor.o((...args) => $options.cropClick && $options.cropClick(...args))
} : {
z: common_vendor.o((...args) => $options.chooseImage && $options.chooseImage(...args))
}, {
w: !!$data.imgSrc,
A: $options.initData.area.zIndex + 99,
B: $props.zIndex
});
}
if (typeof block0 === "function")
block0(_sfc_main);
const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-7129956f"]]);
wx.createComponent(Component);
//# sourceMappingURL=../../../../../.sourcemap/mp-weixin/uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.js.map

View File

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@ -0,0 +1,730 @@
<wxs module="cropper">
/**
* 图片编辑器-手势监听
* 1. wxs 暂不支持 es6 语法
* 2. 支持编译到微信小程序、QQ小程序、app-vue、H5上uni-app 2.2.5及以上版本)
*/
/** 图片偏移量 */
var offset = { x: 0, y: 0 };
/** 图片缩放比例 */
var scale = 1;
/** 图片最小缩放比例 */
var minScale = 1;
/** 图片旋转角度 */
var rotate = 0;
/** 触摸点 */
var touches = [];
/** 图片布局信息 */
var img = {};
/** 系统信息 */
var sys = {};
/** 裁剪区域布局信息 */
var area = {};
/** 触摸行为类型 */
var touchType = '';
/** 操作角的位置 */
var activeAngle = 0;
/** 裁剪区域布局信息偏移量 */
var areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
/** 容错值 */
var fault = 0.000001;
/**
* 获取a、b两数中的最小正数
* @param a
* @param b
*/
function minimum(a, b) {
if (a > 0 && b < 0) return a;
if (a < 0 && b > 0) return b;
if (a > 0 && b > 0) return Math.min(a, b);
return 0;
}
/**
* 在容错访问内获取n近似值
* @param n
*/
function num(n) {
var m = parseFloat((n).toFixed(6));
return m === fault || m === -fault ? 0 : m;
}
/**
* 比较a值在容错值范围内是否等于b值
* @param a
* @param b
*/
function equalsByFault(a, b) {
return Math.abs(a - b) <= fault;
}
/**
* 比较a值在容错值范围内是否小于b值
* @param a
* @param b
*/
function lessThanByFault(a, b) {
var c = a - b;
return c < 0 ? c < -fault : c < fault;
}
/**
* 验证并获取有效最大值
* @param v
* @param max
* @param isInclude
* @param x
* @param y
* @param rate
* @returns
*/
function validMax(v, max, isInclude, x, y, rate) {
if(typeof max === 'number') {
if(isInclude && equalsByFault(max, y)) { // 宽高不等时x轴用y轴值要做等比例转换
var n = num(max * rate);
if (n <= x) return n; // 转化后值在x轴最大值范围内
return x; // 转化后值超出x轴最大值范围则用最大值
}
return max;
}
return v;
}
/**
* 计算两点间距
* @param {Object} touches 触摸点信息
*/
function getDistanceByTouches(touches) {
// 根据勾股定理求两点间距离
var a = touches[1].pageX - touches[0].pageX;
var b = touches[1].pageY - touches[0].pageY;
var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
// 求两点间的中点坐标
// 1. a、b可能为负值
// 2. 在求a、b时如用touches[1]减touches[0]则求中点坐标也得用touches[1]减a/2、b/2
// 3. 同理在求a、b时也可用touches[0]减touches[1]则求中点坐标也得用touches[0]减a/2、b/2
var x = touches[1].pageX - a / 2;
var y = touches[1].pageY - b / 2;
return { c, x, y };
};
/**
* 修正取值
* @param {Object} a
* @param {Object} b
* @param {Object} c
* @param {Object} reverse 是否反向
*/
function correctValue(a, b, c, reverse) {
return num(reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c));
}
/**
* 旋转90°或270°时检查边界限制 x、y 拖动范围,禁止滑出边界
* @param {Object} e 点坐标
* @param {Object} xReverse x是否反向
* @param {Object} yReverse y是否反向
*/
function checkRotateRange(e, xReverse, yReverse) {
var o = num((img.height - img.width) / 2); // 宽高差值一半
return {
x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, xReverse),
y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, yReverse)
};
}
/**
* 检查边界:限制 x、y 拖动范围,禁止滑出边界
* @param {Object} e 点坐标
*/
function checkRange(e) {
var r = rotate / 90 % 2;
if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移
if (area.width === area.height) {
return checkRotateRange(e, img.height < area.height, img.width < area.width);
}
var isInclude = img.height < area.width && img.width < area.height; // 图片是否包含在裁剪区域内
if (img.width < area.height || img.height < area.width) {
if (area.width < area.height && img.width < img.height) {
return isInclude
? checkRotateRange(e, area.width < area.height, area.width < area.height)
: checkRotateRange(e, false, true);
}
if (area.height < area.width && img.height < img.width) {
return isInclude
? checkRotateRange(e, area.height < area.width, area.height < area.width)
: checkRotateRange(e, true, false);
}
}
if (img.height >= area.width && img.width >= area.height) {
return checkRotateRange(e, false, false);
}
if (isInclude) {
return area.height < area.width
? checkRotateRange(e, true, true)
: checkRotateRange(e, area.width < area.height, area.width < area.height);
}
if (img.height < area.width && !img.width < area.height) {
return checkRotateRange(e, true, false);
}
if (!img.height < area.width && img.width < area.height) {
return checkRotateRange(e, false, true);
}
return checkRotateRange(e, img.height < area.height, img.width < area.width);
}
return {
x: correctValue(e.x, -img.width + area.width + area.left, area.left, img.width < area.width),
y: correctValue(e.y, -img.height + area.height + area.top, area.top, img.height < area.height)
};
};
/**
* 变更图片布局信息
* @param {Object} e 布局信息
*/
function changeImageRect(e) {
offset.x += e.x || 0;
offset.y += e.y || 0;
var image = e.instance.selectComponent('.crop-image');
if(e.check && area.checkRange) { // 检查边界
var point = checkRange(offset);
if(offset.x !== point.x || offset.y !== point.y) {
offset = point;
}
}
// image.setStyle({
// width: img.width + 'px',
// height: img.height + 'px',
// transform: 'translate(' + offset.x + 'px, ' + offset.y + 'px) rotate(' + rotate +'deg)'
// });
var ox = (img.width - img.oldWidth) / 2;
var oy = (img.height - img.oldHeight) / 2;
image.setStyle({
width: img.oldWidth + 'px',
height: img.oldHeight + 'px',
transform: (img.gpu ? 'translateZ(0) ' : '') + 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px) rotate(' + rotate +'deg) scale(' + scale + ')'
});
e.instance.callMethod('dataChange', {
width: img.width,
height: img.height,
x: offset.x,
y: offset.y,
rotate: rotate
});
};
/**
* 变更裁剪区域布局信息
* @param {Object} e 布局信息
*/
function changeAreaRect(e) {
// 变更蒙版样式
var masks = e.instance.selectAllComponents('.crop-mask-block');
var maskStyles = [
{
left: 0,
width: (area.left + areaOffset.left) + 'px',
top: 0,
bottom: 0,
'z-index': area.zIndex + 2
},
{
left: (area.right + areaOffset.right) + 'px',
right: 0,
top: 0,
bottom: 0,
'z-index': area.zIndex + 2
},
{
left: (area.left + areaOffset.left) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
top: 0,
height: (area.top + areaOffset.top) + 'px',
'z-index': area.zIndex + 2
},
{
left: (area.left + areaOffset.left) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
top: (area.bottom + areaOffset.bottom) + 'px',
// height: (area.top - areaOffset.bottom + sys.offsetBottom) + 'px',
bottom: 0,
'z-index': area.zIndex + 2
}
];
var len = masks.length;
for (var i = 0; i < len; i++) {
masks[i].setStyle(maskStyles[i]);
}
// 变更边框样式
if(area.showBorder) {
var border = e.instance.selectComponent('.crop-border');
border.setStyle({
left: (area.left + areaOffset.left) + 'px',
top: (area.top + areaOffset.top) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 3
});
}
// 变更参考线样式
if(area.showGrid) {
var grids = e.instance.selectAllComponents('.crop-grid');
var gridStyles = [
{
'border-width': '1px 0 0 0',
left: (area.left + areaOffset.left) + 'px',
right: (area.right + areaOffset.right) + 'px',
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) / 3 - 0.5) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '1px 0 0 0',
left: (area.left + areaOffset.left) + 'px',
right: (area.right + areaOffset.right) + 'px',
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) * 2 / 3 - 0.5) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 1px 0 0',
top: (area.top + areaOffset.top) + 'px',
bottom: (area.bottom + areaOffset.bottom) + 'px',
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) / 3 - 0.5) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 1px 0 0',
top: (area.top + areaOffset.top) + 'px',
bottom: (area.bottom + areaOffset.bottom) + 'px',
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) * 2 / 3 - 0.5) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 3
}
];
var len = grids.length;
for (var i = 0; i < len; i++) {
grids[i].setStyle(gridStyles[i]);
}
}
// 变更四个伸缩角样式
if(area.showAngle) {
var angles = e.instance.selectAllComponents('.crop-angle');
var angleStyles = [
{
'border-width': area.angleBorderWidth + 'px 0 0 ' + area.angleBorderWidth + 'px',
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0 0',
left: (area.right + areaOffset.right - area.angleSize) + 'px',
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px',
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0',
left: (area.right + areaOffset.right - area.angleSize) + 'px',
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
'z-index': area.zIndex + 3
}
];
var len = angles.length;
for (var i = 0; i < len; i++) {
angles[i].setStyle(angleStyles[i]);
}
}
// 变更圆角样式
if(area.radius > 0) {
var circleBox = e.instance.selectComponent('.crop-circle-box');
var circle = e.instance.selectComponent('.crop-circle');
var radius = area.radius;
if(area.width === area.height && area.radius >= area.width / 2) { // 圆形
radius = (area.width / 2);
} else { // 圆角矩形
if(area.width !== area.height) { // 限制圆角半径不能超过短边的一半
radius = Math.min(area.width / 2, area.height / 2, radius);
}
}
circleBox.setStyle({
left: (area.left + areaOffset.left) + 'px',
top: (area.top + areaOffset.top) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 2
});
circle.setStyle({
'box-shadow': '0 0 0 ' + Math.max(area.width, area.height) + 'px rgba(51, 51, 51, 0.8)',
'border-radius': radius + 'px'
});
}
};
/**
* 缩放图片
* @param {Object} e 布局信息
*/
function scaleImage(e) {
var last = scale;
scale = Math.min(Math.max(e.scale + scale, minScale), img.maxScale);
if(last !== scale) {
img.width = num(img.oldWidth * scale);
img.height = num(img.oldHeight * scale);
// 参考问题有一个长4000px、宽4000px的四方形ABCDA点的坐标固定在(-2000,-2000)
// 该四边形上有一个点E坐标为(-100,-300)将该四方形复制一份并缩小到90%后,
// 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合
// 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变
// 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可
e.x = num((e.x - offset.x) * (1 - scale / last));
e.y = num((e.y - offset.y) * (1 - scale / last));
changeImageRect(e);
return true;
}
return false;
};
/**
* 获取触摸点在哪个角
* @param {number} x 触摸点x轴坐标
* @param {number} y 触摸点y轴坐标
* @return {number} 角的位置0=无1=左上2=右上3=左下4=右下;
*/
function getToucheAngle(x, y) {
// console.log('getToucheAngle', x, y, JSON.stringify(area))
var o = area.angleBorderWidth; // 需扩大触发范围则把 o 值加大即可
if(y >= area.top - o && y <= area.top + area.angleSize + o) {
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
return 1; // 左上角
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
return 2; // 右上角
}
} else if(y >= area.bottom - area.angleSize - o && y <= area.bottom + o) {
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
return 3; // 左下角
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
return 4; // 右下角
}
}
return 0; // 无触摸到角
};
/**
* 重置数据
*/
function resetData() {
offset = { x: 0, y: 0 };
scale = 1;
minScale = img.minScale;
rotate = 0;
};
/**
* 顺时针翻转图片90°
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
function rotateImage(e, o, r) {
rotate = (rotate + r) % 360;
if(img.minScale >= 1 && area.checkRange) {
// 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域
minScale = 1;
if(img.width < area.height) {
minScale = area.height / img.oldWidth;
} else if(img.height < area.width) {
minScale = area.width / img.oldHeight;
}
if(minScale !== 1) {
scaleImage({
instance: o,
scale: minScale - scale,
x: sys.windowWidth / 2,
y: (sys.windowHeight - sys.offsetBottom) / 2
});
}
}
// 由于拖动画布后会导致图片位置偏移,翻转时的旋转中心点需是图片区域+偏移区域的中心点
// 翻转x轴中心点 = (超出裁剪区域右侧的图片宽度 - 超出裁剪区域左侧的图片宽度) / 2
// 翻转y轴中心点 = (超出裁剪区域下方的图片宽度 - 超出裁剪区域上方的图片宽度) / 2
var ox = ((offset.x + img.width - area.right) - (area.left - offset.x)) / 2;
var oy = ((offset.y + img.height - area.bottom) - (area.top - offset.y)) / 2;
changeImageRect({
instance: o,
check: true,
x: -ox - oy,
y: -oy + ox
});
};
module.exports = {
/**
* 初始化:观察数据变更
* @param {Object} newVal 新数据
* @param {Object} oldVal 旧数据
* @param {Object} o 组件实例对象
*/
initObserver: function(newVal, oldVal, o, i) {
if(newVal) {
img = newVal.img;
sys = newVal.sys;
area = newVal.area;
minScale = img.minScale;
resetData();
img.src && changeImageRect({
instance: o,
x: (sys.windowWidth - img.width) / 2,
y: (sys.windowHeight - sys.offsetBottom - img.height) / 2
});
changeAreaRect({
instance: o
});
// console.log('initRect', JSON.stringify(newVal))
}
},
/**
* 鼠标滚轮滚动
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
mousewheel: function(e, o) {
if(!img.src) return;
scaleImage({
instance: o,
check: true,
// 鼠标向上滚动时deltaY 固定 -100鼠标向下滚动时deltaY 固定 100
scale: e.detail.deltaY > 0 ? -0.05 : 0.05,
x: e.touches[0].pageX,
y: e.touches[0].pageY
});
},
/**
* 触摸开始
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
touchstart: function(e, o) {
if(!img.src) return;
touches = e.touches;
activeAngle = area.showAngle ? getToucheAngle(touches[0].pageX, touches[0].pageY) : 0;
if(touches.length === 1 && activeAngle !== 0) {
touchType = 'stretch'; // 伸缩裁剪区域
} else {
touchType = '';
}
// console.log('touchstart', JSON.stringify(e), activeAngle)
},
/**
* 触摸移动
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
touchmove: function(e, o) {
if(!img.src) return;
// console.log('touchmove', JSON.stringify(e), JSON.stringify(o))
if(touchType === 'stretch') { // 触摸四个角进行拉伸
var point = e.touches[0];
var start = touches[0];
var x = point.pageX - start.pageX;
var y = point.pageY - start.pageY;
if(x !== 0 || y !== 0) {
var maxX = num(area.width * (1 - area.minScale));
var maxY = num(area.height * (1 - area.minScale));
// console.log(x, y, maxX, maxY, offset, area)
touches[0] = point;
var r = rotate / 90 % 2;
var m = r === 1 ? num((img.height - img.width) / 2) : 0; // 宽高差值一半
var xCompare = r === 1 ? lessThanByFault(img.height, area.width) : lessThanByFault(img.width, area.width);
var yCompare = r === 1 ? lessThanByFault(img.width, area.height) : lessThanByFault(img.height, area.height)
var isInclude = xCompare && yCompare;
var isIntersect = area.checkRange && (xCompare || yCompare); // 图片是否包含在裁剪区域内
var isReverse = !isInclude || num((offset.x - area.left) / area.width) <= num((offset.y - area.top) / area.height) || (area.width > area.height && img.width < img.height && r === 1);
switch(activeAngle) {
case 1: // 左上角
x = num(x + areaOffset.left);
y = num(y + areaOffset.top);
if(x >= 0 && y >= 0) { // 有效滑动
var t = num(offset.y + m - area.top);
var l = num(offset.x - m - area.left);
// && (offset.x + img.width < area.right || offset.y + img.height < area.bottom)
var max = isIntersect && ((l >= 0) || (t >= 0))
? minimum(t, l)
: false;
if(x > y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(x > maxX) x = maxX;
y = num(x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(y > maxY) y = maxY;
x = num(y * area.width / area.height);
}
areaOffset.left = x;
areaOffset.top = y;
}
break;
case 2: // 右上角
x = num(x + areaOffset.right);
y = num(y + areaOffset.top);
if(x <= 0 && y >= 0) { // 有效滑动
var w = (r === 1 ? img.height : img.width);
var t = num(offset.y + m - area.top);
var l = num(area.right + m - offset.x - w);
var max = isIntersect && ((t >= 0) || (l >= 0))
? minimum(t, l)
: false;
// var max = isInclude && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y >= area.top))
// ? minimum(offset.y - area.top, area.right - offset.x - img.width)
// : false;
// console.log(offset.x, offset.y, img.width, img.height, area.top, area.right, m, max)
// console.log(offset.y + m - area.top, area.right + m - offset.x - w)
if(-x > y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(-x > maxX) x = -maxX;
y = num(-x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(y > maxY) y = maxY;
x = num(-y * area.width / area.height);
}
areaOffset.right = x;
areaOffset.top = y;
}
break;
case 3: // 左下角
x += num(x + areaOffset.left);
y += num(y + areaOffset.bottom);
if(x >= 0 && y <= 0) { // 有效滑动
var w = (r === 1 ? img.width : img.height);
var t = num(area.bottom - m - offset.y - w);
var l = num(offset.x - m - area.left);
var max = isIntersect && ((l >= 0) || (t >= 0))
? minimum(t, l)
: false;
if(x > -y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(x > maxX) x = maxX;
y = num(-x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(-y > maxY) y = -maxY;
x = num(-y * area.width / area.height);
}
areaOffset.left = x;
areaOffset.bottom = y;
}
break;
case 4: // 右下角
x = num(x + areaOffset.right);
y = num(y + areaOffset.bottom);
if(x <= 0 && y <= 0) { // 有效滑动
var w = (r === 1 ? img.height : img.width);
var h = (r === 1 ? img.width : img.height);
var t = num(area.bottom - offset.y - h - m);
var l = num(area.right + m - offset.x - w);
var max = isIntersect && ((l >= 0) || (t >= 0))
? minimum(t, l)
: false;
if(-x > -y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(-x > maxX) x = -maxX;
y = num(x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(-y > maxY) y = -maxY;
x = num(y * area.width / area.height);
}
areaOffset.right = x;
areaOffset.bottom = y;
}
break;
}
// console.log(x, y, JSON.stringify(areaOffset))
changeAreaRect({
instance: o,
});
// this.draw();
}
} else if (e.touches.length == 2) { // 双点触摸缩放
var start = getDistanceByTouches(touches);
var end = getDistanceByTouches(e.touches);
scaleImage({
instance: o,
check: !area.bounce,
scale: (end.c - start.c) / 100,
x: end.x,
y: end.y
});
touchType = 'scale';
} else if(touchType === 'scale') {// 从双点触摸变成单点触摸 / 从缩放变成拖动
touchType = 'move';
} else {
changeImageRect({
instance: o,
check: !area.bounce,
x: e.touches[0].pageX - touches[0].pageX,
y: e.touches[0].pageY - touches[0].pageY
});
touchType = 'move';
}
touches = e.touches;
},
/**
* 触摸结束
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
touchend: function(e, o) {
if(!img.src) return;
if(touchType === 'stretch') { // 拉伸裁剪区域的四个角缩放
// 裁剪区域宽度被缩放到多少
var left = areaOffset.left;
var right = areaOffset.right;
var top = areaOffset.top;
var bottom = areaOffset.bottom;
var w = area.width + right - left;
var h = area.height + bottom - top;
// 图像放大倍数
var p = scale * (area.width / w) - scale;
// 复原裁剪区域
areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
changeAreaRect({
instance: o,
});
scaleImage({
instance: o,
scale: p,
x: area.left + left + (1 === activeAngle || 3 === activeAngle ? w : 0),
y: area.top + top + (1 === activeAngle || 2 === activeAngle ? h : 0)
});
} else if (area.bounce) { // 检查边界并矫正,实现拖动到边界时有回弹效果
changeImageRect({
instance: o,
check: true
});
}
},
/**
* 顺时针翻转图片90°
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
rotateImage: function(e, o) {
rotateImage(e, o, 90);
},
rotateImage90: function(e, o) {
rotateImage(e, o, 90)
},
rotateImage270: function(e, o) {
rotateImage(e, o, 270)
},
// 此处只用于对齐其他平台端的样式参数,防止异常,无作用
imageStyles: '',
maskStylesList: ['', '', '', ''],
borderStyles: '',
gridStylesList: ['', '', '', ''],
angleStylesList: ['', '', '', ''],
circleBoxStyles: '',
circleStyles: '',
}
</wxs>
<view class="image-cropper data-v-7129956f" style="{{'z-index:' + B}}" bindwheel="{{cropper.mousewheel}}"><canvas wx:if="{{a}}" type="2d" id="imgCanvas" class="img-canvas data-v-7129956f" style="{{'width:' + b + ';' + ('height:' + c)}}"></canvas><canvas wx:else id="imgCanvas" canvas-id="imgCanvas" class="img-canvas data-v-7129956f" style="{{'width:' + d + ';' + ('height:' + e)}}"></canvas><view id="pic-preview" class="pic-preview data-v-7129956f" change:init="{{cropper.initObserver}}" init="{{p}}" bindtouchstart="{{cropper.touchstart}}" bindtouchmove="{{cropper.touchmove}}" bindtouchend="{{cropper.touchend}}"><image wx:if="{{f}}" id="crop-image" class="crop-image data-v-7129956f" style="{{_s(cropper.imageStyles)}}" src="{{g}}" webp></image><view wx:for="{{h}}" wx:for-item="item" wx:key="a" id="{{item.b}}" class="crop-mask-block data-v-7129956f" style="{{_s(cropper.maskStylesList[index])}}"></view><view wx:if="{{i}}" id="crop-border" class="crop-border data-v-7129956f" style="{{_s(cropper.borderStyles)}}"></view><view wx:if="{{j}}" id="crop-circle-box" class="crop-circle-box data-v-7129956f" style="{{_s(cropper.circleBoxStyles)}}"><view class="crop-circle data-v-7129956f" id="crop-circle" style="{{_s(cropper.circleStyles)}}"></view></view><block wx:if="{{k}}"><view wx:for="{{l}}" wx:for-item="item" wx:key="a" id="{{item.b}}" class="crop-grid data-v-7129956f" style="{{_s(cropper.gridStylesList[index])}}"></view></block><block wx:if="{{m}}"><view wx:for="{{n}}" wx:for-item="item" wx:key="a" id="{{item.b}}" class="crop-angle data-v-7129956f" style="{{_s(cropper.angleStylesList[index])}}"><view class="data-v-7129956f" style="{{o}}"></view></view></block></view><slot/><view class="fixed-bottom safe-area-inset-bottom data-v-7129956f" style="{{'z-index:' + A}}"><view wx:if="{{q}}" class="action-bar data-v-7129956f"><view wx:if="{{r}}" class="rotate-icon data-v-7129956f" bindtap="{{cropper.rotateImage270}}"></view><view wx:if="{{s}}" class="rotate-icon is-reverse data-v-7129956f" bindtap="{{cropper.rotateImage90}}"></view></view><view wx:if="{{t}}" class="choose-btn data-v-7129956f" bindtap="{{v}}">确定</view><block wx:elif="{{w}}"><view class="rechoose data-v-7129956f" bindtap="{{x}}">重选</view><button class="button data-v-7129956f" size="mini" bindtap="{{y}}">确定</button></block><view wx:else class="choose-btn data-v-7129956f" bindtap="{{z}}">选择图片</view></view></view>

View File

@ -0,0 +1,141 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场https://ext.dcloud.net.cn上很多三方插件均使用了这些样式变量
* 如果你是插件开发者建议你使用scss预处理并在插件代码中直接使用这些变量无需 import 这个文件方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者插件使用者你可以通过修改这些变量来定制自己的插件主题实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
/* 文字基本颜色 */
/* 背景颜色 */
/* 边框颜色 */
/* 尺寸变量 */
/* 文字尺寸 */
/* 图片尺寸 */
/* Border Radius */
/* 水平间距 */
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
.image-cropper.data-v-7129956f {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
overflow: hidden;
display: flex;
flex-direction: column;
background-color: #000;
}
.image-cropper .img-canvas.data-v-7129956f {
position: absolute !important;
transform: translateX(-100%);
}
.image-cropper .pic-preview.data-v-7129956f {
width: 100%;
flex: 1;
position: relative;
}
.image-cropper .pic-preview .crop-mask-block.data-v-7129956f {
background-color: rgba(51, 51, 51, 0.8);
z-index: 2;
position: fixed;
box-sizing: border-box;
pointer-events: none;
}
.image-cropper .pic-preview .crop-circle-box.data-v-7129956f {
position: fixed;
box-sizing: border-box;
z-index: 2;
pointer-events: none;
overflow: hidden;
}
.image-cropper .pic-preview .crop-circle-box .crop-circle.data-v-7129956f {
width: 100%;
height: 100%;
}
.image-cropper .pic-preview .crop-image.data-v-7129956f {
padding: 0 !important;
margin: 0 !important;
border-radius: 0 !important;
display: block !important;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
.image-cropper .pic-preview .crop-border.data-v-7129956f {
position: fixed;
border: 1px solid #fff;
box-sizing: border-box;
z-index: 3;
pointer-events: none;
}
.image-cropper .pic-preview .crop-grid.data-v-7129956f {
position: fixed;
z-index: 3;
border-style: dashed;
border-color: #fff;
pointer-events: none;
opacity: 0.5;
}
.image-cropper .pic-preview .crop-angle.data-v-7129956f {
position: fixed;
z-index: 3;
border-style: solid;
border-color: #fff;
pointer-events: none;
}
.image-cropper .fixed-bottom.data-v-7129956f {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 99;
display: flex;
flex-direction: row;
background-color: #f8f8f8;
}
.image-cropper .fixed-bottom .action-bar.data-v-7129956f {
position: absolute;
top: -90rpx;
left: 10rpx;
display: flex;
}
.image-cropper .fixed-bottom .action-bar .rotate-icon.data-v-7129956f {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAABCFJREFUaEPtml3IpVMUx3//ko/ChTIyiGFSMyhllI8bc4F85yuNC2FCqLmQC1+FZORiEkUMNW7UjKjJULgxV+NzSkxDhEkZgwsyigv119J63p7zvOc8z37OmXdOb51dz82711r7/99r7bXXXucVi3xokeNnRqCvB20fDmwAlgK/5bcD+FTSr33tHXQP2H4MeHQE0A+B5yRtLiUyDQJrgVc6AAaBpyV93kXkoBMIQLbfBS5NcK8BRwDXNcD+AdwnaVMbiWkRCPBBohpxHuK7M7865sclRdgNHVMhkF6IMIpwirFEUhzo8M7lwIvASTXEqyVtH8ZgagQSbOzsDknv18HZXpHn5IL8+94IOUm7miSmSqAttjPdbgGuTrnNktYsGgLpoYuAD2qg1zRTbG8P2D4SOC6/Q7vSHPALsE/S7wWy80RsPw/ckxMfSTq/LtRJwPbxwF3ASiCUTxwHCPAnEBfVF8AWSTtL7Ng+LfWOTfmlkn6udFsJ5K15R6a4kvX6yGyUFBvTOWzHXXFzCt4g6c1OArYj9iIGh43YgR+BvztXh1PSa4cMkd0jaVmXDduPAE+k3HpJD7cSGFKvfAc8FQUX8IOk/V2L1udtB/hTgdOBW4Aba/M7Ja1qs2f7euCNlHlZUlx4/495IWQ7Jl+qGbxX0gt9AHfJ2o6zFBVoNVrDKe+F3Sm8VdK1bQQ+A85JgXckXdkFaJx527cC9TpnVdvBtl3h2iapuhsGPdBw1b9xnUvaNw7AEh3bnwDnpuwGSfeP0rN9NvAMELXRXFkxEEK2nwQeSiOtRVQJwC4Z29cAW1Nuu6TVXTrN+SaBt4ErUug2Sa/2NdhH3vZy4NvU2S/p6D768w5xI3WOrAD7LtISFpGdIhVXKfaYvjd20wP13L9M0p4DBbaFRKToSLExVkr6qs+aIwlI6iwz+izUQqC+ab29PiMwqRcmPXczD8w8MFj1zg7xXEqbpdHCw7FgWSjafZL+KcQxtpjteCeflwYulFR/J3TabSslVkj6utPChAK2f6q9uZdLitKieLQRuExSvX9ZbLRUMFs09efpUZL+KtUfVo1GW/umNHC3pOhRLtiwfSbwZS6wV9IJfRdreuBBYH0a2STp9r4G+8jbXgc8mzoDT8VSO00ClwDv1ZR7XyylC4ec7ejaLUmdsV6Aw7oSbwFXpdFdks7qA6pU1na0aR6owgeIR/1cx63UzjAC0YXYVjMQHlkn6ZtSo21ytuPZGKFagQ/xsXZ/3iGuFrYdjafXG0DiQMeBi47c9/GV3BO247UV38n5o0UAP6xmu7jFOGxjRr66On5NPBDOCBsDTapxjHY1dyOcolNXnYlx1himE53p2PmNkxosevfavhg4Izt2k7TXPwZ2S6p6QZPin/2rwcQ7OKmBohCadJGF1P8PG6aaQBKVX/8AAAAASUVORK5CYII=");
background-size: 60% 60%;
background-repeat: no-repeat;
background-position: center;
width: 80rpx;
height: 80rpx;
}
.image-cropper .fixed-bottom .action-bar .rotate-icon.is-reverse.data-v-7129956f {
transform: rotateY(180deg);
}
.image-cropper .fixed-bottom .rechoose.data-v-7129956f {
color: #007aff;
padding: 0 15px;
line-height: 100rpx;
}
.image-cropper .fixed-bottom .choose-btn.data-v-7129956f {
color: #007aff;
text-align: center;
line-height: 100rpx;
flex: 1;
}
.image-cropper .fixed-bottom .button.data-v-7129956f {
margin: auto 15px auto auto;
background-color: #007aff;
color: #fff;
}
.image-cropper .safe-area-inset-bottom.data-v-7129956f {
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}