Compare commits
15 Commits
Author | SHA1 | Date |
---|---|---|
qiushanhe | f5c0df688c | 10 months ago |
qsh | 38cc93cba4 | 10 months ago |
zcx | ccef999bbe | 10 months ago |
zcx | 589eec1d80 | 10 months ago |
zcx | 242744c0f9 | 10 months ago |
zcx | e67e1edf12 | 10 months ago |
zcx | 8f50f42f8c | 10 months ago |
zcx | 65149c38cd | 10 months ago |
zcx | c26e306b3a | 10 months ago |
qiushanhe | 6141270437 | 10 months ago |
qsh | 074b53f307 | 10 months ago |
qsh | ab05529015 | 10 months ago |
zcx | 9e24edad28 | 10 months ago |
zcx | 67b3e63de9 | 10 months ago |
qsh | 056ca965b3 | 10 months ago |
@ -0,0 +1,28 @@ |
||||
{ |
||||
"appid": "wx24c1b58020a5ce66", |
||||
"compileType": "miniprogram", |
||||
"libVersion": "3.3.3", |
||||
"packOptions": { |
||||
"ignore": [], |
||||
"include": [] |
||||
}, |
||||
"setting": { |
||||
"coverView": true, |
||||
"es6": true, |
||||
"postcss": true, |
||||
"minified": true, |
||||
"enhance": true, |
||||
"showShadowRootInWxmlPanel": true, |
||||
"packNpmRelationList": [], |
||||
"babelSetting": { |
||||
"ignore": [], |
||||
"disablePlugins": [], |
||||
"outputPath": "" |
||||
} |
||||
}, |
||||
"condition": {}, |
||||
"editorSetting": { |
||||
"tabIndent": "insertSpaces", |
||||
"tabSize": 2 |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
{ |
||||
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", |
||||
"projectname": "jwl-applet", |
||||
"setting": { |
||||
"compileHotReLoad": true |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,95 @@ |
||||
## 使用方式 |
||||
``` javascript |
||||
<yan-qr /> |
||||
``` |
||||
|
||||
## 属性说明 |
||||
|属性名 |类型 | 默认值 |说明 | |
||||
|-- |-- |-----------|-- | |
||||
|canvasId |String | 'qrcode' |canvas-id | |
||||
|text |String | 'hello' |二维码内容 | |
||||
|size |Number | 340 |单位是px | |
||||
|margin |Number | 0 |边距 | |
||||
|level |String | 'L' |二维码解析度L/M/Q/H | |
||||
|fColor |String | '#000000' |二维码颜色 | |
||||
|bColor |String | '#ffffff' |二维码背景颜色 | |
||||
|fileType |String | 'png' |二维码图片类型 | |
||||
|
||||
## 示例代码 |
||||
``` javascript |
||||
<template> |
||||
<view> |
||||
<view> |
||||
<view> |
||||
<view>需要转换的文本:</view> |
||||
<textarea v-model="content" @blur="inputText" placeholder="请在这里输入" /> |
||||
</view> |
||||
</view> |
||||
二维码 |
||||
<view> |
||||
<yan-qr :filePath.sync="filePath" :text="text" :margin="20" /> |
||||
</view> |
||||
二维码图片地址 |
||||
<view> |
||||
<textarea v-model="filePath" disabled /> |
||||
</view> |
||||
<button @click='btn'>生成二维码</button> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
|
||||
data() { |
||||
return { |
||||
qrShow: false, |
||||
content: '', |
||||
filePath: '', |
||||
text: '' |
||||
} |
||||
}, |
||||
watch: { |
||||
filePath() { |
||||
console.log(this.filePath); |
||||
} |
||||
}, |
||||
|
||||
methods: { |
||||
//*获取文本框内容*// |
||||
inputText: function(e) { |
||||
this.content = e.detail.value |
||||
}, |
||||
|
||||
//*按钮*// |
||||
btn: function() { |
||||
if (this.content == '') { |
||||
uni.showToast({ //显示对话框 |
||||
title: "请输入文本", |
||||
icon: 'none', |
||||
duration: 1000, |
||||
}) |
||||
} else { |
||||
this.text = this.content |
||||
} |
||||
}, |
||||
|
||||
|
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
textarea { |
||||
border: 1px solid #000000; |
||||
width: 98%; |
||||
} |
||||
button { |
||||
width: 80%; |
||||
margin-top: 180rpx; |
||||
border-radius: 25px; |
||||
color: aliceblue; |
||||
background-color: #55aaff; |
||||
} |
||||
|
||||
</style> |
||||
``` |
@ -0,0 +1,115 @@ |
||||
<template> |
||||
<view> |
||||
<canvas :canvas-id="canvasId" v-show="qrShow" :style="qrShowStyle" /> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import uQRCode from './qrcode.js' |
||||
export default { |
||||
props: { |
||||
canvasId: { |
||||
type: String, |
||||
default: 'qrcode' //canvas-id |
||||
}, |
||||
text: { |
||||
type: String, |
||||
default: 'hello' //二维码内容 |
||||
}, |
||||
size: { |
||||
type: Number, |
||||
default: 150 //二维码大小 |
||||
}, |
||||
margin: { |
||||
type: Number, |
||||
default: 0 //二维码边距 |
||||
}, |
||||
level: { |
||||
type: String, |
||||
default: 'L' //二维码质量L/M/Q/H |
||||
}, |
||||
bColor: { |
||||
type: String, |
||||
default: '#ffffff' //二维码背景颜色 |
||||
}, |
||||
fColor: { |
||||
type: String, |
||||
default: '#000000' //二维码颜色 |
||||
}, |
||||
fileType: { |
||||
type: String, |
||||
default: 'png' //二维码图片类型 |
||||
} |
||||
}, |
||||
data() { |
||||
return { |
||||
qrShow: false, |
||||
qrShowStyle: { |
||||
"width": "150px", |
||||
"height": "150px", |
||||
} |
||||
} |
||||
}, |
||||
watch: { |
||||
text(newVal, oldVal) { |
||||
this.onQrFun() |
||||
} |
||||
}, |
||||
mounted() { |
||||
this.onQrFun() |
||||
}, |
||||
methods: { |
||||
onQrFun() { |
||||
this.qrShow = this.text != '' |
||||
if(this.qrShow){ |
||||
this.qrShowStyle.width = this.size+"px" |
||||
this.qrShowStyle.height = this.size+"px" |
||||
let level; |
||||
switch (this.level) { |
||||
case "M": |
||||
case "m": |
||||
level = uQRCode.errorCorrectLevel.M |
||||
break; |
||||
case "Q": |
||||
case "q": |
||||
level = uQRCode.errorCorrectLevel.Q |
||||
break; |
||||
case "H": |
||||
case "h": |
||||
level = uQRCode.errorCorrectLevel.H |
||||
break; |
||||
default: |
||||
level = uQRCode.errorCorrectLevel.L |
||||
break; |
||||
} |
||||
uQRCode.make({ |
||||
canvasId: this.canvasId, |
||||
componentInstance: this, |
||||
text: this.text, |
||||
size: this.size, |
||||
margin: this.margin, |
||||
backgroundColor: this.bColor, |
||||
foregroundColor: this.fColor, |
||||
fileType: this.fileType, |
||||
errorCorrectLevel: level, |
||||
success: res => {} |
||||
}) |
||||
|
||||
setTimeout(() => { |
||||
uni.canvasToTempFilePath({ |
||||
canvasId: this.canvasId, |
||||
success: (res) => { |
||||
// 在H5平台下,tempFilePath 为 base64 |
||||
this.$emit("update:filePath", res.tempFilePath); |
||||
} |
||||
}, this); |
||||
}, 600) |
||||
} |
||||
|
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
</style> |
@ -0,0 +1,118 @@ |
||||
import request from '../request/index.js'; |
||||
|
||||
//查询活动列表
|
||||
export function queryActivityList(data) { |
||||
return request({ |
||||
url: 'activity/applet/activity/list', |
||||
method: 'get', |
||||
data, |
||||
noToken: true |
||||
}); |
||||
} |
||||
//查询活动详情
|
||||
export function queryActivityDetail(data) { |
||||
return request({ |
||||
url: 'activity/applet/activity/detail', |
||||
method: 'get', |
||||
data, |
||||
noToken: true |
||||
}); |
||||
} |
||||
//查询抽奖次数
|
||||
export function queryLuckyNum(data) { |
||||
return request({ |
||||
url: 'activity/applet/activity/lucky/num', |
||||
method: 'get', |
||||
data, |
||||
noToken: true |
||||
}); |
||||
} |
||||
//查询中奖结果
|
||||
export function queryLuckyResult(data) { |
||||
return request({ |
||||
url: 'activity/applet/activity/lucky/result', |
||||
method: 'get', |
||||
data, |
||||
noToken: true |
||||
}); |
||||
} |
||||
//录入中奖结果
|
||||
export function saveWinner(data) { |
||||
return request({ |
||||
url: 'activity/applet/activity/winner/save', |
||||
method: 'POST', |
||||
data, |
||||
noToken: true |
||||
}); |
||||
} |
||||
//核销
|
||||
export function receiveWinner(data) { |
||||
return request({ |
||||
url: 'activity/applet/activity/winner/receive', |
||||
method: 'POST', |
||||
data, |
||||
noToken: true |
||||
}); |
||||
} |
||||
//查询中奖记录
|
||||
export function getLuckyRecord(data) { |
||||
return request({ |
||||
url: 'activity/applet/activity/lucky/record', |
||||
method: 'get', |
||||
data, |
||||
noToken: true |
||||
}); |
||||
} |
||||
|
||||
//查询中奖记录
|
||||
export function canRecieveGift(data) { |
||||
return request({ |
||||
url: 'activity/applet/activity/receive/user', |
||||
method: 'get', |
||||
data, |
||||
noToken: true |
||||
}); |
||||
} |
||||
|
||||
//查询中奖信息
|
||||
export function queryWinnerInfo(data) { |
||||
return request({ |
||||
url: 'activity/applet/activity/winner/info', |
||||
method: 'get', |
||||
data, |
||||
noToken: true |
||||
}); |
||||
} |
||||
|
||||
//查询助力信息
|
||||
export function queryHelpInfo(data) { |
||||
return request({ |
||||
url: 'activity/applet/activity/help/info', |
||||
method: 'get', |
||||
data, |
||||
noToken: true |
||||
}); |
||||
} |
||||
|
||||
//保存助力信息
|
||||
export function saveHelpInfo(data) { |
||||
return request({ |
||||
url: 'activity/applet/activity/help/save', |
||||
method: 'post', |
||||
data, |
||||
noToken: true |
||||
}); |
||||
} |
||||
|
||||
|
||||
//微信登录获取手机号
|
||||
export function wxLogin(data) { |
||||
return request({ |
||||
url: 'activity/applet/activity/wx/login', |
||||
method: 'post', |
||||
data, |
||||
noToken: true |
||||
}); |
||||
} |
||||
|
||||
|
@ -0,0 +1,18 @@ |
||||
{ |
||||
"id": "yan-qr", |
||||
"name": "动态生成二维码", |
||||
"displayName": "动态生成二维码", |
||||
"version": "1.0.0", |
||||
"description": "动态生成二维码", |
||||
"keywords": [ |
||||
"二维码", |
||||
"生成二维码", |
||||
"动态二维码" |
||||
], |
||||
"dcloudext": { |
||||
"category": [ |
||||
"前端组件", |
||||
"通用组件" |
||||
] |
||||
} |
||||
} |
@ -0,0 +1,26 @@ |
||||
<template> |
||||
<view> |
||||
<GGL></GGL> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import GGL from './components/ggl/index' |
||||
export default { |
||||
components: { |
||||
GGL |
||||
}, |
||||
data() { |
||||
return { |
||||
|
||||
} |
||||
}, |
||||
methods: { |
||||
|
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
|
||||
</style> |
@ -0,0 +1,229 @@ |
||||
<template> |
||||
<view style="padding-bottom: 50px;background-color: #9e0f00;"> |
||||
<image class="wp100" mode="widthFix" src="https://jwl-jiakao-bq.oss-cn-hangzhou.aliyuncs.com/%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%9B%BE%E7%89%87/ggl_header.png"></image> |
||||
<view class="tip">您今日还剩{{actiNum}}次刮奖机会,共有{{total}}人参加活动</view> |
||||
<view class="scraping"> |
||||
<scraping-card style="z-index: 20;" :result="result" watermark="刮一刮" title="刮一刮赢取大奖" ref="reset" @complete="handleComplete" > |
||||
<cover-view v-if="showBtn" class="gj"> |
||||
<cover-view class="btn" @tap="handleScrap"> |
||||
{{btnText}} |
||||
</cover-view> |
||||
</cover-view> |
||||
</scraping-card> |
||||
</view> |
||||
<view class="relative mt30 m20lr"> |
||||
<image class="wp100" mode="widthFix" src="/static/image/index/tip.png"></image> |
||||
<view class="title">中奖名单</view> |
||||
<view class="card"> |
||||
<view v-for="(item, index) in winningList" :key="index" class="card-item"> |
||||
<text class="item-text">{{ hidePhoneNumber(item.phone) }}</text> |
||||
<text class="item-text">{{ item.awards }}</text> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
<view class="relative mt30 m20lr"> |
||||
<image class="wp100" mode="widthFix" src="/static/image/index/tip.png"></image> |
||||
<view class="title">活动说明</view> |
||||
<view class="card"> |
||||
<u-parse :content="activityRule"></u-parse> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import useUserStore from '@/jtools/store/user' |
||||
import ScrapingCard from './scraping.vue' |
||||
import { queryActivityDetail,queryLuckyNum,queryLuckyResult,saveWinner,getLuckyRecord } from '@/jtools/api/activity' |
||||
export default { |
||||
components: { |
||||
ScrapingCard |
||||
}, |
||||
data() { |
||||
return { |
||||
detailId: undefined, |
||||
result: '', |
||||
btnText: '点我刮奖', |
||||
showBtn: true, |
||||
actiNum: 0, |
||||
total: 0, |
||||
winningList: [], |
||||
activityRule: undefined |
||||
} |
||||
}, |
||||
onLoad(op) { |
||||
this.detailId = op.detailId |
||||
this.getActivityDetail() |
||||
this.getActivityNum() |
||||
this.searchWinningList() |
||||
}, |
||||
methods: { |
||||
handleScrap() { |
||||
if(!this.actiNum) { |
||||
uni.showToast({ |
||||
icon: 'none', |
||||
title: '暂无抽奖机会' |
||||
}) |
||||
return |
||||
} |
||||
|
||||
queryLuckyResult({ |
||||
detailId: this.detailId |
||||
}).then(resp => { |
||||
if(resp.code == 200) { |
||||
this.result = resp.msg |
||||
this.showBtn = !this.showBtn |
||||
this.$refs.reset.init() |
||||
} |
||||
}) |
||||
}, |
||||
getActivityDetail() { |
||||
queryActivityDetail({detailId: this.detailId}).then(resp => { |
||||
if(resp.code == 200) { |
||||
this.activityRule = resp.data.activity.activityRule |
||||
} |
||||
}) |
||||
}, |
||||
hidePhoneNumber(phoneNumber) { |
||||
// 验证电话号码格式 |
||||
if (!/^\d{11}$/.test(phoneNumber)) { |
||||
return "无效的电话号码"; |
||||
} |
||||
|
||||
// 替换中间四位数字为星号 |
||||
return phoneNumber.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'); |
||||
}, |
||||
getActivityNum() { |
||||
queryLuckyNum({ |
||||
detailId: this.detailId, |
||||
phone: useUserStore().userInfo?.phone || '' |
||||
}).then(resp => { |
||||
if(resp.code == 200) { |
||||
this.actiNum = resp.data.drawNum |
||||
this.total = resp.data.totalPeople |
||||
} |
||||
}) |
||||
}, |
||||
searchWinningList() { |
||||
getLuckyRecord({ |
||||
detailId: this.detailId, |
||||
pageSize: 8 |
||||
}).then(resp => { |
||||
if(resp.code == 200) { |
||||
this.winningList = resp.rows |
||||
} |
||||
}) |
||||
}, |
||||
handleComplete() { |
||||
saveWinner({ |
||||
phone: useUserStore().userInfo?.phone || '', |
||||
detailId: this.detailId, |
||||
awards: this.result |
||||
}).then(resp => { |
||||
this.btnText = '再刮一次!' |
||||
if(resp.code == 200) { |
||||
this.getActivityNum() |
||||
let help = ",完成助力即可领取奖品!" |
||||
uni.showToast({ |
||||
icon: 'none', |
||||
title: `恭喜获得${this.result}` + help |
||||
}) |
||||
this.showBtn = true |
||||
} |
||||
}) |
||||
} |
||||
}, |
||||
} |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
|
||||
|
||||
.tip { |
||||
position: relative; |
||||
margin-top: -80px; |
||||
margin-left: 40px; |
||||
margin-right: 40px; |
||||
height: 52rpx; |
||||
background: rgba(0,0,0,0.5); |
||||
border-radius: 26rpx; |
||||
text-align: center; |
||||
font-size: 24rpx; |
||||
color: #fff; |
||||
line-height: 52rpx; |
||||
} |
||||
.scraping { |
||||
position: relative; |
||||
margin-top: 20rpx; |
||||
margin-left: 40px; |
||||
margin-right: 40px; |
||||
background-color: #fff; |
||||
border: 3px solid #9e0f00; /* 实线边框 */ |
||||
border-top: 3px dashed #9e0f00; /* 虚线边框 */ |
||||
border-right: 3px dashed #9e0f00; /* 虚线边框 */ |
||||
border-bottom: 3px dashed #9e0f00; /* 虚线边框 */ |
||||
border-left: 3px dashed #9e0f00; /* 虚线边框 */ |
||||
border-radius: 5px; /* 圆角 */ |
||||
padding: 10px; /* 内容与边框之间的空间 */ |
||||
} |
||||
.gj { |
||||
position: absolute; |
||||
left: 0; |
||||
top: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
z-index: 21; |
||||
.btn { |
||||
position: absolute; |
||||
top: 50%; |
||||
left: calc(50% - 100rpx); |
||||
width: 200rpx; |
||||
height: 70rpx; |
||||
line-height: 70rpx; |
||||
background-color: #BE1200; |
||||
border-radius: 10rpx; |
||||
text-align: center; |
||||
font-size: 28rpx; |
||||
color: #fff; |
||||
} |
||||
} |
||||
.title { |
||||
position: absolute; |
||||
left: calc(50% - 160rpx); |
||||
top: -14px; |
||||
width: 320rpx; |
||||
height: 80rpx; |
||||
line-height: 80rpx; |
||||
text-align: center; |
||||
background: linear-gradient(0deg, #ECB181, #FFEDD3); |
||||
border: 6rpx solid #E32D1A; |
||||
border-radius: 40rpx 40rpx 0 0; |
||||
font-weight: 600; |
||||
color: #BE1200; |
||||
font-size: 32rpx; |
||||
} |
||||
.card { |
||||
position: relative; |
||||
margin-top: -40rpx; |
||||
padding: 26rpx; |
||||
min-height: 280rpx; |
||||
background: #FFEDD3; |
||||
border: 6rpx solid #E32D1A; |
||||
border-radius: 24rpx; |
||||
.card-item { |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: space-between; |
||||
height: 80rpx; |
||||
line-height: 80rpx; |
||||
border-bottom: 1px solid #ecd3ae; |
||||
} |
||||
.card-item:last-of-type { |
||||
border-bottom: none; |
||||
} |
||||
.item-text { |
||||
font-size: 28rpx; |
||||
color: #BE1200; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,280 @@ |
||||
<template> |
||||
<view class="scraping-happy" id="container"> |
||||
<canvas canvas-id="scraping-happy" class="scraping__canvas" :disable-scroll="true" @touchstart="touchstart" |
||||
@touchmove="touchmove" @touchend="touchend" /> |
||||
<cover-view class="scraping__view"> |
||||
{{ showText }} |
||||
</cover-view> |
||||
<slot></slot> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
/** @name 水印配置默认值 **/ |
||||
const WATERMARK = { |
||||
text: '刮一刮', |
||||
fontSize: 14, |
||||
color: '#C5C5C5', |
||||
} |
||||
/** @name 标题配置默认值 **/ |
||||
const TITLE = { |
||||
text: '刮一刮', |
||||
fontSize: 16, |
||||
color: '#333', |
||||
} |
||||
/** |
||||
* @name 涂层配置默认值 |
||||
* @property { string } color 涂层颜色 |
||||
* @property { number } drawSize 清除涂层的画笔大小 |
||||
*/ |
||||
const MASK = { |
||||
color: '#DDDDDD', |
||||
drawSize: 20, |
||||
} |
||||
/** @name 容错值,解决部分机型涂层没有覆盖满的情况,主要原因是由于像素尺寸不同导致的,应尽可能让width与height保持整数 **/ |
||||
const TOLERANT = 3; |
||||
|
||||
let ctx = null; |
||||
|
||||
export default { |
||||
props: { |
||||
/** @name 涂层设置 **/ |
||||
mask: { |
||||
type: [String, Object], |
||||
}, |
||||
/** @name 水印设置 **/ |
||||
watermark: { |
||||
type: [String, Object], |
||||
}, |
||||
/** @name 提示文字 **/ |
||||
title: { |
||||
type: [String, Object], |
||||
}, |
||||
/** @name 刮开百分之多少直接消除图层,为0的时候不消除 **/ |
||||
percentage: { |
||||
type: Number, |
||||
default: 40, |
||||
}, |
||||
result: { |
||||
type: String, |
||||
default: '' |
||||
} |
||||
}, |
||||
data() { |
||||
return { |
||||
width: 0, |
||||
height: 0, |
||||
touchX: 0, |
||||
touchY: 0, |
||||
showText: '' |
||||
} |
||||
}, |
||||
computed: { |
||||
maskSetting() { |
||||
return { |
||||
...MASK, |
||||
...(typeof this.mask === 'object' ? this.mask : { |
||||
text: this.mask |
||||
}), |
||||
} |
||||
}, |
||||
watermarkSetting() { |
||||
return { |
||||
...WATERMARK, |
||||
...(typeof this.watermark === 'object' ? this.watermark : { |
||||
text: this.watermark |
||||
}), |
||||
}; |
||||
}, |
||||
titleSetting() { |
||||
return { |
||||
...TITLE, |
||||
...(typeof this.title === 'object' ? this.title : { |
||||
text: this.title |
||||
}), |
||||
}; |
||||
} |
||||
}, |
||||
mounted() { |
||||
// 获取画布实例 |
||||
ctx = uni.createCanvasContext('scraping-happy', this); |
||||
this.init(); |
||||
}, |
||||
methods: { |
||||
/** @name 初始化 **/ |
||||
init() { |
||||
const query = uni.createSelectorQuery().in(this); |
||||
query |
||||
.select('#container') |
||||
.boundingClientRect(({ |
||||
width, |
||||
height |
||||
}) => { |
||||
this.width = width; |
||||
this.height = height; |
||||
setTimeout(() => { |
||||
this.initCanvas(); |
||||
this.showText = this.result |
||||
}, 20) |
||||
}) |
||||
.exec(); |
||||
}, |
||||
/** @name 初始化canvas状态 **/ |
||||
initCanvas() { |
||||
const { |
||||
width, |
||||
height |
||||
} = this; |
||||
// 清空矩形内容 |
||||
ctx.clearRect(0, 0, width, height); |
||||
// 设置画笔颜色 |
||||
ctx.setFillStyle(this.maskSetting.color); |
||||
// 绘制矩形 |
||||
ctx.fillRect(0, 0, width, height + TOLERANT); |
||||
// 绘制水印 |
||||
this.drawWatermark(); |
||||
// 绘制提示文字 |
||||
this.drawTitle(); |
||||
// 绘制到canvas身上 |
||||
ctx.draw(); |
||||
}, |
||||
/** @name 绘制水印 **/ |
||||
drawWatermark() { |
||||
if (!this.watermarkSetting.text) return; |
||||
// 保存当前的绘图上下文 |
||||
ctx.save(); |
||||
// 旋转 |
||||
ctx.rotate((-10 * Math.PI) / 180); |
||||
// 水印具体绘制过程 |
||||
const { |
||||
width, |
||||
height |
||||
} = this; |
||||
const watermarkWidth = this.watermarkSetting.text.length * this.watermarkSetting.fontSize; |
||||
let x = 0; |
||||
let y = 0; |
||||
let i = 0; |
||||
while ((x <= width * 5 || y <= height * 5) && i < 300) { |
||||
ctx.setFillStyle(this.watermarkSetting.color); |
||||
ctx.setFontSize(this.watermarkSetting.fontSize); |
||||
ctx.fillText(this.watermarkSetting.text, x, y); |
||||
x += watermarkWidth + watermarkWidth * 1.6; |
||||
if (x > width && y <= height) { |
||||
x = -Math.random() * 100; |
||||
y += this.watermarkSetting.fontSize * 3; |
||||
} |
||||
i++; |
||||
} |
||||
ctx.restore(); |
||||
}, |
||||
/** @name 绘制提示文字 **/ |
||||
drawTitle() { |
||||
if (!this.titleSetting.text) return; |
||||
ctx.setTextAlign('center'); |
||||
ctx.setTextBaseline('middle'); |
||||
ctx.setFillStyle(this.titleSetting.color); |
||||
ctx.setFontSize(this.titleSetting.fontSize); |
||||
ctx.fillText(this.titleSetting.text, this.width / 2, this.height / 3); |
||||
}, |
||||
/** @name 触摸事件 **/ |
||||
touchstart(e) { |
||||
this.touchX = e.touches[0].x; |
||||
this.touchY = e.touches[0].y; |
||||
}, |
||||
async touchmove(e) { |
||||
// 把画笔到画布中的指定点 |
||||
ctx.moveTo(this.touchX, this.touchY); |
||||
// 清除涂层 |
||||
ctx.clearRect(this.touchX, this.touchY, this.maskSetting.drawSize, this.maskSetting.drawSize); |
||||
ctx.draw(true); |
||||
// 记录移动点位 |
||||
this.touchX = e.touches[0].x; |
||||
this.touchY = e.touches[0].y; |
||||
|
||||
// if (this.percentage > 0) { |
||||
// const clearPercent = await this.getClearMaskPercent(); |
||||
// } |
||||
}, |
||||
async touchend() { |
||||
if (this.percentage > 0) { |
||||
const clearPercent = await this.getClearMaskPercent(); |
||||
if (clearPercent >= this.percentage) { |
||||
ctx.moveTo(0, 0); |
||||
ctx.clearRect(0, 0, this.width, this.height); |
||||
ctx.stroke(); |
||||
ctx.draw(true); |
||||
this.$emit('complete') |
||||
} |
||||
} |
||||
}, |
||||
/** @name 计算被清除的涂层百分比 **/ |
||||
getClearMaskPercent() { |
||||
return new Promise(resolve => { |
||||
uni.canvasGetImageData({ |
||||
canvasId: 'scraping-happy', |
||||
x: 0, |
||||
y: 0, |
||||
width: this.width, |
||||
height: this.height, |
||||
success: res => { |
||||
// 区域内所有点的像素信息,它是一个数组,数组中每 "4" 项表示一个点的 rgba 值 |
||||
const allPointPixels = res.data; |
||||
// 储存被清除的点-点的透明度 |
||||
const clearPoint = []; |
||||
// 取透明度来判断,如果透明度值小于一半,则判断为该点已经被清除 |
||||
for (let i = 0; i < allPointPixels.length; i += 4) { |
||||
if (allPointPixels[i + 3] < 128) { |
||||
clearPoint.push(allPointPixels[i + 3]); |
||||
} |
||||
} |
||||
// 已被清除的百分比 = 清除的点 / 全部的点 |
||||
const percent = ( |
||||
(clearPoint.length / (allPointPixels.length / 4)) * |
||||
100 |
||||
).toFixed(2); |
||||
resolve(percent); |
||||
}, |
||||
fail: e => { |
||||
console.log('canvasGetImageData', e); |
||||
}, |
||||
}, this); |
||||
}); |
||||
}, |
||||
/** @name 重置 **/ |
||||
reset() { |
||||
this.initCanvas(); |
||||
this.touchX = 0; |
||||
this.touchY = 0; |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
|
||||
<style> |
||||
.scraping-happy { |
||||
width: 100%; |
||||
height: 200rpx; |
||||
position: relative; |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
} |
||||
|
||||
.scraping__canvas { |
||||
width: 100%; |
||||
height: 100%; |
||||
position: absolute; |
||||
z-index: 10; |
||||
/* background-color: red; */ |
||||
display: inline-block; |
||||
} |
||||
|
||||
.scraping__view { |
||||
position: absolute; |
||||
z-index: 1; |
||||
color: #f29100; |
||||
font-size: 20px; |
||||
font-weight: bold; |
||||
letter-spacing: 8px; |
||||
} |
||||
</style> |
@ -0,0 +1,254 @@ |
||||
<template> |
||||
<view style="padding-bottom: 50px;background-color: #C5121B;"> |
||||
<image class="wp100 img" mode="widthFix" |
||||
src="https://oss-bq.ahduima.com/%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%9B%BE%E7%89%87/%E5%8A%A9%E5%8A%9B%E4%B8%BB%E5%9B%BE.jpg"> |
||||
</image> |
||||
<view class="relative tip">邀请好友帮助你进行助力即可有机会领取奖品</view> |
||||
<view class="relative help_div"> |
||||
<!-- --> |
||||
<view v-if="type == 1" class="relative help_card"> |
||||
<view class="relative help_tip"> |
||||
还差{{diffNum}}位好友助力即可领取奖品: |
||||
</view> |
||||
<view v-if="winnerInfo.awards != undefined" class="relative help_tip" style="font-size: 40rpx;"> |
||||
{{winnerInfo.awards}} |
||||
</view> |
||||
<view class="p20"> |
||||
<u-grid :border="false" col="3"> |
||||
<u-grid-item v-for="(item, index) in helpUserList" :key="index"> |
||||
<u-avatar v-if="item.id" class="p10tb border" size="120rpx" :src="item.avatarUrl"></u-avatar> |
||||
<view v-else class="p10tb"> |
||||
<view class="avatar-text"> |
||||
<u-avatar bg-color="#fff" color="#ccc" size="120rpx" text="邀"></u-avatar> |
||||
</view> |
||||
</view> |
||||
</u-grid-item> |
||||
</u-grid> |
||||
</view> |
||||
<view class="help_btn"> |
||||
<button open-type="share" class="help_btn_font"> 邀请好友助力</button> |
||||
</view> |
||||
</view> |
||||
|
||||
<view v-if="type == 2" class="relative help_card"> |
||||
<view class="relative help_list" style="top: 200rpx;"> |
||||
<view v-if="winnerInfo?.activityName" class="help_tip" |
||||
style="font-size: 40rpx; margin: 25rpx 0rpx;"> |
||||
{{winnerInfo?.activityName}} |
||||
</view> |
||||
<view v-if="winnerInfo.schoolName != undefined" class="help_tip"> |
||||
举办单位:{{winnerInfo.schoolName}} |
||||
</view> |
||||
<view v-if="winnerInfo.awards != undefined" class="help_tip" style="font-size: 45rpx;margin: 25rpx 0rpx;"> |
||||
奖品:{{winnerInfo.awards}} |
||||
</view> |
||||
</view> |
||||
|
||||
<view class="help_btn"> |
||||
<button class="help_btn_font" open-type="chooseAvatar" @chooseavatar="handleHelp" |
||||
:disabled="disBtn">帮好友助力</button> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import useUserStore from '@/jtools/store/user' |
||||
import { |
||||
queryActivityDetail, |
||||
queryHelpInfo, |
||||
saveHelpInfo, |
||||
queryWinnerInfo |
||||
} from '@/jtools/api/activity' |
||||
export default { |
||||
data() { |
||||
return { |
||||
winnerId: undefined, |
||||
helpUserList: [], |
||||
drawNum: 0, |
||||
phone: undefined, |
||||
type: 2, |
||||
winnerInfo: undefined, |
||||
disBtn: false |
||||
} |
||||
}, |
||||
onLoad(op) { |
||||
this.winnerId = op.id |
||||
// this.type = op.type ? Number(op.type) : 1 |
||||
this.phone = useUserStore().userInfo?.phone || undefined |
||||
console.log(this.phone) |
||||
this.getWinnerInfo() |
||||
this.getHelpInfo() |
||||
this.disBtn = false |
||||
|
||||
}, |
||||
onShareAppMessage(res) { |
||||
if (res.from === 'button') { // 来自页面内分享按钮 |
||||
console.log(res.target) |
||||
} |
||||
return { |
||||
title: '我正在参与领奖活动柜,请帮我助力', |
||||
path: '/pages/me/help?id=' + this.winnerId + '&type=2' |
||||
} |
||||
}, |
||||
|
||||
methods: { |
||||
//查询中奖信息 |
||||
getWinnerInfo() { |
||||
queryWinnerInfo({ |
||||
winnerId: this.winnerId |
||||
}).then(resp => { |
||||
if (resp.code == 200) { |
||||
console.log(resp) |
||||
this.winnerInfo = resp.data; |
||||
if (this.phone && this.winnerInfo.phone == this.phone) { |
||||
this.type = 1 |
||||
} else { |
||||
this.type = 2 |
||||
} |
||||
} |
||||
}) |
||||
|
||||
}, |
||||
//查询助力信息 |
||||
getHelpInfo() { |
||||
queryHelpInfo({ |
||||
winnerId: this.winnerId |
||||
}).then(resp => { |
||||
if (resp.code == 200) { |
||||
this.drawNum = resp.data.helpNum; |
||||
const list = resp.data.helpUsers.map(item => ({ |
||||
...item, |
||||
avatarUrl: 'https://jwl.ahduima.com' + item.avatarUrl |
||||
})); |
||||
this.diffNum = (this.drawNum - list.length) < 0 ? 0 : (this.drawNum - list.length); |
||||
const arr = new Array(this.diffNum).fill({}) |
||||
this.helpUserList = [...list,...arr] |
||||
} |
||||
}) |
||||
}, |
||||
//点击助力 |
||||
handleHelp(e) { |
||||
this.disBtn = false |
||||
uni.login({ |
||||
provider: 'weixin', //使用微信登录 |
||||
success: (loginRes) => { |
||||
console.log(loginRes); |
||||
uni.uploadFile({ |
||||
url: 'https://jwl.ahduima.com/activity/applet/activity/help/save', |
||||
// url: 'http://192.168.1.6:8089/applet/activity/help/save', |
||||
filePath: e.detail.avatarUrl, |
||||
name: 'file', |
||||
formData: { |
||||
'code': loginRes.code, |
||||
'winnerId': this.winnerId, |
||||
}, |
||||
success: (uploadFileRes) => { |
||||
console.log(uploadFileRes.data); |
||||
let resp = JSON.parse(uploadFileRes.data); |
||||
console.log(resp); |
||||
|
||||
if (resp.code == 200) { |
||||
uni.showToast({ |
||||
icon: 'none', |
||||
title: `助力成功` |
||||
}) |
||||
this.disBtn = true |
||||
} else { |
||||
uni.showToast({ |
||||
icon: 'error', |
||||
title: resp.msg |
||||
}) |
||||
this.disBtn = true |
||||
} |
||||
} |
||||
}); |
||||
|
||||
} |
||||
}); |
||||
}, |
||||
|
||||
}, |
||||
} |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
.img { |
||||
top: -130rpx !important; |
||||
} |
||||
|
||||
.tip { |
||||
height: 75px; |
||||
font-family: PingFang SC; |
||||
font-weight: 400; |
||||
color: #FEFEFE; |
||||
line-height: 48px; |
||||
top: -520rpx; |
||||
text-align: center; |
||||
font-size: 35rpx; |
||||
} |
||||
|
||||
.avatar-text { |
||||
border-radius: 50%; |
||||
border: 1px dashed #ccc; |
||||
} |
||||
|
||||
|
||||
.help_div { |
||||
width: 710rpx; |
||||
height: 996rpx; |
||||
top: -560rpx; |
||||
margin-left: 20rpx; |
||||
margin-right: 20rpx; |
||||
background: linear-gradient(0deg, #F33D2F 100%, rgba(197, 18, 27, 0.01) 0%); |
||||
border-radius: 40rpx; |
||||
|
||||
|
||||
.help_card { |
||||
width: 630rpx; |
||||
height: 886rpx; |
||||
margin: 0rpx 39rpx; |
||||
top: 55rpx; |
||||
background: #FFFFFF; |
||||
border-radius: 24rpx; |
||||
|
||||
.help_tip { |
||||
text-align: center; |
||||
font-size: 32rpx; |
||||
font-family: PingFang SC; |
||||
font-weight: 400; |
||||
color: #010101; |
||||
line-height: 55rpx; |
||||
top: 15rpx; |
||||
} |
||||
|
||||
.help_list { |
||||
margin: 68rpx 25rpx 25rpx 20rpx; |
||||
height: 580rpx; |
||||
|
||||
} |
||||
|
||||
.help_btn { |
||||
text-align: center; |
||||
margin: 0 75rpx; |
||||
margin-bottom: 30rpx; |
||||
width: 500rpx; |
||||
height: 86rpx; |
||||
background: linear-gradient(0deg, #DE4224 0%, #B81706 100%); |
||||
border-radius: 43rpx; |
||||
|
||||
.help_btn_font { |
||||
font-size: 32rpx; |
||||
font-family: PingFang SC; |
||||
font-weight: 400; |
||||
color: #FFFFFF; |
||||
line-height: 86rpx; |
||||
background: center; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
} |
||||
</style> |
@ -0,0 +1,96 @@ |
||||
<template> |
||||
<view class="p10lr p20tb"> |
||||
<u-list @scrolltolower="loadmore"> |
||||
<u-list-item v-for="(item, index) in list" :key="index" class="item"> |
||||
<view class="relative"> |
||||
<img src="/static/image/mine/giftitem.png" style="width: 100%;" mode="widthFix" alt="" /> |
||||
<view class="ab_full df ai-c jcc">{{item.awards}}</view> |
||||
</view> |
||||
<view class="df ai-c jcsb p20tb p10lr"> |
||||
<view class="item-label"> |
||||
<view>活动名称:{{ item.activityName }}</view> |
||||
<view>参与时间:{{ item.createTime }}</view> |
||||
<view>有效期至:{{ item.endTime }}</view> |
||||
</view> |
||||
<view class="ml20" style="width: 120rpx;"> |
||||
<view v-if="item.status == 0" class="btn" @tap="handleHelp(item)">助力</view> |
||||
<view v-else-if="item.status == 1" class="btn" @tap="handleWriteoff(item)">核销</view> |
||||
<img v-else-if="item.status == 2" src="/static/image/mine/writeoff.png" style="width: 120rpx;height: 120rpx;" /> |
||||
<img v-else src="/static/image/mine/outtime.png" style="width: 120rpx;height: 120rpx;" /> |
||||
</view> |
||||
</view> |
||||
</u-list-item> |
||||
</u-list> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import useUserStore from '@/jtools/store/user' |
||||
import { getLuckyRecord } from '@/jtools/api/activity' |
||||
export default { |
||||
data() { |
||||
return { |
||||
pageNum: 1, |
||||
list: [] |
||||
} |
||||
}, |
||||
onShow() { |
||||
this.pageNum = 1 |
||||
this.list = [] |
||||
this.handleSearch() |
||||
}, |
||||
methods: { |
||||
handleSearch() { |
||||
getLuckyRecord({ |
||||
phone: useUserStore().userInfo.phone, |
||||
pageSize: 10, |
||||
pageNum: this.pageNum |
||||
}).then(resp => { |
||||
if(resp.code == 200) { |
||||
if(resp.rows && resp.rows.length) { |
||||
this.list = [...this.list, ...resp.rows] |
||||
} else { |
||||
this.pageNum = this.pageNum > 1 ? this.pageNum-1 : 1 |
||||
} |
||||
} |
||||
}) |
||||
}, |
||||
loadmore() { |
||||
this.handleSearch(this.pageNum++) |
||||
}, |
||||
handleWriteoff(item) { |
||||
uni.navigateTo({ |
||||
url: `/pages/me/qrCode?id=${item.id}` |
||||
}) |
||||
}, |
||||
handleHelp(item){ |
||||
uni.navigateTo({ |
||||
url: `/pages/me/help?id=${item.id}` |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.item { |
||||
margin-bottom: 30rpx; |
||||
border-radius: 36rpx; |
||||
background-color: #fff; |
||||
.item-label { |
||||
font-size: 28rpx; |
||||
color: #666; |
||||
line-height: 48rpx; |
||||
} |
||||
.btn { |
||||
width: 120rpx; |
||||
height: 68rpx; |
||||
text-align: center; |
||||
line-height: 68rpx; |
||||
border-radius: 34rpx; |
||||
background-color: #BE1200; |
||||
color: #fff; |
||||
font-size: 28rpx; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,26 @@ |
||||
<template> |
||||
<view class="df jcc" style="padding-top: 100px;"> |
||||
<qrcode v-if="value" :value="value" :size="200"/> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import qrcode from '/src/uni_modules/lime-qrcode/components/l-qrcode/l-qrcode.vue' |
||||
export default { |
||||
components: { |
||||
qrcode |
||||
}, |
||||
data() { |
||||
return { |
||||
value: '' |
||||
} |
||||
}, |
||||
onLoad(option) { |
||||
this.value = option.id |
||||
}, |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
|
||||
</style> |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,25 @@ |
||||
## 0.1.2(2023-12-14) |
||||
- fix: uvue 引入 API 自定义包出错 |
||||
## 0.1.1(2023-12-11) |
||||
- chore: uvue的二维码API独立,需要单独下载 |
||||
## 0.1.0(2023-12-07) |
||||
- fix: 修复因utssdk目录导致无法运行 |
||||
## 0.0.9(2023-12-06) |
||||
- feat: 支持uvue |
||||
## 0.0.8(2023-12-06) |
||||
- feat: 支持uvue |
||||
## 0.0.7(2023-12-06) |
||||
- feat: 支持uvue |
||||
## 0.0.6(2023-12-06) |
||||
- feat: 支持uvue |
||||
## 0.0.5(2023-07-30) |
||||
- fix: 修复再次生成前没有清空,导致图形叠加 |
||||
## 0.0.4(2023-07-27) |
||||
- fix: 修复相同尺寸无法再次生成 |
||||
## 0.0.3(2023-06-09) |
||||
- feat: 支持通过`@vue/composition-api`在`vue2`上使用 |
||||
- chore: 更新文档 |
||||
## 0.0.2(2023-06-08) |
||||
- chore: 更新文档 |
||||
## 0.0.1(2023-06-08) |
||||
- 首次 |
@ -0,0 +1,179 @@ |
||||
<template> |
||||
<view class="l-qrcode" ref="l-qrcode" :style="[styles]"> |
||||
<image class="l-qrcode__icon" v-if="icon !=''" :src="icon" :style="[iconStyle]"></image> |
||||
</view> |
||||
</template> |
||||
<script lang="ts"> |
||||
import { type PropType } from 'vue' |
||||
import { QRCodeCanvas, QRCodePropsTypes , ImageSettings } from '@/uni_modules/lime-qrcodegen' |
||||
// import { addUnit } from '@/uni_modules/lime-shared/addUnit' |
||||
// import { unitConvert } from '@/uni_modules/lime-shared/unitConvert' |
||||
import { addUnit, unitConvert } from './utils' |
||||
import { TakeSnapshotFailCallback, TakeSnapshotCompleteCallback, TakeSnapshotSuccessCallback} from './type' |
||||
|
||||
const name = 'l-qrcode' |
||||
export default { |
||||
name, |
||||
props: { |
||||
value: { |
||||
type: String, |
||||
default: '' |
||||
}, |
||||
icon: { |
||||
type: String, |
||||
default: '' |
||||
}, |
||||
size: { |
||||
type: Object, |
||||
default: 160 |
||||
}, |
||||
iconSize: { |
||||
type: Object, |
||||
default: 40 |
||||
}, |
||||
marginSize: { |
||||
type: Number, |
||||
default: 0 |
||||
}, |
||||
color: { |
||||
type: String, |
||||
default: '#000' |
||||
}, |
||||
bgColor: { |
||||
type: String, |
||||
default: 'transparent' |
||||
}, |
||||
bordered: { |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
errorLevel: { |
||||
type: String as PropType<'L' | 'M' | 'Q' | 'H'>, |
||||
default: 'M' // 'L' | 'M' | 'Q' | 'H' |
||||
}, |
||||
useCanvasToTempFilePath: { |
||||
type: Boolean, |
||||
default: false |
||||
} |
||||
// status: { |
||||
// type: String as PropType<'active'|'expired'|'loading'>, |
||||
// default: 'active' // active | expired | loading |
||||
// } |
||||
}, |
||||
emits: ['success'], |
||||
data() { |
||||
return { |
||||
qrcode: null as QRCodeCanvas | null |
||||
} |
||||
}, |
||||
computed: { |
||||
styles() : Map<string, any> { |
||||
const style = new Map<string, any>() |
||||
const size = addUnit(this.size); |
||||
style.set('width', size) |
||||
style.set('height', size) |
||||
style.set('background', this.bgColor) |
||||
return style |
||||
}, |
||||
iconStyle() : Map<string, any> { |
||||
const style = new Map<string, any>() |
||||
const size = addUnit(this.iconSize); |
||||
style.set('width', size) |
||||
style.set('height', size) |
||||
return style |
||||
}, |
||||
qrCodeProps() : QRCodePropsTypes { |
||||
const param = { |
||||
value: this.value, |
||||
size: unitConvert(this.size), |
||||
fgColor: this.color, |
||||
level: ['L', 'M', 'Q', 'H'].includes(this.errorLevel) ? this.errorLevel : 'M', |
||||
marginSize: this.marginSize, |
||||
imageSettings: null, |
||||
includeMargin: this.bordered, |
||||
} as QRCodePropsTypes |
||||
|
||||
if(this.iconSize != '' && this.icon != ''){ |
||||
const size = unitConvert(this.iconSize) |
||||
param.imageSettings = { |
||||
width: size, |
||||
height: size, |
||||
excavate: true |
||||
} as ImageSettings |
||||
} |
||||
return param |
||||
} |
||||
}, |
||||
methods: { |
||||
canvasToTempFilePath(options :UTSJSONObject) : void { |
||||
const el = (this.$refs['l-qrcode'] as Element); |
||||
const format = options.getString('format') ?? 'png'; |
||||
const fail = options.get('fail') as TakeSnapshotFailCallback | null; |
||||
const complete = options.get('complete') as TakeSnapshotCompleteCallback | null; |
||||
const success = options.get('success') as TakeSnapshotSuccessCallback | null; |
||||
const newOptions = { |
||||
format, |
||||
fail, |
||||
complete, |
||||
success, |
||||
} as TakeSnapshotOptions |
||||
el.takeSnapshot(newOptions) |
||||
|
||||
} |
||||
}, |
||||
mounted() { |
||||
const el = (this.$refs['l-qrcode'] as Element) |
||||
const ctx = el.getDrawableContext(); |
||||
if (ctx == null) return |
||||
this.qrcode = new QRCodeCanvas(ctx) |
||||
const render = (v : QRCodePropsTypes) => { |
||||
this.qrcode!.render(v); |
||||
if (this.useCanvasToTempFilePath) { |
||||
setTimeout(() => { |
||||
this.canvasToTempFilePath({ |
||||
success: (res: TakeSnapshotSuccess) => { |
||||
this.$emit('success', res.tempFilePath) |
||||
}, |
||||
fail: (_: TakeSnapshotFail) => { |
||||
uni.showToast({ |
||||
icon: 'error', |
||||
title: '截图失败' |
||||
}) |
||||
} |
||||
}) |
||||
// TakeSnapshotOptions |
||||
},200) |
||||
} |
||||
} |
||||
this.$watch('qrCodeProps', (v: QRCodePropsTypes) => { |
||||
render(v) |
||||
}, {immediate: true}) |
||||
} |
||||
} |
||||
</script> |
||||
<style lang="scss"> |
||||
.l-qrcode { |
||||
position: relative; |
||||
background-color: aqua; |
||||
justify-content: center; |
||||
align-items: center; |
||||
|
||||
&-mask { |
||||
position: absolute; |
||||
// inset: 0; |
||||
// inset-block-start: 0; |
||||
// inset-inline-start: 0; |
||||
z-index: 10; |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
align-items: center; |
||||
// width: 100%; |
||||
// height: 100%; |
||||
color: rgba(0, 0, 0, 0.88); |
||||
line-height: 1.5714285714285714; |
||||
background: rgba(255, 255, 255, 0.96); |
||||
text-align: center; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,223 @@ |
||||
<template> |
||||
<view class="l-qrcode" :style="[styles]"> |
||||
<!-- #ifndef APP-NVUE --> |
||||
<canvas :style="styles" type="2d" :canvas-id="canvasId" :id="canvasId"></canvas> |
||||
<!-- #endif --> |
||||
<!-- #ifdef APP-NVUE --> |
||||
<web-view |
||||
ref="qrcodeRef" |
||||
@pagefinish="onFinished" |
||||
@error="onError" |
||||
@onPostMessage="onMessage" |
||||
:style="styles" src="/uni_modules/lime-qrcode/hybrid/html/index.html?v=1"></web-view> |
||||
<!-- #endif --> |
||||
<!-- <view class="l-qrcode-mask" v-if="['loading', 'expired'].includes(props.status)"> |
||||
<l-loading v-if="props.status == 'loading'"></l-loading> |
||||
<view class="l-qrcode-expired" v-if="props.status == 'expired'"> |
||||
<slot></slot> |
||||
</view> |
||||
</view> --> |
||||
</view> |
||||
</template> |
||||
<script lang="ts"> |
||||
// @ts-nocheck |
||||
import { computed, defineComponent, getCurrentInstance, watch, onUnmounted, onMounted } from '@/uni_modules/lime-shared/vue'; |
||||
import QRCodeProps from './props' |
||||
// #ifndef APP-NVUE |
||||
import { getCanvas, isCanvas2d } from './useCanvas' |
||||
import { QRCodeCanvas } from './qrcode.js'; |
||||
// #endif |
||||
import { addUnit } from '@/uni_modules/lime-shared/addUnit' |
||||
import { createImage } from '@/uni_modules/lime-shared/createImage' |
||||
import { unitConvert } from '@/uni_modules/lime-shared/unitConvert' |
||||
import { isBase64 } from '@/uni_modules/lime-shared/isBase64' |
||||
import { pathToBase64 } from '@/uni_modules/lime-shared/pathToBase64' |
||||
import { debounce } from '@/uni_modules/lime-shared/debounce' |
||||
const name = 'l-qrcode' |
||||
export default defineComponent({ |
||||
name, |
||||
props: QRCodeProps, |
||||
emits: ['success'], |
||||
setup(props, {emit}) { |
||||
const context = getCurrentInstance(); |
||||
const canvasId = `l-qrcode${context.uid}` |
||||
const styles = computed(() => `width: ${addUnit(props.size)}; height: ${addUnit(props.size)};`) |
||||
let qrcode = null |
||||
let canvas = null |
||||
const qrCodeProps = computed(() => { |
||||
const { value, icon, size, color, bgColor, bordered, iconSize, errorLevel, marginSize } = props |
||||
const imageSettings = { |
||||
src: icon, |
||||
x: undefined, |
||||
y: undefined, |
||||
height: unitConvert(iconSize), |
||||
width: unitConvert(iconSize), |
||||
excavate: true, |
||||
} |
||||
return { |
||||
value, |
||||
size: unitConvert(size), |
||||
level: errorLevel, |
||||
bgColor, |
||||
fgColor: color, |
||||
imageSettings: icon ? imageSettings : undefined, |
||||
includeMargin: bordered, |
||||
marginSize: marginSize ?? 0 |
||||
} |
||||
|
||||
}) |
||||
|
||||
// #ifdef APP-NVUE |
||||
const stacks = new Map() |
||||
// #endif |
||||
const canvasToTempFilePath = debounce((args: UniNamespace.CanvasToTempFilePathRes) => { |
||||
if(!canvas) return |
||||
// #ifndef APP-NVUE |
||||
const copyArgs = Object.assign({ |
||||
canvasId, |
||||
canvas: null |
||||
}, args) |
||||
|
||||
if (isCanvas2d) { |
||||
copyArgs.canvas = canvas |
||||
} |
||||
if ('toTempFilePath' in canvas) { |
||||
canvas.toTempFilePath(copyArgs) |
||||
} else { |
||||
uni.canvasToTempFilePath(copyArgs, context); |
||||
} |
||||
// #endif |
||||
// #ifdef APP-NVUE |
||||
if(!stacks.size) { |
||||
const flie = 'file-' + Math.random(); |
||||
const stack = {args, time: +new Date()} |
||||
stacks.set(`${flie}`, stack) |
||||
canvas.toDataURL(flie) |
||||
setTimeout(() => { |
||||
const stack = stacks.get(flie) |
||||
if(stack && 'fail' in stack.args) { |
||||
stack.args.fail({ |
||||
error: '超时' |
||||
}) |
||||
stacks.delete(flie) |
||||
} |
||||
},5000) |
||||
} |
||||
// #endif |
||||
}) |
||||
const useCanvasToTempFilePath = () => { |
||||
if(props.useCanvasToTempFilePath) { |
||||
canvasToTempFilePath({ |
||||
success(res: UniNamespace.CanvasToTempFilePathRes) { |
||||
emit('success', res.tempFilePath) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
// #ifdef APP-NVUE |
||||
const onFinished = () => { |
||||
const { pixelRatio } = uni.getSystemInfoSync() |
||||
canvas = { |
||||
toDataURL(flie: string) { |
||||
const ref: any = context.refs['qrcodeRef']; |
||||
if(ref) { |
||||
ref?.evalJS(`toDataURL('${flie}')`) |
||||
} |
||||
} |
||||
}; |
||||
qrcode = { |
||||
async render(props: any) { |
||||
const ref: any = context.refs['qrcodeRef']; |
||||
const { src } = props.imageSettings || { }; |
||||
if(!ref) return |
||||
if(src && !isBase64(src) && !/^http/.test(src) && /^\/static/.test(src)) { |
||||
props.imageSettings.src = await pathToBase64(src) |
||||
} |
||||
const _props = JSON.stringify(Object.assign({}, props, {pixelRatio})); |
||||
ref?.evalJS(`render(${_props})`); |
||||
} |
||||
} |
||||
qrcode.render(qrCodeProps.value) |
||||
useCanvasToTempFilePath() |
||||
} |
||||
const onError = () => { |
||||
console.warn('lime-qrcode 加载失败') |
||||
} |
||||
const onMessage = (e: any) => { |
||||
const {detail:{data: [res]}} = e |
||||
if(res.event == 'toDataURL') { |
||||
const {file, image, msg} = res.data; |
||||
const stack = stacks.get(file) |
||||
if(stack && image && 'success' in stack.args) { |
||||
stack.args.success({tempFilePath: image}) |
||||
stacks.delete(file) |
||||
} else if(stack && 'fails' in stack.args) { |
||||
stack.args.fail({error: msg}) |
||||
stacks.delete(file) |
||||
} |
||||
} |
||||
} |
||||
// #endif |
||||
const propsWatch = watch(props, () => { |
||||
if (qrcode) { |
||||
qrcode.render(qrCodeProps.value) |
||||
useCanvasToTempFilePath() |
||||
} |
||||
}) |
||||
onMounted(() => { |
||||
// #ifndef APP-NVUE |
||||
getCanvas(canvasId, { context }).then(res => { |
||||
canvas = res |
||||
qrcode = new QRCodeCanvas(res, { |
||||
// #ifdef H5 |
||||
path2D: false, |
||||
// #endif |
||||
pixelRatio: isCanvas2d ? uni.getSystemInfoSync().pixelRatio : 1, |
||||
createImage |
||||
}) |
||||
qrcode.render(qrCodeProps.value) |
||||
useCanvasToTempFilePath() |
||||
}) |
||||
// #endif |
||||
}) |
||||
onUnmounted(() => { |
||||
propsWatch && propsWatch() |
||||
}) |
||||
return { |
||||
canvasId, |
||||
styles, |
||||
props, |
||||
canvasToTempFilePath, |
||||
|
||||
// #ifdef APP-NVUE |
||||
onFinished, |
||||
onError, |
||||
onMessage |
||||
// #endif |
||||
} |
||||
} |
||||
}) |
||||
</script> |
||||
<style lang="scss"> |
||||
.l-qrcode { |
||||
position: relative; |
||||
|
||||
&-mask { |
||||
position: absolute; |
||||
inset: 0; |
||||
// inset-block-start: 0; |
||||
// inset-inline-start: 0; |
||||
z-index: 10; |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
align-items: center; |
||||
// width: 100%; |
||||
// height: 100%; |
||||
color: rgba(0, 0, 0, 0.88); |
||||
line-height: 1.5714285714285714; |
||||
background: rgba(255, 255, 255, 0.96); |
||||
text-align: center; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,36 @@ |
||||
// @ts-nocheck
|
||||
// import type { PropType } from './vue'
|
||||
export default { |
||||
value: String, |
||||
icon: String, |
||||
size: { |
||||
type: [Number, String], |
||||
default: 160 |
||||
}, |
||||
iconSize: { |
||||
type: [Number, String], |
||||
default: 40 |
||||
}, |
||||
marginSize: Number, |
||||
color: { |
||||
type: String, |
||||
default: '#000' |
||||
}, |
||||
bgColor: { |
||||
type: String, |
||||
default: 'transparent' |
||||
}, |
||||
bordered: { |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
errorLevel: { |
||||
type: String as PropType<'L'|'M'|'Q'|'H'>, |
||||
default: 'M' // 'L' | 'M' | 'Q' | 'H'
|
||||
}, |
||||
useCanvasToTempFilePath: Boolean |
||||
// status: {
|
||||
// type: String as PropType<'active'|'expired'|'loading'>,
|
||||
// default: 'active' // active | expired | loading
|
||||
// }
|
||||
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,25 @@ |
||||
export type ImageSettings = { |
||||
width: number |
||||
height: number |
||||
x?: number |
||||
y?: number |
||||
excavate: boolean |
||||
} |
||||
export type QRCodePropsTypes = { |
||||
value?: string |
||||
size?: number |
||||
fgColor?: string |
||||
level?: string |
||||
marginSize: number |
||||
includeMargin: boolean |
||||
imageSettings?: ImageSettings |
||||
} |
||||
|
||||
export type QRCodeCallback = (cells : boolean[][]) => void |
||||
|
||||
export type Excavation = { |
||||
x: number |
||||
y: number |
||||
h: number |
||||
w: number |
||||
} |
@ -0,0 +1,78 @@ |
||||
|
||||
// @ts-nocheck
|
||||
import type { ComponentInternalInstance } from './vue' |
||||
import { getRect } from '@/uni_modules/lime-shared/getRect' |
||||
import { canIUseCanvas2d } from '@/uni_modules/lime-shared/canIUseCanvas2d' |
||||
export const isCanvas2d = canIUseCanvas2d() |
||||
|
||||
export async function getCanvas(canvasId: string, options: {context: ComponentInternalInstance}) { |
||||
let { context } = options |
||||
// #ifdef MP || VUE2
|
||||
if (context.proxy) context = context.proxy |
||||
// #endif
|
||||
return getRect('#' + canvasId, {context, type: isCanvas2d ? 'fields': 'boundingClientRect'}).then(res => { |
||||
if(res.node){ |
||||
return res.node |
||||
} else { |
||||
const ctx = uni.createCanvasContext(canvasId, context) |
||||
return { |
||||
getContext(type: string) { |
||||
if(type == '2d') { |
||||
return ctx |
||||
} |
||||
}, |
||||
width: res.width, |
||||
height: res.height, |
||||
} |
||||
// #ifdef H5
|
||||
// canvas.value = context.proxy.$el.querySelector('#'+ canvasId)
|
||||
// #endif
|
||||
} |
||||
}) |
||||
} |
||||
|
||||
// #ifndef H5 || APP-NVUE
|
||||
class Image { |
||||
currentSrc: string | null = null |
||||
naturalHeight: number = 0 |
||||
naturalWidth: number = 0 |
||||
width: number = 0 |
||||
height: number = 0 |
||||
tagName: string = 'IMG' |
||||
path: any = '' |
||||
crossOrigin: any = '' |
||||
referrerPolicy: any = '' |
||||
onload: () => void |
||||
onerror: () => void |
||||
constructor() {} |
||||
set src(src) { |
||||
this.currentSrc = src |
||||
uni.getImageInfo({ |
||||
src, |
||||
success: (res) => { |
||||
this.path = res.path |
||||
this.naturalWidth = this.width = res.width |
||||
this.naturalHeight = this.height = res.height |
||||
this.onload() |
||||
}, |
||||
fail: () => { |
||||
this.onerror() |
||||
} |
||||
}) |
||||
} |
||||
get src() { |
||||
return this.currentSrc |
||||
} |
||||
} |
||||
// #endif
|
||||
|
||||
export function createImage(canvas: WechatMiniprogram.Canvas) { |
||||
if(canvas && canvas.createImage) { |
||||
return canvas.createImage() |
||||
} else if(typeof window != 'undefined' && window.Image) { |
||||
return new window.Image() |
||||
} |
||||
// #ifndef H5 || APP-NVUE
|
||||
return new Image() |
||||
// #endif
|
||||
} |
@ -0,0 +1,35 @@ |
||||
export function addUnit(value: any|null):string{ |
||||
if(value == null){ |
||||
return '' |
||||
} |
||||
value = `${value}` |
||||
return /^(-)?\d+(\\.\d+)?$/.test(value) ? `${value}px` : value |
||||
} |
||||
|
||||
export function unitConvert(value: any|null): number{ |
||||
if(typeof value == 'number'){ |
||||
return value as number |
||||
} |
||||
if(typeof value == 'string'){ |
||||
value = `${value}` |
||||
if(/^(-)?\d+(\\.\d+)?$/.test(value)){ |
||||
return parseFloat(value); |
||||
} |
||||
|
||||
const reg = /^-?([0-9]+)?([.]{1}[0-9]+){0,1}(em|rpx|px|%)$/g; |
||||
const results = reg.exec(value); |
||||
if (results == null) { |
||||
return 0; |
||||
} |
||||
const unit = results[3]; |
||||
const v = parseFloat(value); |
||||
if (unit == 'rpx') { |
||||
const { windowWidth } = uni.getWindowInfo() |
||||
return windowWidth / 750 * v; |
||||
} |
||||
if (unit == 'px') { |
||||
return v; |
||||
} |
||||
} |
||||
return 0; |
||||
} |
@ -0,0 +1,134 @@ |
||||
<template> |
||||
<view class="demo-block"> |
||||
<text class="demo-block__title-text ultra">QRCode</text> |
||||
<text class="demo-block__desc-text">QRCode</text> |
||||
<view class="demo-block__body"> |
||||
<view class="demo-block"> |
||||
<text class="demo-block__title-text large">基础</text> |
||||
<view class="demo-block__body"> |
||||
<l-qrcode value="https://limeui.qcoon.cn" size="300rpx"></l-qrcode> |
||||
</view> |
||||
</view> |
||||
|
||||
<view class="demo-block"> |
||||
<text class="demo-block__title-text large">icon</text> |
||||
<view class="demo-block__body"> |
||||
<image v-if="image !=''" :src="image" style="width: 300rpx;" mode="widthFix"></image> |
||||
<view style="flex-direction: row; justify-content: space-between"> |
||||
<l-qrcode ref="qrcodeRef" value="https://limeui.qcoon.cn" size="300rpx" icon="https://img10.360buyimg.com/img/jfs/t1/182127/16/37474/11761/64659c31F0cd84976/21f25b952f03a49a.jpg" iconSize="70rpx"></l-qrcode> |
||||
<l-qrcode :useCanvasToTempFilePath="true" @success="success" value="https://limeui.qcoon.cn" size="300rpx" icon="https://img10.360buyimg.com/img/jfs/t1/182127/16/37474/11761/64659c31F0cd84976/21f25b952f03a49a.jpg" iconSize="70rpx"></l-qrcode> |
||||
</view> |
||||
<button @click="onClick">生成图片</button> |
||||
</view> |
||||
</view> |
||||
|
||||
<view class="demo-block"> |
||||
<text class="demo-block__title-text large">颜色</text> |
||||
<view class="demo-block__body"> |
||||
<view style="flex-direction: row; justify-content: space-between"> |
||||
<l-qrcode value="https://limeui.qcoon.cn" size="300rpx" color="rgb(82,196,26)"></l-qrcode> |
||||
<l-qrcode value="https://limeui.qcoon.cn" size="300rpx" color="rgb(22,119,255)" bgColor="rgb(245,245,245)"></l-qrcode> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
|
||||
<view class="demo-block"> |
||||
<text class="demo-block__title-text large">纠错比例</text> |
||||
<view class="demo-block__body"> |
||||
<l-qrcode value="img10.360buyimg.com/img/jfs/t1/182127/16/37474/11761/64659c31F0cd84976/21f25b952f03a49a.jpg" size="300rpx" :errorLevel="levels[index]"></l-qrcode> |
||||
<button @click="onToggle">切换纠错等级:{{levels[index]}}</button> |
||||
</view> |
||||
</view> |
||||
|
||||
<view class="demo-block"> |
||||
<text class="demo-block__title-text large">动态</text> |
||||
<view class="demo-block__body"> |
||||
<l-qrcode :value="text" size="300rpx" :marginSize="1" bgColor="white"></l-qrcode> |
||||
<button @click="update">更新</button> |
||||
</view> |
||||
</view> |
||||
|
||||
</view> |
||||
</view> |
||||
|
||||
</template> |
||||
<script> |
||||
// import {ComponentPublicInstance} from 'vue' |
||||
export default { |
||||
name: 'lime-qrcode', |
||||
data() { |
||||
return { |
||||
text: 'qcoon.com.cn', |
||||
image: '', |
||||
index: 0, |
||||
levels: ['L', 'M', 'Q', 'H'] |
||||
} |
||||
}, |
||||
methods:{ |
||||
success(src: string) { |
||||
console.log(`src`, src) |
||||
}, |
||||
update() { |
||||
this.text = `qcoon.cn?v=${Math.random()}` |
||||
}, |
||||
onToggle() { |
||||
this.index++ |
||||
this.index = this.index % this.levels.length |
||||
}, |
||||
onClick() { |
||||
const el:LQrcodeComponentPublicInstance = this.$refs['qrcodeRef'] as LQrcodeComponentPublicInstance |
||||
el.canvasToTempFilePath({ |
||||
success:(res: TakeSnapshotSuccess)=>{ |
||||
this.image = res.tempFilePath |
||||
} |
||||
}) |
||||
} |
||||
}, |
||||
mounted() { |
||||
|
||||
} |
||||
} |
||||
</script> |
||||
<style lang="scss"> |
||||
.demo-block { |
||||
margin: 32px 20px 0; |
||||
overflow: visible; |
||||
&__title { |
||||
margin: 0; |
||||
margin-top: 8px; |
||||
&-text { |
||||
color: rgba(0, 0, 0, 0.6); |
||||
font-weight: 400; |
||||
font-size: 14px; |
||||
line-height: 16px; |
||||
|
||||
&.large { |
||||
color: rgba(0, 0, 0, 0.9); |
||||
font-size: 18px; |
||||
font-weight: 700; |
||||
line-height: 26px; |
||||
} |
||||
&.ultra { |
||||
color: rgba(0, 0, 0, 0.9); |
||||
font-size: 24px; |
||||
font-weight: 700; |
||||
line-height: 32px; |
||||
} |
||||
} |
||||
} |
||||
&__desc-text { |
||||
color: rgba(0, 0, 0, 0.6); |
||||
margin: 8px 16px 0 0; |
||||
font-size: 14px; |
||||
line-height: 22px; |
||||
} |
||||
&__body { |
||||
margin: 16px 0; |
||||
overflow: visible; |
||||
.demo-block { |
||||
// margin-top: 0px; |
||||
margin: 0; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,79 @@ |
||||
<template> |
||||
<demo-block title="QRCode" type="ultra"> |
||||
<demo-block title="基础"> |
||||
<l-qrcode value="https://limeui.qcoon.cn" size="300rpx"></l-qrcode> |
||||
</demo-block> |
||||
<demo-block title="icon"> |
||||
<view style="display: flex; gap: 10px"> |
||||
<image v-if="image" :src="image" style="width: 300rpx;" mode="widthFix"></image> |
||||
<l-qrcode ref="qrcodeRef" value="https://limeui.qcoon.cn" size="300rpx" icon="https://img10.360buyimg.com/img/jfs/t1/182127/16/37474/11761/64659c31F0cd84976/21f25b952f03a49a.jpg" iconSize="70rpx"></l-qrcode> |
||||
<l-qrcode useCanvasToTempFilePath @success="success" value="https://limeui.qcoon.cn" size="300rpx" icon="https://img10.360buyimg.com/img/jfs/t1/182127/16/37474/11761/64659c31F0cd84976/21f25b952f03a49a.jpg" iconSize="70rpx"></l-qrcode> |
||||
</view> |
||||
<button @click="onClick">生成图片</button> |
||||
</demo-block> |
||||
<demo-block title="颜色"> |
||||
<view style="display: flex; gap: 10px"> |
||||
<l-qrcode value="https://limeui.qcoon.cn" size="300rpx" color="rgb(82,196,26)"></l-qrcode> |
||||
<l-qrcode value="https://limeui.qcoon.cn" size="300rpx" color="rgb(22,119,255)" bgColor="rgb(245,245,245)"></l-qrcode> |
||||
</view> |
||||
</demo-block> |
||||
<demo-block title="纠错比例"> |
||||
<l-qrcode value="img10.360buyimg.com/img/jfs/t1/182127/16/37474/11761/64659c31F0cd84976/21f25b952f03a49a.jpg" size="300rpx" :errorLevel="levels[index]"></l-qrcode> |
||||
<button @click="onToggle">切换纠错等级:{{levels[index]}}</button> |
||||
</demo-block> |
||||
<demo-block title="动态"> |
||||
<l-qrcode :value="text" size="300rpx" marginSize="20rpx" bgColor="white"></l-qrcode> |
||||
<button @click="update">更新</button> |
||||
</demo-block> |
||||
</demo-block> |
||||
</template> |
||||
<script> |
||||
import {ref, defineComponent} from '../l-qrcode/vue' |
||||
export default defineComponent({ |
||||
setup() { |
||||
const qrcodeRef = ref(null) |
||||
const image = ref(null) |
||||
const text = ref('qcoon.com.cn') |
||||
const levels = ['L', 'M', 'Q', 'H'] |
||||
let index = ref(0) |
||||
const onToggle = () => { |
||||
index.value++ |
||||
index.value = index.value % levels.length |
||||
} |
||||
const onClick = () => { |
||||
if(qrcodeRef.value) { |
||||
qrcodeRef.value.canvasToTempFilePath({ |
||||
success(res) { |
||||
image.value = res.tempFilePath |
||||
console.log('success:::', res) |
||||
}, |
||||
fail(err) { |
||||
console.log('err:::', err) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
const success = (res) => { |
||||
console.log('res', res) |
||||
} |
||||
|
||||
const update = () =>{ |
||||
text.value = `qcoon.cn?v=${Math.random()}` |
||||
} |
||||
|
||||
return { |
||||
levels, |
||||
index, |
||||
image, |
||||
text, |
||||
qrcodeRef, |
||||
onClick, |
||||
update, |
||||
success, |
||||
onToggle, |
||||
} |
||||
} |
||||
}) |
||||
</script> |
||||
<style> |
||||
</style> |
@ -0,0 +1,77 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="zh"> |
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
<title>lime-qrcode</title> |
||||
<style> |
||||
html,body,canvas { |
||||
margin: 0; |
||||
padding: 0; |
||||
width: 100%; |
||||
height: 100%; |
||||
pointer-events: none; |
||||
/* background-color: rgba(255,0,0,0.1) */ |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
<canvas id="lime-qrcode"></canvas> |
||||
<script type="text/javascript" src="./uni.webview.1.5.3.js"></script> |
||||
<script type="text/javascript" src="./qrcode.min.js"></script> |
||||
<script> |
||||
var canvas = document.querySelector('#lime-qrcode') |
||||
var pixelRatio = window.devicePixelRatio || 1 |
||||
function appendWatermark(image) { |
||||
emit('append', mark.toDataURL()) |
||||
} |
||||
|
||||
var qrcode = new lime.QRCodeCanvas(canvas, { |
||||
pixelRatio, |
||||
}) |
||||
function render(props) { |
||||
if(props.pixelRatio) { |
||||
pixelRatio = props.pixelRatio |
||||
} |
||||
if(qrcode) { |
||||
qrcode.render(props) |
||||
} |
||||
} |
||||
function toDataURL(file) { |
||||
if(qrcode && canvas) { |
||||
try{ |
||||
const image = canvas.toDataURL() |
||||
emit('toDataURL', { |
||||
file, |
||||
image |
||||
}) |
||||
}catch(e){ |
||||
emit('toDataURL', { |
||||
file, |
||||
msg: e |
||||
}) |
||||
} |
||||
|
||||
} |
||||
} |
||||
function emit(event, data) { |
||||
postMessage({ |
||||
event, |
||||
data |
||||
}); |
||||
}; |
||||
function postMessage(data) { |
||||
uni.postMessage({ |
||||
data |
||||
}); |
||||
}; |
||||
// render({ |
||||
// content: ['Lime UI'], |
||||
// // rotate: -22, |
||||
// // baseSize: 2, |
||||
// // fontGap: 3 |
||||
// }) |
||||
</script> |
||||
</body> |
||||
</html> |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,89 @@ |
||||
{ |
||||
"id": "lime-qrcode", |
||||
"displayName": "qrcode 二维码生成", |
||||
"version": "0.1.2", |
||||
"description": "全端二维码生成插件,draw api绘制。在H5,微信小程序,uvue,nvue上测试过,非uvue为vue3实现vue2需配置@vue/composition-api", |
||||
"keywords": [ |
||||
"qrcode", |
||||
"qr", |
||||
"uvue", |
||||
"生成图片", |
||||
"二维码" |
||||
], |
||||
"repository": "", |
||||
"engines": { |
||||
}, |
||||
"dcloudext": { |
||||
"type": "component-vue", |
||||
"sale": { |
||||
"regular": { |
||||
"price": "0.00" |
||||
}, |
||||
"sourcecode": { |
||||
"price": "0.00" |
||||
} |
||||
}, |
||||
"contact": { |
||||
"qq": "305716444" |
||||
}, |
||||
"declaration": { |
||||
"ads": "无", |
||||
"data": "无", |
||||
"permissions": "无" |
||||
}, |
||||
"npmurl": "" |
||||
}, |
||||
"uni_modules": { |
||||
"dependencies": [ |
||||
"lime-shared" |
||||
], |
||||
"encrypt": [], |
||||
"platforms": { |
||||
"cloud": { |
||||
"tcb": "y", |
||||
"aliyun": "y" |
||||
}, |
||||
"client": { |
||||
"Vue": { |
||||
"vue2": "y", |
||||
"vue3": "y" |
||||
}, |
||||
"App": { |
||||
"app-vue": "y", |
||||
"app-nvue": "y", |
||||
"app-android": { |
||||
"minVersion": "19" |
||||
} |
||||
}, |
||||
"H5-mobile": { |
||||
"Safari": "y", |
||||
"Android Browser": "y", |
||||
"微信浏览器(Android)": "y", |
||||
"QQ浏览器(Android)": "y" |
||||
}, |
||||
"H5-pc": { |
||||
"Chrome": "y", |
||||
"IE": "u", |
||||
"Edge": "u", |
||||
"Firefox": "u", |
||||
"Safari": "u" |
||||
}, |
||||
"小程序": { |
||||
"微信": "y", |
||||
"阿里": "u", |
||||
"百度": "u", |
||||
"字节跳动": "u", |
||||
"QQ": "u", |
||||
"钉钉": "u", |
||||
"快手": "u", |
||||
"飞书": "u", |
||||
"京东": "u" |
||||
}, |
||||
"快应用": { |
||||
"华为": "u", |
||||
"联盟": "u" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,153 @@ |
||||
# lime-qrcode 二维码 |
||||
- uniapp vue3 生成二维码插件 |
||||
- 仅在H5、微信小程序、APP-NVUE测试过,如果你在其它端遇到问题或其它端也能跑,请告之 |
||||
- uvue 需要导入[lime-qrcodegen](https://ext.dcloud.net.cn/plugin?id=15838) |
||||
|
||||
## 使用 |
||||
- 导入插件后直接使用 |
||||
- uvue 需要导入**[lime-qrcodegen](https://ext.dcloud.net.cn/plugin?id=15838)** |
||||
|
||||
#### 基础使用 |
||||
|
||||
```html |
||||
<l-qrcode value="http://lime.qcoon.cn" /> |
||||
``` |
||||
|
||||
|
||||
#### ICON |
||||
- 带 Icon 的二维码 |
||||
|
||||
```html |
||||
<l-qrcode value="https://limeui.qcoon.cn" size="300rpx" icon="/static/logo.png" iconSize="70rpx"></l-qrcode> |
||||
``` |
||||
|
||||
#### 颜色 |
||||
- 通过设置 `color` 自定义二维码颜色,通过设置 `bgColor` 自定义背景颜色。 |
||||
|
||||
```html |
||||
<l-qrcode value="https://limeui.qcoon.cn" size="300rpx" color="rgb(82,196,26)"></l-qrcode> |
||||
<l-qrcode value="https://limeui.qcoon.cn" size="300rpx" color="rgb(22,119,255)" bgColor="rgb(245,245,245)"></l-qrcode> |
||||
``` |
||||
|
||||
#### 纠错比例 |
||||
- 通过设置 `errorLevel` 调整不同的容错等级。 |
||||
|
||||
```html |
||||
<l-qrcode value="img10.360buyimg.com/img/jfs/t1/182127/16/37474/11761/64659c31F0cd84976/21f25b952f03a49a.jpg" size="300rpx" errorLevel="H"></l-qrcode> |
||||
``` |
||||
|
||||
#### 生成图片 |
||||
- 1、通过调用插件的`canvasToTempFilePath`方法生成图片。 |
||||
|
||||
```html |
||||
<image v-if="image" :src="image" style="width: 300rpx;" mode="widthFix"></image> |
||||
<l-qrcode ref="qrcodeRef" value="https://limeui.qcoon.cn" size="300rpx" icon="https://img10.360buyimg.com/img/jfs/t1/182127/16/37474/11761/64659c31F0cd84976/21f25b952f03a49a.jpg" iconSize="70rpx"></l-qrcode> |
||||
<button @click="onClick">生成图片</button> |
||||
``` |
||||
```js |
||||
// vue3 |
||||
const qrcodeRef = ref(null) |
||||
const onClick = () => { |
||||
if(!qrcodeRef.value) return |
||||
qrcodeRef.value.canvasToTempFilePath({ |
||||
success(res) { |
||||
image.value = res.tempFilePath |
||||
console.log('success:::', res) |
||||
}, |
||||
fail(err) { |
||||
console.log('err:::', err) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
// vue2 |
||||
const el = this.$refs['qrcodeRef'] |
||||
el.canvasToTempFilePath({ |
||||
success:(res)=>{ |
||||
this.image = res.tempFilePath |
||||
}, |
||||
fail(err) { |
||||
console.log('err:::', err) |
||||
} |
||||
}) |
||||
|
||||
// uvue |
||||
const el:LQrcodeComponentPublicInstance = this.$refs['qrcodeRef'] as LQrcodeComponentPublicInstance |
||||
el.canvasToTempFilePath({ |
||||
success:(res: TakeSnapshotSuccess)=>{ |
||||
this.image = res.tempFilePath |
||||
}, |
||||
fail(err: TakeSnapshotFail) { |
||||
console.log('err:::', err) |
||||
} |
||||
}) |
||||
``` |
||||
|
||||
- 2、通过设置`useCanvasToTempFilePath`在`success`事件里接收图片地址 |
||||
|
||||
```html |
||||
<image v-if="image" :src="image" style="width: 300rpx;" mode="widthFix"></image> |
||||
<l-qrcode useCanvasToTempFilePath @success="success" value="https://limeui.qcoon.cn"></l-qrcode> |
||||
``` |
||||
```js |
||||
const image = ref(null) |
||||
const success = (img) => { |
||||
image.value = img |
||||
} |
||||
``` |
||||
|
||||
### 关于vue2的使用方式 |
||||
- 插件使用了`composition-api`, 如果你希望在vue2中使用请按官方的教程[vue-composition-api](https://uniapp.dcloud.net.cn/tutorial/vue-composition-api.html)配置 |
||||
- 关键代码是: 在main.js中 在vue2部分加上这一段即可,官方是把它单独成了一个文件. |
||||
|
||||
```js |
||||
// main.js vue2 |
||||
import Vue from 'vue' |
||||
import VueCompositionAPI from '@vue/composition-api' |
||||
Vue.use(VueCompositionAPI) |
||||
``` |
||||
另外插件也用到了TS,vue2可能会遇过官方的TS版本过低的问题,找到HX目录下的`compile-typescript`目录 |
||||
```cmd |
||||
// \HBuilderX\plugins\compile-typescript |
||||
yarn add typescript -D |
||||
- or - |
||||
npm install typescript -D |
||||
``` |
||||
|
||||
### 查看示例 |
||||
- 导入后直接使用这个标签查看演示效果 |
||||
|
||||
```html |
||||
// 代码位于 uni_modules/lime-qrcode/compoents/lime-qrcode |
||||
<lime-qrcode /> |
||||
``` |
||||
|
||||
### 插件标签 |
||||
- 默认 l-qrcode 为 component |
||||
- 默认 lime-qrcode 为 demo |
||||
|
||||
|
||||
|
||||
## API |
||||
|
||||
### Props |
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | |
||||
| --------------------------| ------------------------------------------------------------ | ---------------- | ------------ | |
||||
| value | 扫描后的文本 | <em>string</em> | `-` | |
||||
| icon | 二维码中图片的地址 | <em>string</em> | `-` | |
||||
| size | 二维码大小 | <em>number,string</em> | `160` | |
||||
| iconSize | 二维码中图片的大小 | <em>number,string</em> | `40` | |
||||
| color | 二维码颜色 | <em>string</em> | `-` | |
||||
| bgColor | 二维码背景颜色 | <em>string</em> | `-` | |
||||
| errorLevel | 二维码纠错等级 | `'L' | 'M' | 'Q' | 'H' ` | `M` | |
||||
| marginSize | 边距码大小,默认为0码点 | <em>number</em> | `0` | |
||||
|
||||
### 常见问题 |
||||
- icon 是网络地址时,H5和Nvue需要解决跨域问题,小程序需要配置download |
||||
|
||||
## 打赏 |
||||
|
||||
如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。 |
||||
![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/alipay.png) |
||||
![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/wpay.png) |
@ -0,0 +1,25 @@ |
||||
// @ts-nocheck
|
||||
import {isNumeric} from '../isNumeric' |
||||
import {isDef} from '../isDef' |
||||
/** |
||||
* 给一个值添加单位(像素 px) |
||||
* @param value 要添加单位的值,可以是字符串或数字 |
||||
* @returns 添加了单位的值,如果值为 undefined 则返回 undefined |
||||
*/ |
||||
export function addUnit(value?: string | number): string | undefined { |
||||
if (!isDef(value)) { |
||||
return undefined; |
||||
} |
||||
|
||||
value = String(value); // 将值转换为字符串
|
||||
|
||||
// 如果值是数字,则在后面添加单位 "px",否则保持原始值
|
||||
return isNumeric(value) ? `${value}px` : value; |
||||
} |
||||
|
||||
|
||||
// console.log(addUnit(100)); // 输出: "100px"
|
||||
// console.log(addUnit("200")); // 输出: "200px"
|
||||
// console.log(addUnit("300px")); // 输出: "300px"(已经包含单位)
|
||||
// console.log(addUnit()); // 输出: undefined(值为 undefined)
|
||||
// console.log(addUnit(null)); // 输出: undefined(值为 null)
|
@ -0,0 +1,63 @@ |
||||
// @ts-nocheck
|
||||
import {platform} from '../platform' |
||||
/** |
||||
* buffer转路径 |
||||
* @param {Object} buffer |
||||
*/ |
||||
// @ts-nocheck
|
||||
export function arrayBufferToFile(buffer: ArrayBuffer | Blob, name?: string, format?:string):Promise<(File|string)> { |
||||
return new Promise((resolve, reject) => { |
||||
// #ifdef MP
|
||||
const fs = uni.getFileSystemManager() |
||||
//自定义文件名
|
||||
if (!name && !format) { |
||||
reject(new Error('ERROR_NAME_PARSE')) |
||||
} |
||||
const fileName = `${name || new Date().getTime()}.${format.replace(/(.+)?\//,'')}`; |
||||
let pre = platform() |
||||
const filePath = `${pre.env.USER_DATA_PATH}/${fileName}` |
||||
fs.writeFile({ |
||||
filePath, |
||||
data: buffer,
|
||||
success() { |
||||
resolve(filePath) |
||||
}, |
||||
fail(err) { |
||||
console.error(err) |
||||
reject(err) |
||||
} |
||||
}) |
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
const file = new File([buffer], name, { |
||||
type: format, |
||||
}); |
||||
resolve(file) |
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now()) |
||||
const base64 = uni.arrayBufferToBase64(buffer) |
||||
bitmap.loadBase64Data(base64, () => { |
||||
if (!name && !format) { |
||||
reject(new Error('ERROR_NAME_PARSE')) |
||||
} |
||||
const fileNmae = `${name || new Date().getTime()}.${format.replace(/(.+)?\//,'')}`; |
||||
const filePath = `_doc/uniapp_temp/${fileNmae}` |
||||
bitmap.save(filePath, {}, |
||||
() => { |
||||
bitmap.clear() |
||||
resolve(filePath) |
||||
}, |
||||
(error) => { |
||||
bitmap.clear() |
||||
reject(error) |
||||
}) |
||||
}, (error) => { |
||||
bitmap.clear() |
||||
reject(error) |
||||
}) |
||||
// #endif
|
||||
}) |
||||
} |
@ -0,0 +1,13 @@ |
||||
// @ts-nocheck
|
||||
// 未完成
|
||||
export function base64ToArrayBuffer(base64 : string) { |
||||
const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || []; |
||||
if (!format) { |
||||
new Error('ERROR_BASE64SRC_PARSE') |
||||
} |
||||
if(uni.base64ToArrayBuffer) { |
||||
return uni.base64ToArrayBuffer(bodyData) |
||||
} else { |
||||
|
||||
} |
||||
} |
@ -0,0 +1,76 @@ |
||||
// @ts-nocheck
|
||||
import {platform} from '../platform' |
||||
/** |
||||
* base64转路径 |
||||
* @param {Object} base64 |
||||
*/ |
||||
export function base64ToPath(base64: string, filename?: string):Promise<string> { |
||||
const [, format] = /^data:image\/(\w+);base64,/.exec(base64) || []; |
||||
console.log('format', format) |
||||
return new Promise((resolve, reject) => { |
||||
// #ifdef MP
|
||||
const fs = uni.getFileSystemManager() |
||||
//自定义文件名
|
||||
if (!filename && !format) { |
||||
reject(new Error('ERROR_BASE64SRC_PARSE')) |
||||
} |
||||
// const time = new Date().getTime();
|
||||
const name = filename || `${new Date().getTime()}.${format}`; |
||||
let pre = platform() |
||||
const filePath = `${pre.env.USER_DATA_PATH}/${name}` |
||||
fs.writeFile({ |
||||
filePath, |
||||
data: base64.split(',')[1],
|
||||
encoding: 'base64', |
||||
success() { |
||||
resolve(filePath) |
||||
}, |
||||
fail(err) { |
||||
console.error(err) |
||||
reject(err) |
||||
} |
||||
}) |
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
// mime类型
|
||||
let mimeString = base64.split(',')[0].split(':')[1].split(';')[0]; |
||||
//base64 解码
|
||||
let byteString = atob(base64.split(',')[1]); |
||||
//创建缓冲数组
|
||||
let arrayBuffer = new ArrayBuffer(byteString.length); |
||||
//创建视图
|
||||
let intArray = new Uint8Array(arrayBuffer); |
||||
for (let i = 0; i < byteString.length; i++) { |
||||
intArray[i] = byteString.charCodeAt(i); |
||||
} |
||||
resolve(URL.createObjectURL(new Blob([intArray], { |
||||
type: mimeString |
||||
}))) |
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now()) |
||||
bitmap.loadBase64Data(base64, () => { |
||||
if (!filename && !format) { |
||||
reject(new Error('ERROR_BASE64SRC_PARSE')) |
||||
} |
||||
// const time = new Date().getTime();
|
||||
const name = filename || `${new Date().getTime()}.${format}`; |
||||
const filePath = `_doc/uniapp_temp/${name}` |
||||
bitmap.save(filePath, {}, |
||||
() => { |
||||
bitmap.clear() |
||||
resolve(filePath) |
||||
}, |
||||
(error) => { |
||||
bitmap.clear() |
||||
reject(error) |
||||
}) |
||||
}, (error) => { |
||||
bitmap.clear() |
||||
reject(error) |
||||
}) |
||||
// #endif
|
||||
}) |
||||
} |
@ -0,0 +1,21 @@ |
||||
/** |
||||
* 将字符串转换为 camelCase 或 PascalCase 风格的命名约定 |
||||
* @param str 要转换的字符串 |
||||
* @param isPascalCase 指示是否转换为 PascalCase 的布尔值,默认为 false |
||||
* @returns 转换后的字符串 |
||||
*/ |
||||
export function camelCase(str: string, isPascalCase: boolean = false): string { |
||||
// 将字符串分割成单词数组
|
||||
let words: string[] = str.split(/[\s_-]+/); |
||||
|
||||
// 将数组中的每个单词首字母大写(除了第一个单词)
|
||||
let camelCased: string[] = words.map((word, index) => { |
||||
if (index === 0 && !isPascalCase) { |
||||
return word.toLowerCase(); // 第一个单词全小写
|
||||
} |
||||
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); |
||||
}); |
||||
|
||||
// 将数组中的单词拼接成一个字符串
|
||||
return camelCased.join(''); |
||||
}; |
@ -0,0 +1,58 @@ |
||||
// @ts-nocheck
|
||||
// #ifdef MP-ALIPAY
|
||||
interface My { |
||||
SDKVersion: string |
||||
} |
||||
declare var my: My |
||||
// #endif
|
||||
|
||||
function compareVersion(v1:string, v2:string) { |
||||
let a1 = v1.split('.'); |
||||
let a2 = v2.split('.'); |
||||
const len = Math.max(a1.length, a2.length); |
||||
|
||||
while (a1.length < len) { |
||||
a1.push('0'); |
||||
} |
||||
while (a2.length < len) { |
||||
a2.push('0'); |
||||
} |
||||
|
||||
for (let i = 0; i < len; i++) { |
||||
const num1 = parseInt(a1[i], 10); |
||||
const num2 = parseInt(a2[i], 10); |
||||
|
||||
if (num1 > num2) { |
||||
return 1; |
||||
} |
||||
if (num1 < num2) { |
||||
return -1; |
||||
} |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
function gte(version: string) { |
||||
let {SDKVersion} = uni.getSystemInfoSync(); |
||||
// #ifdef MP-ALIPAY
|
||||
SDKVersion = my.SDKVersion |
||||
// #endif
|
||||
return compareVersion(SDKVersion, version) >= 0; |
||||
} |
||||
|
||||
/** 环境是否支持canvas 2d */ |
||||
export function canIUseCanvas2d() { |
||||
// #ifdef MP-WEIXIN
|
||||
return gte('2.9.0'); |
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
return gte('2.7.0'); |
||||
// #endif
|
||||
// #ifdef MP-TOUTIAO
|
||||
return gte('1.78.0'); |
||||
// #endif
|
||||
// #ifndef MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO
|
||||
return false |
||||
// #endif
|
||||
} |
@ -0,0 +1,30 @@ |
||||
## 0.1.4(2023-09-05) |
||||
- feat: 增加 Hooks `useIntersectionObserver` |
||||
- feat: 增加 `floatAdd` |
||||
- feat: 因为本人插件兼容 vue2 需要使用 `composition-api`,故增加vue文件代码插件的条件编译 |
||||
## 0.1.3(2023-08-13) |
||||
- feat: 增加 `camelCase` |
||||
## 0.1.2(2023-07-17) |
||||
- feat: 增加 `getClassStr` |
||||
## 0.1.1(2023-07-06) |
||||
- feat: 增加 `isNumeric`, 区别于 `isNumber` |
||||
## 0.1.0(2023-06-30) |
||||
- fix: `clamp`忘记导出了 |
||||
## 0.0.9(2023-06-27) |
||||
- feat: 增加`arrayBufferToFile` |
||||
## 0.0.8(2023-06-19) |
||||
- feat: 增加`createAnimation`、`clamp` |
||||
## 0.0.7(2023-06-08) |
||||
- chore: 更新注释 |
||||
## 0.0.6(2023-06-08) |
||||
- chore: 增加`createImage`为`lime-watermark`和`lime-qrcode`提供依赖 |
||||
## 0.0.5(2023-06-03) |
||||
- chore: 更新注释 |
||||
## 0.0.4(2023-05-22) |
||||
- feat: 增加`range`,`exif`,`selectComponent` |
||||
## 0.0.3(2023-05-08) |
||||
- feat: 增加`fillZero`,`debounce`,`throttle`,`random` |
||||
## 0.0.2(2023-05-05) |
||||
- chore: 更新文档 |
||||
## 0.0.1(2023-05-05) |
||||
- 无 |
@ -0,0 +1,16 @@ |
||||
// @ts-nocheck
|
||||
/** |
||||
* 将一个值限制在指定的范围内 |
||||
* @param min 最小值 |
||||
* @param max 最大值 |
||||
* @param val 要限制的值 |
||||
* @returns 限制后的值 |
||||
*/ |
||||
export function clamp(min: number, max: number, val: number): number { |
||||
return Math.max(min, Math.min(max, val)); |
||||
} |
||||
|
||||
|
||||
// console.log(clamp(0, 10, 5)); // 输出: 5(在范围内,不做更改)
|
||||
// console.log(clamp(0, 10, -5)); // 输出: 0(小于最小值,被限制为最小值)
|
||||
// console.log(clamp(0, 10, 15)); // 输出: 10(大于最大值,被限制为最大值)
|
@ -0,0 +1,103 @@ |
||||
// @ts-nocheck
|
||||
/** |
||||
* 深度克隆一个对象或数组 |
||||
* @param obj 要克隆的对象或数组 |
||||
* @returns 克隆后的对象或数组 |
||||
*/ |
||||
export function cloneDeep<T>(obj: any): T { |
||||
// 如果传入的对象为空,返回空
|
||||
if (obj === null) { |
||||
return null as unknown as T; |
||||
} |
||||
|
||||
// 如果传入的对象是 Set 类型,则将其转换为数组,并通过新的 Set 构造函数创建一个新的 Set 对象
|
||||
if (obj instanceof Set) { |
||||
return new Set([...obj]) as unknown as T; |
||||
} |
||||
|
||||
// 如果传入的对象是 Map 类型,则将其转换为数组,并通过新的 Map 构造函数创建一个新的 Map 对象
|
||||
if (obj instanceof Map) { |
||||
return new Map([...obj]) as unknown as T; |
||||
} |
||||
|
||||
// 如果传入的对象是 WeakMap 类型,则直接用传入的 WeakMap 对象进行赋值
|
||||
if (obj instanceof WeakMap) { |
||||
let weakMap = new WeakMap(); |
||||
weakMap = obj; |
||||
return weakMap as unknown as T; |
||||
} |
||||
|
||||
// 如果传入的对象是 WeakSet 类型,则直接用传入的 WeakSet 对象进行赋值
|
||||
if (obj instanceof WeakSet) { |
||||
let weakSet = new WeakSet(); |
||||
weakSet = obj; |
||||
return weakSet as unknown as T; |
||||
} |
||||
|
||||
// 如果传入的对象是 RegExp 类型,则通过新的 RegExp 构造函数创建一个新的 RegExp 对象
|
||||
if (obj instanceof RegExp) { |
||||
return new RegExp(obj) as unknown as T; |
||||
} |
||||
|
||||
// 如果传入的对象是 undefined 类型,则返回 undefined
|
||||
if (typeof obj === 'undefined') { |
||||
return undefined as unknown as T; |
||||
} |
||||
|
||||
// 如果传入的对象是数组,则递归调用 cloneDeep 函数对数组中的每个元素进行克隆
|
||||
if (Array.isArray(obj)) { |
||||
return obj.map(cloneDeep) as unknown as T; |
||||
} |
||||
|
||||
// 如果传入的对象是 Date 类型,则通过新的 Date 构造函数创建一个新的 Date 对象
|
||||
if (obj instanceof Date) { |
||||
return new Date(obj.getTime()) as unknown as T; |
||||
} |
||||
|
||||
// 如果传入的对象是普通对象,则使用递归调用 cloneDeep 函数对对象的每个属性进行克隆
|
||||
if (typeof obj === 'object') { |
||||
const newObj: any = {}; |
||||
for (const [key, value] of Object.entries(obj)) { |
||||
newObj[key] = cloneDeep(value); |
||||
} |
||||
const symbolKeys = Object.getOwnPropertySymbols(obj); |
||||
for (const key of symbolKeys) { |
||||
newObj[key] = cloneDeep(obj[key]); |
||||
} |
||||
return newObj; |
||||
} |
||||
|
||||
// 如果传入的对象是基本数据类型(如字符串、数字等),则直接返回
|
||||
return obj; |
||||
} |
||||
|
||||
// 示例使用
|
||||
|
||||
// // 克隆一个对象
|
||||
// const obj = { name: 'John', age: 30 };
|
||||
// const clonedObj = cloneDeep(obj);
|
||||
|
||||
// console.log(clonedObj); // 输出: { name: 'John', age: 30 }
|
||||
// console.log(clonedObj === obj); // 输出: false (副本与原对象是独立的)
|
||||
|
||||
// // 克隆一个数组
|
||||
// const arr = [1, 2, 3];
|
||||
// const clonedArr = cloneDeep(arr);
|
||||
|
||||
// console.log(clonedArr); // 输出: [1, 2, 3]
|
||||
// console.log(clonedArr === arr); // 输出: false (副本与原数组是独立的)
|
||||
|
||||
// // 克隆一个包含嵌套对象的对象
|
||||
// const person = {
|
||||
// name: 'Alice',
|
||||
// age: 25,
|
||||
// address: {
|
||||
// city: 'New York',
|
||||
// country: 'USA',
|
||||
// },
|
||||
// };
|
||||
// const clonedPerson = cloneDeep(person);
|
||||
|
||||
// console.log(clonedPerson); // 输出: { name: 'Alice', age: 25, address: { city: 'New York', country: 'USA' } }
|
||||
// console.log(clonedPerson === person); // 输出: false (副本与原对象是独立的)
|
||||
// console.log(clonedPerson.address === person.address); // 输出: false (嵌套对象的副本也是独立的)
|
@ -0,0 +1,22 @@ |
||||
// @ts-nocheck
|
||||
|
||||
/** |
||||
* 在给定数组中找到最接近目标数字的元素。 |
||||
* @param arr 要搜索的数字数组。 |
||||
* @param target 目标数字。 |
||||
* @returns 最接近目标数字的数组元素。 |
||||
*/ |
||||
export function closest(arr: number[], target: number) { |
||||
return arr.reduce((pre, cur) => |
||||
Math.abs(pre - target) < Math.abs(cur - target) ? pre : cur |
||||
); |
||||
} |
||||
|
||||
// 示例
|
||||
// // 定义一个数字数组
|
||||
// const numbers = [1, 3, 5, 7, 9];
|
||||
|
||||
// // 在数组中找到最接近目标数字 6 的元素
|
||||
// const closestNumber = closest(numbers, 6);
|
||||
|
||||
// console.log(closestNumber); // 输出结果: 5
|
@ -0,0 +1,149 @@ |
||||
// @ts-nocheck
|
||||
// nvue 需要在节点上设置ref或在export里传入
|
||||
// const animation = createAnimation({
|
||||
// ref: this.$refs['xxx'],
|
||||
// duration: 0,
|
||||
// timingFunction: 'linear'
|
||||
// })
|
||||
// animation.opacity(1).translate(x, y).step({duration})
|
||||
// animation.export(ref)
|
||||
|
||||
// 抹平nvue 与 uni.createAnimation的使用差距
|
||||
// 但是nvue动画太慢~~~无语
|
||||
|
||||
|
||||
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
const nvueAnimation = uni.requireNativePlugin('animation') |
||||
|
||||
type AnimationTypes = 'matrix' | 'matrix3d' | 'rotate' | 'rotate3d' | 'rotateX' | 'rotateY' | 'rotateZ' | 'scale' | 'scale3d' | 'scaleX' | 'scaleY' | 'scaleZ' | 'skew' | 'skewX' | 'skewY' | 'translate' | 'translate3d' | 'translateX' | 'translateY' | 'translateZ' |
||||
| 'opacity' | 'backgroundColor' | 'width' | 'height' | 'left' | 'right' | 'top' | 'bottom' |
||||
|
||||
interface Styles { |
||||
[key : string] : any |
||||
} |
||||
|
||||
interface StepConfig { |
||||
duration?: number |
||||
timingFunction?: string |
||||
delay?: number |
||||
needLayout?: boolean |
||||
transformOrigin?: string |
||||
} |
||||
interface StepAnimate { |
||||
styles?: Styles |
||||
config?: StepConfig |
||||
} |
||||
interface StepAnimates { |
||||
[key: number]: StepAnimate |
||||
} |
||||
interface CreateAnimationOptions extends UniApp.CreateAnimationOptions { |
||||
ref?: string |
||||
} |
||||
|
||||
type Callback = (time: number) => void |
||||
const animateTypes1 : AnimationTypes[] = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d', |
||||
'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY', |
||||
'translateZ' |
||||
] |
||||
const animateTypes2 : AnimationTypes[] = ['opacity', 'backgroundColor'] |
||||
const animateTypes3 : AnimationTypes[] = ['width', 'height', 'left', 'right', 'top', 'bottom'] |
||||
|
||||
class LimeAnimation { |
||||
ref : any |
||||
context : any |
||||
options : UniApp.CreateAnimationOptions |
||||
// stack : any[] = []
|
||||
next : number = 0 |
||||
currentStepAnimates : StepAnimates = {} |
||||
duration : number = 0 |
||||
constructor(options : CreateAnimationOptions) { |
||||
const {ref} = options |
||||
this.ref = ref |
||||
this.options = options |
||||
} |
||||
addAnimate(type : AnimationTypes, args: (string | number)[]) { |
||||
let aniObj = this.currentStepAnimates[this.next] |
||||
let stepAnimate:StepAnimate = {} |
||||
if (!aniObj) { |
||||
stepAnimate = {styles: {}, config: {}} |
||||
} else { |
||||
stepAnimate = aniObj |
||||
} |
||||
|
||||
if (animateTypes1.includes(type)) { |
||||
if (!stepAnimate.styles.transform) { |
||||
stepAnimate.styles.transform = '' |
||||
} |
||||
let unit = '' |
||||
if (type === 'rotate') { |
||||
unit = 'deg' |
||||
} |
||||
stepAnimate.styles.transform += `${type}(${args.map((v: number) => v + unit).join(',')}) ` |
||||
} else { |
||||
stepAnimate.styles[type] = `${args.join(',')}` |
||||
} |
||||
this.currentStepAnimates[this.next] = stepAnimate |
||||
} |
||||
animateRun(styles: Styles = {}, config:StepConfig = {}, ref: any) { |
||||
const el = ref || this.ref |
||||
if (!el) return |
||||
return new Promise((resolve) => { |
||||
const time = +new Date() |
||||
nvueAnimation.transition(el, { |
||||
styles, |
||||
...config |
||||
}, () => { |
||||
resolve(+new Date() - time) |
||||
}) |
||||
}) |
||||
} |
||||
nextAnimate(animates: StepAnimates, step: number = 0, ref: any, cb: Callback) { |
||||
let obj = animates[step] |
||||
if (obj) { |
||||
let { styles, config } = obj |
||||
// this.duration += config.duration
|
||||
this.animateRun(styles, config, ref).then((time: number) => { |
||||
step += 1 |
||||
this.duration += time |
||||
this.nextAnimate(animates, step, ref, cb) |
||||
}) |
||||
} else { |
||||
this.currentStepAnimates = {} |
||||
cb && cb(this.duration) |
||||
} |
||||
} |
||||
step(config:StepConfig = {}) { |
||||
this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config) |
||||
this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin |
||||
this.next++ |
||||
return this |
||||
} |
||||
export(ref: any, cb?: Callback) { |
||||
ref = ref || this.ref |
||||
if(!ref) return |
||||
this.duration = 0 |
||||
this.next = 0 |
||||
this.nextAnimate(this.currentStepAnimates, 0, ref, cb) |
||||
return null |
||||
} |
||||
} |
||||
|
||||
|
||||
animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => { |
||||
LimeAnimation.prototype[type] = function(...args: (string | number)[]) { |
||||
this.addAnimate(type, args) |
||||
return this |
||||
} |
||||
}) |
||||
// #endif
|
||||
export function createAnimation(options : CreateAnimationOptions) { |
||||
// #ifndef APP-NVUE
|
||||
// 在iOS10+QQ小程序平台下,传给原生的对象一定是个普通对象而不是Proxy对象,否则会报parameter should be Object instead of ProxyObject的错误
|
||||
return uni.createAnimation({ ...options }) |
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
return new LimeAnimation(options) |
||||
// #endif
|
||||
} |
@ -0,0 +1,61 @@ |
||||
// @ts-nocheck
|
||||
import {isBrowser} from '../isBrowser' |
||||
class Image { |
||||
currentSrc: string | null = null |
||||
naturalHeight: number = 0 |
||||
naturalWidth: number = 0 |
||||
width: number = 0 |
||||
height: number = 0 |
||||
tagName: string = 'IMG' |
||||
path: string = '' |
||||
crossOrigin: string = '' |
||||
referrerPolicy: string = '' |
||||
onload: () => void = () => {} |
||||
onerror: () => void = () => {} |
||||
complete: boolean = false |
||||
constructor() {} |
||||
set src(src: string) { |
||||
console.log('src', src) |
||||
if(!src) { |
||||
return this.onerror() |
||||
} |
||||
src = src.replace(/^@\//,'/') |
||||
this.currentSrc = src |
||||
uni.getImageInfo({ |
||||
src, |
||||
success: (res) => { |
||||
const localReg = /^\.|^\/(?=[^\/])/; |
||||
// #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO
|
||||
res.path = localReg.test(src) ? `/${res.path}` : res.path; |
||||
// #endif
|
||||
this.complete = true |
||||
this.path = res.path |
||||
this.naturalWidth = this.width = res.width |
||||
this.naturalHeight = this.height = res.height |
||||
this.onload() |
||||
}, |
||||
fail: () => { |
||||
this.onerror() |
||||
} |
||||
}) |
||||
} |
||||
get src() { |
||||
return this.currentSrc |
||||
} |
||||
} |
||||
interface UniImage extends WechatMiniprogram.Image { |
||||
complete?: boolean |
||||
naturalHeight?: number |
||||
naturalWidth?: number |
||||
} |
||||
/** 创建用于 canvas 的 img */ |
||||
export function createImage(canvas?: any): HTMLImageElement | UniImage { |
||||
if(canvas && canvas.createImage) { |
||||
return (canvas as WechatMiniprogram.Canvas).createImage() |
||||
} else if(this.tagName == 'canvas' && !('toBlob' in this) || canvas && !('toBlob' in canvas)){ |
||||
return new Image() |
||||
} else if(isBrowser) { |
||||
return new window.Image() |
||||
} |
||||
return new Image() |
||||
} |
@ -0,0 +1,38 @@ |
||||
// @ts-nocheck
|
||||
type Timeout = ReturnType<typeof setTimeout> | null; |
||||
/** |
||||
* 防抖函数,通过延迟一定时间来限制函数的执行频率。 |
||||
* @param fn 要防抖的函数。 |
||||
* @param wait 触发防抖的等待时间,单位为毫秒。 |
||||
* @returns 防抖函数。 |
||||
*/ |
||||
export function debounce(fn: (...args: any[]) => void, wait = 300) { |
||||
let timer: Timeout = null; // 用于存储 setTimeout 的标识符的变量
|
||||
|
||||
return function (this: any, ...args: any[]) { |
||||
if (timer) clearTimeout(timer); // 如果上一个 setTimeout 存在,则清除它
|
||||
|
||||
// 设置一个新的 setTimeout,在指定的等待时间后调用防抖函数
|
||||
timer = setTimeout(() => { |
||||
fn.apply(this, args); // 使用提供的参数调用原始函数
|
||||
}, wait); |
||||
}; |
||||
}; |
||||
|
||||
|
||||
|
||||
// 示例
|
||||
// 定义一个函数
|
||||
// function saveData(data: string) {
|
||||
// // 模拟保存数据的操作
|
||||
// console.log(`Saving data: ${data}`);
|
||||
// }
|
||||
|
||||
// // 创建一个防抖函数,延迟 500 毫秒后调用 saveData 函数
|
||||
// const debouncedSaveData = debounce(saveData, 500);
|
||||
|
||||
// // 连续调用防抖函数
|
||||
// debouncedSaveData('Data 1'); // 不会立即调用 saveData 函数
|
||||
// debouncedSaveData('Data 2'); // 不会立即调用 saveData 函数
|
||||
|
||||
// 在 500 毫秒后,只会调用一次 saveData 函数,输出结果为 "Saving data: Data 2"
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@ |
||||
// @ts-nocheck
|
||||
/** |
||||
* 在数字前填充零,返回字符串形式的结果 |
||||
* @param number 要填充零的数字 |
||||
* @param length 填充零后的字符串长度,默认为2 |
||||
* @returns 填充零后的字符串 |
||||
*/ |
||||
export function fillZero(number: number, length: number = 2): string { |
||||
// 将数字转换为字符串,然后使用 padStart 方法填充零到指定长度
|
||||
return `${number}`.padStart(length, '0'); |
||||
} |
@ -0,0 +1,36 @@ |
||||
import {isNumber} from '../isNumber' |
||||
/** |
||||
* 返回两个浮点数相加的结果 |
||||
* @param num1 第一个浮点数 |
||||
* @param num2 第二个浮点数 |
||||
* @returns 两个浮点数的相加结果 |
||||
*/ |
||||
export function floatAdd(num1: number, num2: number): number { |
||||
// 检查 num1 和 num2 是否为数字类型
|
||||
if (!(isNumber(num1) || isNumber(num2))) { |
||||
console.warn('Please pass in the number type'); |
||||
return NaN; |
||||
} |
||||
|
||||
let r1: number, r2: number, m: number; |
||||
|
||||
try { |
||||
// 获取 num1 小数点后的位数
|
||||
r1 = num1.toString().split('.')[1].length; |
||||
} catch (error) { |
||||
r1 = 0; |
||||
} |
||||
|
||||
try { |
||||
// 获取 num2 小数点后的位数
|
||||
r2 = num2.toString().split('.')[1].length; |
||||
} catch (error) { |
||||
r2 = 0; |
||||
} |
||||
|
||||
// 计算需要扩大的倍数
|
||||
m = Math.pow(10, Math.max(r1, r2)); |
||||
|
||||
// 返回相加结果
|
||||
return (num1 * m + num2 * m) / m; |
||||
} |
@ -0,0 +1,27 @@ |
||||
// @ts-nocheck
|
||||
/** |
||||
* 获取对象的类名字符串 |
||||
* @param obj - 需要处理的对象 |
||||
* @returns 由对象属性作为类名组成的字符串 |
||||
*/ |
||||
export function getClassStr<T>(obj: T): string { |
||||
let classNames: string[] = []; |
||||
|
||||
// 遍历对象的属性
|
||||
for (let key in obj) { |
||||
// 检查属性确实属于对象自身且其值为true
|
||||
if ((obj as any).hasOwnProperty(key) && obj[key]) { |
||||
// 将属性名添加到类名数组中
|
||||
classNames.push(key); |
||||
} |
||||
} |
||||
|
||||
// 将类名数组用空格连接成字符串并返回
|
||||
return classNames.join(' '); |
||||
} |
||||
|
||||
|
||||
// 示例
|
||||
// const obj = { foo: true, bar: false, baz: true };
|
||||
// const classNameStr = getClassStr(obj);
|
||||
// console.log(classNameStr); // 输出: "foo baz"
|
@ -0,0 +1,6 @@ |
||||
// @ts-nocheck
|
||||
/** 获取当前页 */ |
||||
export const getCurrentPage = () => { |
||||
const pages = getCurrentPages(); |
||||
return pages[pages.length - 1] //as T & WechatMiniprogram.Page.TrivialInstance;
|
||||
}; |
@ -0,0 +1,14 @@ |
||||
// @ts-nocheck
|
||||
export const getLocalFilePath = (path: string) => { |
||||
if(typeof plus == 'undefined') return path |
||||
if(/^(_www|_doc|_documents|_downloads|file:\/\/|\/storage\/emulated\/0\/)/.test(path)) return path |
||||
if (/^\//.test(path)) { |
||||
const localFilePath = plus.io.convertAbsoluteFileSystem(path) |
||||
if (localFilePath !== path) { |
||||
return localFilePath |
||||
} else { |
||||
path = path.slice(1) |
||||
} |
||||
} |
||||
return '_www/' + path |
||||
} |
@ -0,0 +1,86 @@ |
||||
// @ts-nocheck
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
// 当编译环境是 APP-NVUE 时,引入 uni.requireNativePlugin('dom'),具体插件用途未知
|
||||
const dom = uni.requireNativePlugin('dom') |
||||
// #endif
|
||||
|
||||
interface RectOptions { |
||||
/** |
||||
* 上下文 |
||||
*/ |
||||
context ?: any // ComponentInternalInstance 类型,用于指定上下文
|
||||
|
||||
/** |
||||
* 是否需要获取所有节点,nvue 环境下不支持 |
||||
*/ |
||||
needAll ?: boolean, |
||||
|
||||
/** |
||||
* 节点引用对象,类型为 UniNamespace.NodesRef |
||||
*/ |
||||
nodes ?: UniNamespace.NodesRef |
||||
|
||||
/** |
||||
* 节点引用对象的键,类型为 UniNamespace.NodesRef 中的某个键 |
||||
*/ |
||||
type ?: keyof UniNamespace.NodesRef |
||||
} |
||||
|
||||
/** |
||||
* 获取节点信息 |
||||
* @param selector 选择器字符串 |
||||
* @param options RectOptions 对象,用于配置选项 |
||||
* @returns 包含节点信息的 Promise 对象 |
||||
*/ |
||||
export function getRect(selector : string, options : RectOptions = {}) { |
||||
// #ifndef APP-NVUE
|
||||
const typeDefault = 'boundingClientRect' |
||||
let { context, needAll, type = typeDefault } = options |
||||
// #endif
|
||||
|
||||
// #ifdef MP || VUE2
|
||||
if (context.proxy) context = context.proxy |
||||
// #endif
|
||||
|
||||
return new Promise<UniNamespace.NodeInfo>((resolve, reject) => { |
||||
// #ifndef APP-NVUE
|
||||
const dom = uni.createSelectorQuery().in(context)[needAll ? 'selectAll' : 'select'](selector); |
||||
const result = (rect: UniNamespace.NodeInfo) => { |
||||
if (rect) { |
||||
resolve(rect) |
||||
} else { |
||||
reject('no rect') |
||||
} |
||||
} |
||||
if (type == typeDefault) { |
||||
dom[type](result).exec() |
||||
} else { |
||||
dom[type]({ |
||||
node: true, |
||||
size: true, |
||||
rect: true |
||||
}, result).exec() |
||||
} |
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
let { context } = options |
||||
if (/#|\./.test(selector) && context.refs) { |
||||
selector = selector.replace(/#|\./, '') |
||||
if (context.refs[selector]) { |
||||
selector = context.refs[selector] |
||||
if(Array.isArray(selector)) { |
||||
selector = selector[0] |
||||
} |
||||
} |
||||
} |
||||
dom.getComponentRect(selector, (res) => { |
||||
if (res.size) { |
||||
resolve(res.size) |
||||
} else { |
||||
reject('no rect') |
||||
} |
||||
}) |
||||
// #endif
|
||||
}); |
||||
}; |
@ -0,0 +1,30 @@ |
||||
// @ts-nocheck
|
||||
interface CSSProperties { |
||||
[key: string]: string | number |
||||
} |
||||
/** |
||||
* 将字符串转换为带有连字符分隔的小写形式 |
||||
* @param key - 要转换的字符串 |
||||
* @returns 转换后的字符串 |
||||
*/ |
||||
export function toLowercaseSeparator(key: string) { |
||||
return key.replace(/([A-Z])/g, '-$1').toLowerCase(); |
||||
} |
||||
|
||||
/** |
||||
* 获取样式对象对应的样式字符串 |
||||
* @param style - CSS样式对象 |
||||
* @returns 由非空有效样式属性键值对组成的字符串 |
||||
*/ |
||||
export function getStyleStr(style: CSSProperties): string { |
||||
return Object.keys(style) |
||||
.filter(key => style[key] !== undefined && style[key] !== null && style[key] !== '') |
||||
.map((key: string) => `${toLowercaseSeparator(key)}: ${style[key]};`) |
||||
.join(' '); |
||||
} |
||||
|
||||
// 示例
|
||||
// const style = { color: 'red', fontSize: '16px', backgroundColor: '', border: null };
|
||||
// const styleStr = getStyleStr(style);
|
||||
// console.log(styleStr);
|
||||
// 输出: "color: red; font-size: 16px;"
|
@ -0,0 +1,30 @@ |
||||
// @ts-nocheck
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty |
||||
/** |
||||
* 检查对象或数组是否具有指定的属性或键 |
||||
* @param obj 要检查的对象或数组 |
||||
* @param key 指定的属性或键 |
||||
* @returns 如果对象或数组具有指定的属性或键,则返回true;否则返回false |
||||
*/ |
||||
export function hasOwn(obj: Object | Array<any>, key: string): boolean { |
||||
return hasOwnProperty.call(obj, key); |
||||
} |
||||
|
||||
// 示例
|
||||
// const obj = { name: 'John', age: 30 };
|
||||
|
||||
// if (hasOwn(obj, 'name')) {
|
||||
// console.log("对象具有 'name' 属性");
|
||||
// } else {
|
||||
// console.log("对象不具有 'name' 属性");
|
||||
// }
|
||||
// // 输出: 对象具有 'name' 属性
|
||||
|
||||
// const arr = [1, 2, 3];
|
||||
|
||||
// if (hasOwn(arr, 'length')) {
|
||||
// console.log("数组具有 'length' 属性");
|
||||
// } else {
|
||||
// console.log("数组不具有 'length' 属性");
|
||||
// }
|
||||
// 输出: 数组具有 'length' 属性
|
@ -0,0 +1,43 @@ |
||||
// @ts-nocheck
|
||||
// validator
|
||||
export * from './isString' |
||||
export * from './isNumber' |
||||
export * from './isNumeric' |
||||
export * from './isDef' |
||||
export * from './isFunction' |
||||
export * from './isObject' |
||||
export * from './isPromise' |
||||
export * from './isBase64' |
||||
|
||||
export * from './hasOwn' |
||||
|
||||
// 单位转换
|
||||
export * from './addUnit' |
||||
export * from './unitConvert' |
||||
export * from './toNumber' |
||||
|
||||
export * from './random' |
||||
export * from './range' |
||||
export * from './fillZero' |
||||
|
||||
// image
|
||||
export * from './base64ToPath' |
||||
export * from './pathToBase64' |
||||
export * from './exif' |
||||
|
||||
// canvas
|
||||
export * from './canIUseCanvas2d' |
||||
|
||||
// page
|
||||
export * from './getCurrentPage' |
||||
|
||||
// dom
|
||||
export * from './getRect' |
||||
export * from './selectComponent' |
||||
export * from './createAnimation' |
||||
|
||||
// delay
|
||||
export * from './sleep' |
||||
export * from './debounce' |
||||
export * from './throttle' |
||||
|
@ -0,0 +1,9 @@ |
||||
// @ts-nocheck
|
||||
/** |
||||
* 判断给定的路径是否为Base64编码的图像路径 |
||||
* @param path 图像路径 |
||||
* @returns 如果路径是Base64编码,则返回true;否则返回false |
||||
*/ |
||||
export const isBase64 = (path: string): boolean => { |
||||
return /^data:image\/(\w+);base64/.test(path); |
||||
}; |
@ -0,0 +1,2 @@ |
||||
// @ts-nocheck
|
||||
export const isBrowser = typeof window !== 'undefined'; |
@ -0,0 +1,9 @@ |
||||
// @ts-nocheck
|
||||
/** |
||||
* 检查一个值是否已定义(不为 undefined)且不为 null |
||||
* @param value 要检查的值 |
||||
* @returns 如果值已定义且不为 null,则返回 true;否则返回 false |
||||
*/ |
||||
export function isDef(value: unknown): boolean { |
||||
return value !== undefined && value !== null; |
||||
} |
@ -0,0 +1,8 @@ |
||||
// @ts-nocheck
|
||||
/** |
||||
* 检查一个值是否为函数类型 |
||||
* @param val 要检查的值 |
||||
* @returns 如果值的类型是函数类型,则返回 true;否则返回 false |
||||
*/ |
||||
export const isFunction = (val: unknown): val is Function => |
||||
typeof val === 'function'; |
@ -0,0 +1,9 @@ |
||||
// @ts-nocheck
|
||||
/** |
||||
* 检查一个值是否为数字类型 |
||||
* @param value 要检查的值,可以是 number 类型或 string 类型的数字 |
||||
* @returns 如果值是数字类型且不是 NaN,则返回 true;否则返回 false |
||||
*/ |
||||
export function isNumber(value: number | string): boolean { |
||||
return typeof value === 'number' && !isNaN(value); |
||||
} |
@ -0,0 +1,9 @@ |
||||
// @ts-nocheck
|
||||
/** |
||||
* 检查一个值是否为数字类型或表示数字的字符串 |
||||
* @param value 要检查的值,可以是 string 类型或 number 类型 |
||||
* @returns 如果值是数字类型或表示数字的字符串,则返回 true;否则返回 false |
||||
*/ |
||||
export function isNumeric(value: string | number): boolean { |
||||
return /^(-)?\d+(\.\d+)?$/.test(value); |
||||
} |
@ -0,0 +1,8 @@ |
||||
// @ts-nocheck
|
||||
/** |
||||
* 检查一个值是否为对象类型 |
||||
* @param val 要检查的值 |
||||
* @returns 如果值的类型是对象类型,则返回 true;否则返回 false |
||||
*/ |
||||
export const isObject = (val : unknown) : val is Record<any, any> => |
||||
val !== null && typeof val === 'object'; |
@ -0,0 +1,13 @@ |
||||
// @ts-nocheck
|
||||
import {isFunction} from '../isFunction' |
||||
import {isObject} from '../isObject' |
||||
/** |
||||
* 检查一个值是否为 Promise 类型 |
||||
* @param val 要检查的值 |
||||
* @returns 如果值的类型是 Promise 类型,则返回 true;否则返回 false |
||||
*/ |
||||
export const isPromise = <T = any>(val: unknown): val is Promise<T> => { |
||||
// 使用 isObject 函数判断值是否为对象类型
|
||||
// 使用 isFunction 函数判断值是否具有 then 方法和 catch 方法
|
||||
return isObject(val) && isFunction(val.then) && isFunction(val.catch); |
||||
}; |
@ -0,0 +1,7 @@ |
||||
// @ts-nocheck
|
||||
/** |
||||
* 检查一个值是否为字符串类型 |
||||
* @param str 要检查的值 |
||||
* @returns 如果值的类型是字符串类型,则返回 true;否则返回 false |
||||
*/ |
||||
export const isString = (str: unknown): str is string => typeof str === 'string'; |
@ -0,0 +1,17 @@ |
||||
// export function toLowercaseSeparator(key: string) {
|
||||
// return key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||
// }
|
||||
|
||||
/** |
||||
* 将字符串转换为指定连接符的命名约定 |
||||
* @param str 要转换的字符串 |
||||
* @param separator 指定的连接符,默认为 "-" |
||||
* @returns 转换后的字符串 |
||||
*/ |
||||
export function kebabCase(str: string, separator: string = "-"): string { |
||||
return str |
||||
.replace(/[A-Z]/g, match => `${separator}${match.toLowerCase()}`) // 将大写字母替换为连接符加小写字母
|
||||
.replace(/[\s_-]+/g, separator) // 将空格、下划线和短横线替换为指定连接符
|
||||
.replace(new RegExp(`^${separator}|${separator}$`, "g"), "") // 删除开头和结尾的连接符
|
||||
.toLowerCase(); // 将结果转换为全小写
|
||||
} |
@ -0,0 +1,83 @@ |
||||
{ |
||||
"id": "lime-shared", |
||||
"displayName": "lime-shared", |
||||
"version": "0.1.4", |
||||
"description": "本人插件的几个公共函数,获取当前页,图片的base64转临时路径,图片的exif信息等", |
||||
"keywords": [ |
||||
"lime-shared", |
||||
"exif", |
||||
"selectComponent" |
||||
], |
||||
"repository": "", |
||||
"engines": { |
||||
"HBuilderX": "^3.1.0" |
||||
}, |
||||
"dcloudext": { |
||||
"type": "sdk-js", |
||||
"sale": { |
||||
"regular": { |
||||
"price": "0.00" |
||||
}, |
||||
"sourcecode": { |
||||
"price": "0.00" |
||||
} |
||||
}, |
||||
"contact": { |
||||
"qq": "" |
||||
}, |
||||
"declaration": { |
||||
"ads": "无", |
||||
"data": "无", |
||||
"permissions": "无" |
||||
}, |
||||
"npmurl": "" |
||||
}, |
||||
"uni_modules": { |
||||
"dependencies": [], |
||||
"encrypt": [], |
||||
"platforms": { |
||||
"cloud": { |
||||
"tcb": "y", |
||||
"aliyun": "y" |
||||
}, |
||||
"client": { |
||||
"Vue": { |
||||
"vue2": "y", |
||||
"vue3": "y" |
||||
}, |
||||
"App": { |
||||
"app-vue": "y", |
||||
"app-nvue": "y" |
||||
}, |
||||
"H5-mobile": { |
||||
"Safari": "y", |
||||
"Android Browser": "y", |
||||
"微信浏览器(Android)": "y", |
||||
"QQ浏览器(Android)": "y" |
||||
}, |
||||
"H5-pc": { |
||||
"Chrome": "y", |
||||
"IE": "u", |
||||
"Edge": "u", |
||||
"Firefox": "u", |
||||
"Safari": "u" |
||||
}, |
||||
"小程序": { |
||||
"微信": "y", |
||||
"阿里": "y", |
||||
"百度": "y", |
||||
"字节跳动": "y", |
||||
"QQ": "y", |
||||
"钉钉": "y", |
||||
"快手": "y", |
||||
"飞书": "y", |
||||
"京东": "u" |
||||
}, |
||||
"快应用": { |
||||
"华为": "u", |
||||
"联盟": "u" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,121 @@ |
||||
// @ts-nocheck
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
import { getLocalFilePath } from '../getLocalFilePath' |
||||
// #endif
|
||||
function isImage(extension : string) { |
||||
const imageExtensions = ["jpg", "jpeg", "png", "gif", "bmp", "svg"]; |
||||
return imageExtensions.includes(extension.toLowerCase()); |
||||
} |
||||
// #ifdef H5
|
||||
function getSVGFromURL(url: string) { |
||||
return new Promise((resolve, reject) => { |
||||
const xhr = new XMLHttpRequest(); |
||||
xhr.open('GET', url, true); |
||||
xhr.responseType = 'text'; |
||||
|
||||
xhr.onload = function () { |
||||
if (xhr.status === 200) { |
||||
const svg = xhr.responseText; |
||||
resolve(svg); |
||||
} else { |
||||
reject(new Error(xhr.statusText)); |
||||
} |
||||
}; |
||||
|
||||
xhr.onerror = function () { |
||||
reject(new Error('Network error')); |
||||
}; |
||||
|
||||
xhr.send(); |
||||
}); |
||||
} |
||||
// #endif
|
||||
/** |
||||
* 路径转base64 |
||||
* @param {Object} string |
||||
*/ |
||||
export function pathToBase64(path : string) : Promise<string> { |
||||
if (/^data:/.test(path)) return path |
||||
let extension = path.substring(path.lastIndexOf('.') + 1); |
||||
const isImageFile = isImage(extension) |
||||
let prefix = '' |
||||
if (isImageFile) { |
||||
prefix = 'image/'; |
||||
if(extension == 'svg') { |
||||
extension += '+xml' |
||||
} |
||||
} else if (extension === 'pdf') { |
||||
prefix = 'application/pdf'; |
||||
} else if (extension === 'txt') { |
||||
prefix = 'text/plain'; |
||||
} else { |
||||
// 添加更多文件类型的判断
|
||||
// 如果不是图片、PDF、文本等类型,可以设定默认的前缀或采取其他处理
|
||||
prefix = 'application/octet-stream'; |
||||
} |
||||
return new Promise((resolve, reject) => { |
||||
// #ifdef H5
|
||||
if (isImageFile) { |
||||
if(extension == 'svg') { |
||||
getSVGFromURL(path).then(svg => { |
||||
const base64 = btoa(svg); |
||||
resolve(`data:image/svg+xml;base64,${base64}`); |
||||
}) |
||||
} else { |
||||
let image = new Image(); |
||||
image.setAttribute("crossOrigin", 'Anonymous'); |
||||
image.onload = function () { |
||||
let canvas = document.createElement('canvas'); |
||||
canvas.width = this.naturalWidth; |
||||
canvas.height = this.naturalHeight; |
||||
canvas.getContext('2d').drawImage(image, 0, 0); |
||||
let result = canvas.toDataURL(`${prefix}${extension}`) |
||||
resolve(result); |
||||
canvas.height = canvas.width = 0 |
||||
} |
||||
image.src = path + '?v=' + Math.random() |
||||
image.onerror = (error) => { |
||||
reject(error); |
||||
}; |
||||
} |
||||
|
||||
} else { |
||||
reject('not image'); |
||||
} |
||||
|
||||
// #endif
|
||||
|
||||
// #ifdef MP
|
||||
if (uni.canIUse('getFileSystemManager')) { |
||||
uni.getFileSystemManager().readFile({ |
||||
filePath: path, |
||||
encoding: 'base64', |
||||
success: (res) => { |
||||
resolve(`data:${prefix}${extension};base64,${res.data}`) |
||||
}, |
||||
fail: (error) => { |
||||
console.error({ error, path }) |
||||
reject(error) |
||||
} |
||||
}) |
||||
} |
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
plus.io.resolveLocalFileSystemURL(getLocalFilePath(path), (entry) => { |
||||
entry.file((file : any) => { |
||||
const fileReader = new plus.io.FileReader() |
||||
fileReader.onload = (data) => { |
||||
resolve(data.target.result) |
||||
} |
||||
fileReader.onerror = (error) => { |
||||
console.error({ error, path }) |
||||
reject(error) |
||||
} |
||||
fileReader.readAsDataURL(file) |
||||
}, reject) |
||||
}, reject) |
||||
// #endif
|
||||
}) |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,27 @@ |
||||
// @ts-nocheck
|
||||
declare var tt: Uni |
||||
declare var swan: Uni |
||||
declare var my: Uni |
||||
declare var dd: Uni |
||||
declare var ks: Uni |
||||
declare var jd: Uni |
||||
declare var qa: Uni |
||||
declare var qq: Uni |
||||
declare var qh: Uni |
||||
declare var qq: Uni |
||||
|
||||
export function platform(): Uni | WechatMiniprogram.Wx { |
||||
const UNDEFINED = 'undefined' |
||||
if(typeof wx !== UNDEFINED) return wx // 微信
|
||||
if(typeof tt !== UNDEFINED) return tt // 字节 飞书
|
||||
if(typeof swan !== UNDEFINED) return swan // 百度
|
||||
if(typeof my !== UNDEFINED) return my // 支付宝
|
||||
if(typeof dd !== UNDEFINED) return dd // 钉钉
|
||||
if(typeof ks !== UNDEFINED) return ks // 快手
|
||||
if(typeof jd !== UNDEFINED) return jd // 京东
|
||||
if(typeof qa !== UNDEFINED) return qa // 快应用
|
||||
if(typeof qq !== UNDEFINED) return qq // qq
|
||||
if(typeof qh !== UNDEFINED) return qh // 360
|
||||
if(typeof uni !== UNDEFINED) return uni |
||||
return null |
||||
} |
@ -0,0 +1,30 @@ |
||||
// @ts-nocheck
|
||||
import {isBrowser} from '../isBrowser' |
||||
|
||||
// 是否支持被动事件监听
|
||||
export const supportsPassive = true; |
||||
|
||||
// 请求动画帧
|
||||
export function raf(fn: FrameRequestCallback): number { |
||||
// 如果是在浏览器环境下,使用 requestAnimationFrame 方法
|
||||
if (isBrowser) { |
||||
return requestAnimationFrame(fn); // 请求动画帧
|
||||
} else { // 在非浏览器环境下,使用 setTimeout 模拟
|
||||
return setTimeout(fn, 1000 / 30); // 使用 setTimeout 模拟动画帧,每秒钟执行 30 次
|
||||
} |
||||
} |
||||
|
||||
// 取消动画帧
|
||||
export function cancelRaf(id: number) { |
||||
// 如果是在浏览器环境下,使用 cancelAnimationFrame 方法
|
||||
if (isBrowser) { |
||||
cancelAnimationFrame(id); // 取消动画帧
|
||||
} else { // 在非浏览器环境下,使用 clearTimeout 模拟
|
||||
clearTimeout(id); // 使用 clearTimeout 模拟取消动画帧
|
||||
} |
||||
} |
||||
|
||||
// 双倍动画帧
|
||||
export function doubleRaf(fn: FrameRequestCallback): void { |
||||
raf(() => raf(fn)); // 在下一帧回调中再次请求动画帧,实现双倍动画帧效果
|
||||
} |
@ -0,0 +1,22 @@ |
||||
// @ts-nocheck
|
||||
/** |
||||
* 生成一个指定范围内的随机数 |
||||
* @param min 随机数的最小值 |
||||
* @param max 随机数的最大值 |
||||
* @param fixed 随机数的小数位数,默认为 0 |
||||
* @returns 生成的随机数 |
||||
*/ |
||||
export function random(min: number, max: number, fixed: number = 0) { |
||||
// 将 min 和 max 转换为数字类型
|
||||
min = +min || 0; |
||||
max = +max || 0; |
||||
// 计算随机数范围内的一个随机数
|
||||
const num = Math.random() * (max - min) + min; |
||||
// 如果 fixed 参数为 0,则返回四舍五入的整数随机数;否则保留固定小数位数
|
||||
return fixed == 0 ? Math.round(num) : Number(num.toFixed(fixed)); |
||||
} |
||||
|
||||
// 示例
|
||||
// console.log(random(0, 10)); // 输出:在 0 和 10 之间的一个整数随机数
|
||||
// console.log(random(0, 1, 2)); // 输出:在 0 和 1 之间的一个保留两位小数的随机数
|
||||
// console.log(random(1, 100, 3)); // 输出:在 1 和 100 之间的一个保留三位小数的随机数
|
@ -0,0 +1,31 @@ |
||||
// @ts-nocheck
|
||||
/** |
||||
* 生成一个数字范围的数组 |
||||
* @param start 范围的起始值 |
||||
* @param end 范围的结束值 |
||||
* @param step 步长,默认为 1 |
||||
* @param fromRight 是否从右侧开始生成,默认为 false |
||||
* @returns 生成的数字范围数组 |
||||
*/ |
||||
export function range(start: number, end: number, step: number = 1, fromRight: boolean = false): number[] { |
||||
let index = -1; |
||||
// 计算范围的长度
|
||||
let length = Math.max(Math.ceil((end - start) / (step || 1)), 0); |
||||
// 创建一个长度为 length 的数组
|
||||
const result = new Array(length); |
||||
|
||||
// 使用循环生成数字范围数组
|
||||
while (length--) { |
||||
// 根据 fromRight 参数决定从左侧还是右侧开始填充数组
|
||||
result[fromRight ? length : ++index] = start; |
||||
start += step; |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
|
||||
// 示例
|
||||
// console.log(range(0, 5)); // 输出: [0, 1, 2, 3, 4]
|
||||
// console.log(range(1, 10, 2, true)); // 输出: [9, 7, 5, 3, 1]
|
||||
// console.log(range(5, 0, -1)); // 输出: [5, 4, 3, 2, 1]
|
@ -0,0 +1,251 @@ |
||||
# lime-shared 工具库 |
||||
- 本人插件的几个公共函数 |
||||
|
||||
## 引入 |
||||
```js |
||||
// 按需引入 |
||||
// 这种只会引入相关的方法 |
||||
import {getRect} from '@/uni_modules/lime-shared/getRect' |
||||
|
||||
// 全量引入 |
||||
// 这种引入方式,会全量打包 |
||||
import {getRect} from '@/uni_modules/lime-shared' |
||||
``` |
||||
|
||||
## Utils |
||||
|
||||
#### getRect |
||||
- 返回节点尺寸信息 |
||||
|
||||
```js |
||||
// 组件内需要传入上下文 |
||||
// 如果是nvue 则需要在节点上加与id或class同名的ref |
||||
getRect('#id',{context: this}).then(res => {}) |
||||
``` |
||||
|
||||
#### addUnit |
||||
- 将未带单位的数值添加px,如果有单位则返回原值 |
||||
|
||||
```js |
||||
addUnit(10) |
||||
// 10px |
||||
``` |
||||
|
||||
#### unitConvert |
||||
- 将带有rpx|px的字符转成number,若本身是number则直接返回 |
||||
|
||||
```js |
||||
unitConvert('10rpx') |
||||
// 5 设备不同 返回的值也不同 |
||||
unitConvert('10px') |
||||
// 10 |
||||
unitConvert(10) |
||||
// 10 |
||||
``` |
||||
|
||||
#### canIUseCanvas2d |
||||
- 环境是否支持使用 canvas 2d |
||||
|
||||
```js |
||||
canIUseCanvas2d() |
||||
// 若支持返回 true 否则 false |
||||
``` |
||||
|
||||
|
||||
#### getCurrentPage |
||||
- 获取当前页 |
||||
|
||||
```js |
||||
const page = getCurrentPage() |
||||
``` |
||||
|
||||
|
||||
#### base64ToPath |
||||
- 把base64的图片转成临时路径 |
||||
|
||||
```js |
||||
base64ToPath(`xxxxx`).then(res => {}) |
||||
``` |
||||
|
||||
#### pathToBase64 |
||||
- 把图片的临时路径转成base64 |
||||
|
||||
```js |
||||
pathToBase64(`xxxxx/xxx.png`).then(res => {}) |
||||
``` |
||||
|
||||
#### sleep |
||||
- 睡眠,让 async 内部程序等待一定时间后再执行 |
||||
|
||||
```js |
||||
async next () => { |
||||
await sleep(300) |
||||
console.log('limeui'); |
||||
} |
||||
``` |
||||
|
||||
#### isBase64 |
||||
- 判断字符串是否为base64 |
||||
|
||||
```js |
||||
isBase64('xxxxx') |
||||
``` |
||||
|
||||
#### throttle |
||||
- 节流 |
||||
|
||||
```js |
||||
throttle((nama) => {console.log(nama)}, 200)('limeui'); |
||||
``` |
||||
|
||||
#### debounce |
||||
- 防抖 |
||||
|
||||
```js |
||||
debounce((nama) => {console.log(nama)}, 200)('limeui'); |
||||
``` |
||||
|
||||
#### random |
||||
- 返回指定范围的随机数 |
||||
|
||||
```js |
||||
random(1, 5); |
||||
``` |
||||
|
||||
#### range |
||||
- 生成区间数组 |
||||
|
||||
```js |
||||
range(0, 5) |
||||
// [0,1,2,3,4,5] |
||||
``` |
||||
|
||||
#### clamp |
||||
- 夹在min和max之间的数值,如小于min,返回min, 如大于max,返回max,否侧原值返回 |
||||
|
||||
```js |
||||
clamp(0, 10, -1) |
||||
// 0 |
||||
clamp(0, 10, 11) |
||||
// 10 |
||||
clamp(0, 10, 9) |
||||
// 9 |
||||
``` |
||||
|
||||
#### floatAdd |
||||
- 返回两个浮点数相加的结果 |
||||
|
||||
```js |
||||
floatAdd(0.1, 0.2) // 0.3 |
||||
``` |
||||
|
||||
|
||||
#### fillZero |
||||
- 补零,如果传入的是`个位数`则在前面补0 |
||||
|
||||
```js |
||||
fillZero(9); |
||||
// 09 |
||||
``` |
||||
|
||||
#### exif |
||||
- 获取图片exif |
||||
- 支持临时路径、base64 |
||||
|
||||
```js |
||||
uni.chooseImage({ |
||||
count: 1, //最多可以选择的图片张数 |
||||
sizeType: "original", |
||||
success: (res) => { |
||||
exif.getData(res.tempFiles[0], function() { |
||||
let tagj = exif.getTag(this, "GPSLongitude"); |
||||
let Orientation = exif.getTag(this, 'Orientation'); |
||||
console.log(tagj, Orientation) |
||||
}) |
||||
} |
||||
}) |
||||
``` |
||||
|
||||
#### selectComponent |
||||
- 获取页面或当前实例的指定组件,会在页面或实例向所有的节点查找(包括子组件或子子组件) |
||||
- 仅vue3,vue2没有测试过 |
||||
|
||||
```js |
||||
// 当前页面 |
||||
const page = getCurrentPage() |
||||
selectComponent('.custom', {context: page}).then(res => { |
||||
}) |
||||
``` |
||||
|
||||
|
||||
#### createAnimation |
||||
- 创建动画,与uni.createAnimation使用方法一致,只为了抹平nvue |
||||
|
||||
```html |
||||
<view ref="ball" :animation="animationData"></view> |
||||
``` |
||||
```js |
||||
const ball = ref(null) |
||||
const animation = createAnimation({ |
||||
transformOrigin: "50% 50%", |
||||
duration: 1000, |
||||
timingFunction: "ease", |
||||
delay: 0 |
||||
}) |
||||
|
||||
animation.scale(2,2).rotate(45).step() |
||||
// nvue 无导出数据,这样写只为了平台一致, |
||||
// nvue 需要把 ref 传入,其它平台不需要 |
||||
const animationData = animation.export(ball.value) |
||||
``` |
||||
|
||||
|
||||
## composition-api |
||||
- 因本人插件需要兼容vue2/vue3,故增加一个vue文件,代替条件编译 |
||||
- vue2需要在main.js加上这一段 |
||||
```js |
||||
// vue2 |
||||
import Vue from 'vue' |
||||
import VueCompositionAPI from '@vue/composition-api' |
||||
Vue.use(VueCompositionAPI) |
||||
``` |
||||
|
||||
```js |
||||
//使用 |
||||
import {computed, onMounted, watch, reactive} from '@/uni_modules/lime-shared/vue' |
||||
``` |
||||
|
||||
|
||||
## Hooks |
||||
#### useIntersectionObserver |
||||
- 使用 Intersection Observer 观察元素可见性的钩子函数 |
||||
|
||||
```html |
||||
<div class="target"> |
||||
<h1>Hello world</h1> |
||||
</div> |
||||
``` |
||||
|
||||
```js |
||||
// options 接口可传的参数,若在插件里context为必传 |
||||
interface UseIntersectionObserverOptions { |
||||
root ?: string; // 观察器的根元素选择器字符串 |
||||
rootMargin ?: { |
||||
top ?: number; // 根元素顶部边距 |
||||
bottom ?: number; // 根元素底部边距 |
||||
left ?: number; // 根元素左侧边距 |
||||
right ?: number; // 根元素右侧边距 |
||||
}; // 根元素的边距 |
||||
thresholds ?: any[]; // 交叉比例数组,用于指定何时触发回调函数 |
||||
context ?: any; // 上下文对象,用于指定观察器的上下文 |
||||
initialRatio ?: number; // 初始的交叉比例 |
||||
observeAll ?: boolean; // 是否同时观察所有交叉对象 |
||||
} |
||||
const options: UseIntersectionObserverOptions = { |
||||
rootMargin: {top: 44}, |
||||
context: this |
||||
} |
||||
const {stop} = useIntersectionObserver('.target', (result) => { |
||||
|
||||
}, options) |
||||
``` |
@ -0,0 +1,152 @@ |
||||
// @ts-nocheck
|
||||
interface SelectOptions { |
||||
context?: any |
||||
needAll?: boolean |
||||
node?: boolean |
||||
} |
||||
// #ifdef MP
|
||||
function selectMPComponent(key: string, name: string, context: any, needAll: boolean) { |
||||
const {proxy, $vm} = context |
||||
context = $vm || proxy |
||||
if(!['ref','component'].includes(key)) { |
||||
const queue = [context] |
||||
let result = null |
||||
const selector = (key == 'id' ? '#': '.') + name; |
||||
while(queue.length > 0) { |
||||
const child = queue.shift(); |
||||
const flag = child?.selectComponent(selector) |
||||
if(flag) { |
||||
if(!needAll) {return result = flag.$vm} |
||||
return result = child.selectAllComponents(selector).map(item => item.$vm) |
||||
} else { |
||||
child.$children && (queue.push(...child.$children)); |
||||
} |
||||
} |
||||
return result |
||||
} else { |
||||
const {$templateRefs} = context.$ |
||||
const nameMap = {} |
||||
for (var i = 0; i < $templateRefs.length; i++) { |
||||
const item = $templateRefs[i] |
||||
nameMap[item.i] = item.r |
||||
} |
||||
let result = [] |
||||
if(context.$children.length) { |
||||
const queue = [...context.$children] |
||||
while(queue.length > 0) { |
||||
const child = queue.shift(); |
||||
if(key == 'component' && (child.type?.name === name || child.$?.type?.name === name)) { |
||||
result.push(child) |
||||
} else if(child.$refs && child.$refs[name]) { |
||||
result = child.$refs[name] |
||||
} else if(nameMap[child.id] === name){ |
||||
result.push(child) |
||||
} else { |
||||
child.$children && (queue.push(...child.$children)); |
||||
} |
||||
if(result.length && !needAll) { |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
return needAll ? result : result[0] |
||||
} |
||||
} |
||||
// #endif
|
||||
// #ifdef H5
|
||||
function selectH5Component(key: string, name: string, context: any, needAll: boolean) { |
||||
const {_, component } = context |
||||
const child = {component: _ || component || context, children: null , subTree: null, props: null} |
||||
let result = [] |
||||
let queue = [child] |
||||
while(queue.length > 0 ) { |
||||
const child = queue.shift() |
||||
const {component, children , props, subTree} = child |
||||
if(key === 'component' && component?.type?.name == name) { |
||||
result.push(component) |
||||
} else if(key === 'ref' && component && (props?.ref == name || component[key][name])) { |
||||
if(props?.ref == name) { |
||||
//exposed
|
||||
result.push(component) |
||||
} else if(component[key][name]) { |
||||
result.push(component[key][name]) |
||||
} |
||||
} else if(key !== 'ref' && component?.exposed && new RegExp(`\\b${name}\\b`).test(component.attrs[key])) { |
||||
// exposed
|
||||
result.push(component) |
||||
} else if(children && Array.isArray(children)) { |
||||
queue.push(...children) |
||||
} else if(!component && subTree) { |
||||
queue.push(subTree) |
||||
} else if(component?.subTree) { |
||||
queue.push(component.subTree) |
||||
} |
||||
if(result.length && !needAll) { |
||||
break |
||||
} |
||||
} |
||||
return needAll ? result : result[0] |
||||
} |
||||
// #endif
|
||||
// #ifdef APP
|
||||
function selectAPPComponent(key: string, name: string, context: any, needAll: boolean, node: boolean) { |
||||
let result = [] |
||||
// const {_, component} = context
|
||||
// const child = {component: _ || component || context, children: null, props: null, subTree: null}
|
||||
const queue = [context] |
||||
while(queue.length > 0) { |
||||
const child = queue.shift() |
||||
const {component, children, props, subTree} = child |
||||
const isComp = component && props && component.exposed && !node |
||||
if(key == 'component' && child.type && child.type.name === name) { |
||||
result.push(component) |
||||
} else if(props?.[key] === name && node) { |
||||
result.push(child) |
||||
} else if(key === 'ref' && isComp && (props.ref === name || props.ref_key === name)) { |
||||
// exposed
|
||||
result.push(component) |
||||
} else if(key !== 'ref' && isComp && new RegExp(`\\b${name}\\b`).test(props[key])) { |
||||
// exposed
|
||||
result.push(component) |
||||
} |
||||
// else if(component && component.subTree && Array.isArray(component.subTree.children)){
|
||||
// queue.push(...component.subTree.children)
|
||||
// }
|
||||
else if(subTree) { |
||||
queue.push(subTree) |
||||
} else if(component && component.subTree){ |
||||
queue.push(component.subTree) |
||||
}
|
||||
else if(children && Array.isArray(children)) { |
||||
queue.push(...children) |
||||
} |
||||
if(result.length && !needAll) { |
||||
break; |
||||
} |
||||
} |
||||
return needAll ? result : result[0] |
||||
} |
||||
// #endif
|
||||
export function selectComponent(selector: string, options: SelectOptions = {}) { |
||||
// . class
|
||||
// # id
|
||||
// $ ref
|
||||
// @ component name
|
||||
const reg = /^(\.|#|@|\$)([a-zA-Z_0-9\-]+)$/; |
||||
if(!reg.test(selector)) return null |
||||
let { context, needAll, node} = options |
||||
const [,prefix, name] = selector.match(reg) |
||||
const symbolMappings = {'.': 'class', '#': 'id', '$':'ref', '@':'component'} |
||||
|
||||
const key = symbolMappings [prefix] //prefix === '.' ? 'class' : prefix === '#' ? 'id' : 'ref';
|
||||
console.log('key', key) |
||||
// #ifdef MP
|
||||
return selectMPComponent(key, name, context, needAll) |
||||
// #endif
|
||||
// #ifdef H5
|
||||
return selectH5Component(key, name, context, needAll) |
||||
// #endif
|
||||
// #ifdef APP
|
||||
return selectAPPComponent(key, name, context, needAll, node) |
||||
// #endif
|
||||
} |
@ -0,0 +1,30 @@ |
||||
// @ts-nocheck
|
||||
/** |
||||
* 延迟指定时间后解析的 Promise |
||||
* @param delay 延迟的时间(以毫秒为单位),默认为 300 毫秒 |
||||
* @returns 一个 Promise,在延迟结束后解析 |
||||
*/ |
||||
export const sleep = (delay: number = 300) => |
||||
new Promise(resolve => setTimeout(resolve, delay)); |
||||
|
||||
|
||||
// 示例
|
||||
// async function example() {
|
||||
// console.log("Start");
|
||||
|
||||
// // 延迟 1 秒后执行
|
||||
// await sleep(1000);
|
||||
// console.log("1 second later");
|
||||
|
||||
// // 延迟 500 毫秒后执行
|
||||
// await sleep(500);
|
||||
// console.log("500 milliseconds later");
|
||||
|
||||
// // 延迟 2 秒后执行
|
||||
// await sleep(2000);
|
||||
// console.log("2 seconds later");
|
||||
|
||||
// console.log("End");
|
||||
// }
|
||||
|
||||
// example();
|
@ -0,0 +1,41 @@ |
||||
// @ts-nocheck
|
||||
/** |
||||
* 节流函数,用于限制函数的调用频率 |
||||
* @param fn 要进行节流的函数 |
||||
* @param delay 两次调用之间的最小间隔时间 |
||||
* @returns 节流后的函数 |
||||
*/ |
||||
export function throttle(fn: (...args: any[]) => void, delay: number) { |
||||
let flag = true; // 标记是否可以执行函数
|
||||
|
||||
return (...args: any[]) => { |
||||
if (flag) { |
||||
flag = false; // 设置为不可执行状态
|
||||
fn(...args); // 执行传入的函数
|
||||
|
||||
setTimeout(() => { |
||||
flag = true; // 经过指定时间后,设置为可执行状态
|
||||
}, delay); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
|
||||
// // 示例
|
||||
// // 定义一个被节流的函数
|
||||
// function handleScroll() {
|
||||
// console.log("Scroll event handled!");
|
||||
// }
|
||||
|
||||
// // 使用节流函数对 handleScroll 进行节流,间隔时间为 500 毫秒
|
||||
// const throttledScroll = throttle(handleScroll, 500);
|
||||
|
||||
// // 模拟多次调用 handleScroll
|
||||
// throttledScroll(); // 输出 "Scroll event handled!"
|
||||
// throttledScroll(); // 不会输出
|
||||
// throttledScroll(); // 不会输出
|
||||
|
||||
// // 经过 500 毫秒后,再次调用 handleScroll
|
||||
// setTimeout(() => {
|
||||
// throttledScroll(); // 输出 "Scroll event handled!"
|
||||
// }, 500);
|
@ -0,0 +1,13 @@ |
||||
// @ts-nocheck
|
||||
/** |
||||
* 将一个或多个元素转换为数组 |
||||
* @param item 要转换为数组的元素 |
||||
* @returns 转换后的数组 |
||||
*/ |
||||
export const toArray = <T>(item: T | T[]): T[] => Array.isArray(item) ? item : [item]; |
||||
|
||||
// 示例
|
||||
// console.log(toArray(5)); // 输出: [5]
|
||||
// console.log(toArray("hello")); // 输出: ["hello"]
|
||||
// console.log(toArray([1, 2, 3])); // 输出: [1, 2, 3]
|
||||
// console.log(toArray(["apple", "banana"])); // 输出: ["apple", "banana"]
|
@ -0,0 +1,15 @@ |
||||
// @ts-nocheck
|
||||
/** |
||||
* 将字符串转换为数字 |
||||
* @param val 要转换的字符串 |
||||
* @returns 转换后的数字或原始字符串 |
||||
*/ |
||||
export function toNumber(val: string): number | string { |
||||
const n = parseFloat(val); // 使用 parseFloat 函数将字符串转换为浮点数
|
||||
return isNaN(n) ? val : n; // 使用 isNaN 函数判断是否为非数字,返回转换后的数字或原始字符串
|
||||
} |
||||
|
||||
// 示例
|
||||
// console.log(toNumber("123")); // 输出: 123
|
||||
// console.log(toNumber("3.14")); // 输出: 3.14
|
||||
// console.log(toNumber("hello")); // 输出: "hello"
|
@ -0,0 +1,39 @@ |
||||
// @ts-nocheck
|
||||
import {isString} from '../isString' |
||||
import {isNumeric} from '../isNumeric' |
||||
|
||||
/** |
||||
* 单位转换函数,将字符串数字或带有单位的字符串转换为数字 |
||||
* @param value 要转换的值,可以是字符串数字或带有单位的字符串 |
||||
* @returns 转换后的数字,如果无法转换则返回0 |
||||
*/ |
||||
export function unitConvert(value: string | number): number { |
||||
// 如果是字符串数字
|
||||
if (isNumeric(value)) { |
||||
return Number(value); |
||||
} |
||||
// 如果有单位
|
||||
if (isString(value)) { |
||||
const reg = /^-?([0-9]+)?([.]{1}[0-9]+){0,1}(em|rpx|px|%)$/g; |
||||
const results = reg.exec(value); |
||||
if (!value || !results) { |
||||
return 0; |
||||
} |
||||
const unit = results[3]; |
||||
value = parseFloat(value); |
||||
if (unit === 'rpx') { |
||||
return uni.upx2px(value); |
||||
} |
||||
if (unit === 'px') { |
||||
return value * 1; |
||||
} |
||||
// 如果是其他单位,可以继续添加对应的转换逻辑
|
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
// 示例
|
||||
// console.log(unitConvert("123")); // 输出: 123 (字符串数字转换为数字)
|
||||
// console.log(unitConvert("3.14em")); // 输出: 0 (无法识别的单位)
|
||||
// console.log(unitConvert("20rpx")); // 输出: 根据具体情况而定 (根据单位进行转换)
|
||||
// console.log(unitConvert(10)); // 输出: 10 (数字不需要转换)
|
@ -0,0 +1,81 @@ |
||||
import { watch, unref, Ref } from "../vue" |
||||
// #ifdef APP-NVUE
|
||||
// const dom = weex.requireModule('dom')
|
||||
// const dom = uni.requireNativePlugin('dom')
|
||||
// #endif
|
||||
|
||||
interface UseIntersectionObserverOptions { |
||||
root ?: string; // 观察器的根元素选择器字符串
|
||||
rootMargin ?: { |
||||
top ?: number; // 根元素顶部边距
|
||||
bottom ?: number; // 根元素底部边距
|
||||
left ?: number; // 根元素左侧边距
|
||||
right ?: number; // 根元素右侧边距
|
||||
}; // 根元素的边距
|
||||
thresholds ?: any[]; // 交叉比例数组,用于指定何时触发回调函数
|
||||
context ?: any; // 上下文对象,用于指定观察器的上下文
|
||||
initialRatio ?: number; // 初始的交叉比例
|
||||
observeAll ?: boolean; // 是否同时观察所有交叉对象
|
||||
} |
||||
|
||||
/** |
||||
* 使用 Intersection Observer 观察元素可见性的自定义钩子函数 |
||||
* @param {Ref<string> | string} target - 目标元素,可以是一个字符串或 ref 对象 |
||||
* @param {(result: UniNamespace.ObserveResult) => void} callback - 回调函数,当目标元素的可见性发生变化时调用 |
||||
* @param {UseIntersectionObserverOptions} options - 可选的配置参数 |
||||
* @returns {Object} - 包含 stop 方法的对象,用于停止观察 |
||||
*/ |
||||
export function useIntersectionObserver( |
||||
target : Ref<string> | string, |
||||
callback : (result : UniNamespace.ObserveResult) => void, |
||||
options : UseIntersectionObserverOptions = {}) { |
||||
const { |
||||
root, // 观察器的根元素选择器
|
||||
rootMargin = { top: 0, bottom: 0 }, // 根元素的边距,默认为顶部和底部都为0
|
||||
thresholds = [0], // 交叉比例数组,默认为[0]
|
||||
initialRatio = 0, // 初始交叉比例,默认为0
|
||||
observeAll = false, // 是否同时观察所有交叉对象,默认为false
|
||||
context // 上下文对象,用于指定观察器的上下文
|
||||
} = options |
||||
const noop = () => { }; // 空函数,用于初始化 cleanup
|
||||
let cleanup = noop; // 清理函数,用于停止 Intersection Observer 的观察
|
||||
|
||||
const stopWatch = watch(() => ({ el: unref(target), root: unref(root) }), ({ el, root }) => { |
||||
if (!el) { |
||||
return |
||||
} |
||||
// #ifndef APP-NVUE
|
||||
// 创建 Intersection Observer 实例
|
||||
const observer = uni.createIntersectionObserver(context, { thresholds, initialRatio, observeAll }) |
||||
if (root) { |
||||
// 相对于根元素设置边界
|
||||
observer.relativeTo(root, rootMargin)
|
||||
} else { |
||||
// 相对于视口设置边界
|
||||
observer.relativeToViewport(rootMargin) |
||||
} |
||||
// 观察目标元素的可见性变化
|
||||
observer.observe(el, callback) |
||||
cleanup = () => { |
||||
// 停止观察
|
||||
observer.disconnect() |
||||
// 将 cleanup 函数重置为空函数
|
||||
cleanup = noop |
||||
} |
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
// dom.getComponentRect(el, (res) => {
|
||||
// console.log('res', res)
|
||||
// })
|
||||
// #endif
|
||||
}, { immediate: true, flush: 'post' }) |
||||
|
||||
const stop = () => { |
||||
// 调用 cleanup 函数停止观察
|
||||
cleanup && cleanup() |
||||
// 停止 watch
|
||||
stopWatch && stopWatch() |
||||
} |
||||
|
||||
return { stop } |
||||
} |
@ -0,0 +1,8 @@ |
||||
// @ts-nocheck
|
||||
|
||||
// #ifdef VUE3
|
||||
export * from 'vue'; |
||||
// #endif
|
||||
// #ifndef VUE3
|
||||
export * from '@vue/composition-api'; |
||||
// #endif
|
Loading…
Reference in new issue