feat: 初始化

This commit is contained in:
George
2025-07-07 16:05:18 +08:00
commit c169958240
986 changed files with 132574 additions and 0 deletions

View File

@ -0,0 +1,235 @@
<template>
<!-- #ifdef MP -->
<canvas :style="styles" type="2d" :canvas-id="canvasId" :id="canvasId"></canvas>
<!-- #endif -->
<!-- #ifdef APP -->
<view class="l-qrcode" ref="drawableRef" :style="[styles]">
<image class="l-qrcode__icon" v-if="icon" :src="icon" :style="[iconStyle]"></image>
</view>
<!-- #endif -->
<!-- #ifdef WEB -->
<view class="l-qrcode" ref="drawableRef" :style="[styles]">
</view>
<!-- #endif -->
</template>
<script lang="uts" setup>
import { type PropType, nextTick } from 'vue'
// #ifndef APP
import { QRCodeCanvas } from './qrcode.js';
import { QRCodePropsTypes , ImageSettings } from './type'
// #endif
// #ifdef APP
import { QRCodeCanvas, QRCodePropsTypes , ImageSettings } from '@/uni_modules/lime-qrcodegen'
// #endif
// import { addUnit } from '@/uni_modules/lime-shared/addUnit'
// import { unitConvert } from '@/uni_modules/lime-shared/unitConvert'
// import { toBoolean } from '@/uni_modules/lime-shared/toBoolean'
import { addUnit, unitConvert } from './utils'
import { LQrcodeFailCallback, LQrcodeCompleteCallback, LQrcodeSuccessCallback} from './type'
const name = 'l-qrcode'
const props = defineProps({
value: {
type: String
},
icon: {
type: String
},
// #ifdef APP-ANDROID
size: {
type: Object,
default: 160
},
iconSize: {
type: Object,
default: 40
},
// #endif
// #ifndef APP-ANDROID
size: {
type: [Number, String],
default: 160
},
iconSize: {
type: [Number, String],
default: 40
},
// #endif
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
// }
})
const emits = defineEmits(['success'])
const context = getCurrentInstance();
const canvasId = `l-qrcode${context!.uid}`
const styles = computed<Map<string, any>>(():Map<string, any>=>{
const style = new Map<string, any>()
const size = addUnit(props.size);
if(size!=null){
style.set('width', size)
style.set('height', size)
}
style.set('background', props.bgColor)
return style
})
// #ifdef APP
const iconStyle = computed<Map<string, any>>(():Map<string, any>=>{
const style = new Map<string, any>()
const size = addUnit(props.iconSize);
if(size!=null){
style.set('width', size)
style.set('height', size)
}
return style
})
// #endif
const drawableRef = ref<UniElement|null>(null);
// #ifdef WEB
let canvas:HTMLCanvasElement|null = null
// #endif
let qrcode:QRCodeCanvas|null = null
const canvasToTempFilePath = (options: UTSJSONObject)=>{
const format = options.getString('format') ?? 'png';
const fail = options.get('fail') as LQrcodeFailCallback | null;
const complete = options.get('complete') as LQrcodeCompleteCallback | null;
const success = options.get('success') as LQrcodeSuccessCallback | null;
// #ifdef APP
const newOptions = {
format,
fail,
complete,
success,
} as TakeSnapshotOptions
drawableRef.value!.takeSnapshot(newOptions)
// #endif
// #ifdef WEB
success?.({
tempFilePath: canvas?.toDataURL('image/'+format)
})
// #endif
}
const render = ()=>{
const param:QRCodePropsTypes = {
value: props.value,
size: unitConvert(props.size),
fgColor: props.color,
level: ['L', 'M', 'Q', 'H'].includes(props.errorLevel) ? props.errorLevel : 'M',
marginSize: props.marginSize,
includeMargin: props.bordered,
imageSettings: null,
} as QRCodePropsTypes
if(props.icon != null){
// if(toBoolean(props.iconSize) && toBoolean(props.icon)){
const size = unitConvert(props.iconSize)
param.imageSettings = {
src: props.icon,
width: size,
height: size,
excavate: true
} as ImageSettings
}
qrcode?.render(param)
if(props.useCanvasToTempFilePath){
setTimeout(()=>{
canvasToTempFilePath({
success: (res: TakeSnapshotSuccess)=>{
emits('success', res.tempFilePath)
}
})
},100)
}
}
defineExpose({
canvasToTempFilePath
})
onMounted(()=>{
nextTick(()=>{
// #ifdef APP
const ctx = drawableRef.value!.getDrawableContext();
qrcode = new QRCodeCanvas(ctx!)
// #endif
// #ifdef WEB
canvas = document.createElement('canvas')
canvas.style.width = '100%'
canvas.style.height = '100%'
drawableRef.value!.appendChild(canvas)
qrcode = new QRCodeCanvas(canvas, {
pixelRatio: uni.getSystemInfoSync().pixelRatio,
createImage: () => {
const image = new Image();
image.crossOrigin = 'anonymous';
return image;
}
})
// #endif
watchEffect(()=>{
render()
})
})
})
onUnmounted(()=>{
// #ifdef WEB
canvas?.remove();
// #endif
qrcode = null;
})
</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>

View File

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

View File

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

View File

@ -0,0 +1,48 @@
// @ts-nocheck
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
}
/**
* 成功回调函数定义
*/
export type TakeSnapshotSuccessCallback = (res: TakeSnapshotSuccess) => void
/**
* 失败回调函数定义
*/
export type TakeSnapshotFailCallback = (res: TakeSnapshotFail) => void
/**
* 完成回调函数定义
*/
export type TakeSnapshotCompleteCallback = (res: any) => void
export type LQrcodeFailCallback = TakeSnapshotFailCallback
export type LQrcodeCompleteCallback = TakeSnapshotCompleteCallback
export type LQrcodeSuccessCallback = TakeSnapshotSuccessCallback

View File

@ -0,0 +1,78 @@
// @ts-nocheck
import type { ComponentInternalInstance } from '@/uni_modules/lime-shared/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, isCanvas2d).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
}

View File

@ -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;
}