1
This commit is contained in:
24
uni_modules/jp-signature/changelog.md
Normal file
24
uni_modules/jp-signature/changelog.md
Normal file
@@ -0,0 +1,24 @@
|
||||
## 3.2.0(2024-08-27)
|
||||
修复全屏签名图片未正确旋转问题
|
||||
## 3.1.0(2024-06-03)
|
||||
修复签名部分BUG
|
||||
## 3.0.4(2024-05-28)
|
||||
修改部分文档内容
|
||||
## 3.0.0(2024-05-28)
|
||||
新增jp-merge组件,及修复jp-signature-popup组件签字滚动问题
|
||||
## 2.5.2(2023-12-19)
|
||||
弹窗签名新增boundingBox属性,文档新增vue3使用方法
|
||||
## 2.5.1(2023-11-17)
|
||||
修复vue3兼容问题,新增点击图片除非事件
|
||||
## 2.5(2023-11-14)
|
||||
优化代码部分能力
|
||||
## 2.4(2023-11-14)
|
||||
新增弹框签名笔锋配置
|
||||
## 2.3(2023-11-14)
|
||||
修复滚动无法签名问题,弹框签名样式改变
|
||||
## 2.2(2023-11-14)
|
||||
修复app滚动无法签名等问题,修改了弹框签名样式
|
||||
## 2.1(2023-08-15)
|
||||
修改弹窗高度,修改实例项目
|
||||
## 2.0(2023-08-15)
|
||||
修复签名不兼容问题,改变签名方式,使用uni_modules方式
|
||||
103
uni_modules/jp-signature/components/jp-merge/jp-merge.vue
Normal file
103
uni_modules/jp-signature/components/jp-merge/jp-merge.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<view class="share">
|
||||
<canvas
|
||||
canvas-id="shareCanvas"
|
||||
class="canvas"
|
||||
bindlongpress="saveImg"
|
||||
catchtouchmove="true"
|
||||
style="position:fixed;left:500%"
|
||||
:style="{height: canvasHeight+'px',width:canvasWidth+'px'}"
|
||||
>
|
||||
</canvas>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 有项目需要开发的请联系 扣 - 371524845 -->
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
canvasHeight: {
|
||||
type: Number,
|
||||
default: 400,
|
||||
},
|
||||
canvasWidth: {
|
||||
type: Number,
|
||||
default: 400,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 80,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 50,
|
||||
},
|
||||
left: {
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
top: {
|
||||
type: Number,
|
||||
default: 320,
|
||||
},
|
||||
bgImage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
ctx:null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
//初始化画布
|
||||
this.ctx = wx.createCanvasContext('shareCanvas',this)
|
||||
},
|
||||
methods:{
|
||||
//获取图片的基本信息,即将网络图片转成本地图片,
|
||||
getImageInfo(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.getImageInfo({
|
||||
src,
|
||||
success: (res) => resolve(res),
|
||||
fail: (res) => reject(res)
|
||||
})
|
||||
});
|
||||
},
|
||||
exportPost(image2){
|
||||
let that = this
|
||||
return new Promise(function (resolve, reject) {
|
||||
let image = that.bgImage
|
||||
//获取系统的基本信息,为后期的画布和底图适配宽高
|
||||
uni.getSystemInfo({
|
||||
success: function (res) {
|
||||
Promise.all([that.getImageInfo(image),that.getImageInfo(image2)]).then(res=>{
|
||||
//获取底图和二维码图片的基本信息,通常前端导出的二维码是base64格式的,所以要转成图片格式的才可以获取图片的基本信息
|
||||
that.ctx.drawImage(res[0].path,0 , 0,that.canvasWidth,that.canvasHeight);
|
||||
that.ctx.drawImage(res[1].path,that.left,that.top,that.width, that.height);
|
||||
that.ctx.draw(false,function(){
|
||||
wx.canvasToTempFilePath({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width:that.canvasWidth,
|
||||
height:that.canvasHeight,
|
||||
destWidth:that.canvasWidth*2,//这里乘以2是为了保证合成图片的清晰度
|
||||
destHeight:that.canvasHeight*2,
|
||||
canvasId: 'shareCanvas',
|
||||
fileType:'jpg',//设置导出图片的后缀名
|
||||
success: function (res) {
|
||||
resolve(res.tempFilePath)
|
||||
},
|
||||
fail: function (res) {
|
||||
reject(res)
|
||||
},
|
||||
})
|
||||
});
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,328 @@
|
||||
<template>
|
||||
<div class="signature">
|
||||
<div class="inputs" v-if="!popup">
|
||||
<div class="label" :class="required?'labelqr':''">{{label}}</div>
|
||||
<div>
|
||||
<div v-if="value" class="images">
|
||||
<image @tap="toImg" class="images" mode="aspectFit" :src="value"></image>
|
||||
<view v-if="!readonly" @click="toDeleteImg" class="icons">
|
||||
<view class="Deletes">×</view>
|
||||
</view>
|
||||
</div>
|
||||
<div v-if="!value && !readonly" class="explain" @click="toPop">
|
||||
{{placeholder?placeholder:'点击签名'}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<view class="bottomPopup" v-if="showPopup" @touchmove.stop.prevent="moveHandle">
|
||||
<transition name="slide-up" appear>
|
||||
<view class="popup-content">
|
||||
<view class="popup">
|
||||
<div class="hader" v-if="!isHeight">
|
||||
<div @click="toclear">取消</div>
|
||||
<div class="text">{{label}}</div>
|
||||
<div @click="isEmpty">确定</div>
|
||||
</div>
|
||||
<div :class="isHeight?'wgSignatureq':'wgSignature'">
|
||||
<div v-if="isHeight" key="999" style="width: 750rpx ;height: 100vh;">
|
||||
<jp-signature :beforeDelay="200" :landscape="true" disableScroll ref="signatureRef" :openSmooth="openSmooth" :penSize="6" :bounding-box="boundingBox"></jp-signature>
|
||||
</div>
|
||||
<div v-else key="888" style="width: 750rpx ;height: 35vh;">
|
||||
<jp-signature :beforeDelay="200" disableScroll ref="signatureRef" :openSmooth="openSmooth" :bounding-box="boundingBox" :penSize="3"></jp-signature>
|
||||
</div>
|
||||
<div v-if="!isHeight" class="appBut" >
|
||||
<div class="buts" @click="undo" >撤销</div>
|
||||
<div class="buts" @click="deleteImg" >清除</div>
|
||||
<div class="buts" style="background-color: #55aaff;color: #fff;" @click="Tomagnify" >全屏</div>
|
||||
</div>
|
||||
<div v-else class="appBut" style="height: 80px;">
|
||||
<div class="butx" @click="undo" >撤销</div>
|
||||
<div class="butx" @click="deleteImg">清除</div>
|
||||
<div class="butx" style="background-color: #55aaff;color: #fff;" @click="Tomagnify" >小屏</div>
|
||||
<div class="butx" @click="toclear">取消</div>
|
||||
<div class="butx" style="background-color: #E59C36;color: #fff;" @click="isEmpty">完成</div>
|
||||
</div>
|
||||
</div>
|
||||
</view>
|
||||
</view>
|
||||
</transition>
|
||||
</view>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<!-- 有项目需要开发的请联系 扣 - 371524845 -->
|
||||
<script>
|
||||
/**
|
||||
* 手写签名组件
|
||||
* 用于手写签名(弹框签名支持小屏和全屏)
|
||||
*
|
||||
*********参数********
|
||||
* label 选项名称
|
||||
* value 初始值String(支持bas64,url 等图片显示)
|
||||
* required 是否显示必填
|
||||
* placeholder 默认值
|
||||
* readonly 是否只读
|
||||
*
|
||||
* *********回调********
|
||||
* @input(e) 点击确认 e生成的图片数据(bas64)
|
||||
*
|
||||
*********方法********
|
||||
* isEmpty() 生成图片
|
||||
* deleteImg() 删除图片
|
||||
*/
|
||||
export default {
|
||||
props: {
|
||||
popup: {
|
||||
type: [Boolean, String],
|
||||
default: false,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '手写签名',
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
required: {
|
||||
type: [Boolean, String],
|
||||
default: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '点击签名',
|
||||
},
|
||||
readonly: {
|
||||
type: [Boolean, String],
|
||||
default: false,
|
||||
},
|
||||
openSmooth: {
|
||||
type: [Boolean, String],
|
||||
default: true,
|
||||
},
|
||||
boundingBox: {
|
||||
type: [Boolean, String],
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showPopup: false,
|
||||
isHeight: false,
|
||||
height1: uni.getSystemInfoSync().windowWidth / 2,
|
||||
width: uni.getSystemInfoSync().windowWidth, //实时屏幕宽度
|
||||
height: uni.getSystemInfoSync().windowHeight, //实时屏幕高度
|
||||
showPicker: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
moveHandle(){
|
||||
|
||||
},
|
||||
toImg(){
|
||||
this.$emit('toImg',this.value)
|
||||
},
|
||||
undo() {
|
||||
this.$refs.signatureRef.undo()
|
||||
},
|
||||
toPop() {
|
||||
this.showPopup = true
|
||||
},
|
||||
toDeleteImg() {
|
||||
// #ifndef VUE3
|
||||
this.$emit('input','')
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
this.$emit('update:value','')
|
||||
// #endif
|
||||
},
|
||||
toclear() {
|
||||
this.isHeight = false
|
||||
this.showPopup = false
|
||||
},
|
||||
close() {
|
||||
this.isHeight = false
|
||||
this.showPopup = false
|
||||
const {signatureRef} = this.$refs
|
||||
signatureRef.clear()
|
||||
},
|
||||
deleteImg() {
|
||||
const {signatureRef} = this.$refs
|
||||
signatureRef.clear()
|
||||
},
|
||||
toDataURL(url) {
|
||||
// #ifndef VUE3
|
||||
this.$emit('input',url)
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
this.$emit('update:value',url)
|
||||
// #endif
|
||||
this.showPicker = false
|
||||
},
|
||||
Tomagnify() {
|
||||
this.isHeight = !this.isHeight
|
||||
const {signatureRef} = this.$refs
|
||||
signatureRef.clear()
|
||||
},
|
||||
isEmpty() {
|
||||
const {signatureRef} = this.$refs
|
||||
signatureRef.canvasToTempFilePath({
|
||||
quality: 0.8,
|
||||
success: (res) => {
|
||||
if (this.required) {
|
||||
if (!res.isEmpty) {
|
||||
// #ifndef VUE3
|
||||
this.$emit('input', res.tempFilePath)
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
this.$emit('update:value',res.tempFilePath)
|
||||
// #endif
|
||||
this.$emit('change', res.tempFilePath)
|
||||
this.isHeight = false
|
||||
this.showPopup = false
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '请先签名',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// #ifndef VUE3
|
||||
this.$emit('input', res.tempFilePath)
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
this.$emit('update:value',res.tempFilePath)
|
||||
// #endif
|
||||
this.$emit('change', res.tempFilePath)
|
||||
this.isHeight = false
|
||||
this.showPopup = false
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
beforeCreate() {},
|
||||
created() {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.wgSignatureq{
|
||||
|
||||
}
|
||||
.appBut{
|
||||
display: flex;justify-content: flex-start;align-items: center;text-align: center;height: 50px;line-height: 35px;
|
||||
.buts{
|
||||
color: #333;flex: 1;margin: 0 15px;background-color: #ccc;border-radius: 5px;height: 35px;
|
||||
}
|
||||
.butx{
|
||||
color: #333;flex: 1;margin: 0 5px;background-color: #ccc;border-radius: 5px;height: 35px;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.bottomPopup {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
|
||||
.popup-content {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
// top: 0;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.slide-up-enter-active,
|
||||
.slide-up-leave-active {
|
||||
transition: all .3s ease;
|
||||
}
|
||||
|
||||
.slide-up-enter,
|
||||
.slide-up-leave-to {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
}
|
||||
|
||||
.signature {
|
||||
.inputs {
|
||||
background-color: #fff;
|
||||
padding: 10px 16px;
|
||||
|
||||
.label {
|
||||
line-height: 35px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.labelqr:before {
|
||||
content: "*";
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
.explain {
|
||||
width: 100%;
|
||||
background-color: #f1f1f1;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
border: 1px dotted #ccc;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.Deletes {
|
||||
border: 1px solid #f00;
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
border-radius: 50%;
|
||||
color: #f00;
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
line-height: 30rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.images {
|
||||
width: 300rpx;
|
||||
height: 150rpx;
|
||||
position: relative;
|
||||
|
||||
.icons {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.hader {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
height: 45px;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
align-items: center;
|
||||
|
||||
div {
|
||||
text-align: center;
|
||||
width: 80px;
|
||||
color: #E59C36;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
199
uni_modules/jp-signature/components/jp-signature/context.js
Normal file
199
uni_modules/jp-signature/components/jp-signature/context.js
Normal file
@@ -0,0 +1,199 @@
|
||||
const uniPlatform = uni.getSystemInfoSync().uniPlatform
|
||||
|
||||
export const uniContext = (canvasId, context) => {
|
||||
let ctx = uni.createCanvasContext(canvasId, context)
|
||||
if (!ctx.uniDrawImage) {
|
||||
ctx.uniDrawImage = ctx.drawImage
|
||||
ctx.drawImage = (image, ...agrs) => {
|
||||
ctx.uniDrawImage(image.src, ...agrs)
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx.getImageData) {
|
||||
ctx.getImageData = (x, y, width, height) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef MP || VUE2
|
||||
if (context.proxy) context = context.proxy
|
||||
// #endif
|
||||
uni.canvasGetImageData({
|
||||
canvasId,
|
||||
x,
|
||||
y,
|
||||
width:parseInt(width),
|
||||
height:parseInt(height),
|
||||
success(res) {
|
||||
resolve(res)
|
||||
},
|
||||
fail(error) {
|
||||
reject(error)
|
||||
}
|
||||
}, context)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
ctx._getImageData = ctx.getImageData
|
||||
ctx.getImageData = (x, y, width, height) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
ctx._getImageData({
|
||||
x,
|
||||
y,
|
||||
width: parseInt(width) ,
|
||||
height:parseInt(height),
|
||||
success(res) {
|
||||
resolve(res)
|
||||
},
|
||||
fail(error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
class Image {
|
||||
constructor() {
|
||||
this.currentSrc = null
|
||||
this.naturalHeight = 0
|
||||
this.naturalWidth = 0
|
||||
this.width = 0
|
||||
this.height = 0
|
||||
this.tagName = 'IMG'
|
||||
}
|
||||
onerror() {}
|
||||
onload() {}
|
||||
set src(src) {
|
||||
this.currentSrc = src
|
||||
uni.getImageInfo({
|
||||
src,
|
||||
success: (res) => {
|
||||
this.naturalWidth = this.width = res.width
|
||||
this.naturalHeight = this.height = res.height
|
||||
this.onload()
|
||||
},
|
||||
fail: () => {
|
||||
this.onerror()
|
||||
}
|
||||
})
|
||||
}
|
||||
get src() {
|
||||
return this.currentSrc
|
||||
}
|
||||
}
|
||||
|
||||
export const createImage = () => {
|
||||
return new Image()
|
||||
}
|
||||
export function useCurrentPage() {
|
||||
const pages = getCurrentPages();
|
||||
return pages[pages.length - 1];
|
||||
}
|
||||
export const toDataURL = (canvasId, context, options = {}) => {
|
||||
// #ifdef MP-QQ
|
||||
// context = context.$scope
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
context = ''
|
||||
// #endif
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let {
|
||||
canvas,
|
||||
width,
|
||||
height,
|
||||
destWidth = 0,
|
||||
destHeight = 0,
|
||||
x = 0,
|
||||
y = 0,
|
||||
preferToDataURL
|
||||
} = options
|
||||
const {
|
||||
pixelRatio
|
||||
} = uni.getSystemInfoSync()
|
||||
|
||||
// #ifdef MP-ALIPAY
|
||||
const isDD = typeof dd != 'undefined'
|
||||
if (!isDD && (!destWidth || !destHeight)) {
|
||||
destWidth = width * pixelRatio;
|
||||
destHeight = height * pixelRatio;
|
||||
width = destWidth;
|
||||
height = destHeight;
|
||||
x = x * pixelRatio
|
||||
y = y * pixelRatio
|
||||
}
|
||||
// #endif
|
||||
const params = {
|
||||
...options,
|
||||
canvasId,
|
||||
id: canvasId,
|
||||
// #ifdef MP-ALIPAY
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
destWidth,
|
||||
destHeight,
|
||||
// #endif
|
||||
canvas,
|
||||
success: (res) => {
|
||||
resolve(res.tempFilePath)
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err)
|
||||
}
|
||||
}
|
||||
// 抖音小程序canvas 2d不支持canvasToTempFilePath
|
||||
if (canvas && canvas.toDataURL && preferToDataURL) {
|
||||
let next = true
|
||||
const devtools = uni.getSystemInfoSync().platform == 'devtools'
|
||||
// #ifdef MP-TOUTIAO
|
||||
next = uni.getSystemInfoSync().platform != 'devtools'
|
||||
if (!next) {
|
||||
console.warn('[lime-signature] 抖音开发工具不支持bbox')
|
||||
}
|
||||
// #endif
|
||||
if ((x || y) && next) {
|
||||
const offCanvas = uni.createOffscreenCanvas({
|
||||
type: '2d'
|
||||
});
|
||||
const ctx = offCanvas.getContext("2d");
|
||||
const destWidth = Math.floor(width * pixelRatio)
|
||||
const destHeight = Math.floor(height * pixelRatio)
|
||||
offCanvas.width = destWidth // canvas.width;
|
||||
offCanvas.height = destHeight // canvas.height;
|
||||
// ctx.scale(pixelRatio, pixelRatio)
|
||||
// ctx.drawImage(canvas, Math.floor(x*pixelRatio), Math.floor(y*pixelRatio), destWidth, destHeight, 0,0, destWidth, destHeight);
|
||||
// 抖音不能在drawImage使用canvas
|
||||
const image = canvas.createImage()
|
||||
image.onload = () => {
|
||||
ctx.drawImage(image, Math.floor(x * pixelRatio), Math.floor(y * pixelRatio),
|
||||
destWidth, destHeight, 0, 0, destWidth, destHeight)
|
||||
const tempFilePath = offCanvas.toDataURL();
|
||||
resolve(tempFilePath)
|
||||
if (params.success) {
|
||||
params.success({
|
||||
tempFilePath
|
||||
})
|
||||
}
|
||||
}
|
||||
image.src = canvas.toDataURL()
|
||||
|
||||
} else {
|
||||
const tempFilePath = canvas.toDataURL()
|
||||
resolve(tempFilePath)
|
||||
if (params.success) {
|
||||
params.success({
|
||||
tempFilePath
|
||||
})
|
||||
}
|
||||
}
|
||||
} else if (canvas && canvas.toTempFilePath) {
|
||||
canvas.toTempFilePath(params)
|
||||
} else {
|
||||
uni.canvasToTempFilePath(params, context)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
<template>
|
||||
<view class="l-signature" ref="signatureRef" :style="drawableStyle">
|
||||
<!-- #ifdef APP -->
|
||||
<view class="l-signature-landscape" ref="signatureLandscapeRef" v-if="landscape && url !=''"
|
||||
:style="landscapeStyle">
|
||||
<image class="l-signature-image" :style="landscapeImageStyle" :src="url"></image>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef WEB -->
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
<script lang="uts" setup>
|
||||
// @ts-nocheck
|
||||
// #ifdef APP
|
||||
import { Signature } from './signature.uts'
|
||||
// #endif
|
||||
// #ifndef APP
|
||||
import { Signature } from './signature.js'
|
||||
// #endif
|
||||
import { nextTick } from 'vue'
|
||||
import { LSignatureToTempFilePathOptions, LSignatureToFileSuccess, LSignatureOptions } from '../../index.uts'
|
||||
// type SignatureToFileSuccessCallback = (res : UTSJSONObject) => void
|
||||
// type SignatureToFileFailCallback = (res : TakeSnapshotFail) => void
|
||||
// type SignatureToFileCompleteCallback = (res : any) => void
|
||||
|
||||
/**
|
||||
* LimeSignature 手写板签名
|
||||
* @description 手写板签名插件,uvue专用版。
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=4354
|
||||
* @property {Number} penSize 画笔大小
|
||||
* @property {String} penColor 画笔颜色
|
||||
* @property {String} backgroundColor 背景颜色,不填则为透明
|
||||
* @property {Boolean} disableScroll 当在写字时,禁止屏幕滚动以及下拉刷新,nvue无效
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
styles: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
penColor: {
|
||||
type: String,
|
||||
default: 'black'
|
||||
},
|
||||
penSize: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
openSmooth: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
minLineWidth: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
maxLineWidth: {
|
||||
type: Number,
|
||||
default: 6
|
||||
},
|
||||
minSpeed: {
|
||||
type: Number,
|
||||
default: 1.5
|
||||
},
|
||||
maxWidthDiffRate: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
maxHistoryLength: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
disableScroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
landscape: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
})
|
||||
|
||||
const drawableStyle = computed<string>(() : string => {
|
||||
let style : string = ''
|
||||
|
||||
if (props.backgroundColor != '') {
|
||||
style += `background-color: ${props.backgroundColor};`
|
||||
}
|
||||
if (props.styles != '') {
|
||||
style += props.styles
|
||||
}
|
||||
return style
|
||||
})
|
||||
const signatureRef = ref<UniElement | null>(null)
|
||||
let signatureLandscapeRef = ref<UniElement | null>(null)
|
||||
let landscapeStyle = ref<Map<string, string>>(new Map())
|
||||
let landscapeImageStyle = ref<Map<string, string>>(new Map())
|
||||
|
||||
let signature : Signature | null = null
|
||||
let url = ref('')
|
||||
// #ifdef WEB
|
||||
let canvas : HTMLCanvasElement | null = null
|
||||
let touchstart,touchmove,touchend
|
||||
// #endif
|
||||
|
||||
const clear = () => {
|
||||
signature?.clear()
|
||||
}
|
||||
const redo = () => {
|
||||
signature?.redo()
|
||||
}
|
||||
const undo = () => {
|
||||
signature?.undo()
|
||||
}
|
||||
const canvasToTempFilePath = (options : LSignatureToTempFilePathOptions) => {
|
||||
const success = options.success // as SignatureToFileSuccessCallback | null
|
||||
const fail = options.fail // as SignatureToFileFailCallback | null
|
||||
const complete = options.complete// as SignatureToFileCompleteCallback | null
|
||||
const format = options.format ?? 'png'
|
||||
// #ifdef APP
|
||||
signatureRef.value?.takeSnapshot({
|
||||
format,
|
||||
success: (res) => {
|
||||
if (props.landscape) {
|
||||
url.value = res.tempFilePath;
|
||||
setTimeout(() => {
|
||||
signatureLandscapeRef.value?.takeSnapshot({
|
||||
format,
|
||||
success: (res2) => {
|
||||
success?.({
|
||||
tempFilePath: res2.tempFilePath,
|
||||
isEmpty: signature?.isEmpty ?? false
|
||||
} as LSignatureToFileSuccess)
|
||||
}
|
||||
})
|
||||
}, 300)
|
||||
|
||||
} else {
|
||||
success?.({
|
||||
tempFilePath: res.tempFilePath,
|
||||
isEmpty: signature?.isEmpty ?? false
|
||||
} as LSignatureToFileSuccess)
|
||||
}
|
||||
},
|
||||
fail: (res) => {
|
||||
fail?.(res)
|
||||
},
|
||||
complete: (res) => {
|
||||
complete?.(res)
|
||||
}
|
||||
} as TakeSnapshotOptions)
|
||||
// #endif
|
||||
|
||||
// #ifdef WEB
|
||||
// @ts-ignore
|
||||
const { backgroundColor, backgroundImage, landscape, boundingBox } = props
|
||||
const { quality = 1 } = options
|
||||
const flag = landscape || backgroundColor || boundingBox
|
||||
const type = `image/${format}`.replace(/jpg/, 'jpeg');
|
||||
const image = canvas?.toDataURL(!flag && type, !flag && quality)
|
||||
|
||||
if (flag) {
|
||||
// @ts-ignore
|
||||
const canvas = document.createElement('canvas')
|
||||
// @ts-ignore
|
||||
const pixelRatio = signature?.canvas.get('pixelRatio')
|
||||
// @ts-ignore
|
||||
let width = signature?.canvas.get('width')
|
||||
// @ts-ignore
|
||||
let height = signature?.canvas.get('height')
|
||||
let x = 0
|
||||
let y = 0
|
||||
// @ts-ignore
|
||||
const next = () => {
|
||||
const size = [width, height]
|
||||
if (landscape) {
|
||||
size.reverse()
|
||||
}
|
||||
canvas.width = size[0] * pixelRatio
|
||||
canvas.height = size[1] * pixelRatio
|
||||
const param = [x, y, width, height, 0, 0, width, height].map(item => item * pixelRatio)
|
||||
const context = canvas.getContext('2d')
|
||||
if (landscape) {
|
||||
context.translate(0, width * pixelRatio)
|
||||
context.rotate(-Math.PI / 2)
|
||||
}
|
||||
if (backgroundColor) {
|
||||
context.fillStyle = backgroundColor
|
||||
context.fillRect(0, 0, width * pixelRatio, height * pixelRatio)
|
||||
}
|
||||
const drawImage = () => {
|
||||
// @ts-ignore
|
||||
context.drawImage(signature?.canvas!.get('el'), ...param)
|
||||
success?.({
|
||||
tempFilePath: canvas.toDataURL(type, quality),
|
||||
// @ts-ignore
|
||||
isEmpty: signature?.isEmpty() ?? false
|
||||
} as LSignatureToFileSuccess)
|
||||
canvas.remove()
|
||||
}
|
||||
if (backgroundImage) {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
context.drawImage(img, ...param)
|
||||
drawImage()
|
||||
}
|
||||
img.src = backgroundImage
|
||||
}
|
||||
if (!backgroundImage) {
|
||||
drawImage()
|
||||
}
|
||||
}
|
||||
if (boundingBox) {
|
||||
// @ts-ignore
|
||||
const res = signature?.getContentBoundingBox()
|
||||
width = res.width
|
||||
height = res.height
|
||||
x = res.startX
|
||||
y = res.startY
|
||||
next()
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
} else {
|
||||
success?.({
|
||||
tempFilePath: image,
|
||||
// @ts-ignore
|
||||
isEmpty: signature?.isEmpty() ?? false
|
||||
} as LSignatureToFileSuccess)
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
defineExpose({
|
||||
clear,
|
||||
redo,
|
||||
undo,
|
||||
canvasToTempFilePath,
|
||||
})
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
const width = signatureRef.value?.offsetWidth
|
||||
const height = signatureRef.value?.offsetHeight
|
||||
// #ifdef APP
|
||||
landscapeStyle.value.set('width', `${height}px`)
|
||||
landscapeStyle.value.set('height', `${width}px`)
|
||||
landscapeImageStyle.value.set('width', `${width}px`)
|
||||
landscapeImageStyle.value.set('height', `${height}px`)
|
||||
landscapeImageStyle.value.set('transform', `rotate(-90deg) translateY(${width}px)`)
|
||||
|
||||
signature = new Signature(signatureRef.value!)
|
||||
// #endif
|
||||
// #ifdef WEB
|
||||
canvas = document.createElement('canvas')
|
||||
canvas!.style = 'width: 100%; height: 100%;'
|
||||
signatureRef.value?.appendChild(canvas as UniElement)
|
||||
// @ts-ignore
|
||||
signature = new Signature({ el: canvas })
|
||||
let isTouch = false
|
||||
touchstart = (event: UniMouseEvent) => {
|
||||
isTouch = true
|
||||
const rect = canvas?.getBoundingClientRect()
|
||||
// @ts-ignore
|
||||
signature!.canvas.emit('touchstart', {
|
||||
points: [
|
||||
{
|
||||
x: event.clientX - rect.left,
|
||||
y: event.clientY - rect.top
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
touchmove = (event: UniMouseEvent) => {
|
||||
if(!isTouch) return
|
||||
const rect = canvas?.getBoundingClientRect()
|
||||
// @ts-ignore
|
||||
signature!.canvas.emit('touchmove', {
|
||||
points: [
|
||||
{
|
||||
x: event.clientX - rect.left,
|
||||
y: event.clientY - rect.top
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
touchend = (event: UniMouseEvent) => {
|
||||
isTouch = false
|
||||
const rect = canvas?.getBoundingClientRect();
|
||||
// @ts-ignore
|
||||
signature!.canvas.emit('touchend', {
|
||||
points: [
|
||||
{
|
||||
x: event.clientX - rect.left,
|
||||
y: event.clientY - rect.top
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
canvas?.addEventListener('mousedown', touchstart)
|
||||
canvas?.addEventListener('mousemove', touchmove)
|
||||
canvas?.addEventListener('mouseup', touchend)
|
||||
canvas?.addEventListener('mouseleave', touchend)
|
||||
|
||||
|
||||
// #endif
|
||||
|
||||
watchEffect(() => {
|
||||
const options : LSignatureOptions = {
|
||||
penColor: props.penColor,
|
||||
openSmooth: props.openSmooth,
|
||||
disableScroll: props.disableScroll,
|
||||
disabled: props.disabled,
|
||||
penSize: props.penSize,
|
||||
minLineWidth: props.minLineWidth,
|
||||
maxLineWidth: props.maxLineWidth,
|
||||
minSpeed: props.minSpeed,
|
||||
maxWidthDiffRate: props.maxWidthDiffRate,
|
||||
maxHistoryLength: props.maxHistoryLength
|
||||
}
|
||||
// #ifdef APP
|
||||
signature?.setOption(options)
|
||||
// #endif
|
||||
// #ifdef WEB
|
||||
// @ts-ignore
|
||||
signature?.pen.setOption(options)
|
||||
// #endif
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(()=>{
|
||||
// #ifdef WEB
|
||||
canvas?.removeEventListener('mousedown', touchstart)
|
||||
canvas?.removeEventListener('mousemove', touchmove)
|
||||
canvas?.removeEventListener('mouseup', touchend)
|
||||
canvas?.removeEventListener('mouseleave', touchend)
|
||||
canvas?.remove()
|
||||
// #endif
|
||||
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.l-signature {
|
||||
flex: 1;
|
||||
|
||||
&-landscape {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
left: 1000rpx;
|
||||
}
|
||||
|
||||
&-image {
|
||||
transform-origin: 0% 0%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,728 @@
|
||||
<template>
|
||||
<view class="lime-signature" v-if="show" :style="[canvasStyle, styles]" ref="limeSignature">
|
||||
<!-- #ifndef APP-VUE || APP-NVUE -->
|
||||
<canvas v-if="useCanvas2d" class="lime-signature__canvas" :id="canvasId" type="2d"
|
||||
:disableScroll="disableScroll" @touchstart="touchStart" @touchmove="touchMove"
|
||||
@touchend="touchEnd"></canvas>
|
||||
<canvas v-else :disableScroll="disableScroll" class="lime-signature__canvas" :canvas-id="canvasId"
|
||||
:id="canvasId" :width="canvasWidth" :height="canvasHeight" @touchstart="touchStart" @touchmove="touchMove"
|
||||
@touchend="touchEnd" @mousedown="touchStart" @mousemove="touchMove" @mouseup="touchEnd"></canvas>
|
||||
<canvas v-if="showOffscreen" class="offscreen" canvas-id="offscreen" id="offscreen"
|
||||
:style="'width:' + offscreenSize[0] + 'px;height:' + offscreenSize[1] + 'px'" :width="offscreenSize[0]"
|
||||
:height="offscreenSize[1]">
|
||||
</canvas>
|
||||
<view v-if="showMask" class="mask" @touchstart="touchStart" @touchmove.stop.prevent="touchMove"
|
||||
@touchend="touchEnd"></view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-VUE -->
|
||||
<view :id="canvasId" :disableScroll="disableScroll" :rparam="param" :change:rparam="sign.update"
|
||||
:rclear="rclear" :change:rclear="sign.clear" :rundo="rundo" :rredo="rredo" :change:rredo="sign.redo"
|
||||
:change:rundo="sign.undo" :rsave="rsave" :rmask="rmask" :change:rsave="sign.save" :change:rmask="sign.mask"
|
||||
:rdestroy="rdestroy" :change:rdestroy="sign.destroy" :rempty="rempty" :change:rempty="sign.isEmpty">
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<web-view src="/uni_modules/lime-signature/hybrid/html/index.html" class="lime-signature__canvas" ref="webview"
|
||||
@pagefinish="onPageFinish" @error="onError" @onPostMessage="onMessage"></web-view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifndef APP-NVUE
|
||||
import {
|
||||
canIUseCanvas2d,
|
||||
wrapEvent,
|
||||
requestAnimationFrame,
|
||||
sleep,
|
||||
isTransparent
|
||||
} from './utils'
|
||||
import {
|
||||
Signature
|
||||
} from './signature.js'
|
||||
// import {Signature} from '@signature';
|
||||
import {
|
||||
uniContext,
|
||||
createImage,
|
||||
toDataURL
|
||||
} from './context'
|
||||
// #endif
|
||||
import props from './props';
|
||||
import {
|
||||
base64ToPath,
|
||||
getRect
|
||||
} from './utils'
|
||||
import { nextTick } from 'vue';
|
||||
|
||||
/**
|
||||
* LimeSignature 手写板签名
|
||||
* @description 手写板签名插件:一款能跑在uniapp各端中的签名插件,支持横屏、背景色、笔画颜色、笔画大小等功能,可生成有内容的区域,减小图片尺寸,节省空间。
|
||||
* @property {Number} penSize 画笔大小
|
||||
* @property {Number} minLineWidth 线条最小宽
|
||||
* @property {Number} maxLineWidth 线条最大宽
|
||||
* @property {String} penColor 画笔颜色
|
||||
* @property {String} backgroundColor 背景颜色,不填则为透明
|
||||
* @property {type} 指定 canvas 类型
|
||||
* @value 2d canvas 2d
|
||||
* @value '' 非 canvas 2d 旧接口,微信不再维护
|
||||
* @property {Boolean} openSmooth 模拟笔锋
|
||||
* @property {Number} beforeDelay 延时初始化,在放在弹窗里可以使用 (毫秒)
|
||||
* @property {Number} maxHistoryLength 限制历史记录数,即最大可撤销数,传入0则关闭历史记录功能
|
||||
* @property {Boolean} landscape 横屏,使用后在最后生成图片时会图片旋转90度
|
||||
* @property {Boolean} disableScroll 当在写字时,禁止屏幕滚动以及下拉刷新,nvue无效
|
||||
* @property {Boolean} boundingBox 只生成内容区域,即未画部分不生成,有性能的损耗
|
||||
*/
|
||||
export default {
|
||||
props,
|
||||
data() {
|
||||
return {
|
||||
canvasWidth: null,
|
||||
canvasHeight: null,
|
||||
offscreenWidth: null,
|
||||
offscreenHeight: null,
|
||||
useCanvas2d: true,
|
||||
show: true,
|
||||
offscreenStyles: '',
|
||||
showMask: false,
|
||||
showOffscreen: false,
|
||||
isPC: false,
|
||||
// #ifdef APP-PLUS
|
||||
rclear: 0,
|
||||
rdestroy: 0,
|
||||
rundo: 0,
|
||||
rredo: 0,
|
||||
rsave: JSON.stringify({
|
||||
n: 0,
|
||||
fileType: 'png',
|
||||
quality: 1,
|
||||
destWidth: 0,
|
||||
destHeight: 0,
|
||||
}),
|
||||
rmask: JSON.stringify({
|
||||
n: 0,
|
||||
destWidth: 0,
|
||||
destHeight: 0,
|
||||
}),
|
||||
rempty: 0,
|
||||
risEmpty: true,
|
||||
toDataURL: null,
|
||||
tempFilePath: [],
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
canvasId() {
|
||||
// #ifdef VUE2
|
||||
return `lime-signature${this._uid}`
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
return `lime-signature${this._.uid}`
|
||||
// #endif
|
||||
},
|
||||
offscreenId() {
|
||||
return this.canvasId + 'offscreen'
|
||||
},
|
||||
offscreenSize() {
|
||||
const {
|
||||
offscreenWidth,
|
||||
offscreenHeight
|
||||
} = this
|
||||
return this.landscape ? [offscreenHeight, offscreenWidth] : [offscreenWidth, offscreenHeight]
|
||||
},
|
||||
canvasStyle() {
|
||||
const {
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
backgroundColor
|
||||
} = this
|
||||
return {
|
||||
width: canvasWidth && (canvasWidth + 'px'),
|
||||
height: canvasHeight && (canvasHeight + 'px'),
|
||||
background: backgroundColor
|
||||
}
|
||||
},
|
||||
param() {
|
||||
const {
|
||||
penColor,
|
||||
penSize,
|
||||
backgroundColor,
|
||||
backgroundImage,
|
||||
landscape,
|
||||
boundingBox,
|
||||
openSmooth,
|
||||
minLineWidth,
|
||||
maxLineWidth,
|
||||
minSpeed,
|
||||
maxWidthDiffRate,
|
||||
maxHistoryLength,
|
||||
disableScroll,
|
||||
disabled
|
||||
} = this
|
||||
return JSON.parse(JSON.stringify({
|
||||
penColor,
|
||||
penSize,
|
||||
backgroundColor,
|
||||
backgroundImage,
|
||||
landscape,
|
||||
boundingBox,
|
||||
openSmooth,
|
||||
minLineWidth,
|
||||
maxLineWidth,
|
||||
minSpeed,
|
||||
maxWidthDiffRate,
|
||||
maxHistoryLength,
|
||||
disableScroll,
|
||||
disabled
|
||||
}))
|
||||
}
|
||||
},
|
||||
// #ifdef APP-NVUE
|
||||
watch: {
|
||||
param(v) {
|
||||
this.$refs.webview.evalJS(`update(${JSON.stringify(v)})`)
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
created() {
|
||||
const {
|
||||
platform
|
||||
} = uni.getSystemInfoSync()
|
||||
this.isPC = /windows|mac/.test(platform)
|
||||
this.useCanvas2d = this.type == '2d' && canIUseCanvas2d() && !this.isPC
|
||||
// #ifndef H5
|
||||
this.showMask = this.isPC
|
||||
// #endif
|
||||
|
||||
},
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
async mounted() {
|
||||
if (this.beforeDelay) {
|
||||
await sleep(this.beforeDelay)
|
||||
}
|
||||
const config = await this.getContext()
|
||||
this.signature = new Signature(config)
|
||||
this.canvasEl = this.signature.canvas.get('el')
|
||||
this.offscreenWidth = this.canvasWidth = this.signature.canvas.get('width')
|
||||
this.offscreenHeight = this.canvasHeight = this.signature.canvas.get('height')
|
||||
|
||||
this.stopWatch = this.$watch('param', (v) => {
|
||||
this.signature.pen.setOption(v)
|
||||
}, {
|
||||
immediate: true
|
||||
})
|
||||
},
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.stopWatch && this.stopWatch()
|
||||
this.signature.destroy()
|
||||
this.signature = null
|
||||
this.show = false;
|
||||
// #ifdef APP-VUE || APP-NVUE
|
||||
this.rdestroy++
|
||||
// #endif
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE2
|
||||
beforeDestroy() {
|
||||
this.stopWatch && this.stopWatch()
|
||||
this.signature.destroy()
|
||||
this.show = false;
|
||||
this.signature = null
|
||||
// #ifdef APP-VUE || APP-NVUE
|
||||
this.rdestroy++
|
||||
// #endif
|
||||
},
|
||||
// #endif
|
||||
// #endif
|
||||
methods: {
|
||||
// #ifdef MP-QQ
|
||||
// toJSON() { return this },
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
onPageFinish() {
|
||||
this.$refs.webview.evalJS(`update(${JSON.stringify(this.param)})`)
|
||||
},
|
||||
onMessage(e = {}) {
|
||||
const {
|
||||
detail: {
|
||||
data: [res]
|
||||
}
|
||||
} = e
|
||||
if (res.event?.save) {
|
||||
this.toDataURL = res.event.save
|
||||
}
|
||||
if (res.event?.changeSize) {
|
||||
const {
|
||||
width,
|
||||
height
|
||||
} = res.event.changeSize
|
||||
}
|
||||
if (res.event.hasOwnProperty('isEmpty')) {
|
||||
this.risEmpty = res.event.isEmpty
|
||||
}
|
||||
if (res.event?.file) {
|
||||
this.tempFilePath.push(res.event.file)
|
||||
if (this.tempFilePath.length > 7) {
|
||||
this.tempFilePath.shift()
|
||||
}
|
||||
return
|
||||
}
|
||||
if (res.event?.success) {
|
||||
if (res.event.success) {
|
||||
this.tempFilePath.push(res.event.success)
|
||||
if (this.tempFilePath.length > 8) {
|
||||
this.tempFilePath.shift()
|
||||
}
|
||||
this.toDataURL = this.tempFilePath.join('')
|
||||
this.tempFilePath = []
|
||||
} else {
|
||||
this.$emit('fail', 'canvas no data')
|
||||
}
|
||||
return
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
redo() {
|
||||
// #ifdef APP-VUE || APP-NVUE
|
||||
this.rredo += 1
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this.$refs.webview.evalJS(`redo()`)
|
||||
// #endif
|
||||
// #ifndef APP-VUE
|
||||
if (this.signature)
|
||||
this.signature.redo()
|
||||
// #endif
|
||||
},
|
||||
restore() {
|
||||
this.redo()
|
||||
},
|
||||
undo() {
|
||||
// #ifdef APP-VUE || APP-NVUE
|
||||
this.rundo += 1
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this.$refs.webview.evalJS(`undo()`)
|
||||
// #endif
|
||||
// #ifndef APP-VUE
|
||||
if (this.signature)
|
||||
this.signature.undo()
|
||||
// #endif
|
||||
},
|
||||
clear() {
|
||||
// #ifdef APP-VUE || APP-NVUE
|
||||
this.rclear += 1
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this.$refs.webview.evalJS(`clear()`)
|
||||
// #endif
|
||||
// #ifndef APP-VUE
|
||||
if (this.signature)
|
||||
this.signature.clear()
|
||||
// #endif
|
||||
},
|
||||
isEmpty() {
|
||||
// #ifdef APP-NVUE
|
||||
this.$refs.webview.evalJS(`isEmpty()`)
|
||||
// #endif
|
||||
// #ifdef APP-VUE || APP-NVUE
|
||||
this.rempty += 1
|
||||
// #endif
|
||||
// #ifndef APP-VUE || APP-NVUE
|
||||
return this.signature.isEmpty()
|
||||
// #endif
|
||||
},
|
||||
async canvasToMaskPath(param = {}) {
|
||||
const isEmpty = this.isEmpty()
|
||||
// #ifdef APP-NVUE
|
||||
this.$refs.webview.evalJS(`mask(${JSON.stringify(param)})`)
|
||||
// #endif
|
||||
// #ifdef APP-VUE || APP-NVUE
|
||||
const stopURLWatch = this.$watch('toDataURL', (v, n) => {
|
||||
if (v && v !== n) {
|
||||
// if(param.pathType == 'url') {
|
||||
base64ToPath(v).then(res => {
|
||||
param.success({
|
||||
tempFilePath: res,
|
||||
isEmpty: this.risEmpty
|
||||
})
|
||||
})
|
||||
// } else {
|
||||
// param.success({tempFilePath: v,isEmpty: this.risEmpty })
|
||||
// }
|
||||
this.toDataURL = ''
|
||||
}
|
||||
stopURLWatch && stopURLWatch()
|
||||
})
|
||||
const {
|
||||
fileType,
|
||||
quality
|
||||
} = param
|
||||
const rmask = JSON.parse(this.rmask)
|
||||
rmask.n++
|
||||
rmask.destWidth = param.destWidth ?? 0
|
||||
rmask.destHeight = param.destHeight ?? 0
|
||||
// rmask.fileType = fileType
|
||||
// rmask.quality = quality
|
||||
this.rmask = JSON.stringify(rmask)
|
||||
// #endif
|
||||
// #ifndef APP-VUE || APP-NVUE
|
||||
this.showOffscreen = true
|
||||
|
||||
let width = this.signature.canvas.get('width')
|
||||
let height = this.signature.canvas.get('height')
|
||||
let {
|
||||
pixelRatio
|
||||
} = uni.getSystemInfoSync()
|
||||
if (this.useCanvas2d) {
|
||||
this.offscreenWidth = width * pixelRatio
|
||||
this.offscreenHeight = height * pixelRatio
|
||||
} else {
|
||||
this.offscreenWidth = width
|
||||
this.offscreenHeight = height
|
||||
}
|
||||
await sleep(100)
|
||||
const context = uni.createCanvasContext('offscreen', this)
|
||||
const size = Math.max(this.offscreenWidth, this.offscreenHeight)
|
||||
const success = (success) => param.success && param.success(success)
|
||||
const fail = (fail) => param.fail && param.fail(fail)
|
||||
|
||||
this.signature.pen.getMaskedImageData((imageData) => {
|
||||
let canvasPutImageData = (options, comp) => {
|
||||
if (uni.canvasPutImageData) {
|
||||
uni.canvasPutImageData(options, comp)
|
||||
} else if (context.putImageData) {
|
||||
context.putImageData(options)
|
||||
}
|
||||
}
|
||||
canvasPutImageData({
|
||||
canvasId: 'offscreen',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: width,
|
||||
height:height,
|
||||
data: imageData,
|
||||
fail(err) {
|
||||
fail(err)
|
||||
},
|
||||
success: (re) => {
|
||||
toDataURL('offscreen', this, param).then((res) => {
|
||||
context.restore()
|
||||
context.clearRect(0, 0, size, size)
|
||||
this.offscreenWidth = width
|
||||
this.offscreenHeight = height
|
||||
this.showOffscreen = false
|
||||
success({
|
||||
tempFilePath: res,
|
||||
isEmpty
|
||||
})
|
||||
})
|
||||
}
|
||||
}, this)
|
||||
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
canvasToTempFilePath(param = {}) {
|
||||
|
||||
const isEmpty = this.isEmpty()
|
||||
// #ifdef APP-NVUE
|
||||
this.$refs.webview.evalJS(`save(${JSON.stringify(param)})`)
|
||||
// #endif
|
||||
// #ifdef APP-VUE || APP-NVUE
|
||||
const stopURLWatch = this.$watch('toDataURL', (v, n) => {
|
||||
if (v && v !== n) {
|
||||
if (this.preferToDataURL) {
|
||||
param.success({
|
||||
tempFilePath: v,
|
||||
isEmpty: this.risEmpty
|
||||
})
|
||||
} else {
|
||||
base64ToPath(v).then(res => {
|
||||
param.success({
|
||||
tempFilePath: res,
|
||||
isEmpty: this.risEmpty
|
||||
})
|
||||
})
|
||||
}
|
||||
this.toDataURL = ''
|
||||
}
|
||||
stopURLWatch && stopURLWatch()
|
||||
})
|
||||
const {
|
||||
fileType,
|
||||
quality
|
||||
} = param
|
||||
const rsave = JSON.parse(this.rsave)
|
||||
rsave.n++
|
||||
rsave.fileType = fileType
|
||||
rsave.quality = quality
|
||||
rsave.destWidth = param.destWidth ?? 0
|
||||
rsave.destHeight = param.destHeight ?? 0
|
||||
this.rsave = JSON.stringify(rsave)
|
||||
// #endif
|
||||
// #ifndef APP-VUE || APP-NVUE
|
||||
const useCanvas2d = this.useCanvas2d
|
||||
const success = (success) => param.success && param.success(success)
|
||||
const fail = (err) => param.fail && param.fail(err)
|
||||
const {
|
||||
canvas
|
||||
} = this.signature.canvas.get('el')
|
||||
const {
|
||||
backgroundColor,
|
||||
landscape,
|
||||
boundingBox
|
||||
} = this
|
||||
let width = this.signature.canvas.get('width')
|
||||
let height = this.signature.canvas.get('height')
|
||||
let x = 0
|
||||
let y = 0
|
||||
const devtools = uni.getSystemInfoSync().platform == 'devtools'
|
||||
let preferToDataURL = this.preferToDataURL
|
||||
let scale = 1
|
||||
// #ifdef MP-TOUTIAO
|
||||
scale = devtools ? uni.getSystemInfoSync().pixelRatio : scale
|
||||
// 由于抖音不支持canvasToTempFilePath故优先使用createOffscreenCanvas
|
||||
preferToDataURL = true
|
||||
// #endif
|
||||
const canvasToTempFilePath = async (image) => {
|
||||
const createCanvasContext = () => {
|
||||
const useOffscreen = (useCanvas2d && !!uni.createOffscreenCanvas && preferToDataURL)
|
||||
if (useOffscreen && !devtools) {
|
||||
const offCanvas = uni.createOffscreenCanvas({
|
||||
type: '2d'
|
||||
});
|
||||
offCanvas.width = this.offscreenSize[0] * scale
|
||||
offCanvas.height = this.offscreenSize[1] * scale
|
||||
const context = offCanvas.getContext("2d");
|
||||
return [context, offCanvas]
|
||||
} else {
|
||||
const context = uni.createCanvasContext('offscreen', this)
|
||||
return [context]
|
||||
}
|
||||
}
|
||||
|
||||
if (boundingBox && !this.isPC || landscape || backgroundColor && !isTransparent(backgroundColor)) {
|
||||
|
||||
this.showOffscreen = true
|
||||
await sleep(100)
|
||||
const [context, offCanvas] = createCanvasContext()
|
||||
context.save()
|
||||
context.setTransform(1, 0, 0, 1, 0, 0)
|
||||
if (landscape) {
|
||||
context.translate(0, width * scale)
|
||||
context.rotate(-Math.PI / 2)
|
||||
}
|
||||
if (backgroundColor && !isTransparent(backgroundColor)) {
|
||||
context.fillStyle = backgroundColor
|
||||
context.fillRect(0, 0, width, height)
|
||||
}
|
||||
if (offCanvas) {
|
||||
const img = canvas.createImage();
|
||||
img.src = image
|
||||
img.onload = () => {
|
||||
context.drawImage(img, 0, 0, width * scale, height * scale);
|
||||
const tempFilePath = offCanvas.toDataURL()
|
||||
this.showOffscreen = false
|
||||
success({
|
||||
tempFilePath,
|
||||
isEmpty
|
||||
})
|
||||
}
|
||||
|
||||
} else {
|
||||
context.drawImage(image, 0, 0, width * scale, height * scale);
|
||||
context.draw(false, () => {
|
||||
toDataURL('offscreen', this, param).then((res) => {
|
||||
const size = Math.max(width, height)
|
||||
context.restore()
|
||||
context.clearRect(0, 0, size, size)
|
||||
this.showOffscreen = false
|
||||
success({
|
||||
tempFilePath: res,
|
||||
isEmpty
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
success({
|
||||
tempFilePath: image,
|
||||
isEmpty
|
||||
})
|
||||
}
|
||||
}
|
||||
const next = async () => {
|
||||
if (this.offscreenWidth != width || this.offscreenHeight != height) {
|
||||
this.offscreenWidth = width
|
||||
this.offscreenHeight = height
|
||||
await sleep(100)
|
||||
}
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
const param = {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
canvas,
|
||||
preferToDataURL
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
const param = {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
canvas: useCanvas2d ? canvas : null,
|
||||
preferToDataURL
|
||||
}
|
||||
// #endif
|
||||
toDataURL(this.canvasId, this, param).then(canvasToTempFilePath).catch(fail)
|
||||
}
|
||||
// PC端小程序获取不到 ImageData 数据,长度为0
|
||||
if (boundingBox && !this.isPC) {
|
||||
this.signature.getContentBoundingBox(async res => {
|
||||
this.offscreenWidth = width = res.width
|
||||
this.offscreenHeight = height = res.height
|
||||
|
||||
x = res.startX
|
||||
y = res.startY
|
||||
|
||||
next()
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
// #ifndef APP-PLUS
|
||||
getContext() {
|
||||
return getRect(`#${this.canvasId}`, {
|
||||
context: this,
|
||||
type: this.useCanvas2d ? 'fields' : 'boundingClientRect'
|
||||
}).then(res => {
|
||||
if (res) {
|
||||
let {
|
||||
width,
|
||||
height,
|
||||
node: canvas,
|
||||
left,
|
||||
top,
|
||||
right
|
||||
} = res
|
||||
let {
|
||||
pixelRatio
|
||||
} = uni.getSystemInfoSync()
|
||||
let context;
|
||||
if (canvas) {
|
||||
context = canvas.getContext('2d')
|
||||
canvas.width = width * pixelRatio;
|
||||
canvas.height = height * pixelRatio;
|
||||
} else {
|
||||
pixelRatio = 1
|
||||
context = uniContext(this.canvasId, this)
|
||||
canvas = {
|
||||
getContext: (type) => type == '2d' ? context : null,
|
||||
createImage,
|
||||
toDataURL: () => toDataURL(this.canvasId, this),
|
||||
requestAnimationFrame
|
||||
}
|
||||
}
|
||||
// 支付宝小程序 使用stroke有个默认背景色
|
||||
context.clearRect(0, 0, width, height)
|
||||
return {
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
width,
|
||||
height,
|
||||
context,
|
||||
canvas,
|
||||
pixelRatio
|
||||
};
|
||||
}
|
||||
})
|
||||
},
|
||||
getTouch(e) {
|
||||
if (this.isPC && this.canvasRect) {
|
||||
e.touches = e.touches.map(item => {
|
||||
return {
|
||||
...item,
|
||||
x: item.clientX - this.canvasRect.left,
|
||||
y: item.clientY - this.canvasRect.top,
|
||||
}
|
||||
})
|
||||
}
|
||||
return e
|
||||
},
|
||||
touchStart(e) {
|
||||
if (!this.canvasEl) return
|
||||
this.isStart = true
|
||||
// 微信小程序PC端不支持事件,使用这方法模拟一下
|
||||
if (this.isPC) {
|
||||
getRect(`#${this.canvasId}`, {
|
||||
context: this
|
||||
}).then(res => {
|
||||
this.canvasRect = res
|
||||
this.canvasEl.dispatchEvent('touchstart', wrapEvent(this.getTouch(e)))
|
||||
})
|
||||
return
|
||||
}
|
||||
this.canvasEl.dispatchEvent('touchstart', wrapEvent(e))
|
||||
},
|
||||
touchMove(e) {
|
||||
if (!this.canvasEl || !this.isStart && this.canvasEl) return
|
||||
this.canvasEl.dispatchEvent('touchmove', wrapEvent(this.getTouch(e)))
|
||||
},
|
||||
touchEnd(e) {
|
||||
if (!this.canvasEl) return
|
||||
this.isStart = false
|
||||
this.canvasEl.dispatchEvent('touchend', wrapEvent(e))
|
||||
},
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<!-- #ifdef APP-VUE -->
|
||||
<script module="sign" lang="renderjs">
|
||||
import sign from './render'
|
||||
export default sign
|
||||
</script>
|
||||
<!-- #endif -->
|
||||
<style lang="scss">
|
||||
.lime-signature,
|
||||
.lime-signature__canvas {
|
||||
/* #ifndef APP-NVUE */
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
flex: 1;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.offscreen {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
// left: 0;
|
||||
pointer-events:none;
|
||||
// background: rgba(0,255,0,0.5);
|
||||
left: 9999px;
|
||||
}
|
||||
</style>
|
||||
64
uni_modules/jp-signature/components/jp-signature/props.js
Normal file
64
uni_modules/jp-signature/components/jp-signature/props.js
Normal file
@@ -0,0 +1,64 @@
|
||||
export default {
|
||||
styles: String,
|
||||
disableScroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '2d'
|
||||
},
|
||||
// 画笔颜色
|
||||
penColor: {
|
||||
type: String,
|
||||
default: 'black'
|
||||
},
|
||||
penSize: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
// 画板背景颜色
|
||||
backgroundColor: String,
|
||||
backgroundImage: String,
|
||||
// 笔锋
|
||||
openSmooth: Boolean,
|
||||
// 画笔最小值
|
||||
minLineWidth: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
// 画笔最大值
|
||||
maxLineWidth: {
|
||||
type: Number,
|
||||
default: 6
|
||||
},
|
||||
// 画笔达到最小宽度所需最小速度(px/ms),取值范围1.0-10.0,值越小,画笔越容易变细,笔锋效果会比较明显,可以自行调整查看效果,选出自己满意的值。
|
||||
minSpeed: {
|
||||
type: Number,
|
||||
default: 1.5
|
||||
},
|
||||
// 相邻两线宽度增(减)量最大百分比,取值范围1-100,为了达到笔锋效果,画笔宽度会随画笔速度而改变,如果相邻两线宽度差太大,过渡效果就会很突兀,使用maxWidthDiffRate限制宽度差,让过渡效果更自然。可以自行调整查看效果,选出自己满意的值。
|
||||
maxWidthDiffRate: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
// 限制历史记录数,即最大可撤销数,传入0则关闭历史记录功能
|
||||
maxHistoryLength: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
beforeDelay: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
landscape: {
|
||||
type: Boolean
|
||||
},
|
||||
boundingBox: {
|
||||
type: Boolean
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean
|
||||
},
|
||||
preferToDataURL: Boolean
|
||||
}
|
||||
228
uni_modules/jp-signature/components/jp-signature/render.js
Normal file
228
uni_modules/jp-signature/components/jp-signature/render.js
Normal file
@@ -0,0 +1,228 @@
|
||||
// #ifdef APP-VUE
|
||||
// import { Signature } from '@signature'
|
||||
import {
|
||||
Signature
|
||||
} from './signature.js'
|
||||
import {
|
||||
isTransparent
|
||||
} from './utils'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
canvasid: null,
|
||||
signature: null,
|
||||
observer: null,
|
||||
options: {},
|
||||
saveCount: 0,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(this.init)
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
const el = this.$refs.limeSignature || this.$ownerInstance.$el;
|
||||
this.canvas = document.createElement('canvas')
|
||||
this.canvas.style = 'width: 100%; height: 100%;'
|
||||
el.appendChild(this.canvas)
|
||||
this.signature = new Signature({
|
||||
el: this.canvas
|
||||
})
|
||||
this.signature.pen.setOption(this.options)
|
||||
const width = this.signature.canvas.get('width')
|
||||
const height = this.signature.canvas.get('height')
|
||||
|
||||
this.emit({
|
||||
changeSize: {
|
||||
width,
|
||||
height
|
||||
}
|
||||
})
|
||||
},
|
||||
redo(v) {
|
||||
if (v && this.signature) {
|
||||
this.signature.redo()
|
||||
}
|
||||
},
|
||||
undo(v) {
|
||||
if (v && this.signature) {
|
||||
this.signature.undo()
|
||||
}
|
||||
},
|
||||
clear(v) {
|
||||
if (v && this.signature) {
|
||||
this.signature.clear()
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
if (this.canvas) {
|
||||
this.canvas.remove()
|
||||
}
|
||||
},
|
||||
mask(param={}) {
|
||||
if (this.signature) {
|
||||
let {destWidth=0, destHeight=0} = JSON.parse(param)
|
||||
let canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d');
|
||||
const pixelRatio = this.signature.canvas.get('pixelRatio')
|
||||
let width = this.signature.canvas.get('width')
|
||||
let height = this.signature.canvas.get('height')
|
||||
let context = this.signature.canvas.get('context')
|
||||
canvas.width = width * pixelRatio
|
||||
canvas.height = height * pixelRatio
|
||||
|
||||
const imageData = context.getImageData(0, 0, width * pixelRatio, height * pixelRatio);
|
||||
for (let i = 0; i < imageData.data.length; i += 4) {
|
||||
// 判断当前像素是否透明
|
||||
const isTransparent = imageData.data[i + 3] === 0;
|
||||
|
||||
if (isTransparent) {
|
||||
// 将透明像素设置为黑色背景
|
||||
imageData.data[i] = 0;
|
||||
imageData.data[i + 1] = 0;
|
||||
imageData.data[i + 2] = 0;
|
||||
} else {
|
||||
// 将非透明像素设置为白色内容
|
||||
imageData.data[i] = 255;
|
||||
imageData.data[i + 1] = 255;
|
||||
imageData.data[i + 2] = 255;
|
||||
}
|
||||
}
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
if(destWidth&&destHeight){
|
||||
const _canvas = document.createElement('canvas')
|
||||
_canvas.width = destWidth
|
||||
_canvas.height = destHeight
|
||||
const _context = _canvas.getContext('2d')
|
||||
_context.drawImage(canvas, 0, 0, destWidth, destHeight)
|
||||
canvas.remove()
|
||||
canvas = _canvas
|
||||
}
|
||||
this.emit({
|
||||
save: canvas.toDataURL()
|
||||
})
|
||||
canvas.remove()
|
||||
}
|
||||
|
||||
},
|
||||
save(param) {
|
||||
let {
|
||||
fileType = 'png',
|
||||
quality = 1,
|
||||
n,
|
||||
destWidth = 0,
|
||||
destHeight = 0,
|
||||
} = JSON.parse(param)
|
||||
const type = `image/${fileType}`.replace(/jpg/, 'jpeg');
|
||||
if (n !== this.saveCount) {
|
||||
this.saveCount = n;
|
||||
const {
|
||||
backgroundColor,
|
||||
backgroundImage,
|
||||
landscape,
|
||||
boundingBox
|
||||
} = this.options
|
||||
const flag = landscape || backgroundColor || boundingBox||destWidth&&destHeight
|
||||
const image = this.signature.canvas.get('el').toDataURL(!flag && type, !flag && quality)
|
||||
if (flag) {
|
||||
let canvas = document.createElement('canvas')
|
||||
const pixelRatio = this.signature.canvas.get('pixelRatio')
|
||||
let width = this.signature.canvas.get('width')
|
||||
let height = this.signature.canvas.get('height')
|
||||
let x = 0
|
||||
let y = 0
|
||||
|
||||
const next = () => {
|
||||
const size = [width, height]
|
||||
if (landscape) {
|
||||
size.reverse()
|
||||
}
|
||||
canvas.width = size[0] * pixelRatio
|
||||
canvas.height = size[1] * pixelRatio
|
||||
const param = [x, y, width, height, 0, 0, width, height].map(item => item * pixelRatio)
|
||||
const context = canvas.getContext('2d')
|
||||
if (landscape) {
|
||||
context.translate(0, width * pixelRatio)
|
||||
context.rotate(-Math.PI / 2)
|
||||
}
|
||||
if (backgroundColor && !isTransparent(backgroundColor)) {
|
||||
context.fillStyle = backgroundColor
|
||||
context.fillRect(0, 0, width * pixelRatio, height * pixelRatio)
|
||||
}
|
||||
const drawImage = () => {
|
||||
// param
|
||||
context.drawImage(this.signature.canvas.get('el'), ...param)
|
||||
if(destWidth&&destHeight){
|
||||
const _canvas = document.createElement('canvas')
|
||||
_canvas.width = destWidth
|
||||
_canvas.height = destHeight
|
||||
const _context = _canvas.getContext('2d')
|
||||
_context.drawImage(canvas, 0, 0, destWidth, destHeight)
|
||||
canvas.remove()
|
||||
canvas = _canvas
|
||||
}
|
||||
this.emit({
|
||||
save: canvas.toDataURL(type, quality)
|
||||
})
|
||||
canvas.remove()
|
||||
}
|
||||
if (backgroundImage) {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
context.drawImage(img, ...param)
|
||||
drawImage()
|
||||
}
|
||||
img.src = backgroundImage
|
||||
}
|
||||
if (!backgroundImage) {
|
||||
drawImage()
|
||||
}
|
||||
}
|
||||
if (boundingBox) {
|
||||
const res = this.signature.getContentBoundingBox()
|
||||
width = res.width
|
||||
height = res.height
|
||||
x = res.startX
|
||||
y = res.startY
|
||||
next()
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
|
||||
} else {
|
||||
this.emit({
|
||||
save: image
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
isEmpty(v) {
|
||||
if (v && this.signature) {
|
||||
const isEmpty = this.signature.isEmpty()
|
||||
this.emit({
|
||||
isEmpty
|
||||
})
|
||||
}
|
||||
},
|
||||
emit(event) {
|
||||
this.$ownerInstance.callMethod('onMessage', {
|
||||
detail: {
|
||||
data: [{
|
||||
event
|
||||
}]
|
||||
}
|
||||
})
|
||||
},
|
||||
update(v) {
|
||||
if (v) {
|
||||
if (this.signature) {
|
||||
this.options = v
|
||||
this.signature.pen.setOption(v)
|
||||
} else {
|
||||
this.options = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
File diff suppressed because one or more lines are too long
165
uni_modules/jp-signature/components/jp-signature/signature.uts
Normal file
165
uni_modules/jp-signature/components/jp-signature/signature.uts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { LSignatureOptions, Point, Line } from '../../index.uts'
|
||||
|
||||
let points : Line = []
|
||||
let undoStack : Line[] = [];
|
||||
let redoStack : Line[] = [];
|
||||
let lastX = 0;
|
||||
let lastY = 0;
|
||||
|
||||
|
||||
export class Signature {
|
||||
el : UniElement
|
||||
options : LSignatureOptions = {
|
||||
penColor: 'black',
|
||||
openSmooth: true,
|
||||
disableScroll: true,
|
||||
disabled: false,
|
||||
penSize: 2,
|
||||
minLineWidth: 2,
|
||||
maxLineWidth: 6,
|
||||
minSpeed: 1.5,
|
||||
maxWidthDiffRate: 20,
|
||||
maxHistoryLength: 20
|
||||
} as LSignatureOptions
|
||||
ctx : DrawableContext
|
||||
isEmpty : boolean = true
|
||||
isDrawing : boolean = false
|
||||
// historyList : Point[][] = []
|
||||
// id : string
|
||||
// instance : ComponentPublicInstance
|
||||
touchstartCallbackWrapper: UniCallbackWrapper|null = null
|
||||
touchmoveCallbackWrapper: UniCallbackWrapper|null= null
|
||||
touchendCallbackWrapper: UniCallbackWrapper|null= null
|
||||
constructor(el : UniElement) {
|
||||
this.el = el
|
||||
this.ctx = el.getDrawableContext() as DrawableContext
|
||||
this.init()
|
||||
}
|
||||
init() {
|
||||
this.touchstartCallbackWrapper = this.el.addEventListener('touchstart', this.onTouchStart)
|
||||
this.touchmoveCallbackWrapper = this.el.addEventListener('touchmove', this.onTouchMove)
|
||||
this.touchendCallbackWrapper = this.el.addEventListener('touchend', this.onTouchEnd)
|
||||
}
|
||||
remove() {
|
||||
if(this.touchstartCallbackWrapper == null) return
|
||||
this.el.removeEventListener('touchstart', this.touchstartCallbackWrapper!)
|
||||
this.el.removeEventListener('touchmove', this.touchmoveCallbackWrapper!)
|
||||
this.el.removeEventListener('touchend', this.touchendCallbackWrapper!)
|
||||
}
|
||||
setOption(options : LSignatureOptions) {
|
||||
this.options = options
|
||||
}
|
||||
disableScroll(event : UniTouchEvent) {
|
||||
event.stopPropagation()
|
||||
if (this.options.disableScroll) {
|
||||
{
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
}
|
||||
getTouchPoint(event : UniTouchEvent) : Point {
|
||||
const rect = this.el.getBoundingClientRect()
|
||||
const touche = event.touches[0];
|
||||
const x = touche.clientX
|
||||
const y = touche.clientY
|
||||
// const force = touche.force
|
||||
return {
|
||||
x: x - rect.left,
|
||||
y: y - rect.top
|
||||
} as Point
|
||||
}
|
||||
onTouchStart: (event : UniTouchEvent) => void = (event : UniTouchEvent) =>{
|
||||
if (this.options.disabled) {
|
||||
return
|
||||
}
|
||||
this.disableScroll(event)
|
||||
const { x, y } = this.getTouchPoint(event)
|
||||
this.isDrawing = true;
|
||||
this.isEmpty = false
|
||||
lastX = x
|
||||
lastY = y
|
||||
points.push({ x, y } as Point);
|
||||
}
|
||||
onTouchMove: (event : UniTouchEvent) => void = (event : UniTouchEvent) =>{
|
||||
if (this.options.disabled || !this.isDrawing) {
|
||||
return
|
||||
}
|
||||
this.disableScroll(event)
|
||||
const { x, y } = this.getTouchPoint(event)
|
||||
const lineWidth = this.options.penSize
|
||||
const strokeStyle = this.options.penColor
|
||||
const point = { x, y } as Point
|
||||
const last = { x: lastX, y: lastY } as Point
|
||||
this.drawLine(point, last, lineWidth, strokeStyle)
|
||||
|
||||
lastX = x
|
||||
lastY = y
|
||||
points.push({ x, y, c: strokeStyle, w: lineWidth } as Point);
|
||||
}
|
||||
onTouchEnd: (event : UniTouchEvent) => void = (event : UniTouchEvent) =>{
|
||||
this.disableScroll(event)
|
||||
this.isDrawing = false;
|
||||
undoStack.push(points);
|
||||
redoStack = [] as Line[];
|
||||
points = [] as Point[];
|
||||
}
|
||||
drawLine(point : Point, last : Point, lineWidth : number, strokeStyle : string) {
|
||||
const ctx = this.ctx
|
||||
ctx.lineWidth = lineWidth
|
||||
ctx.strokeStyle = strokeStyle
|
||||
ctx.lineCap = 'round'
|
||||
ctx.lineJoin = 'round'
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(last.x, last.y)
|
||||
ctx.lineTo(point.x, point.y)
|
||||
ctx.stroke()
|
||||
ctx.update()
|
||||
}
|
||||
// addHistory() { }
|
||||
clear() {
|
||||
this.ctx.reset()
|
||||
this.ctx.update()
|
||||
this.isEmpty = true
|
||||
undoStack = [] as Line[];
|
||||
redoStack = [] as Line[];
|
||||
points = [] as Point[];
|
||||
}
|
||||
undo() {
|
||||
if(redoStack.length == this.options.maxHistoryLength && this.options.maxHistoryLength != 0){
|
||||
return
|
||||
}
|
||||
this.ctx.reset()
|
||||
if(undoStack.length > 0){
|
||||
const lastPath : Line = undoStack.pop()!;
|
||||
redoStack.push(lastPath);
|
||||
if(undoStack.length == 0){
|
||||
this.isEmpty = true
|
||||
this.ctx.update()
|
||||
return
|
||||
}
|
||||
for (let l = 0; l < undoStack.length; l++) {
|
||||
for (let i = 1; i < undoStack[l].length; i++) {
|
||||
const last = undoStack[l][i - 1]
|
||||
const point = undoStack[l][i]
|
||||
this.drawLine(point, last, point.w!, point.c!)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.ctx.update()
|
||||
}
|
||||
}
|
||||
redo() {
|
||||
if(redoStack.length < 1) return
|
||||
const lastPath : Line = redoStack.pop()!;
|
||||
undoStack.push(lastPath);
|
||||
this.isEmpty = false
|
||||
for (let l = 0; l < undoStack.length; l++) {
|
||||
for (let i = 1; i < undoStack[l].length; i++) {
|
||||
const last = undoStack[l][i - 1]
|
||||
const point = undoStack[l][i]
|
||||
this.drawLine(point, last, point.w!, point.c!)
|
||||
}
|
||||
}
|
||||
}
|
||||
// restore() { }
|
||||
}
|
||||
181
uni_modules/jp-signature/components/jp-signature/utils.js
Normal file
181
uni_modules/jp-signature/components/jp-signature/utils.js
Normal file
@@ -0,0 +1,181 @@
|
||||
export function compareVersion(v1, v2) {
|
||||
v1 = v1.split('.')
|
||||
v2 = v2.split('.')
|
||||
const len = Math.max(v1.length, v2.length)
|
||||
while (v1.length < len) {
|
||||
v1.push('0')
|
||||
}
|
||||
while (v2.length < len) {
|
||||
v2.push('0')
|
||||
}
|
||||
for (let i = 0; i < len; i++) {
|
||||
const num1 = parseInt(v1[i], 10)
|
||||
const num2 = parseInt(v2[i], 10)
|
||||
|
||||
if (num1 > num2) {
|
||||
return 1
|
||||
} else if (num1 < num2) {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function gte(version) {
|
||||
let { SDKVersion } = uni.getSystemInfoSync()
|
||||
// #ifdef MP-ALIPAY
|
||||
SDKVersion = my.SDKVersion
|
||||
// #endif
|
||||
return compareVersion(SDKVersion, version) >= 0;
|
||||
}
|
||||
|
||||
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
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
export const wrapEvent = (e) => {
|
||||
if (!e) return;
|
||||
if (!e.preventDefault) {
|
||||
e.preventDefault = function() {};
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
export const requestAnimationFrame = (cb) => {
|
||||
setTimeout(cb, 30)
|
||||
}
|
||||
|
||||
// #ifdef MP
|
||||
export const prefix = () => {
|
||||
// #ifdef MP-TOUTIAO
|
||||
return tt
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
return wx
|
||||
// #endif
|
||||
// #ifdef MP-BAIDU
|
||||
return swan
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
return my
|
||||
// #endif
|
||||
// #ifdef MP-QQ
|
||||
return qq
|
||||
// #endif
|
||||
// #ifdef MP-360
|
||||
return qh
|
||||
// #endif
|
||||
}
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* base64转路径
|
||||
* @param {Object} base64
|
||||
*/
|
||||
export function base64ToPath(base64) {
|
||||
const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef MP
|
||||
const p = prefix()
|
||||
const fs = p.getFileSystemManager()
|
||||
//自定义文件名
|
||||
if (!format) {
|
||||
reject(new Error('ERROR_BASE64SRC_PARSE'))
|
||||
}
|
||||
const time = new Date().getTime();
|
||||
const filePath = `${p.env.USER_DATA_PATH}/${time}.${format}`;
|
||||
fs.writeFile({
|
||||
filePath,
|
||||
data: base64.split(',')[1],
|
||||
encoding: 'base64',
|
||||
success() {
|
||||
resolve(filePath)
|
||||
},
|
||||
fail(err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
|
||||
bitmap.loadBase64Data(base64, () => {
|
||||
if (!format) {
|
||||
reject(new Error('ERROR_BASE64SRC_PARSE'))
|
||||
}
|
||||
const time = new Date().getTime();
|
||||
const filePath = `_doc/uniapp_temp/${time}.${format}`
|
||||
bitmap.save(filePath, {},
|
||||
() => {
|
||||
bitmap.clear()
|
||||
resolve(filePath)
|
||||
},
|
||||
(error) => {
|
||||
bitmap.clear()
|
||||
reject(error)
|
||||
})
|
||||
}, (error) => {
|
||||
bitmap.clear()
|
||||
reject(error)
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export function sleep(delay) {
|
||||
return new Promise(resolve => setTimeout(resolve, delay))
|
||||
}
|
||||
|
||||
export function getRect(selector, options = {}) {
|
||||
const typeDefault = 'boundingClientRect'
|
||||
const { context, type = typeDefault} = options
|
||||
return new Promise((resolve, reject) => {
|
||||
const dom = uni.createSelectorQuery().in(context).select(selector);
|
||||
const result = (rect) => {
|
||||
if(rect) {
|
||||
resolve(rect)
|
||||
} else {
|
||||
reject()
|
||||
}
|
||||
}
|
||||
if(type == typeDefault) {
|
||||
dom[type](result).exec()
|
||||
} else {
|
||||
dom[type]({
|
||||
node: true,
|
||||
size: true,
|
||||
rect: true
|
||||
}, result).exec()
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export function isTransparent(color) {
|
||||
// 判断颜色是否为 transparent
|
||||
if (color === 'transparent') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 判断颜色是否为 rgba 的 a 为 0
|
||||
if (color.startsWith('rgba')) {
|
||||
const regex = /\d+(\.\d+)?/g;
|
||||
const matches = color.match(regex);
|
||||
if (matches !== null) {
|
||||
const alpha = parseFloat(matches[3]);
|
||||
if (alpha === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
224
uni_modules/jp-signature/hybrid/html/index.html
Normal file
224
uni_modules/jp-signature/hybrid/html/index.html
Normal file
@@ -0,0 +1,224 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<title></title>
|
||||
<style type="text/css">
|
||||
html,
|
||||
body,
|
||||
canvas {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas id="lime-signature"></canvas>
|
||||
<script type="text/javascript" src="./uni.webview.1.5.3.js"></script>
|
||||
<script type="text/javascript" src="./signature.js"></script>
|
||||
<script>
|
||||
var signature = null;
|
||||
var timer = null;
|
||||
var isStart = false;
|
||||
var options = null
|
||||
console.log = function(...args) {
|
||||
postMessage(args);
|
||||
};
|
||||
// function stringify(key, value) {
|
||||
// if (typeof value === 'object' && value !== null) {
|
||||
// if (cache.indexOf(value) !== -1) {
|
||||
// return;
|
||||
// }
|
||||
// cache.push(value);
|
||||
// }
|
||||
// return value;
|
||||
// };
|
||||
function emit(event, data) {
|
||||
postMessage({
|
||||
event,
|
||||
data: typeof data !== "object" && data !== null ? data : JSON.stringify(data),
|
||||
});
|
||||
// cache = [];
|
||||
}
|
||||
|
||||
function postMessage(data) {
|
||||
uni.postMessage({
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
function update(v = {}) {
|
||||
if (signature) {
|
||||
options = v
|
||||
signature.pen.setOption(v);
|
||||
} else {
|
||||
signature = new Signature.Signature({el: "lime-signature"});
|
||||
canvasEl = signature.canvas.get("el");
|
||||
options = v
|
||||
signature.pen.setOption(v)
|
||||
const width = signature.canvas.get("width");
|
||||
const height = signature.canvas.get("height");
|
||||
|
||||
emit({changeSize: {width,height}})
|
||||
}
|
||||
}
|
||||
|
||||
function clear() {
|
||||
signature.clear()
|
||||
}
|
||||
|
||||
function undo() {
|
||||
signature.undo()
|
||||
}
|
||||
function redo() {
|
||||
signature.redo()
|
||||
}
|
||||
function isEmpty() {
|
||||
const isEmpty = signature.isEmpty()
|
||||
emit({isEmpty});
|
||||
}
|
||||
function isTransparent(color) {
|
||||
// 判断颜色是否为 transparent
|
||||
if (color === 'transparent') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 判断颜色是否为 rgba 的 a 为 0
|
||||
if (color.startsWith('rgba')) {
|
||||
const regex = /\d+(\.\d+)?/g;
|
||||
const matches = color.match(regex);
|
||||
if (matches !== null) {
|
||||
const alpha = parseFloat(matches[3]);
|
||||
if (alpha === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function mask(param){
|
||||
clearTimeout(timer);
|
||||
let {destWidth=0, destHeight=0} = param
|
||||
let width = this.signature.canvas.get('width')
|
||||
let height = this.signature.canvas.get('height')
|
||||
let canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d');
|
||||
const pixelRatio = signature.canvas.get('pixelRatio')
|
||||
canvas.width = width * pixelRatio
|
||||
canvas.height = height * pixelRatio
|
||||
|
||||
this.signature.pen.getMaskedImageData((imageData)=>{
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
if(destWidth&&destHeight){
|
||||
const _canvas = document.createElement('canvas')
|
||||
_canvas.width = destWidth
|
||||
_canvas.height = destHeight
|
||||
const _context = _canvas.getContext('2d')
|
||||
_context.drawImage(canvas, 0, 0, destWidth, destHeight)
|
||||
canvas.remove()
|
||||
canvas = _canvas
|
||||
}
|
||||
const path = canvas.toDataURL();
|
||||
canvas.remove()
|
||||
if (typeof path == "string") {
|
||||
const index = Math.ceil(path.length / 8);
|
||||
for (var i = 0; i < 8; i++) {
|
||||
if (i == 7) {
|
||||
emit({"success": path.substr(i * index, index)});
|
||||
} else {
|
||||
emit({"file": path.substr(i * index, index)});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error("canvas no data");
|
||||
emit({"fail": "canvas no data"});
|
||||
}
|
||||
})
|
||||
}
|
||||
function save(param) {
|
||||
// delete param.success;
|
||||
// delete param.fail;
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
let {fileType = 'png', quality = 1, n, destWidth=0, destHeight=0} = param
|
||||
const type = `image/${fileType}`.replace(/jpg/, 'jpeg');
|
||||
const {backgroundColor, landscape, boundingBox} = options
|
||||
const flag = backgroundColor || landscape || boundingBox||destWidth&&destHeight
|
||||
let path = canvasEl.toDataURL(!flag && type, !flag && quality)
|
||||
if(flag) {
|
||||
let canvas = document.createElement('canvas')
|
||||
const pixelRatio = signature.canvas.get('pixelRatio')
|
||||
let width = signature.canvas.get('width')
|
||||
let height = signature.canvas.get('height')
|
||||
let x = 0
|
||||
let y = 0
|
||||
|
||||
const next = () => {
|
||||
const size = [width, height]
|
||||
if(landscape) {size.reverse()}
|
||||
canvas.width = size[0] * pixelRatio
|
||||
canvas.height = size[1] * pixelRatio
|
||||
const param = [x, y, width, height, 0 , 0, width, height].map(item => item * pixelRatio)
|
||||
const context = canvas.getContext('2d')
|
||||
// context.scale(pixelRatio, pixelRatio)
|
||||
if (landscape) {
|
||||
context.translate(0, width * pixelRatio)
|
||||
context.rotate(-Math.PI / 2)
|
||||
}
|
||||
if (backgroundColor && !isTransparent(backgroundColor)) {
|
||||
context.fillStyle = backgroundColor
|
||||
context.fillRect(0, 0, width * pixelRatio, height * pixelRatio)
|
||||
}
|
||||
const drawImage = ()=>{
|
||||
|
||||
}
|
||||
// param
|
||||
context.drawImage(signature.canvas.get('el'), ...param)
|
||||
if(destWidth&&destHeight){
|
||||
const _canvas = document.createElement('canvas')
|
||||
_canvas.width = destWidth
|
||||
_canvas.height = destHeight
|
||||
const _context = _canvas.getContext('2d')
|
||||
_context.drawImage(canvas, 0, 0, destWidth, destHeight)
|
||||
canvas.remove()
|
||||
canvas = _canvas
|
||||
}
|
||||
path = canvas.toDataURL(type, quality)
|
||||
canvas.remove()
|
||||
}
|
||||
if(boundingBox) {
|
||||
const res = signature.getContentBoundingBox()
|
||||
width = res.width
|
||||
height = res.height
|
||||
x = res.startX
|
||||
y = res.startY
|
||||
next()
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
if (typeof path == "string") {
|
||||
const index = Math.ceil(path.length / 8);
|
||||
for (var i = 0; i < 8; i++) {
|
||||
if (i == 7) {
|
||||
emit({"success": path.substr(i * index, index)});
|
||||
} else {
|
||||
emit({"file": path.substr(i * index, index)});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error("canvas no data");
|
||||
emit({"fail": "canvas no data"});
|
||||
}
|
||||
}, 30);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1
uni_modules/jp-signature/hybrid/html/signature.js
Normal file
1
uni_modules/jp-signature/hybrid/html/signature.js
Normal file
File diff suppressed because one or more lines are too long
78
uni_modules/jp-signature/package.json
Normal file
78
uni_modules/jp-signature/package.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"id": "jp-signature",
|
||||
"displayName": "手写签名组件,弹框签名,可配置签名,签名返回base64,签名专用,手写一键使用 文档签字",
|
||||
"version": "3.2.0",
|
||||
"description": "用于手写签名,同时内置了弹框签名组件及文档上签字组件,对于不想布局的同学来说可以开箱即用。",
|
||||
"keywords": [
|
||||
"手写签名",
|
||||
"弹框签名",
|
||||
"手写一键使用",
|
||||
"文档签字",
|
||||
"小白专用签名"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.7.8"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y",
|
||||
"alipay": "n"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "n",
|
||||
"IE": "n",
|
||||
"Edge": "n",
|
||||
"Firefox": "n",
|
||||
"Safari": "n"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "u",
|
||||
"百度": "u",
|
||||
"字节跳动": "u",
|
||||
"QQ": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
214
uni_modules/jp-signature/readme.md
Normal file
214
uni_modules/jp-signature/readme.md
Normal file
@@ -0,0 +1,214 @@
|
||||
## jp-signature 、jp-signature-popup、 jp-merge 写字板
|
||||
### jp-signature 写字板,可用业务签名等场景,方便用户自行改造
|
||||
### jp-signature-popup 小白专用弹框签名组件,方便小白开发使用,和输入框一样使用简单
|
||||
### jp-merge 图片签字组件,提供用户在图片文档上进行签字
|
||||
|
||||
# 有合作需求请私
|
||||
## 开发不易,如果帮助到你的,请支持 有问题请留言,作者会积极更新
|
||||
|
||||
## 平台兼容
|
||||
| H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ 小程序 | App |
|
||||
| --- | ---------- | ------------ | ---------- | ---------- | --------- | --- |
|
||||
| √ | √ | √ | 未测 | 未测 | 未测 | √ |
|
||||
|
||||
|
||||
## 代码演示
|
||||
|
||||
### jp-signature 基本用法
|
||||
```html
|
||||
<view style="width: 750rpx ;height: 500rpx;">
|
||||
<jp-signature ref="signatureRef" ></jp-signature>
|
||||
</view>
|
||||
<view>
|
||||
<button @click="clear">清空</button>
|
||||
<button @click="">撤消</button>
|
||||
<button @click="save">保存</button>
|
||||
</view>
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
url: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
save(){
|
||||
this.$refs.signatureRef.canvasToTempFilePath({
|
||||
success: (res) => {
|
||||
// 是否为空画板 无签名
|
||||
console.log(res.isEmpty)
|
||||
// 生成图片的临时路径
|
||||
// H5 生成的是base64
|
||||
this.url = res.tempFilePath
|
||||
}
|
||||
})
|
||||
},
|
||||
clear(){
|
||||
this.$refs.signatureRef.clear()
|
||||
},
|
||||
undo(){
|
||||
this.$refs.signatureRef.undo()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## API
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| -------------- | ------------ | ---------------- | ------------ |
|
||||
| penSize | 画笔大小 | <em>number</em> | `2` |
|
||||
| minLineWidth | 线条最小宽 | <em>number</em> | `2` |
|
||||
| maxLineWidth | 线条最大宽 | <em>number</em> | `6` |
|
||||
| penColor | 画笔颜色 | <em>string</em> | `black` |
|
||||
| backgroundColor | 背景颜色 | <em>string</em> | `` |
|
||||
| type | 指定 canvas 类型 | <em>string</em> | `2d` |
|
||||
| openSmooth | 是否模拟压感 | <em>boolean</em> | `false` |
|
||||
| beforeDelay | 延时初始化,在放在弹窗里可以使用 (毫秒) | <em>number</em> | `0` |
|
||||
| maxHistoryLength | 限制历史记录数,即最大可撤销数,传入0则关闭历史记录功能 | <em>boolean</em> | `20` |
|
||||
| landscape | 横屏 | <em>boolean</em> | `` |
|
||||
| disableScroll | 当在写字时,禁止屏幕滚动以及下拉刷新 | <em>boolean</em> | `true` |
|
||||
| boundingBox | 只生成内容区域,即未画部分不生成,有性能的损耗(微信小程序pc不支持) | <em>boolean</em> | `false` |
|
||||
|
||||
|
||||
### 事件 Events
|
||||
|
||||
| 事件名 | 说明 | 回调 |
|
||||
| ------- | ------------ | -------------- |
|
||||
| undo | 撤消,回退到上一步 | |
|
||||
| clear | 清空,清空画板 | |
|
||||
| canvasToTempFilePath | 保存,生成图片,与官方保持一致,但不需要传canvasId | |
|
||||
|
||||
### 常见问题
|
||||
- 放在弹窗里时,尺寸不对 可以延时手写板出现时机,给手写板加vif或beforeDelay="300"
|
||||
- boundingBox 微信小程序 pc 不支持, 因为获取不到 ImageData 数据
|
||||
|
||||
|
||||
|
||||
## jp-signature-popup 基础用法
|
||||
```html
|
||||
<template>
|
||||
<view class="content">
|
||||
<!-- #ifdef VUE2 -->
|
||||
<jp-signature-popup v-model="title" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef VUE3 -->
|
||||
<jp-signature-popup v-model:value="title" />
|
||||
<!-- #endif -->
|
||||
{{title}}
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
title: ''
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
####参数
|
||||
| 参数名 | 类型 | 默认值 | 说明 |
|
||||
| -------- | -------- | --------| --------|
|
||||
| value | String | | 签名内容,可以通过v-model和v-model:value绑定 |
|
||||
| label | String | 手写签名 | |
|
||||
| popup | Boolean | false | 是否隐藏原有样式,该模式只使用弹框 |
|
||||
| required | Boolean | false | |
|
||||
| placeholder | String | 点击签名 | 签名说明 |
|
||||
| readonly | Boolean | false | 是否只能可读 |
|
||||
| openSmooth | Boolean | false | 是否开启签名笔锋 |
|
||||
| boundingBox | boolean| false | 只生成内容区域,即未画部分不生成,有性能的损耗(微信小程序不支持) |
|
||||
|
||||
|
||||
####方法
|
||||
| 方法名 | 返回参数 | 说明 |
|
||||
| -------- | -------- | --------|
|
||||
| toPop | | 打开弹窗 |
|
||||
| close | | 关闭弹窗 |
|
||||
| deleteImg | | 删除内容 |
|
||||
|
||||
####事假
|
||||
| 事件名 | 返回参数 | 说明 |
|
||||
| -------- | -------- | --------|
|
||||
| input | 签名内容 | 签名内容 |
|
||||
| change | 签名内容 | 签名内容改变后触发 |
|
||||
| toImg | 图片编码 | 点击图片时触发 |
|
||||
|
||||
|
||||
## jp-merge 基础用法
|
||||
```html
|
||||
<template>
|
||||
<view class="content">
|
||||
<view>下面是使用 jp-signature-popup 结合 jp-merge 在文档上签字</view>
|
||||
<view style="text-align: center;padding-bottom: 150px;">
|
||||
<image :src="image4" v-if="image4" style="width: 350px;height: 350px;border: 1px solid #ccc;"></image>
|
||||
<image src="../../static/sqs.jpg" v-else style="width: 350px;height: 350px;border: 1px solid #ccc;"></image>
|
||||
<view class="but" style="margin: 0 25px;" @click="toPop">我要在上面签字</view>
|
||||
<jp-signature-popup ref="signature" @change="setImg" popup v-model:value="image3" />
|
||||
<jp-merge bgImage="../../static/sqs.jpg" ref="jpMerge"></jp-merge>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
image3:'',
|
||||
image4:''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setImg(val){
|
||||
if(val){
|
||||
<!-- 生成签字结果的方法可以传入网络及本地图片 -->
|
||||
this.$refs.jpMerge.exportPost(val).then(res => {
|
||||
this.image4 = res
|
||||
})
|
||||
}
|
||||
},
|
||||
toPop(){
|
||||
this.$refs.signature.toPop()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.but{
|
||||
margin: 25px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
background-color: #55aaff;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
####参数
|
||||
| 参数名 | 类型 | 默认值 | 说明 |
|
||||
| -------- | -------- | --------| --------|
|
||||
| bgImage | String | | 文档图片地址,支持本地及线上图片(小程序如果是网络图片需要配置白名单) |
|
||||
| canvasWidth | number | 400 | 生成图片的最终宽,建议长高和文档长高一致 |
|
||||
| canvasHeight | number | 400 | 生成图片的最终高,建议长高和文档长高一致 |
|
||||
| width | number | 80 | 签字图片宽 |
|
||||
| height | number | 80 | 签字图片高 |
|
||||
| left | number | 80 | 签字图片距离左边位置 |
|
||||
| top | number | 80 | 签字图片距离顶边位置 |
|
||||
|
||||
|
||||
####方法
|
||||
| 方法名 | 返回参数 | 说明 |
|
||||
| -------- | -------- | --------|
|
||||
| exportPost | | 传入签字图片生成最终结果 |
|
||||
|
||||
|
||||
|
||||
### 常见问题
|
||||
- 在vue2和vue3中使用v-model有区别,vue2中为v-model,vue3为v-model:value
|
||||
- 使用实例已放在 uni_modules/jp-signature/pages 中,可复制后测试
|
||||
|
||||
|
||||
|
||||
70
uni_modules/jp-signature/实例/index.vue
Normal file
70
uni_modules/jp-signature/实例/index.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<view class="h100">
|
||||
<!-- #ifdef VUE2 -->
|
||||
<jp-signature-popup v-model="image" required />
|
||||
<view>{{image}}</view>
|
||||
<view class="but" @click="toPop1">自定义样式,弹框调用</view>
|
||||
<jp-signature-popup ref="signature1" popup v-model="image2" />
|
||||
<image :src="image2" style="width: 200px;" mode="widthFix"></image>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef VUE3 -->
|
||||
<jp-signature-popup v-model:value="image" required />
|
||||
<view>{{image}}</view>
|
||||
<view class="but" @click="toPop1">自定义样式,弹框调用</view>
|
||||
<jp-signature-popup ref="signature1" popup v-model:value="image2" />
|
||||
<image :src="image2" style="width: 200px;" mode="widthFix"></image>
|
||||
<!-- #endif -->
|
||||
{{image2}}
|
||||
|
||||
|
||||
<view>下面是使用 jp-signature-popup 结合 jp-merge 在文档上签字</view>
|
||||
<view style="text-align: center;padding-bottom: 150px;">
|
||||
<image :src="image4" v-if="image4" style="width: 350px;height: 350px;border: 1px solid #ccc;"></image>
|
||||
<image src="../../static/sqs.jpg" v-else style="width: 350px;height: 350px;border: 1px solid #ccc;"></image>
|
||||
<view class="but" style="margin: 0 25px;" @click="toPop2">我要在上面签字</view>
|
||||
<jp-signature-popup ref="signature2" @change="setImg" popup v-model:value="image3" />
|
||||
<jp-merge bgImage="../../static/sqs.jpg" ref="jpMerge"></jp-merge>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 有项目需要开发的请联系 扣 - 371524845 -->
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
image:'',
|
||||
image2:'',
|
||||
image3:'',
|
||||
image4:''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setImg(val){
|
||||
if(val){
|
||||
this.$refs.jpMerge.exportPost(val).then(res => {
|
||||
this.image4 = res
|
||||
})
|
||||
}
|
||||
},
|
||||
toPop1(){
|
||||
this.$refs.signature1.toPop()
|
||||
},
|
||||
toPop2(){
|
||||
this.$refs.signature2.toPop()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.but{
|
||||
margin: 25px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
background-color: #55aaff;
|
||||
color: #fff;
|
||||
}
|
||||
.sssv{
|
||||
height: 1150px;
|
||||
}
|
||||
</style>
|
||||
108
uni_modules/jp-signature/实例/微信小程序签名到指定位置.vue
Normal file
108
uni_modules/jp-signature/实例/微信小程序签名到指定位置.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<view class="h100">
|
||||
<view style="text-align: center;padding-bottom: 150px;">
|
||||
<image :src="image" v-if="image" style="width: 200px;height: 200px;border: 1px solid #ccc;"></image>
|
||||
<image :src="imgs" v-else style="width: 200px;height: 200px;border: 1px solid #ccc;"></image>
|
||||
<view class="but" style="margin: 0 25px;" @click="toPop">我要在上面签字</view>
|
||||
<jp-signature-popup ref="signature2" @change="setImg" popup />
|
||||
<canvas canvas-id="shareCanvas" class="canvas" bindlongpress="saveImg" catchtouchmove="true"
|
||||
style="position:fixed;left:500%"
|
||||
:style="{height: canvasHeight+'px',width:canvasWidth+'px'}">
|
||||
</canvas>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 有项目需要开发的请联系 扣 - 371524845 -->
|
||||
<!--
|
||||
注意:需要采用线上图片且需要配置白名单,未配置手机无法签名,采用真机调试2.0不配置白名单也可以签名,正式版本需要线上图片且需要配置白名单
|
||||
-->
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
canvasHeight: 400,
|
||||
canvasWidth: 400,
|
||||
width:80,
|
||||
height: 50,
|
||||
left: 20,
|
||||
top: 20,
|
||||
ctx:null,
|
||||
image: '',
|
||||
imgs: 'http://mmbiz.qpic.cn/sz_mmbiz_jpg/GEWVeJPFkSGTfkSpSbg9cHUqcibBv38r8GXDIVy4W6FN7a1TMWf6RSNQLemKBwG8VqjlxUhicIzz3NTONVrD96ibg/0?wx_fmt=jpeg'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
//初始化画布
|
||||
this.ctx = wx.createCanvasContext('shareCanvas', this)
|
||||
},
|
||||
methods: {
|
||||
setImg(val) {
|
||||
if (val) {
|
||||
this.exportPost(val).then(res => {
|
||||
this.image = res
|
||||
})
|
||||
}
|
||||
},
|
||||
toPop() {
|
||||
this.$refs.signature2.toPop()
|
||||
},
|
||||
getImageInfo(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.getImageInfo({
|
||||
src,
|
||||
success: (res) => resolve(res),
|
||||
fail: (res) => reject(res)
|
||||
})
|
||||
});
|
||||
},
|
||||
exportPost(image2){
|
||||
let that = this
|
||||
return new Promise(function (resolve, reject) {
|
||||
let image = that.imgs
|
||||
//获取系统的基本信息,为后期的画布和底图适配宽高
|
||||
uni.getSystemInfo({
|
||||
success: function (res) {
|
||||
Promise.all([that.getImageInfo(image),that.getImageInfo(image2)]).then(res=>{
|
||||
//获取底图和二维码图片的基本信息,通常前端导出的二维码是base64格式的,所以要转成图片格式的才可以获取图片的基本信息
|
||||
that.ctx.drawImage(res[0].path,0 , 0,that.canvasWidth,that.canvasHeight);
|
||||
that.ctx.drawImage(res[1].path,that.left,that.top,that.width, that.height);
|
||||
that.ctx.draw(false,function(){
|
||||
wx.canvasToTempFilePath({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width:that.canvasWidth,
|
||||
height:that.canvasHeight,
|
||||
destWidth:that.canvasWidth*2,//这里乘以2是为了保证合成图片的清晰度
|
||||
destHeight:that.canvasHeight*2,
|
||||
canvasId: 'shareCanvas',
|
||||
fileType:'jpg',//设置导出图片的后缀名
|
||||
success: function (res) {
|
||||
resolve(res.tempFilePath)
|
||||
},
|
||||
fail: function (res) {
|
||||
reject(res)
|
||||
},
|
||||
})
|
||||
});
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.but {
|
||||
margin: 25px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
background-color: #55aaff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sssv {
|
||||
height: 1150px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,225 +0,0 @@
|
||||
## 1.9.6.6(2024-09-25)
|
||||
- fix: 修复background-position无效的问题
|
||||
## 1.9.6.5(2024-04-14)
|
||||
- fix: 修复`nvue`无法生图的问题
|
||||
## 1.9.6.4(2024-03-10)
|
||||
- fix: 修复代理ctx导致H5不能使用ctx.save
|
||||
## 1.9.6.3(2024-03-08)
|
||||
- fix: 修复支付宝真机无法使用的问题
|
||||
## 1.9.6.2(2024-02-22)
|
||||
- fix: 修复使用render函数报错的问题
|
||||
## 1.9.6.1(2023-12-22)
|
||||
- fix: 修复字节小程序非2d字体偏移
|
||||
- fix: 修复`canvasToTempFilePathSync`会触发两次的问题
|
||||
- fix: 修复`parser`图片没有宽度的问题
|
||||
## 1.9.6(2023-12-06)
|
||||
- fix: 修复背景图受padding影响
|
||||
- fix: 修复因字节报错改了代理实现导致微信报错
|
||||
- 1.9.5.8(2023-11-16)
|
||||
- fix: 修复margin问题
|
||||
- fix: 修复borderWidth问题
|
||||
- fix: 修复textBox问题
|
||||
- fix: 修复字节开发工具报`could not be cloned.`问题
|
||||
## 1.9.5.7(2023-07-27)
|
||||
- fix: 去掉多余的方法
|
||||
- chore: 更新文档,增加自定义字体说明
|
||||
## 1.9.5.6(2023-07-21)
|
||||
- feat: 有限的支持富文本
|
||||
- feat: H5和APP 增加 `hidpi` prop,主要用于大尺寸无法生成图片时用
|
||||
- fix: 修复 钉钉小程序 缺少 `measureText` 方法
|
||||
- chore: 由于微信小程序 pc 端的 canvas 2d 时不时抽风,故不使用canvas 2d
|
||||
## 1.9.5.5(2023-06-27)
|
||||
- fix: 修复把`emoji`表情字符拆分成多个字符的情况
|
||||
## 1.9.5.4(2023-06-05)
|
||||
- fix: 修复因`canvasToTempFilePathSync`监听导致重复调用
|
||||
## 1.9.5.3(2023-05-23)
|
||||
- fix: 因isPc错写成了isPC导致小程序PC不能生成图片
|
||||
## 1.9.5.2(2023-05-22)
|
||||
- feat: 删除多余文件
|
||||
## 1.9.5.1(2023-05-22)
|
||||
- fix: 修复 文字行数与`line-clamp`相同但不满一行时也加了省略号的问题
|
||||
## 1.9.5(2023-05-14)
|
||||
- feat: 增加 `text-indent` 和 `calc` 方法
|
||||
- feat: 优化 布局时间
|
||||
## 1.9.4.4(2023-04-15)
|
||||
- fix: 修复无法匹配负值
|
||||
- fix: 修复 Nvue IOS getImageInfo `useCORS` 为 undefined
|
||||
## 1.9.4.3(2023-04-01)
|
||||
- feat: 增加支持文字描边 `text-stroke: '5rpx #fff'`
|
||||
## 1.9.4.2(2023-03-30)
|
||||
- fix: 修复 支付宝小程序 isPC 在手机也为true的问题
|
||||
- feat: 由 微信开发工具 3060 版 无法获取图片尺寸,现 微信开发工具 3220 版 修复该问题,故还原上一版的获取图片方式。
|
||||
## 1.9.4.1(2023-03-28)
|
||||
- fix: 修复固定高度不正确问题
|
||||
## 1.9.4(2023-03-17)
|
||||
- fix: nvue ios getImageInfo缺少this报错
|
||||
- fix: pathType 非2d无效问题
|
||||
- fix: 修复 小米9se 可能会存在多次init 导致画面多次放大
|
||||
- fix: 修复 border 分开写 width style无效问题
|
||||
- fix: 修复 支付宝小程序IOS 再次进入不渲染的问题
|
||||
- fix: 修复 支付宝小程序安卓Zindex排序错乱问题
|
||||
- fix: 修复 微信开发工具 3060 版 无法获取图片的问题
|
||||
- feat: 把 for in 改为 forEach
|
||||
- feat: 增加 hidden
|
||||
- feat: 根节点 box-sizing 默认 `border-box`
|
||||
- feat: 增加支持 `vw` `wh`
|
||||
- chore: pathType 取消 默认值,因为字节开发工具不能显示
|
||||
- chore: 支付宝小程序开发工具不支持 生成图片 请以真机调试为准
|
||||
- bug: 企业微信 2.20.3无法使用
|
||||
## 1.9.3.5(2022-06-29)
|
||||
- feat: justifyContent 增加 `space-around`、`space-between`
|
||||
- feat: canvas 2d 也使用`getImageInfo`
|
||||
- fix: 修复 `text`的 `text-decoration`错位
|
||||
## 1.9.3.4(2022-06-20)
|
||||
- fix: 修复 因创建节点速度问题导致顺序出错。
|
||||
- fix: 修复 微信小程序 PC 无法显示本地图片
|
||||
- fix: 修复 flex-box 对齐问题
|
||||
- feat: 增加 `text-shadow`
|
||||
- feat: 重写 `text` 对齐方式
|
||||
- chore: 更新文档
|
||||
## 1.9.3.3(2022-06-17)
|
||||
- fix: 修复 支付宝小程序 canvas 2d 存在ctx.draw问题导致报错
|
||||
- fix: 修复 支付宝小程序 toDataURL 存在权限问题改用 `toTempFilePath`
|
||||
- fix: 修复 支付宝小程序 image size 问题导致 `objectFit` 无效
|
||||
## 1.9.3.2(2022-06-14)
|
||||
- fix: 修复 image 设置背景色不生效问题
|
||||
- fix: 修复 nvue 环境判断缺少参数问题
|
||||
## 1.9.3.1(2022-06-14)
|
||||
- fix: 修复 bottom 定位不对问题
|
||||
- fix: 修复 因小数导致计算出错换行问题
|
||||
- feat: 增加 `useCORS` h5端图片跨域 在设置请求头无效果后试一下设置这个值
|
||||
- chore: 更新文档
|
||||
## 1.9.3(2022-06-13)
|
||||
- feat: 增加 `zIndex`
|
||||
- feat: 增加 `flex-box` 该功能处于原始阶段,非常简陋。
|
||||
- tips: QQ小程序 vue3 不支持, 为 uni 官方BUG
|
||||
## 1.9.2.9(2022-06-10)
|
||||
- fix: 修复`text-align`及`margin`居中问题
|
||||
## 1.9.2.8(2022-06-10)
|
||||
- fix: 修复 Nvue `canvasToTempFilePathSync` 不生效问题
|
||||
## 1.9.2.7(2022-06-10)
|
||||
- fix: 修复 margin及padding的bug
|
||||
- fix: 修复 Nvue `isCanvasToTempFilePath` 不生效问题
|
||||
## 1.9.2.6(2022-06-09)
|
||||
- fix: 修复 Nvue 不显示
|
||||
- feat: 增加支持字体渐变
|
||||
```html
|
||||
<l-painter-text
|
||||
text="水调歌头\n明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。"
|
||||
css="background: linear-gradient(,#ff971b 0%, #1989fa 100%); background-clip: text" />
|
||||
```
|
||||
## 1.9.2.5(2022-06-09)
|
||||
- chore: 更变获取父级宽度的设定
|
||||
- chore: `pathType` 在canvas 2d 默认为 `url`
|
||||
## 1.9.2.4(2022-06-08)
|
||||
- fix: 修复 `pathType` 不生效问题
|
||||
## 1.9.2.3(2022-06-08)
|
||||
- fix: 修复 `canvasToTempFilePath` 漏写 `success` 参数
|
||||
## 1.9.2.2(2022-06-07)
|
||||
- chore: 更新文档
|
||||
## 1.9.2.1(2022-06-07)
|
||||
- fix: 修复 vue3 赋值给this再传入导致image无法绘制
|
||||
- fix: 修复 `canvasToTempFilePathSync` 时机问题
|
||||
- feat: canvas 2d 更改图片生成方式 `toDataURL`
|
||||
## 1.9.2(2022-05-30)
|
||||
- fix: 修复 `canvasToTempFilePathSync` 在 vue3 下只生成一次
|
||||
## 1.9.1.7(2022-05-28)
|
||||
- fix: 修复 `qrcode`显示不全问题
|
||||
## 1.9.1.6(2022-05-28)
|
||||
- fix: 修复 `canvasToTempFilePathSync` 会重复多次问题
|
||||
- fix: 修复 `view` css `backgroundImage` 图片下载失败导致 子节点不渲染
|
||||
## 1.9.1.5(2022-05-27)
|
||||
- fix: 修正支付宝小程序 canvas 2d版本号 2.7.15
|
||||
## 1.9.1.4(2022-05-22)
|
||||
- fix: 修复字节小程序无法使用xml方式
|
||||
- fix: 修复字节小程序无法使用base64(非2D情况下工具上无法显示)
|
||||
- fix: 修复支付宝小程序 `canvasToTempFilePath` 报错
|
||||
## 1.9.1.3(2022-04-29)
|
||||
- fix: 修复vue3打包后uni对象为空后的报错
|
||||
## 1.9.1.2(2022-04-25)
|
||||
- fix: 删除多余文件
|
||||
## 1.9.1.1(2022-04-25)
|
||||
- fix: 修复图片不显示问题
|
||||
## 1.9.1(2022-04-12)
|
||||
- fix: 因四舍五入导致有些机型错位
|
||||
- fix: 修复无views报错
|
||||
- chore: nvue下因ios无法读取插件内static文件,改由下载方式
|
||||
## 1.9.0(2022-03-20)
|
||||
- fix: 因无法固定尺寸导致生成图片不全
|
||||
- fix: 特定情况下text判断无效
|
||||
- chore: 本地化APP Nvue webview
|
||||
## 1.8.9(2022-02-20)
|
||||
- fix: 修复 小程序下载最多10次并发的问题
|
||||
- fix: 修复 APP端无法获取本地图片
|
||||
- fix: 修复 APP Nvue端不执行问题
|
||||
- chore: 增加图片缓存机制
|
||||
## 1.8.8.8(2022-01-27)
|
||||
- fix: 修复 主动调用尺寸问题
|
||||
## 1.8.8.6(2022-01-26)
|
||||
- fix: 修复 nvue 下无宽度时获取父级宽度
|
||||
- fix: 修复 ios app 无法渲染问题
|
||||
## 1.8.8(2022-01-23)
|
||||
- fix: 修复 主动调用时无节点问题
|
||||
- fix: 修复 `box-shadow` 颜色问题
|
||||
- fix: 修复 `transform:rotate` 角度位置问题
|
||||
- feat: 增加 `overflow:hidden`
|
||||
## 1.8.7(2022-01-07)
|
||||
- fix: 修复 image 方向为 `right` 时原始宽高问题
|
||||
- feat: 支持 view 设置背景图 `background-image: url(xxx)`
|
||||
- chore: 去掉可选链
|
||||
## 1.8.6(2021-11-28)
|
||||
- feat: 支持`view`对`inline-block`的子集使用`text-align`
|
||||
## 1.8.5.5(2021-08-17)
|
||||
- chore: 更新文档,删除 replace
|
||||
- fix: 修复 text 值为 number时报错
|
||||
## 1.8.5.4(2021-08-16)
|
||||
- fix: 字节小程序兼容
|
||||
## 1.8.5.3(2021-08-15)
|
||||
- fix: 修复线性渐变与css现实效果不一致的问题
|
||||
- chore: 更新文档
|
||||
## 1.8.5.2(2021-08-13)
|
||||
- chore: 增加`background-image`、`background-repeat` 能力,主要用于背景纹理的绘制,并不是代替`image`。例如:大面积的重复平铺的水印
|
||||
- 注意:这个功能H5暂时无法使用,因为[官方的API有BUG](https://ask.dcloud.net.cn/question/128793),待官方修复!!!
|
||||
## 1.8.5.1(2021-08-10)
|
||||
- fix: 修复因`margin`报错问题
|
||||
## 1.8.5(2021-08-09)
|
||||
- chore: 增加margin支持`auto`,以达到居中效果
|
||||
## 1.8.4(2021-08-06)
|
||||
- chore: 增加判断缓存文件条件
|
||||
- fix: 修复css 多余空格报错问题
|
||||
## 1.8.3(2021-08-04)
|
||||
- tips: 1.6.x 以下的版本升级到1.8.x后要为每个元素都加上定位:position: 'absolute'
|
||||
- fix: 修复只有一个view子元素时不计算高度的问题
|
||||
## 1.8.2(2021-08-03)
|
||||
- fix: 修复 path-type 为 `url` 无效问题
|
||||
- fix: 修复 qrcode `text` 为空时报错问题
|
||||
- fix: 修复 image `src` 动态设置时不生效问题
|
||||
- feat: 增加 css 属性 `min-width` `max-width`
|
||||
## 1.8.1(2021-08-02)
|
||||
- fix: 修复无法加载本地图片
|
||||
## 1.8.0(2021-08-02)
|
||||
- chore 文档更新
|
||||
- 使用旧版的同学不要升级!
|
||||
## 1.8.0-beta(2021-07-30)
|
||||
- ## 全新布局方式 不兼容旧版!
|
||||
- chore: 布局方式变更
|
||||
- tips: 微信canvas 2d 不支持真机调试
|
||||
## 1.6.6(2021-07-09)
|
||||
- chore: 统一命名规范,无须主动引入组件
|
||||
## 1.6.5(2021-06-08)
|
||||
- chore: 去掉console
|
||||
## 1.6.4(2021-06-07)
|
||||
- fix: 修复 数字 为纯字符串时不转换的BUG
|
||||
## 1.6.3(2021-06-06)
|
||||
- fix: 修复 PC 端放大的BUG
|
||||
## 1.6.2(2021-05-31)
|
||||
- fix: 修复 报`adaptor is not a function`错误
|
||||
- fix: 修复 text 多行高度
|
||||
- fix: 优化 默认文字的基准线
|
||||
- feat: `@progress`事件,监听绘制进度
|
||||
## 1.6.1(2021-02-28)
|
||||
- 删除多余节点
|
||||
## 1.6.0(2021-02-26)
|
||||
- 调整为uni_modules目录规范
|
||||
- 修复:transform的rotate不能为负数问题
|
||||
- 新增:`pathType` 指定生成图片返回的路径类型,可选值有 `base64`、`url`
|
||||
@@ -1,150 +0,0 @@
|
||||
const styles = (v ='') => v.split(';').filter(v => v && !/^[\n\s]+$/.test(v)).map(v => {
|
||||
const key = v.slice(0, v.indexOf(':'))
|
||||
const value = v.slice(v.indexOf(':')+1)
|
||||
return {
|
||||
[key
|
||||
.replace(/-([a-z])/g, function() { return arguments[1].toUpperCase()})
|
||||
.replace(/\s+/g, '')
|
||||
]: value.replace(/^\s+/, '').replace(/\s+$/, '') || ''
|
||||
}
|
||||
})
|
||||
export function parent(parent) {
|
||||
return {
|
||||
provide() {
|
||||
return {
|
||||
[parent]: this
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
el: {
|
||||
id: null,
|
||||
css: {},
|
||||
views: []
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
css: {
|
||||
handler(v) {
|
||||
if(this.canvasId) {
|
||||
this.el.css = (typeof v == 'object' ? v : v && Object.assign(...styles(v))) || {}
|
||||
this.canvasWidth = this.el.css && this.el.css.width || this.canvasWidth
|
||||
this.canvasHeight = this.el.css && this.el.css.height || this.canvasHeight
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export function children(parent, options = {}) {
|
||||
const indexKey = options.indexKey || 'index'
|
||||
return {
|
||||
inject: {
|
||||
[parent]: {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
el: {
|
||||
handler(v, o) {
|
||||
if(JSON.stringify(v) != JSON.stringify(o))
|
||||
this.bindRelation()
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
},
|
||||
src: {
|
||||
handler(v, o) {
|
||||
if(v != o)
|
||||
this.bindRelation()
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
text: {
|
||||
handler(v, o) {
|
||||
if(v != o) this.bindRelation()
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
css: {
|
||||
handler(v, o) {
|
||||
if(v != o)
|
||||
this.el.css = (typeof v == 'object' ? v : v && Object.assign(...styles(v))) || {}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
replace: {
|
||||
handler(v, o) {
|
||||
if(JSON.stringify(v) != JSON.stringify(o))
|
||||
this.bindRelation()
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if(!this._uid) {
|
||||
this._uid = this._.uid
|
||||
}
|
||||
Object.defineProperty(this, 'parent', {
|
||||
get: () => this[parent] || [],
|
||||
})
|
||||
Object.defineProperty(this, 'index', {
|
||||
get: () => {
|
||||
this.bindRelation();
|
||||
const {parent: {el: {views=[]}={}}={}} = this
|
||||
return views.indexOf(this.el)
|
||||
},
|
||||
});
|
||||
this.el.type = this.type
|
||||
if(this.uid) {
|
||||
this.el.uid = this.uid
|
||||
}
|
||||
this.bindRelation()
|
||||
},
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.removeEl()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE2
|
||||
beforeDestroy() {
|
||||
this.removeEl()
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
removeEl() {
|
||||
if (this.parent) {
|
||||
this.parent.el.views = this.parent.el.views.filter(
|
||||
(item) => item._uid !== this._uid
|
||||
);
|
||||
}
|
||||
},
|
||||
bindRelation() {
|
||||
if(!this.el._uid) {
|
||||
this.el._uid = this._uid
|
||||
}
|
||||
if(['text','qrcode'].includes(this.type)) {
|
||||
this.el.text = this.$slots && this.$slots.default && this.$slots.default[0].text || `${this.text || ''}`.replace(/\\n/g, '\n')
|
||||
}
|
||||
if(this.type == 'image') {
|
||||
this.el.src = this.src
|
||||
}
|
||||
if (!this.parent) {
|
||||
return;
|
||||
}
|
||||
let views = this.parent.el.views || [];
|
||||
if(views.indexOf(this.el) !== -1) {
|
||||
this.parent.el.views = views.map(v => v._uid == this._uid ? this.el : v)
|
||||
} else {
|
||||
this.parent.el.views = [...views, this.el];
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// this.bindRelation()
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {parent, children} from '../common/relation';
|
||||
export default {
|
||||
name: 'lime-painter-image',
|
||||
mixins:[children('painter')],
|
||||
props: {
|
||||
id: String,
|
||||
css: [String, Object],
|
||||
src: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: 'image',
|
||||
el: {
|
||||
css: {},
|
||||
src: null
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,27 +0,0 @@
|
||||
<template>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {parent, children} from '../common/relation';
|
||||
export default {
|
||||
name: 'lime-painter-qrcode',
|
||||
mixins:[children('painter')],
|
||||
props: {
|
||||
id: String,
|
||||
css: [String, Object],
|
||||
text: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: 'qrcode',
|
||||
el: {
|
||||
css: {},
|
||||
text: null
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,33 +0,0 @@
|
||||
<template>
|
||||
<text style="opacity: 0;height: 0;"><slot/></text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {parent, children} from '../common/relation';
|
||||
export default {
|
||||
name: 'lime-painter-text',
|
||||
mixins:[children('painter')],
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
uid: String,
|
||||
css: [String, Object],
|
||||
text: [String, Number],
|
||||
replace: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// type: 'text',
|
||||
el: {
|
||||
css: {},
|
||||
text: null
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,34 +0,0 @@
|
||||
<template>
|
||||
<view><slot/></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {parent, children} from '../common/relation';
|
||||
export default {
|
||||
name: 'lime-painter-view',
|
||||
mixins:[children('painter'), parent('painter')],
|
||||
props: {
|
||||
id: String,
|
||||
type: {
|
||||
type: String,
|
||||
default: 'view'
|
||||
},
|
||||
css: [String, Object],
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// type: 'view',
|
||||
el: {
|
||||
css: {},
|
||||
views:[]
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,461 +0,0 @@
|
||||
<template>
|
||||
<view class="lime-painter" ref="limepainter">
|
||||
<view v-if="canvasId && size" :style="styles">
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<canvas class="lime-painter__canvas" v-if="use2dCanvas" :id="canvasId" type="2d" :style="size"></canvas>
|
||||
<canvas class="lime-painter__canvas" v-else :id="canvasId" :canvas-id="canvasId" :style="size"
|
||||
:width="boardWidth * dpr" :height="boardHeight * dpr" :hidpi="hidpi"></canvas>
|
||||
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<web-view :style="size" ref="webview"
|
||||
src="/uni_modules/lime-painter/hybrid/html/index.html"
|
||||
class="lime-painter__canvas" @pagefinish="onPageFinish" @error="onError" @onPostMessage="onMessage">
|
||||
</web-view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { parent } from '../common/relation'
|
||||
import props from './props'
|
||||
import {toPx, base64ToPath, pathToBase64, isBase64, sleep, getImageInfo }from './utils';
|
||||
// #ifndef APP-NVUE
|
||||
import { canIUseCanvas2d, isPC} from './utils';
|
||||
import Painter from './painter';
|
||||
// import Painter from '@painter'
|
||||
const nvue = {}
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
import nvue from './nvue'
|
||||
// #endif
|
||||
export default {
|
||||
name: 'lime-painter',
|
||||
mixins: [props, parent('painter'), nvue],
|
||||
data() {
|
||||
return {
|
||||
use2dCanvas: false,
|
||||
canvasHeight: 150,
|
||||
canvasWidth: null,
|
||||
parentWidth: 0,
|
||||
inited: false,
|
||||
progress: 0,
|
||||
firstRender: 0,
|
||||
done: false,
|
||||
tasks: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
styles() {
|
||||
return `${this.size}${this.customStyle||''};` + (this.hidden && 'position: fixed; left: 1500rpx;')
|
||||
},
|
||||
canvasId() {
|
||||
return `l-painter${this._ && this._.uid || this._uid}`
|
||||
},
|
||||
size() {
|
||||
if (this.boardWidth && this.boardHeight) {
|
||||
return `width:${this.boardWidth}px; height: ${this.boardHeight}px;`;
|
||||
}
|
||||
},
|
||||
dpr() {
|
||||
return this.pixelRatio || uni.getSystemInfoSync().pixelRatio;
|
||||
},
|
||||
boardWidth() {
|
||||
const {width = 0} = (this.elements && this.elements.css) || this.elements || this
|
||||
const w = toPx(width||this.width)
|
||||
return w || Math.max(w, toPx(this.canvasWidth));
|
||||
},
|
||||
boardHeight() {
|
||||
const {height = 0} = (this.elements && this.elements.css) || this.elements || this
|
||||
const h = toPx(height||this.height)
|
||||
return h || Math.max(h, toPx(this.canvasHeight));
|
||||
},
|
||||
hasBoard() {
|
||||
return this.board && Object.keys(this.board).length
|
||||
},
|
||||
elements() {
|
||||
return this.hasBoard ? this.board : JSON.parse(JSON.stringify(this.el))
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.use2dCanvas = this.type === '2d' && canIUseCanvas2d() && !isPC
|
||||
},
|
||||
async mounted() {
|
||||
await sleep(30)
|
||||
await this.getParentWeith()
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.$watch('elements', this.watchRender, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
});
|
||||
}, 30)
|
||||
})
|
||||
},
|
||||
// #ifdef VUE3
|
||||
unmounted() {
|
||||
this.done = false
|
||||
this.inited = false
|
||||
this.firstRender = 0
|
||||
this.progress = 0
|
||||
this.painter = null
|
||||
clearTimeout(this.rendertimer)
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE2
|
||||
destroyed() {
|
||||
this.done = false
|
||||
this.inited = false
|
||||
this.firstRender = 0
|
||||
this.progress = 0
|
||||
this.painter = null
|
||||
clearTimeout(this.rendertimer)
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
async watchRender(val, old) {
|
||||
if (!val || !val.views || (!this.firstRender ? !val.views.length : !this.firstRender) || !Object.keys(val).length || JSON.stringify(val) == JSON.stringify(old)) return;
|
||||
this.firstRender = 1
|
||||
this.progress = 0
|
||||
this.done = false
|
||||
clearTimeout(this.rendertimer)
|
||||
this.rendertimer = setTimeout(() => {
|
||||
this.render(val);
|
||||
}, this.beforeDelay)
|
||||
},
|
||||
async setFilePath(path, param) {
|
||||
let filePath = path
|
||||
const {pathType = this.pathType} = param || this
|
||||
if (pathType == 'base64' && !isBase64(path)) {
|
||||
filePath = await pathToBase64(path)
|
||||
} else if (pathType == 'url' && isBase64(path)) {
|
||||
filePath = await base64ToPath(path)
|
||||
}
|
||||
if (param && param.isEmit) {
|
||||
this.$emit('success', filePath);
|
||||
}
|
||||
return filePath
|
||||
},
|
||||
async getSize(args) {
|
||||
const {width} = args.css || args
|
||||
const {height} = args.css || args
|
||||
if (!this.size) {
|
||||
if (width || height) {
|
||||
this.canvasWidth = width || this.canvasWidth
|
||||
this.canvasHeight = height || this.canvasHeight
|
||||
await sleep(30);
|
||||
} else {
|
||||
await this.getParentWeith()
|
||||
}
|
||||
}
|
||||
},
|
||||
canvasToTempFilePathSync(args) {
|
||||
// this.stopWatch && this.stopWatch()
|
||||
// this.stopWatch = this.$watch('done', (v) => {
|
||||
// if (v) {
|
||||
// this.canvasToTempFilePath(args)
|
||||
// this.stopWatch && this.stopWatch()
|
||||
// }
|
||||
// }, {
|
||||
// immediate: true
|
||||
// })
|
||||
this.tasks.push(args)
|
||||
if(this.done){
|
||||
this.runTask()
|
||||
}
|
||||
},
|
||||
runTask(){
|
||||
while(this.tasks.length){
|
||||
const task = this.tasks.shift()
|
||||
this.canvasToTempFilePath(task)
|
||||
}
|
||||
},
|
||||
// #ifndef APP-NVUE
|
||||
getParentWeith() {
|
||||
return new Promise(resolve => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(`.lime-painter`)
|
||||
.boundingClientRect()
|
||||
.exec(res => {
|
||||
const {width, height} = res[0]||{}
|
||||
this.parentWidth = Math.ceil(width||0)
|
||||
this.canvasWidth = this.parentWidth || 300
|
||||
this.canvasHeight = height || this.canvasHeight||150
|
||||
resolve(res[0])
|
||||
})
|
||||
})
|
||||
},
|
||||
async render(args = {}) {
|
||||
if(!Object.keys(args).length) {
|
||||
return console.error('空对象')
|
||||
}
|
||||
this.progress = 0
|
||||
this.done = false
|
||||
// #ifdef APP-NVUE
|
||||
this.tempFilePath.length = 0
|
||||
// #endif
|
||||
await this.getSize(args)
|
||||
const ctx = await this.getContext();
|
||||
|
||||
let {
|
||||
use2dCanvas,
|
||||
boardWidth,
|
||||
boardHeight,
|
||||
canvas,
|
||||
afterDelay
|
||||
} = this;
|
||||
if (use2dCanvas && !canvas) {
|
||||
return Promise.reject(new Error('canvas 没创建'));
|
||||
}
|
||||
this.boundary = {
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: boardWidth,
|
||||
height: boardHeight
|
||||
};
|
||||
this.painter = null
|
||||
if (!this.painter) {
|
||||
const {width} = args.css || args
|
||||
const {height} = args.css || args
|
||||
if(!width && this.parentWidth) {
|
||||
Object.assign(args, {width: this.parentWidth})
|
||||
}
|
||||
const param = {
|
||||
context: ctx,
|
||||
canvas,
|
||||
width: boardWidth,
|
||||
height: boardHeight,
|
||||
pixelRatio: this.dpr,
|
||||
useCORS: this.useCORS,
|
||||
createImage: getImageInfo.bind(this),
|
||||
performance: this.performance,
|
||||
listen: {
|
||||
onProgress: (v) => {
|
||||
this.progress = v
|
||||
this.$emit('progress', v)
|
||||
},
|
||||
onEffectFail: (err) => {
|
||||
this.$emit('faill', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.painter = new Painter(param)
|
||||
}
|
||||
try{
|
||||
// vue3 赋值给data会引起图片无法绘制
|
||||
const { width, height } = await this.painter.source(JSON.parse(JSON.stringify(args)))
|
||||
this.boundary.height = this.canvasHeight = height
|
||||
this.boundary.width = this.canvasWidth = width
|
||||
await sleep(this.sleep);
|
||||
await this.painter.render()
|
||||
await new Promise(resolve => this.$nextTick(resolve));
|
||||
if (!use2dCanvas) {
|
||||
await this.canvasDraw();
|
||||
}
|
||||
if (afterDelay && use2dCanvas) {
|
||||
await sleep(afterDelay);
|
||||
}
|
||||
this.$emit('done');
|
||||
this.done = true
|
||||
if (this.isCanvasToTempFilePath) {
|
||||
this.canvasToTempFilePath()
|
||||
.then(res => {
|
||||
this.$emit('success', res.tempFilePath)
|
||||
})
|
||||
.catch(err => {
|
||||
this.$emit('fail', new Error(JSON.stringify(err)));
|
||||
});
|
||||
}
|
||||
this.runTask()
|
||||
return Promise.resolve({
|
||||
ctx,
|
||||
draw: this.painter,
|
||||
node: this.node
|
||||
});
|
||||
}catch(e){
|
||||
//TODO handle the exception
|
||||
}
|
||||
|
||||
},
|
||||
canvasDraw(flag = false) {
|
||||
return new Promise((resolve, reject) => this.ctx.draw(flag, () => setTimeout(() => resolve(), this
|
||||
.afterDelay)));
|
||||
},
|
||||
async getContext() {
|
||||
if (!this.canvasWidth) {
|
||||
this.$emit('fail', 'painter no size')
|
||||
console.error('[lime-painter]: 给画板或父级设置尺寸')
|
||||
return Promise.reject();
|
||||
}
|
||||
if (this.ctx && this.inited) {
|
||||
return Promise.resolve(this.ctx);
|
||||
}
|
||||
const { type, use2dCanvas, dpr, boardWidth, boardHeight } = this;
|
||||
const _getContext = () => {
|
||||
return new Promise(resolve => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(`#${this.canvasId}`)
|
||||
.boundingClientRect()
|
||||
.exec(res => {
|
||||
if (res) {
|
||||
const ctx = uni.createCanvasContext(this.canvasId, this);
|
||||
if (!this.inited) {
|
||||
this.inited = true;
|
||||
this.use2dCanvas = false;
|
||||
this.canvas = res;
|
||||
}
|
||||
|
||||
// 钉钉小程序框架不支持 measureText 方法,用此方法 mock
|
||||
if (!ctx.measureText) {
|
||||
function strLen(str) {
|
||||
let len = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
|
||||
len++;
|
||||
} else {
|
||||
len += 2;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
ctx.measureText = text => {
|
||||
let fontSize = ctx.state && ctx.state.fontSize || 12;
|
||||
const font = ctx.__font
|
||||
if (font && fontSize == 12) {
|
||||
fontSize = parseInt(font.split(' ')[3], 10);
|
||||
}
|
||||
fontSize /= 2;
|
||||
return {
|
||||
width: strLen(text) * fontSize
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// #ifdef MP-ALIPAY
|
||||
ctx.scale(dpr, dpr);
|
||||
// #endif
|
||||
this.ctx = ctx
|
||||
resolve(this.ctx);
|
||||
} else {
|
||||
console.error('[lime-painter] no node')
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
if (!use2dCanvas) {
|
||||
return _getContext();
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(`#${this.canvasId}`)
|
||||
.node()
|
||||
.exec(res => {
|
||||
let {node: canvas} = res && res[0]||{};
|
||||
if(canvas) {
|
||||
const ctx = canvas.getContext(type);
|
||||
if (!this.inited) {
|
||||
this.inited = true;
|
||||
this.use2dCanvas = true;
|
||||
this.canvas = canvas;
|
||||
}
|
||||
this.ctx = ctx
|
||||
resolve(this.ctx);
|
||||
} else {
|
||||
console.error('[lime-painter]: no size')
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
canvasToTempFilePath(args = {}) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const { use2dCanvas, canvasId, dpr, fileType, quality } = this;
|
||||
const success = async (res) => {
|
||||
try {
|
||||
const tempFilePath = await this.setFilePath(res.tempFilePath || res, args)
|
||||
const result = Object.assign(res, {tempFilePath})
|
||||
args.success && args.success(result)
|
||||
resolve(result)
|
||||
} catch (e) {
|
||||
this.$emit('fail', e)
|
||||
}
|
||||
}
|
||||
|
||||
let { top: y = 0, left: x = 0, width, height } = this.boundary || this;
|
||||
// let destWidth = width * dpr;
|
||||
// let destHeight = height * dpr;
|
||||
// #ifdef MP-ALIPAY
|
||||
// width = destWidth;
|
||||
// height = destHeight;
|
||||
// #endif
|
||||
|
||||
const copyArgs = Object.assign({
|
||||
// x,
|
||||
// y,
|
||||
// width,
|
||||
// height,
|
||||
// destWidth,
|
||||
// destHeight,
|
||||
canvasId,
|
||||
id: canvasId,
|
||||
fileType,
|
||||
quality,
|
||||
}, args, {success});
|
||||
// if(this.isPC || use2dCanvas) {
|
||||
// copyArgs.canvas = this.canvas
|
||||
// }
|
||||
if (use2dCanvas) {
|
||||
copyArgs.canvas = this.canvas
|
||||
try{
|
||||
// #ifndef MP-ALIPAY
|
||||
const oFilePath = this.canvas.toDataURL(`image/${args.fileType||fileType}`.replace(/pg/, 'peg'), args.quality||quality)
|
||||
if(/data:,/.test(oFilePath)) {
|
||||
uni.canvasToTempFilePath(copyArgs, this);
|
||||
} else {
|
||||
const tempFilePath = await this.setFilePath(oFilePath, args)
|
||||
args.success && args.success({tempFilePath})
|
||||
resolve({tempFilePath})
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
this.canvas.toTempFilePath(copyArgs)
|
||||
// #endif
|
||||
}catch(e){
|
||||
args.fail && args.fail(e)
|
||||
reject(e)
|
||||
}
|
||||
} else {
|
||||
// #ifdef MP-ALIPAY
|
||||
if(this.ctx.toTempFilePath) {
|
||||
// 钉钉
|
||||
const ctx = uni.createCanvasContext(canvasId);
|
||||
ctx.toTempFilePath(copyArgs);
|
||||
} else {
|
||||
my.canvasToTempFilePath(copyArgs);
|
||||
}
|
||||
// #endif
|
||||
// #ifndef MP-ALIPAY
|
||||
uni.canvasToTempFilePath(copyArgs, this);
|
||||
// #endif
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.lime-painter,
|
||||
.lime-painter__canvas {
|
||||
// #ifndef APP-NVUE
|
||||
width: 100%;
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
flex: 1;
|
||||
// #endif
|
||||
}
|
||||
</style>
|
||||
@@ -1,214 +0,0 @@
|
||||
// #ifdef APP-NVUE
|
||||
import {
|
||||
sleep,
|
||||
getImageInfo,
|
||||
isBase64,
|
||||
networkReg
|
||||
} from './utils';
|
||||
const dom = weex.requireModule('dom')
|
||||
import {
|
||||
version
|
||||
} from '../../package.json'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tempFilePath: [],
|
||||
isInitFile: false,
|
||||
osName: uni.getSystemInfoSync().osName
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getParentWeith() {
|
||||
return new Promise(resolve => {
|
||||
dom.getComponentRect(this.$refs.limepainter, (res) => {
|
||||
this.parentWidth = Math.ceil(res.size.width)
|
||||
this.canvasWidth = this.canvasWidth || this.parentWidth || 300
|
||||
this.canvasHeight = res.size.height || this.canvasHeight || 150
|
||||
resolve(res.size)
|
||||
})
|
||||
})
|
||||
},
|
||||
onPageFinish() {
|
||||
this.webview = this.$refs.webview
|
||||
this.webview.evalJS(`init(${this.dpr})`)
|
||||
},
|
||||
onMessage(e) {
|
||||
const res = e.detail.data[0] || null;
|
||||
if (res.event) {
|
||||
if (res.event == 'inited') {
|
||||
this.inited = true
|
||||
}
|
||||
if (res.event == 'fail') {
|
||||
this.$emit('fail', res)
|
||||
}
|
||||
if (res.event == 'layoutChange') {
|
||||
const data = typeof res.data == 'string' ? JSON.parse(res.data) : res.data
|
||||
this.canvasWidth = Math.ceil(data.width);
|
||||
this.canvasHeight = Math.ceil(data.height);
|
||||
}
|
||||
if (res.event == 'progressChange') {
|
||||
this.progress = res.data * 1
|
||||
}
|
||||
if (res.event == 'file') {
|
||||
this.tempFilePath.push(res.data)
|
||||
if (this.tempFilePath.length > 7) {
|
||||
this.tempFilePath.shift()
|
||||
}
|
||||
return
|
||||
}
|
||||
if (res.event == 'success') {
|
||||
if (res.data) {
|
||||
this.tempFilePath.push(res.data)
|
||||
if (this.tempFilePath.length > 8) {
|
||||
this.tempFilePath.shift()
|
||||
}
|
||||
if (this.isCanvasToTempFilePath) {
|
||||
this.setFilePath(this.tempFilePath.join(''), {
|
||||
isEmit: true
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.$emit('fail', 'canvas no data')
|
||||
}
|
||||
return
|
||||
}
|
||||
this.$emit(res.event, JSON.parse(res.data));
|
||||
} else if (res.file) {
|
||||
this.file = res.data;
|
||||
} else {
|
||||
console.info(res[0])
|
||||
}
|
||||
},
|
||||
getWebViewInited() {
|
||||
if (this.inited) return Promise.resolve(this.inited);
|
||||
return new Promise((resolve) => {
|
||||
this.$watch(
|
||||
'inited',
|
||||
async val => {
|
||||
if (val) {
|
||||
resolve(val)
|
||||
}
|
||||
}, {
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
})
|
||||
},
|
||||
getTempFilePath() {
|
||||
if (this.tempFilePath.length == 8) return Promise.resolve(this.tempFilePath)
|
||||
return new Promise((resolve) => {
|
||||
this.$watch(
|
||||
'tempFilePath',
|
||||
async val => {
|
||||
if (val.length == 8) {
|
||||
resolve(val.join(''))
|
||||
}
|
||||
}, {
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
})
|
||||
},
|
||||
getWebViewDone() {
|
||||
if (this.progress == 1) return Promise.resolve(this.progress);
|
||||
return new Promise((resolve) => {
|
||||
this.$watch(
|
||||
'progress',
|
||||
async val => {
|
||||
if (val == 1) {
|
||||
this.$emit('done')
|
||||
this.done = true
|
||||
this.runTask()
|
||||
resolve(val)
|
||||
}
|
||||
}, {
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
})
|
||||
},
|
||||
async render(args) {
|
||||
try {
|
||||
await this.getSize(args)
|
||||
const {
|
||||
width
|
||||
} = args.css || args
|
||||
if (!width && this.parentWidth) {
|
||||
Object.assign(args, {
|
||||
width: this.parentWidth
|
||||
})
|
||||
}
|
||||
const newNode = await this.calcImage(args);
|
||||
await this.getWebViewInited()
|
||||
this.webview.evalJS(`source(${JSON.stringify(newNode)})`)
|
||||
await this.getWebViewDone()
|
||||
await sleep(this.afterDelay)
|
||||
if (this.isCanvasToTempFilePath) {
|
||||
const params = {
|
||||
fileType: this.fileType,
|
||||
quality: this.quality
|
||||
}
|
||||
this.webview.evalJS(`save(${JSON.stringify(params)})`)
|
||||
}
|
||||
return Promise.resolve()
|
||||
} catch (e) {
|
||||
this.$emit('fail', e)
|
||||
}
|
||||
},
|
||||
async calcImage(args) {
|
||||
let node = JSON.parse(JSON.stringify(args))
|
||||
const urlReg = /url\((.+)\)/
|
||||
const {
|
||||
backgroundImage
|
||||
} = node.css || {}
|
||||
const isBG = backgroundImage && urlReg.exec(backgroundImage)[1]
|
||||
const url = node.url || node.src || isBG
|
||||
if (['text', 'qrcode'].includes(node.type)) {
|
||||
return node
|
||||
}
|
||||
if ((node.type === "image" || isBG) && url && !isBase64(url) && (this.osName == 'ios' || !networkReg
|
||||
.test(url))) {
|
||||
let {
|
||||
path
|
||||
} = await getImageInfo(url, true)
|
||||
if (isBG) {
|
||||
node.css.backgroundImage = `url(${path})`
|
||||
} else {
|
||||
node.src = path
|
||||
}
|
||||
} else if (node.views && node.views.length) {
|
||||
for (let i = 0; i < node.views.length; i++) {
|
||||
node.views[i] = await this.calcImage(node.views[i])
|
||||
}
|
||||
}
|
||||
return node
|
||||
},
|
||||
async canvasToTempFilePath(args = {}) {
|
||||
if (!this.inited) {
|
||||
return this.$emit('fail', 'no init')
|
||||
}
|
||||
this.tempFilePath = []
|
||||
if (args.fileType == 'jpg') {
|
||||
args.fileType = 'jpeg'
|
||||
}
|
||||
|
||||
this.webview.evalJS(`save(${JSON.stringify(args)})`)
|
||||
try {
|
||||
let tempFilePath = await this.getTempFilePath()
|
||||
|
||||
tempFilePath = await this.setFilePath(tempFilePath, args)
|
||||
args.success({
|
||||
errMsg: "canvasToTempFilePath:ok",
|
||||
tempFilePath
|
||||
})
|
||||
} catch (e) {
|
||||
console.log('e', e)
|
||||
args.fail({
|
||||
error: e
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
File diff suppressed because one or more lines are too long
@@ -1,56 +0,0 @@
|
||||
export default {
|
||||
props: {
|
||||
board: Object,
|
||||
pathType: String, // 'base64'、'url'
|
||||
fileType: {
|
||||
type: String,
|
||||
default: 'png'
|
||||
},
|
||||
hidden: Boolean,
|
||||
quality: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
css: [String, Object],
|
||||
// styles: [String, Object],
|
||||
width: [Number, String],
|
||||
height: [Number, String],
|
||||
pixelRatio: Number,
|
||||
customStyle: String,
|
||||
isCanvasToTempFilePath: Boolean,
|
||||
// useCanvasToTempFilePath: Boolean,
|
||||
sleep: {
|
||||
type: Number,
|
||||
default: 1000 / 30
|
||||
},
|
||||
beforeDelay: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
afterDelay: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
performance: Boolean,
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
|
||||
type: {
|
||||
type: String,
|
||||
default: '2d'
|
||||
},
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
hybrid: Boolean,
|
||||
timeout: {
|
||||
type: Number,
|
||||
default: 2000
|
||||
},
|
||||
// #endif
|
||||
// #ifdef H5 || APP-PLUS
|
||||
useCORS: Boolean,
|
||||
hidpi: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,368 +0,0 @@
|
||||
export const networkReg = /^(http|\/\/)/;
|
||||
export const isBase64 = (path) => /^data:image\/(\w+);base64/.test(path);
|
||||
export function sleep(delay) {
|
||||
return new Promise(resolve => setTimeout(resolve, delay))
|
||||
}
|
||||
let {platform, SDKVersion} = uni.getSystemInfoSync()
|
||||
export const isPC = /windows|mac/.test(platform)
|
||||
// 缓存图片
|
||||
let cache = {}
|
||||
export function isNumber(value) {
|
||||
return /^-?\d+(\.\d+)?$/.test(value);
|
||||
}
|
||||
export function toPx(value, baseSize, isDecimal = false) {
|
||||
// 如果是数字
|
||||
if (typeof value === 'number') {
|
||||
return value
|
||||
}
|
||||
// 如果是字符串数字
|
||||
if (isNumber(value)) {
|
||||
return value * 1
|
||||
}
|
||||
// 如果有单位
|
||||
if (typeof value === 'string') {
|
||||
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);
|
||||
let res = 0;
|
||||
if (unit === 'rpx') {
|
||||
res = uni.upx2px(value);
|
||||
} else if (unit === 'px') {
|
||||
res = value * 1;
|
||||
} else if (unit === '%') {
|
||||
res = value * toPx(baseSize) / 100;
|
||||
} else if (unit === 'em') {
|
||||
res = value * toPx(baseSize || 14);
|
||||
}
|
||||
return isDecimal ? res.toFixed(2) * 1 : Math.round(res);
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// 计算版本
|
||||
export function compareVersion(v1, v2) {
|
||||
v1 = v1.split('.')
|
||||
v2 = v2.split('.')
|
||||
const len = Math.max(v1.length, v2.length)
|
||||
while (v1.length < len) {
|
||||
v1.push('0')
|
||||
}
|
||||
while (v2.length < len) {
|
||||
v2.push('0')
|
||||
}
|
||||
for (let i = 0; i < len; i++) {
|
||||
const num1 = parseInt(v1[i], 10)
|
||||
const num2 = parseInt(v2[i], 10)
|
||||
|
||||
if (num1 > num2) {
|
||||
return 1
|
||||
} else if (num1 < num2) {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function gte(version) {
|
||||
// #ifdef MP-ALIPAY
|
||||
SDKVersion = my.SDKVersion
|
||||
// #endif
|
||||
return compareVersion(SDKVersion, version) >= 0;
|
||||
}
|
||||
export function canIUseCanvas2d() {
|
||||
// #ifdef MP-WEIXIN
|
||||
return gte('2.9.2');
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
return gte('2.7.15');
|
||||
// #endif
|
||||
// #ifdef MP-TOUTIAO
|
||||
return gte('1.78.0');
|
||||
// #endif
|
||||
return false
|
||||
}
|
||||
|
||||
// #ifdef MP
|
||||
export const prefix = () => {
|
||||
// #ifdef MP-TOUTIAO
|
||||
return tt
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
return wx
|
||||
// #endif
|
||||
// #ifdef MP-BAIDU
|
||||
return swan
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
return my
|
||||
// #endif
|
||||
// #ifdef MP-QQ
|
||||
return qq
|
||||
// #endif
|
||||
// #ifdef MP-360
|
||||
return qh
|
||||
// #endif
|
||||
}
|
||||
// #endif
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* base64转路径
|
||||
* @param {Object} base64
|
||||
*/
|
||||
export function base64ToPath(base64) {
|
||||
const [, format] = /^data:image\/(\w+);base64,/.exec(base64) || [];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef MP
|
||||
const fs = uni.getFileSystemManager()
|
||||
//自定义文件名
|
||||
if (!format) {
|
||||
reject(new Error('ERROR_BASE64SRC_PARSE'))
|
||||
}
|
||||
const time = new Date().getTime();
|
||||
let pre = prefix()
|
||||
// #ifdef MP-TOUTIAO
|
||||
const filePath = `${pre.getEnvInfoSync().common.USER_DATA_PATH}/${time}.${format}`
|
||||
// #endif
|
||||
// #ifndef MP-TOUTIAO
|
||||
const filePath = `${pre.env.USER_DATA_PATH}/${time}.${format}`
|
||||
// #endif
|
||||
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 (!format) {
|
||||
reject(new Error('ERROR_BASE64SRC_PARSE'))
|
||||
}
|
||||
const time = new Date().getTime();
|
||||
const filePath = `_doc/uniapp_temp/${time}.${format}`
|
||||
bitmap.save(filePath, {},
|
||||
() => {
|
||||
bitmap.clear()
|
||||
resolve(filePath)
|
||||
},
|
||||
(error) => {
|
||||
bitmap.clear()
|
||||
reject(error)
|
||||
})
|
||||
}, (error) => {
|
||||
bitmap.clear()
|
||||
reject(error)
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径转base64
|
||||
* @param {Object} string
|
||||
*/
|
||||
export function pathToBase64(path) {
|
||||
if (/^data:/.test(path)) return path
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef H5
|
||||
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('image/png')
|
||||
resolve(result);
|
||||
canvas.height = canvas.width = 0
|
||||
}
|
||||
image.src = path + '?v=' + Math.random()
|
||||
image.onerror = (error) => {
|
||||
reject(error);
|
||||
};
|
||||
// #endif
|
||||
|
||||
// #ifdef MP
|
||||
if (uni.canIUse('getFileSystemManager')) {
|
||||
uni.getFileSystemManager().readFile({
|
||||
filePath: path,
|
||||
encoding: 'base64',
|
||||
success: (res) => {
|
||||
resolve('data:image/png;base64,' + res.data)
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error({error, path})
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
plus.io.resolveLocalFileSystemURL(getLocalFilePath(path), (entry) => {
|
||||
entry.file((file) => {
|
||||
const fileReader = new plus.io.FileReader()
|
||||
fileReader.onload = (data) => {
|
||||
resolve(data.target.result)
|
||||
}
|
||||
fileReader.onerror = (error) => {
|
||||
reject(error)
|
||||
}
|
||||
fileReader.readAsDataURL(file)
|
||||
}, reject)
|
||||
}, reject)
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function getImageInfo(path, useCORS) {
|
||||
const isCanvas2D = this && this.canvas && this.canvas.createImage
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// let time = +new Date()
|
||||
let src = path.replace(/^@\//,'/')
|
||||
if (cache[path] && cache[path].errMsg) {
|
||||
resolve(cache[path])
|
||||
} else {
|
||||
try {
|
||||
// #ifdef MP || APP-PLUS
|
||||
if (isBase64(path) && (isCanvas2D ? isPC : true)) {
|
||||
src = await base64ToPath(path)
|
||||
}
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
if(useCORS) {
|
||||
src = await pathToBase64(path)
|
||||
}
|
||||
// #endif
|
||||
} catch (error) {
|
||||
reject({
|
||||
...error,
|
||||
src
|
||||
})
|
||||
}
|
||||
// #ifndef APP-NVUE
|
||||
if(isCanvas2D && !isPC) {
|
||||
const img = this.canvas.createImage()
|
||||
img.onload = function() {
|
||||
const image = {
|
||||
path: img,
|
||||
width: img.width,
|
||||
height: img.height
|
||||
}
|
||||
cache[path] = image
|
||||
resolve(cache[path])
|
||||
}
|
||||
img.onerror = function(err) {
|
||||
reject({err,path})
|
||||
}
|
||||
img.src = src
|
||||
return
|
||||
}
|
||||
// #endif
|
||||
uni.getImageInfo({
|
||||
src,
|
||||
success: (image) => {
|
||||
const localReg = /^\.|^\/(?=[^\/])/;
|
||||
// #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO
|
||||
image.path = localReg.test(src) ? `/${image.path}` : image.path;
|
||||
// #endif
|
||||
if(isCanvas2D) {
|
||||
const img = this.canvas.createImage()
|
||||
img.onload = function() {
|
||||
image.path = img
|
||||
cache[path] = image
|
||||
resolve(cache[path])
|
||||
}
|
||||
img.onerror = function(err) {
|
||||
reject({err,path})
|
||||
}
|
||||
img.src = src
|
||||
return
|
||||
}
|
||||
// #ifdef APP-PLUS
|
||||
// console.log('getImageInfo', +new Date() - time)
|
||||
// ios 比较严格 可能需要设置跨域
|
||||
if(uni.getSystemInfoSync().osName == 'ios' && useCORS) {
|
||||
pathToBase64(image.path).then(base64 => {
|
||||
image.path = base64
|
||||
cache[path] = image
|
||||
resolve(cache[path])
|
||||
}).catch(err => {
|
||||
console.error({err, path})
|
||||
reject({err,path})
|
||||
})
|
||||
return
|
||||
}
|
||||
// #endif
|
||||
cache[path] = image
|
||||
resolve(cache[path])
|
||||
},
|
||||
fail(err) {
|
||||
console.error({err, path})
|
||||
reject({err,path})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
const getLocalFilePath = (path) => {
|
||||
if (path.indexOf('_www') === 0 || path.indexOf('_doc') === 0 || path.indexOf('_documents') === 0 || path
|
||||
.indexOf('_downloads') === 0) {
|
||||
return path
|
||||
}
|
||||
if (path.indexOf('file://') === 0) {
|
||||
return path
|
||||
}
|
||||
if (path.indexOf('/storage/emulated/0/') === 0) {
|
||||
return path
|
||||
}
|
||||
if (path.indexOf('/') === 0) {
|
||||
const localFilePath = plus.io.convertAbsoluteFileSystem(path)
|
||||
if (localFilePath !== path) {
|
||||
return localFilePath
|
||||
} else {
|
||||
path = path.substr(1)
|
||||
}
|
||||
}
|
||||
return '_www/' + path
|
||||
}
|
||||
// #endif
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,119 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title></title>
|
||||
<style type="text/css">
|
||||
html,
|
||||
body,
|
||||
canvas {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas id="lime-painter"></canvas>
|
||||
<script type="text/javascript" src="./uni.webview.1.5.3.js"></script>
|
||||
<script type="text/javascript" src="./painter.js"></script>
|
||||
<script>
|
||||
var cache = [];
|
||||
var painter = null;
|
||||
var canvas = null;
|
||||
var context = null;
|
||||
var timer = null;
|
||||
var pixelRatio = 1;
|
||||
console.log = function (...args) {
|
||||
postMessage(args);
|
||||
};
|
||||
// function stringify(key, value) {
|
||||
// if (typeof value === 'object' && value !== null) {
|
||||
// if (cache.indexOf(value) !== -1) {
|
||||
// return;
|
||||
// }
|
||||
// cache.push(value);
|
||||
// }
|
||||
// return value;
|
||||
// };
|
||||
|
||||
function emit(event, data) {
|
||||
postMessage({
|
||||
event,
|
||||
data: (typeof data !== 'object' && data !== null ? data : JSON.stringify(data))
|
||||
});
|
||||
cache = [];
|
||||
};
|
||||
function postMessage(data) {
|
||||
uni.postMessage({
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
function init(dpr) {
|
||||
canvas = document.querySelector('#lime-painter');
|
||||
context = canvas.getContext('2d');
|
||||
pixelRatio = dpr || window.devicePixelRatio;
|
||||
painter = new Painter({
|
||||
id: 'lime-painter',
|
||||
context,
|
||||
canvas,
|
||||
pixelRatio,
|
||||
width: canvas.offsetWidth,
|
||||
height: canvas.offsetHeight,
|
||||
listen: {
|
||||
onProgress(v) {
|
||||
emit('progressChange', v);
|
||||
},
|
||||
onEffectFail(err) {
|
||||
//console.error(err)
|
||||
emit('fail', err);
|
||||
}
|
||||
}
|
||||
});
|
||||
emit('inited', true);
|
||||
};
|
||||
function save(args) {
|
||||
delete args.success;
|
||||
delete args.fail;
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
const path = painter.save(args);
|
||||
if (typeof path == 'string') {
|
||||
const index = Math.ceil(path.length / 8);
|
||||
for (var i = 0; i < 8; i++) {
|
||||
if (i == 7) {
|
||||
emit('success', path.substr(i * index, index));
|
||||
} else {
|
||||
emit('file', path.substr(i * index, index));
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// console.log('canvas no data')
|
||||
emit('fail', 'canvas no data');
|
||||
};
|
||||
}, 30);
|
||||
};
|
||||
async function source(args) {
|
||||
let size = await painter.source(args);
|
||||
emit('layoutChange', size);
|
||||
if(!canvas.height) {
|
||||
console.log('canvas no size')
|
||||
emit('fail', 'canvas no size');
|
||||
}
|
||||
painter.render().catch(err => {
|
||||
// console.error(err)
|
||||
emit('fail', err);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
@@ -1,388 +0,0 @@
|
||||
/*
|
||||
* HTML5 Parser By Sam Blowes
|
||||
*
|
||||
* Designed for HTML5 documents
|
||||
*
|
||||
* Original code by John Resig (ejohn.org)
|
||||
* http://ejohn.org/blog/pure-javascript-html-parser/
|
||||
* Original code by Erik Arvidsson, Mozilla Public License
|
||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
* License
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* This code is triple licensed using Apache Software License 2.0,
|
||||
* Mozilla Public License or GNU Public License
|
||||
*
|
||||
* ////////////////////////////////////////////////////////////////////////////
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* ////////////////////////////////////////////////////////////////////////////
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License
|
||||
* Version 1.1 (the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS"
|
||||
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing rights and limitations
|
||||
* under the License.
|
||||
*
|
||||
* The Original Code is Simple HTML Parser.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Erik Arvidsson.
|
||||
* Portions created by Erik Arvidssson are Copyright (C) 2004. All Rights
|
||||
* Reserved.
|
||||
*
|
||||
* ////////////////////////////////////////////////////////////////////////////
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
* Usage
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* // Use like so:
|
||||
* HTMLParser(htmlString, {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* });
|
||||
*
|
||||
* // or to get an XML string:
|
||||
* HTMLtoXML(htmlString);
|
||||
*
|
||||
* // or to get an XML DOM Document
|
||||
* HTMLtoDOM(htmlString);
|
||||
*
|
||||
* // or to inject into an existing document/DOM node
|
||||
* HTMLtoDOM(htmlString, document);
|
||||
* HTMLtoDOM(htmlString, document.body);
|
||||
*
|
||||
*/
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
|
||||
var endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
|
||||
var attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 5
|
||||
|
||||
var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr'); // Block Elements - HTML 5
|
||||
// fixed by xxx 将 ins 标签从块级名单中移除
|
||||
|
||||
var block = makeMap('a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video'); // Inline Elements - HTML 5
|
||||
|
||||
var inline = makeMap('abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'); // Elements that you can, intentionally, leave open
|
||||
// (and which close themselves)
|
||||
|
||||
var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); // Attributes that have their values filled in disabled="disabled"
|
||||
|
||||
var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'); // Special Elements (can contain anything)
|
||||
|
||||
var special = makeMap('script,style');
|
||||
function HTMLParser(html, handler) {
|
||||
var index;
|
||||
var chars;
|
||||
var match;
|
||||
var stack = [];
|
||||
var last = html;
|
||||
|
||||
stack.last = function () {
|
||||
return this[this.length - 1];
|
||||
};
|
||||
|
||||
while (html) {
|
||||
chars = true; // Make sure we're not in a script or style element
|
||||
|
||||
if (!stack.last() || !special[stack.last()]) {
|
||||
// Comment
|
||||
if (html.indexOf('<!--') == 0) {
|
||||
index = html.indexOf('-->');
|
||||
|
||||
if (index >= 0) {
|
||||
if (handler.comment) {
|
||||
handler.comment(html.substring(4, index));
|
||||
}
|
||||
|
||||
html = html.substring(index + 3);
|
||||
chars = false;
|
||||
} // end tag
|
||||
|
||||
} else if (html.indexOf('</') == 0) {
|
||||
match = html.match(endTag);
|
||||
|
||||
if (match) {
|
||||
html = html.substring(match[0].length);
|
||||
match[0].replace(endTag, parseEndTag);
|
||||
chars = false;
|
||||
} // start tag
|
||||
|
||||
} else if (html.indexOf('<') == 0) {
|
||||
match = html.match(startTag);
|
||||
|
||||
if (match) {
|
||||
html = html.substring(match[0].length);
|
||||
match[0].replace(startTag, parseStartTag);
|
||||
chars = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (chars) {
|
||||
index = html.indexOf('<');
|
||||
var text = index < 0 ? html : html.substring(0, index);
|
||||
html = index < 0 ? '' : html.substring(index);
|
||||
|
||||
if (handler.chars) {
|
||||
handler.chars(text);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
html = html.replace(new RegExp('([\\s\\S]*?)<\/' + stack.last() + '[^>]*>'), function (all, text) {
|
||||
text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, '$1$2');
|
||||
|
||||
if (handler.chars) {
|
||||
handler.chars(text);
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
parseEndTag('', stack.last());
|
||||
}
|
||||
|
||||
if (html == last) {
|
||||
throw 'Parse Error: ' + html;
|
||||
}
|
||||
|
||||
last = html;
|
||||
} // Clean up any remaining tags
|
||||
|
||||
|
||||
parseEndTag();
|
||||
|
||||
function parseStartTag(tag, tagName, rest, unary) {
|
||||
tagName = tagName.toLowerCase();
|
||||
if (block[tagName]) {
|
||||
while (stack.last() && inline[stack.last()]) {
|
||||
parseEndTag('', stack.last());
|
||||
}
|
||||
}
|
||||
|
||||
if (closeSelf[tagName] && stack.last() == tagName) {
|
||||
parseEndTag('', tagName);
|
||||
}
|
||||
|
||||
unary = empty[tagName] || !!unary;
|
||||
|
||||
if (!unary) {
|
||||
stack.push(tagName);
|
||||
}
|
||||
|
||||
if (handler.start) {
|
||||
var attrs = [];
|
||||
rest.replace(attr, function (match, name) {
|
||||
var value = arguments[2] ? arguments[2] : arguments[3] ? arguments[3] : arguments[4] ? arguments[4] : fillAttrs[name] ? name : '';
|
||||
attrs.push({
|
||||
name: name,
|
||||
value: value,
|
||||
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') // "
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
if (handler.start) {
|
||||
handler.start(tagName, attrs, unary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseEndTag(tag, tagName) {
|
||||
// If no tag name is provided, clean shop
|
||||
if (!tagName) {
|
||||
var pos = 0;
|
||||
} // Find the closest opened tag of the same type
|
||||
else {
|
||||
for (var pos = stack.length - 1; pos >= 0; pos--) {
|
||||
if (stack[pos] == tagName) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pos >= 0) {
|
||||
// Close all the open elements, up the stack
|
||||
for (var i = stack.length - 1; i >= pos; i--) {
|
||||
if (handler.end) {
|
||||
handler.end(stack[i]);
|
||||
}
|
||||
} // Remove the open elements from the stack
|
||||
|
||||
|
||||
stack.length = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makeMap(str) {
|
||||
var obj = {};
|
||||
var items = str.split(',');
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
obj[items[i]] = true;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function removeDOCTYPE(html) {
|
||||
return html.replace(/<\?xml.*\?>\n/, '').replace(/<!doctype.*>\n/, '').replace(/<!DOCTYPE.*>\n/, '');
|
||||
}
|
||||
|
||||
function parseAttrs(attrs) {
|
||||
return attrs.reduce(function (pre, attr) {
|
||||
var value = attr.value;
|
||||
var name = attr.name;
|
||||
if (pre[name]) {
|
||||
pre[name] = pre[name] + " " + value;
|
||||
} else {
|
||||
pre[name] = value;
|
||||
}
|
||||
|
||||
return pre;
|
||||
}, {});
|
||||
}
|
||||
function convertStyleStringToJSON(styleString) {
|
||||
var styles = styleString.split(";"); // 通过分号将样式字符串分割为多个样式声明
|
||||
var result = {};
|
||||
|
||||
styles.forEach(function(style) {
|
||||
var styleParts = style.split(":"); // 通过冒号将样式声明分割为属性和值
|
||||
var property = styleParts[0].trim();
|
||||
var value = styleParts[1] && styleParts[1].trim();
|
||||
|
||||
if (property && value) {
|
||||
result[property] = value; // 将属性和值添加到结果对象中
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
function parseHtml(html) {
|
||||
html = removeDOCTYPE(html);
|
||||
var stacks = [];
|
||||
var results = {
|
||||
node: 'root',
|
||||
children: []
|
||||
};
|
||||
HTMLParser(html, {
|
||||
start: function start(tag, attrs, unary) {
|
||||
var node = {
|
||||
name: tag
|
||||
};
|
||||
|
||||
if (attrs.length !== 0) {
|
||||
node.attrs = parseAttrs(attrs);
|
||||
node.styles = node.attrs.style ? convertStyleStringToJSON(node.attrs.style) : {}
|
||||
}
|
||||
|
||||
if(!node.type) {
|
||||
if(inline[node.name] && node.name !== 'img' ) {
|
||||
node.type = 'text';
|
||||
if(node.name == 'br') {
|
||||
node.text = '\n'
|
||||
} else if(node.name == 'strong'){
|
||||
node.styles.fontWeight = 'bold'
|
||||
}
|
||||
} else if(node.name == 'img'){
|
||||
node.type = 'image'
|
||||
node.src = node.attrs.src
|
||||
} else {
|
||||
node.type = 'view'
|
||||
if(['h1','h2','h3','h4','h5','h6'].includes(node.name)) {
|
||||
node.styles.fontWeight = 'bold'
|
||||
}
|
||||
}
|
||||
}
|
||||
if (unary) {
|
||||
var parent = stacks[0] || results;
|
||||
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
|
||||
parent.children.push(node);
|
||||
} else {
|
||||
stacks.unshift(node);
|
||||
}
|
||||
},
|
||||
end: function end(tag) {
|
||||
var node = stacks.shift();
|
||||
if (node.name !== tag) console.error('invalid state: mismatch end tag');
|
||||
if (stacks.length === 0) {
|
||||
results.children.push(node);
|
||||
} else {
|
||||
var parent = stacks[0];
|
||||
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
parent.children.push(node);
|
||||
}
|
||||
const isTextBox = node.children && node.children.length > 1 && node.children.every(child => {
|
||||
return ['text','image'].includes(child.type)
|
||||
})
|
||||
if(isTextBox) {
|
||||
node.type = 'textBox'
|
||||
}
|
||||
},
|
||||
chars: function chars(text) {
|
||||
var node = {
|
||||
type: 'text',
|
||||
text: text
|
||||
};
|
||||
|
||||
if (stacks.length === 0) {
|
||||
results.children.push(node);
|
||||
} else {
|
||||
var parent = stacks[0];
|
||||
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
|
||||
parent.children.push(node);
|
||||
}
|
||||
},
|
||||
comment: function comment(text) {
|
||||
var node = {
|
||||
node: 'comment',
|
||||
text: text
|
||||
};
|
||||
var parent = stacks[0];
|
||||
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
|
||||
parent.children.push(node);
|
||||
}
|
||||
});
|
||||
return results.children;
|
||||
}
|
||||
|
||||
export default parseHtml;
|
||||
@@ -1,961 +0,0 @@
|
||||
# Painter 画板 测试版
|
||||
|
||||
> uniapp 海报画板,更优雅的海报生成方案
|
||||
> [查看更多](https://limeui.qcoon.cn/#/painter)
|
||||
|
||||
## 平台兼容
|
||||
|
||||
| H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ 小程序 | App |
|
||||
| --- | ---------- | ------------ | ---------- | ---------- | --------- | --- |
|
||||
| √ | √ | √ | 未测 | √ | √ | √ |
|
||||
|
||||
## 安装
|
||||
在市场导入**[海报画板](https://ext.dcloud.net.cn/plugin?id=2389)uni_modules**版本的即可,无需`import`
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 插件demo
|
||||
- lime-painter 为 demo
|
||||
- 位于 uni_modules/lime-painter/components/lime-painter
|
||||
- 导入插件后直接使用可查看demo
|
||||
```vue
|
||||
<lime-painter />
|
||||
```
|
||||
|
||||
|
||||
### 基本用法
|
||||
|
||||
- 插件提供 JSON 及 Template 的方式绘制海报
|
||||
- 参考 css 块状流布局模拟 css schema。
|
||||
- 另外flex布局还不是成完善,请谨慎使用,普通的流布局我觉得已经够用了。
|
||||
|
||||
#### 方式一 Template
|
||||
|
||||
- 提供`l-painter-view`、`l-painter-text`、`l-painter-image`、`l-painter-qrcode`四种类型组件
|
||||
- 通过 `css` 属性绘制样式,与 style 使用方式保持一致。
|
||||
```html
|
||||
<l-painter>
|
||||
//如果使用Template出现顺序错乱,可使用`template` 等所有变量完成再显示
|
||||
<template v-if="show">
|
||||
<l-painter-view
|
||||
css="background: #07c160; height: 120rpx; width: 120rpx; display: inline-block"
|
||||
></l-painter-view>
|
||||
<l-painter-view
|
||||
css="background: #1989fa; height: 120rpx; width: 120rpx; border-top-right-radius: 60rpx; border-bottom-left-radius: 60rpx; display: inline-block; margin: 0 30rpx;"
|
||||
></l-painter-view>
|
||||
<l-painter-view
|
||||
css="background: #ff9d00; height: 120rpx; width: 120rpx; border-radius: 50%; display: inline-block"
|
||||
></l-painter-view>
|
||||
<template>
|
||||
</l-painter>
|
||||
```
|
||||
|
||||
#### 方式二 JSON
|
||||
|
||||
- 在 json 里四种类型组件的`type`为`view`、`text`、`image`、`qrcode`
|
||||
- 通过 `board` 设置海报所需的 JSON 数据进行绘制或`ref`获取组件实例调用组件内的`render(json)`
|
||||
- 所有类型的 schema 都具有`css`字段,css 的 key 值使用**驼峰**如:`lineHeight`
|
||||
|
||||
```html
|
||||
<l-painter :board="poster"/>
|
||||
```
|
||||
|
||||
```js
|
||||
data() {
|
||||
return {
|
||||
poster: {
|
||||
css: {
|
||||
// 根节点若无尺寸,自动获取父级节点
|
||||
width: '750rpx'
|
||||
},
|
||||
views: [
|
||||
{
|
||||
css: {
|
||||
background: "#07c160",
|
||||
height: "120rpx",
|
||||
width: "120rpx",
|
||||
display: "inline-block"
|
||||
},
|
||||
type: "view"
|
||||
},
|
||||
{
|
||||
css: {
|
||||
background: "#1989fa",
|
||||
height: "120rpx",
|
||||
width: "120rpx",
|
||||
borderTopRightRadius: "60rpx",
|
||||
borderBottomLeftRadius: "60rpx",
|
||||
display: "inline-block",
|
||||
margin: "0 30rpx"
|
||||
},
|
||||
views: [],
|
||||
type: "view"
|
||||
},
|
||||
{
|
||||
css: {
|
||||
background: "#ff9d00",
|
||||
height: "120rpx",
|
||||
width: "120rpx",
|
||||
borderRadius: "50%",
|
||||
display: "inline-block"
|
||||
},
|
||||
views: [],
|
||||
type: "view"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### View 容器
|
||||
|
||||
- 类似于 `div` 可以嵌套承载更多的 view、text、image,qrcode 共同构建一颗完整的节点树
|
||||
- 在 JSON 里具有 `views` 的数组字段,用于嵌套承载节点。
|
||||
|
||||
#### 方式一 Template
|
||||
|
||||
```html
|
||||
<l-painter>
|
||||
<l-painter-view css="background: #f0f0f0; padding-top: 100rpx;">
|
||||
<l-painter-view
|
||||
css="background: #d9d9d9; width: 33.33%; height: 100rpx; display: inline-block"
|
||||
></l-painter-view>
|
||||
<l-painter-view
|
||||
css="background: #bfbfbf; width: 66.66%; height: 100rpx; display: inline-block"
|
||||
></l-painter-view>
|
||||
</l-painter-view>
|
||||
</l-painter>
|
||||
```
|
||||
|
||||
#### 方式二 JSON
|
||||
|
||||
```js
|
||||
{
|
||||
css: {},
|
||||
views: [
|
||||
{
|
||||
type: 'view',
|
||||
css: {
|
||||
background: '#f0f0f0',
|
||||
paddingTop: '100rpx'
|
||||
},
|
||||
views: [
|
||||
{
|
||||
type: 'view',
|
||||
css: {
|
||||
background: '#d9d9d9',
|
||||
width: '33.33%',
|
||||
height: '100rpx',
|
||||
display: 'inline-block'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'view',
|
||||
css: {
|
||||
background: '#bfbfbf',
|
||||
width: '66.66%',
|
||||
height: '100rpx',
|
||||
display: 'inline-block'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Text 文本
|
||||
|
||||
- 通过 `text` 属性填写文本内容。
|
||||
- 支持`\n`换行符
|
||||
- 支持省略号,使用 css 的`line-clamp`设置行数,当文字内容超过会显示省略号。
|
||||
- 支持`text-decoration`
|
||||
|
||||
#### 方式一 Template
|
||||
|
||||
```html
|
||||
<l-painter>
|
||||
<l-painter-view css="background: #e0e2db; padding: 30rpx; color: #222a29">
|
||||
<l-painter-text
|
||||
text="登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼"
|
||||
/>
|
||||
<l-painter-text
|
||||
text="登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼"
|
||||
css="text-align:center; padding-top: 20rpx; text-decoration: line-through "
|
||||
/>
|
||||
<l-painter-text
|
||||
text="登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼"
|
||||
css="text-align:right; padding-top: 20rpx"
|
||||
/>
|
||||
<l-painter-text
|
||||
text="水调歌头\n明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。"
|
||||
css="line-clamp: 3; padding-top: 20rpx; background: linear-gradient(,#ff971b 0%, #ff5000 100%); background-clip: text"
|
||||
/>
|
||||
</l-painter-view>
|
||||
</l-painter>
|
||||
```
|
||||
|
||||
#### 方式二 JSON
|
||||
|
||||
```js
|
||||
// 基础用法
|
||||
{
|
||||
type: 'text',
|
||||
text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
|
||||
css: {
|
||||
// 设置居中对齐
|
||||
textAlign: 'center',
|
||||
// 设置中划线
|
||||
textDecoration: 'line-through'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
|
||||
css: {
|
||||
// 设置右对齐
|
||||
textAlign: 'right',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
|
||||
css: {
|
||||
// 设置行数,超出显示省略号
|
||||
lineClamp: 3,
|
||||
// 渐变文字
|
||||
background: 'linear-gradient(,#ff971b 0%, #1989fa 100%)',
|
||||
backgroundClip: 'text'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Image 图片
|
||||
|
||||
- 通过 `src` 属性填写图片路径。
|
||||
- 图片路径支持:网络图片,本地 static 里的图片路径,缓存路径,**字节的static目录是写相对路径**
|
||||
- 通过 `css` 的 `object-fit`属性可以设置图片的填充方式,可选值见下方 CSS 表格。
|
||||
- 通过 `css` 的 `object-position`配合 `object-fit` 可以设置图片的对齐方式,类似于`background-position`,详情见下方 CSS 表格。
|
||||
- 使用网络图片时:小程序需要去公众平台配置 [downloadFile](https://mp.weixin.qq.com/) 域名
|
||||
- 使用网络图片时:**H5 和 Nvue 需要决跨域问题**
|
||||
|
||||
#### 方式一 Template
|
||||
|
||||
```html
|
||||
<l-painter>
|
||||
<!-- 基础用法 -->
|
||||
<l-painter-image
|
||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
||||
css="width: 200rpx; height: 200rpx"
|
||||
/>
|
||||
<!-- 填充方式 -->
|
||||
<!-- css object-fit 设置 填充方式 见下方表格-->
|
||||
<l-painter-image
|
||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
||||
css="width: 200rpx; height: 200rpx; object-fit: contain; background: #eee"
|
||||
/>
|
||||
<!-- css object-position 设置 图片的对齐方式-->
|
||||
<l-painter-image
|
||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
||||
css="width: 200rpx; height: 200rpx; object-fit: contain; object-position: 50% 50%; background: #eee"
|
||||
/>
|
||||
</l-painter>
|
||||
```
|
||||
|
||||
#### 方式二 JSON
|
||||
|
||||
```js
|
||||
// 基础用法
|
||||
{
|
||||
type: 'image',
|
||||
src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg',
|
||||
css: {
|
||||
width: '200rpx',
|
||||
height: '200rpx'
|
||||
}
|
||||
},
|
||||
// 填充方式
|
||||
// css objectFit 设置 填充方式 见下方表格
|
||||
{
|
||||
type: 'image',
|
||||
src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg',
|
||||
css: {
|
||||
width: '200rpx',
|
||||
height: '200rpx',
|
||||
objectFit: 'contain'
|
||||
}
|
||||
},
|
||||
// css objectPosition 设置 图片的对齐方式
|
||||
{
|
||||
type: 'image',
|
||||
src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg',
|
||||
css: {
|
||||
width: '200rpx',
|
||||
height: '200rpx',
|
||||
objectFit: 'contain',
|
||||
objectPosition: '50% 50%'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Qrcode 二维码
|
||||
|
||||
- 通过`text`属性填写需要生成二维码的文本。
|
||||
- 通过 `css` 里的 `color` 可设置生成码点的颜色。
|
||||
- 通过 `css` 里的 `background`可设置背景色。
|
||||
- 通过 `css `里的 `width`、`height`设置尺寸。
|
||||
|
||||
#### 方式一 Template
|
||||
|
||||
```html
|
||||
<l-painter>
|
||||
<l-painter-qrcode
|
||||
text="limeui.qcoon.cn"
|
||||
css="width: 200rpx; height: 200rpx"
|
||||
/>
|
||||
</l-painter>
|
||||
```
|
||||
|
||||
#### 方式二 JSON
|
||||
|
||||
```js
|
||||
{
|
||||
type: 'qrcode',
|
||||
text: 'limeui.qcoon.cn',
|
||||
css: {
|
||||
width: '200rpx',
|
||||
height: '200rpx',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 富文本
|
||||
- 这是一个有限支持的测试能力,只能通过JSON方式,不要抱太大希望!
|
||||
- 首先需要把富文本转成JSON,这需要引入`parser`这个包,如果你不使用是不会进入主包
|
||||
|
||||
```html
|
||||
<l-painter ref="painter"/>
|
||||
```
|
||||
```js
|
||||
import parseHtml from '@/uni_modules/lime-painter/parser'
|
||||
const json = parseHtml(`<p><span>测试测试</span><img src="/static/logo.png"/></p>`)
|
||||
this.$refs.painter.render(json)
|
||||
```
|
||||
|
||||
### 生成图片
|
||||
|
||||
- 方式1、通过设置`isCanvasToTempFilePath`自动生成图片并在 `@success` 事件里接收海报临时路径
|
||||
- 方式2、通过调用内部方法生成图片:
|
||||
|
||||
```html
|
||||
<l-painter ref="painter">...code</l-painter>
|
||||
```
|
||||
|
||||
```js
|
||||
this.$refs.painter.canvasToTempFilePathSync({
|
||||
fileType: "jpg",
|
||||
// 如果返回的是base64是无法使用 saveImageToPhotosAlbum,需要设置 pathType为url
|
||||
pathType: 'url',
|
||||
quality: 1,
|
||||
success: (res) => {
|
||||
console.log(res.tempFilePath);
|
||||
// 非H5 保存到相册
|
||||
// H5 提示用户长按图另存
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: function () {
|
||||
console.log('save success');
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### 主动调用方式
|
||||
|
||||
- 通过获取组件实例内部的`render`函数 传递`JSON`即可
|
||||
|
||||
```html
|
||||
<l-painter ref="painter" />
|
||||
```
|
||||
|
||||
```js
|
||||
// 渲染
|
||||
this.$refs.painter.render(jsonSchema);
|
||||
// 生成图片
|
||||
this.$refs.painter.canvasToTempFilePathSync({
|
||||
fileType: "jpg",
|
||||
// 如果返回的是base64是无法使用 saveImageToPhotosAlbum,需要设置 pathType为url
|
||||
pathType: 'url',
|
||||
quality: 1,
|
||||
success: (res) => {
|
||||
console.log(res.tempFilePath);
|
||||
// 非H5 保存到相册
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: function () {
|
||||
console.log('save success');
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
### H5跨域
|
||||
- 一般是需要后端或管理OSS资源的大佬处理
|
||||
- 一般OSS的处理方式:
|
||||
|
||||
1、设置来源
|
||||
```cmd
|
||||
*
|
||||
```
|
||||
|
||||
2、允许Methods
|
||||
```html
|
||||
GET
|
||||
```
|
||||
|
||||
3、允许Headers
|
||||
```html
|
||||
access-control-allow-origin:*
|
||||
```
|
||||
|
||||
4、最后如果还是不行,可试下给插件设置`useCORS`
|
||||
```html
|
||||
<l-painter useCORS>
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 海报示例
|
||||
|
||||
- 提供一份示例,只把插件当成生成图片的工具,非必要不要在弹窗里使用。
|
||||
- 通过设置`isCanvasToTempFilePath`主动生成图片,再由 `@success` 事件接收海报临时路径
|
||||
- 设置`hidden`隐藏画板。
|
||||
请注意,示例用到了图片,海报的渲染是包括下载图片的时间,也许在某天图片会失效或访问超级慢,请更换为你的图片再查看,另外如果你是小程序请在使用示例时把**不校验合法域名**勾上!!!!!不然不显示还以为是插件的锅,求求了大佬们!
|
||||
#### 方式一 Template
|
||||
|
||||
```html
|
||||
<image :src="path" mode="widthFix"></image>
|
||||
<l-painter
|
||||
isCanvasToTempFilePath
|
||||
@success="path = $event"
|
||||
hidden
|
||||
css="width: 750rpx; padding-bottom: 40rpx; background: linear-gradient(,#ff971b 0%, #ff5000 100%)"
|
||||
>
|
||||
<l-painter-image
|
||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
||||
css="margin-left: 40rpx; margin-top: 40rpx; width: 84rpx; height: 84rpx; border-radius: 50%;"
|
||||
/>
|
||||
<l-painter-view
|
||||
css="margin-top: 40rpx; padding-left: 20rpx; display: inline-block"
|
||||
>
|
||||
<l-painter-text
|
||||
text="隔壁老王"
|
||||
css="display: block; padding-bottom: 10rpx; color: #fff; font-size: 32rpx; fontWeight: bold"
|
||||
/>
|
||||
<l-painter-text
|
||||
text="为您挑选了一个好物"
|
||||
css="color: rgba(255,255,255,.7); font-size: 24rpx"
|
||||
/>
|
||||
</l-painter-view>
|
||||
<l-painter-view
|
||||
css="margin-left: 40rpx; margin-top: 30rpx; padding: 32rpx; box-sizing: border-box; background: #fff; border-radius: 16rpx; width: 670rpx; box-shadow: 0 20rpx 58rpx rgba(0,0,0,.15)"
|
||||
>
|
||||
<l-painter-image
|
||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
||||
css="object-fit: cover; object-position: 50% 50%; width: 606rpx; height: 606rpx; border-radius: 12rpx;"
|
||||
/>
|
||||
<l-painter-view
|
||||
css="margin-top: 32rpx; color: #FF0000; font-weight: bold; font-size: 28rpx; line-height: 1em;"
|
||||
>
|
||||
<l-painter-text text="¥" css="vertical-align: bottom" />
|
||||
<l-painter-text
|
||||
text="39"
|
||||
css="vertical-align: bottom; font-size: 58rpx"
|
||||
/>
|
||||
<l-painter-text text=".39" css="vertical-align: bottom" />
|
||||
<l-painter-text
|
||||
text="¥59.99"
|
||||
css="vertical-align: bottom; padding-left: 10rpx; font-weight: normal; text-decoration: line-through; color: #999999"
|
||||
/>
|
||||
</l-painter-view>
|
||||
<l-painter-view css="margin-top: 32rpx; font-size: 26rpx; color: #8c5400">
|
||||
<l-painter-text text="自营" css="color: #212121; background: #ffb400;" />
|
||||
<l-painter-text
|
||||
text="30天最低价"
|
||||
css="margin-left: 16rpx; background: #fff4d9; text-decoration: line-through;"
|
||||
/>
|
||||
<l-painter-text
|
||||
text="满减优惠"
|
||||
css="margin-left: 16rpx; background: #fff4d9"
|
||||
/>
|
||||
<l-painter-text
|
||||
text="超高好评"
|
||||
css="margin-left: 16rpx; background: #fff4d9"
|
||||
/>
|
||||
</l-painter-view>
|
||||
<l-painter-view css="margin-top: 30rpx">
|
||||
<l-painter-text
|
||||
css="line-clamp: 2; color: #333333; line-height: 1.8em; font-size: 36rpx; width: 478rpx; padding-right:32rpx; box-sizing: border-box"
|
||||
text="360儿童电话手表9X 智能语音问答定位支付手表 4G全网通20米游泳级防水视频通话拍照手表男女孩星空蓝"
|
||||
></l-painter-text>
|
||||
<l-painter-qrcode
|
||||
css="width: 128rpx; height: 128rpx;"
|
||||
text="limeui.qcoon.cn"
|
||||
></l-painter-qrcode>
|
||||
</l-painter-view>
|
||||
</l-painter-view>
|
||||
</l-painter>
|
||||
```
|
||||
|
||||
```js
|
||||
data() {
|
||||
return {
|
||||
path: ''
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 方式二 JSON
|
||||
|
||||
```html
|
||||
<image :src="path" mode="widthFix"></image>
|
||||
<l-painter
|
||||
:board="poster"
|
||||
isCanvasToTempFilePath
|
||||
@success="path = $event"
|
||||
hidden
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
data() {
|
||||
return {
|
||||
path: '',
|
||||
poster: {
|
||||
css: {
|
||||
width: "750rpx",
|
||||
paddingBottom: "40rpx",
|
||||
background: "linear-gradient(,#000 0%, #ff5000 100%)"
|
||||
},
|
||||
views: [
|
||||
{
|
||||
src: "https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg",
|
||||
type: "image",
|
||||
css: {
|
||||
background: "#fff",
|
||||
objectFit: "cover",
|
||||
marginLeft: "40rpx",
|
||||
marginTop: "40rpx",
|
||||
width: "84rpx",
|
||||
border: "2rpx solid #fff",
|
||||
boxSizing: "border-box",
|
||||
height: "84rpx",
|
||||
borderRadius: "50%"
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "view",
|
||||
css: {
|
||||
marginTop: "40rpx",
|
||||
paddingLeft: "20rpx",
|
||||
display: "inline-block"
|
||||
},
|
||||
views: [
|
||||
{
|
||||
text: "隔壁老王",
|
||||
type: "text",
|
||||
css: {
|
||||
display: "block",
|
||||
paddingBottom: "10rpx",
|
||||
color: "#fff",
|
||||
fontSize: "32rpx",
|
||||
fontWeight: "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "为您挑选了一个好物",
|
||||
type: "text",
|
||||
css: {
|
||||
color: "rgba(255,255,255,.7)",
|
||||
fontSize: "24rpx"
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
css: {
|
||||
marginLeft: "40rpx",
|
||||
marginTop: "30rpx",
|
||||
padding: "32rpx",
|
||||
boxSizing: "border-box",
|
||||
background: "#fff",
|
||||
borderRadius: "16rpx",
|
||||
width: "670rpx",
|
||||
boxShadow: "0 20rpx 58rpx rgba(0,0,0,.15)"
|
||||
},
|
||||
views: [
|
||||
{
|
||||
src: "https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg",
|
||||
type: "image",
|
||||
css: {
|
||||
objectFit: "cover",
|
||||
objectPosition: "50% 50%",
|
||||
width: "606rpx",
|
||||
height: "606rpx"
|
||||
},
|
||||
}, {
|
||||
css: {
|
||||
marginTop: "32rpx",
|
||||
color: "#FF0000",
|
||||
fontWeight: "bold",
|
||||
fontSize: "28rpx",
|
||||
lineHeight: "1em"
|
||||
},
|
||||
views: [{
|
||||
text: "¥",
|
||||
type: "text",
|
||||
css: {
|
||||
verticalAlign: "bottom"
|
||||
},
|
||||
}, {
|
||||
text: "39",
|
||||
type: "text",
|
||||
css: {
|
||||
verticalAlign: "bottom",
|
||||
fontSize: "58rpx"
|
||||
},
|
||||
}, {
|
||||
text: ".39",
|
||||
type: "text",
|
||||
css: {
|
||||
verticalAlign: "bottom"
|
||||
},
|
||||
}, {
|
||||
text: "¥59.99",
|
||||
type: "text",
|
||||
css: {
|
||||
verticalAlign: "bottom",
|
||||
paddingLeft: "10rpx",
|
||||
fontWeight: "normal",
|
||||
textDecoration: "line-through",
|
||||
color: "#999999"
|
||||
}
|
||||
}],
|
||||
|
||||
type: "view"
|
||||
}, {
|
||||
css: {
|
||||
marginTop: "32rpx",
|
||||
fontSize: "26rpx",
|
||||
color: "#8c5400"
|
||||
},
|
||||
views: [{
|
||||
text: "自营",
|
||||
type: "text",
|
||||
css: {
|
||||
color: "#212121",
|
||||
background: "#ffb400"
|
||||
},
|
||||
}, {
|
||||
text: "30天最低价",
|
||||
type: "text",
|
||||
css: {
|
||||
marginLeft: "16rpx",
|
||||
background: "#fff4d9",
|
||||
textDecoration: "line-through"
|
||||
},
|
||||
}, {
|
||||
text: "满减优惠",
|
||||
type: "text",
|
||||
css: {
|
||||
marginLeft: "16rpx",
|
||||
background: "#fff4d9"
|
||||
},
|
||||
}, {
|
||||
text: "超高好评",
|
||||
type: "text",
|
||||
css: {
|
||||
marginLeft: "16rpx",
|
||||
background: "#fff4d9"
|
||||
},
|
||||
|
||||
}],
|
||||
|
||||
type: "view"
|
||||
}, {
|
||||
css: {
|
||||
marginTop: "30rpx"
|
||||
},
|
||||
views: [
|
||||
{
|
||||
text: "360儿童电话手表9X 智能语音问答定位支付手表 4G全网通20米游泳级防水视频通话拍照手表男女孩星空蓝",
|
||||
type: "text",
|
||||
css: {
|
||||
paddingRight: "32rpx",
|
||||
boxSizing: "border-box",
|
||||
lineClamp: 2,
|
||||
color: "#333333",
|
||||
lineHeight: "1.8em",
|
||||
fontSize: "36rpx",
|
||||
width: "478rpx"
|
||||
},
|
||||
}, {
|
||||
text: "limeui.qcoon.cn",
|
||||
type: "qrcode",
|
||||
css: {
|
||||
width: "128rpx",
|
||||
height: "128rpx",
|
||||
},
|
||||
|
||||
}],
|
||||
type: "view"
|
||||
}],
|
||||
type: "view"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 自定义字体
|
||||
- 需要平台的支持,已知微信小程序支持,其它的没试过,如果可行请告之
|
||||
|
||||
```
|
||||
// 需要在app.vue中下载字体
|
||||
uni.loadFontFace({
|
||||
global:true,
|
||||
scopes: ['native'],
|
||||
family: '自定义字体名称',
|
||||
source: 'url("https://sungd.github.io/Pacifico.ttf")',
|
||||
|
||||
success() {
|
||||
console.log('success')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// 然后就可以在插件的css中写font-family: '自定义字体名称'
|
||||
```
|
||||
|
||||
|
||||
### Nvue
|
||||
- 必须为HBX 3.4.11及以上
|
||||
|
||||
|
||||
### 原生小程序
|
||||
|
||||
- 插件里的`painter.js`支持在原生小程序中使用
|
||||
- new Painter 之后在`source`里传入 JSON
|
||||
- 再调用`render`绘制海报
|
||||
- 如需生成图片,请查看微信小程序 cavnas 的[canvasToTempFilePath](https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.canvasToTempFilePath.html)
|
||||
|
||||
```html
|
||||
<canvas type="2d" id="painter" style="width: 100%"></canvas>
|
||||
```
|
||||
|
||||
```js
|
||||
import { Painter } from "./painter";
|
||||
page({
|
||||
data: {
|
||||
poster: {
|
||||
css: {
|
||||
width: "750rpx",
|
||||
},
|
||||
views: [
|
||||
{
|
||||
type: "view",
|
||||
css: {
|
||||
background: "#d2d4c8",
|
||||
paddingTop: "100rpx",
|
||||
},
|
||||
views: [
|
||||
{
|
||||
type: "view",
|
||||
css: {
|
||||
background: "#5f7470",
|
||||
width: "33.33%",
|
||||
height: "100rpx",
|
||||
display: "inline-block",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "view",
|
||||
css: {
|
||||
background: "#889696",
|
||||
width: "33.33%",
|
||||
height: "100rpx",
|
||||
display: "inline-block",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "view",
|
||||
css: {
|
||||
background: "#b8bdb5",
|
||||
width: "33.33%",
|
||||
height: "100rpx",
|
||||
display: "inline-block",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
async onLoad() {
|
||||
const res = await this.getCentext();
|
||||
const painter = new Painter(res);
|
||||
// 返回计算布局后的整个内容尺寸
|
||||
const { width, height } = await painter.source(this.data.poster);
|
||||
// 得到计算后的尺寸后 可给canvas尺寸赋值,达到动态响应效果
|
||||
// 渲染
|
||||
await painter.render();
|
||||
},
|
||||
// 获取canvas 2d
|
||||
// 非2d 需要传一个 createImage 方法用于获取图片信息 即把 getImageInfo 的 success 通过 promise resolve 返回
|
||||
getCentext() {
|
||||
return new Promise((resolve) => {
|
||||
wx.createSelectorQuery()
|
||||
.select(`#painter`)
|
||||
.node()
|
||||
.exec((res) => {
|
||||
let { node: canvas } = res[0];
|
||||
resolve({
|
||||
canvas,
|
||||
context: canvas.getContext("2d"),
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
// createImage: getImageInfo()
|
||||
pixelRatio: 2,
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### 旧版(1.6.x)更新
|
||||
|
||||
- 由于 1.8.x 版放弃了以定位的方式,所以 1.6.x 版更新之后要每个样式都加上`position: absolute`
|
||||
- 旧版的 `image` mode 模式被放弃,使用`object-fit`
|
||||
- 旧版的 `isRenderImage` 改成 `is-canvas-to-temp-file-path`
|
||||
- 旧版的 `maxLines` 改成 `line-clamp`
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| -------------------------- | ------------------------------------------------------------ | ---------------- | ------------ |
|
||||
| board | JSON 方式的海报元素对象集 | <em>object</em> | - |
|
||||
| css | 海报内容最外层的样式,可以理解为`body` | <em>object</em> | 参数请向下看 |
|
||||
| custom-style | canvas 元素的样式 | <em>string</em> | |
|
||||
| hidden | 隐藏画板 | <em>boolean</em> | `false` |
|
||||
| is-canvas-to-temp-file-path | 是否生成图片,在`@success`事件接收图片地址 | <em>boolean</em> | `false` |
|
||||
| after-delay | 生成图片错乱,可延时生成图片 | <em>number</em> | `100` |
|
||||
| type | canvas 类型,对微信头条支付宝小程序可有效,可选值:`2d`,`''` | <em>string</em> | `2d` |
|
||||
| file-type | 生成图片的后缀类型, 可选值:`png`、`jpg` | <em>string</em> | `png` |
|
||||
| path-type | 生成图片路径类型,可选值`url`、`base64` | <em>string</em> | `-` |
|
||||
| pixel-ratio | 生成图片的像素密度,默认为对应手机的像素密度,`nvue`无效 | <em>number</em> | `-` |
|
||||
| hidpi | H5和APP是否使用高清处理 | <em>boolean</em> | `true` |
|
||||
| width | **废弃** 画板的宽度,一般只用于通过内部方法时加上 | <em>number</em> | `` |
|
||||
| height | **废弃** 画板的高度 ,同上 | <em>number</em> | `` |
|
||||
|
||||
### css
|
||||
| 属性名 | 支持的值或类型 | 默认值 |
|
||||
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- |
|
||||
| (min\max)width | 支持`%`、`rpx`、`px` | - |
|
||||
| height | 同上 | - |
|
||||
| color | `string` | - |
|
||||
| position | 定位,可选值:`absolute`、`fixed` | - |
|
||||
| ↳ left、top、right、bottom | 配合`position`才生效,支持`%`、`rpx`、`px` | - |
|
||||
| margin | 可简写或各方向分别写,如:`margin-top`,支持`auto`、`rpx`、`px` | - |
|
||||
| padding | 可简写或各方向分别写,支持`rpx`、`px` | - |
|
||||
| border | 可简写或各个值分开写:`border-width`、`border-style` 、`border-color`,简写请按顺序写 | - |
|
||||
| line-clamp | `number`,超过行数显示省略号 | - |
|
||||
| vertical-align | 文字垂直对齐,可选值:`bottom`、`top`、`middle` | `middle` |
|
||||
| line-height | 文字行高,支持`rpx`、`px`、`em` | `1.4em` |
|
||||
| font-weight | 文字粗细,可选值:`normal`、`bold` | `normal` |
|
||||
| font-size | 文字大小,`string`,支持`rpx`、`px` | `14px` |
|
||||
| text-decoration | 文本修饰,可选值:`underline` 、`line-through`、`overline` | - |
|
||||
| text-stroke | 文字描边,可简写或各个值分开写,如:`text-stroke-color`, `text-stroke-width` | - |
|
||||
| text-align | 文本水平对齐,可选值:`right` 、`center` | `left` |
|
||||
| display | 框类型,可选值:`block`、`inline-block`、`flex`、`none`,当为`none`时是不渲染该段, `flex`功能简陋。 | - |
|
||||
| flex | 配合 display: flex; 属性定义了在分配多余空间,目前只用为数值如: flex: 1 | - |
|
||||
| align-self | 配合 display: flex; 单个项目垂直轴对齐方式: `flex-start` `flex-end` `center` | `flex-start` |
|
||||
| justify-content | 配合 display: flex; 水平轴对齐方式: `flex-start` `flex-end` `center` | `flex-start` |
|
||||
| align-items | 配合 display: flex; 垂直轴对齐方式: `flex-start` `flex-end` `center` | `flex-start` |
|
||||
| border-radius | 圆角边框,支持`%`、`rpx`、`px` | - |
|
||||
| box-sizing | 可选值:`border-box` | - |
|
||||
| box-shadow | 投影 | - |
|
||||
| background(color) | 支持渐变,但必须写百分比!如:`linear-gradient(,#ff971b 0%, #ff5000 100%)`、`radial-gradient(#0ff 15%, #f0f 60%)`,目前 radial-gradient 渐变的圆心为元素中点,半径为最长边,不支持设置 | - |
|
||||
| background-clip | 文字渐变,配合`background`背景渐变,设置`background-clip: text` 达到文字渐变效果 | - |
|
||||
| background-image | view 元素背景:`url(src)`,若只是设置背景图,请不要设置`background-repeat` | - |
|
||||
| background-repeat | 设置是否及如何重复背景纹理,可选值:`repeat`、`repeat-x`、`repeat-y`、`no-repeat` | `repeat` |
|
||||
| [object-fit](https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-fit/) | 图片元素适应容器方式,类似于`mode`,可选值:`cover`、 `contain`、 `fill`、 `none` | - |
|
||||
| [object-position](https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-position) | 图片的对齐方式,配合`object-fit`使用 | - |
|
||||
|
||||
### 图片填充模式 object-fit
|
||||
|
||||
| 名称 | 含义 |
|
||||
| ------- | ------------------------------------------------------ |
|
||||
| contain | 保持宽高缩放图片,使图片的长边能完全显示出来 |
|
||||
| cover | 保持宽高缩放图片,使图片的短边能完全显示出来,裁剪长边 |
|
||||
| fill | 拉伸图片,使图片填满元素 |
|
||||
| none | 保持图片原有尺寸 |
|
||||
|
||||
### 事件 Events
|
||||
|
||||
| 事件名 | 说明 | 返回值 |
|
||||
| -------- | ---------------------------------------------------------------- | ------ |
|
||||
| success | 生成图片成功,若使用`is-canvas-to-temp-filePath` 可以接收图片地址 | path |
|
||||
| fail | 生成图片失败 | error |
|
||||
| done | 绘制成功 | |
|
||||
| progress | 绘制进度 | number |
|
||||
|
||||
### 暴露函数 Expose
|
||||
| 事件名 | 说明 | 返回值 |
|
||||
| -------- | ---------------------------------------------------------------- | ------ |
|
||||
| render(object) | 渲染器,传入JSON 绘制海报 | promise |
|
||||
| [canvasToTempFilePath](https://uniapp.dcloud.io/api/canvas/canvasToTempFilePath.html#canvastotempfilepath)(object) | 把当前画布指定区域的内容导出生成指定大小的图片,并返回文件临时路径。 | |
|
||||
| canvasToTempFilePathSync(object) | 同步接口,同上 | |
|
||||
|
||||
|
||||
## 常见问题
|
||||
|
||||
- 1、H5 端使用网络图片需要解决跨域问题。
|
||||
- 2、小程序使用网络图片需要去公众平台增加下载白名单!二级域名也需要配!
|
||||
- 3、H5 端生成图片是 base64,有时显示只有一半可以使用原生标签`<IMG/>`
|
||||
- 4、发生保存图片倾斜变形或提示 native buffer exceed size limit 时,使用 pixel-ratio="2"参数,降分辨率。
|
||||
- 5、h5 保存图片不需要调接口,提示用户长按图片保存。
|
||||
- 6、画板不能隐藏,包括`v-if`,`v-show`、`display:none`、`opacity:0`,另外也不要把画板放在弹窗里。如果需要隐藏画板请设置 `custom-style="position: fixed; left: 200%"`
|
||||
- 7、微信小程序真机调试请使用 **真机调试2.0**,不支持1.0。
|
||||
- 8、微信小程序打开调试时可以生但并闭无法生成时,这种情况一般是没有在公众号配置download域名
|
||||
- 9、HBX 3.4.5之前的版本不支持vue3
|
||||
- 10、在微信开发工具上 canvas 层级最高无法zindex,并不影响真机
|
||||
- 11、请不要导入非uni_modules插件
|
||||
- 12、关于QQ小程序 报 Propertyor method"toJSON"is not defined 请把基础库调到 1.50.3
|
||||
- 13、支付宝小程序 IDE 不支持 生成图片 请以真机调试结果为准
|
||||
- 14、返回值为字符串 `data:,` 大概是尺寸超过限制,设置 pixel-ratio="2"
|
||||
- 华为手机 APP 上无法生成图片,请使用 HBX2.9.11++(已过时,忽略这条)
|
||||
- IOS APP 请勿使用 HBX2.9.3.20201014 的版本!这个版本无法生成图片。(已过时,忽略这条)
|
||||
- 苹果微信 7.0.20 存在闪退和图片无法 onload 为微信 bug(已过时,忽略这条)
|
||||
- 微信小程序 IOS 旧接口 如父级设置圆角,子级也设会导致子级的失效,为旧接口BUG。
|
||||
- 微信小程序 安卓 旧接口 如使用图片必须加背景色,为旧接口BUG。
|
||||
- 微信小程序 安卓端 [图片可能在首次可以加载成功,再次加载会不触发任何事件](https://developers.weixin.qq.com/community/develop/doc/000ee2b8dacf4009337f51f4556800?highLine=canvas%25202d%2520createImage),临时解决方法是给图片加个时间戳
|
||||
## 打赏
|
||||
|
||||
如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
|
||||
|
||||

|
||||

|
||||
@@ -1,194 +0,0 @@
|
||||
## 为减小组件包的大小,默认组件包中不包含编辑、latex 公式等扩展功能,需要使用扩展功能的请参考下方的 插件扩展 栏的说明
|
||||
|
||||
## 功能介绍
|
||||
- 全端支持(含 `v3、NVUE`)
|
||||
- 支持丰富的标签(包括 `table`、`video`、`svg` 等)
|
||||
- 支持丰富的事件效果(自动预览图片、链接处理等)
|
||||
- 支持设置占位图(加载中、出错时、预览时)
|
||||
- 支持锚点跳转、长按复制等丰富功能
|
||||
- 支持大部分 *html* 实体
|
||||
- 丰富的插件(关键词搜索、内容编辑、`latex` 公式等)
|
||||
- 效率高、容错性强且轻量化
|
||||
|
||||
查看 [功能介绍](https://jin-yufeng.gitee.io/mp-html/#/overview/feature) 了解更多
|
||||
|
||||
## 使用方法
|
||||
- `uni_modules` 方式
|
||||
1. 点击右上角的 `使用 HBuilder X 导入插件` 按钮直接导入项目或点击 `下载插件 ZIP` 按钮下载插件包并解压到项目的 `uni_modules/mp-html` 目录下
|
||||
2. 在需要使用页面的 `(n)vue` 文件中添加
|
||||
```html
|
||||
<!-- 不需要引入,可直接使用 -->
|
||||
<mp-html :content="html" />
|
||||
```
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
html: '<div>Hello World!</div>'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
3. 需要更新版本时在 `HBuilder X` 中右键 `uni_modules/mp-html` 目录选择 `从插件市场更新` 即可
|
||||
|
||||
- 源码方式
|
||||
1. 从 [github](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 或 [gitee](https://gitee.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 下载源码
|
||||
插件市场的 **非 uni_modules 版本** 无法更新,不建议从插件市场获取
|
||||
2. 在需要使用页面的 `(n)vue` 文件中添加
|
||||
```html
|
||||
<mp-html :content="html" />
|
||||
```
|
||||
```javascript
|
||||
import mpHtml from '@/components/mp-html/mp-html'
|
||||
export default {
|
||||
// HBuilderX 2.5.5+ 可以通过 easycom 自动引入
|
||||
components: {
|
||||
mpHtml
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
html: '<div>Hello World!</div>'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- npm 方式
|
||||
1. 在项目根目录下执行
|
||||
```bash
|
||||
npm install mp-html
|
||||
```
|
||||
2. 在需要使用页面的 `(n)vue` 文件中添加
|
||||
```html
|
||||
<mp-html :content="html" />
|
||||
```
|
||||
```javascript
|
||||
import mpHtml from 'mp-html/dist/uni-app/components/mp-html/mp-html'
|
||||
export default {
|
||||
// 不可省略
|
||||
components: {
|
||||
mpHtml
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
html: '<div>Hello World!</div>'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
3. 需要更新版本时执行以下命令即可
|
||||
```bash
|
||||
npm update mp-html
|
||||
```
|
||||
|
||||
使用 *cli* 方式运行的项目,通过 *npm* 方式引入时,需要在 *vue.config.js* 中配置 *transpileDependencies*,详情可见 [#330](https://github.com/jin-yufeng/mp-html/issues/330#issuecomment-913617687)
|
||||
如果在 **nvue** 中使用还要将 `dist/uni-app/static` 目录下的内容拷贝到项目的 `static` 目录下,否则无法运行
|
||||
|
||||
查看 [快速开始](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart) 了解更多
|
||||
|
||||
## 组件属性
|
||||
|
||||
| 属性 | 类型 | 默认值 | 说明 |
|
||||
|:---:|:---:|:---:|---|
|
||||
| container-style | String | | 容器的样式([2.1.0+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v210)) |
|
||||
| content | String | | 用于渲染的 html 字符串 |
|
||||
| copy-link | Boolean | true | 是否允许外部链接被点击时自动复制 |
|
||||
| domain | String | | 主域名(用于链接拼接) |
|
||||
| error-img | String | | 图片出错时的占位图链接 |
|
||||
| lazy-load | Boolean | false | 是否开启图片懒加载 |
|
||||
| loading-img | String | | 图片加载过程中的占位图链接 |
|
||||
| pause-video | Boolean | true | 是否在播放一个视频时自动暂停其他视频 |
|
||||
| preview-img | Boolean | true | 是否允许图片被点击时自动预览 |
|
||||
| scroll-table | Boolean | false | 是否给每个表格添加一个滚动层使其能单独横向滚动 |
|
||||
| selectable | Boolean | false | 是否开启文本长按复制 |
|
||||
| set-title | Boolean | true | 是否将 title 标签的内容设置到页面标题 |
|
||||
| show-img-menu | Boolean | true | 是否允许图片被长按时显示菜单 |
|
||||
| tag-style | Object | | 设置标签的默认样式 |
|
||||
| use-anchor | Boolean | false | 是否使用锚点链接 |
|
||||
|
||||
查看 [属性](https://jin-yufeng.gitee.io/mp-html/#/basic/prop) 了解更多
|
||||
|
||||
## 组件事件
|
||||
|
||||
| 名称 | 触发时机 |
|
||||
|:---:|---|
|
||||
| load | dom 树加载完毕时 |
|
||||
| ready | 图片加载完毕时 |
|
||||
| error | 发生渲染错误时 |
|
||||
| imgtap | 图片被点击时 |
|
||||
| linktap | 链接被点击时 |
|
||||
| play | 音视频播放时 |
|
||||
|
||||
查看 [事件](https://jin-yufeng.gitee.io/mp-html/#/basic/event) 了解更多
|
||||
|
||||
## api
|
||||
组件实例上提供了一些 `api` 方法可供调用
|
||||
|
||||
| 名称 | 作用 |
|
||||
|:---:|---|
|
||||
| in | 将锚点跳转的范围限定在一个 scroll-view 内 |
|
||||
| navigateTo | 锚点跳转 |
|
||||
| getText | 获取文本内容 |
|
||||
| getRect | 获取富文本内容的位置和大小 |
|
||||
| setContent | 设置富文本内容 |
|
||||
| imgList | 获取所有图片的数组 |
|
||||
| pauseMedia | 暂停播放音视频([2.2.2+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v222)) |
|
||||
| setPlaybackRate | 设置音视频播放速率([2.4.0+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v240)) |
|
||||
|
||||
查看 [api](https://jin-yufeng.gitee.io/mp-html/#/advanced/api) 了解更多
|
||||
|
||||
## 插件扩展
|
||||
除基本功能外,本组件还提供了丰富的扩展,可按照需要选用
|
||||
|
||||
| 名称 | 作用 |
|
||||
|:---:|---|
|
||||
| audio | 音乐播放器 |
|
||||
| editable | 富文本 **编辑**([示例项目](https://mp-html.oss-cn-hangzhou.aliyuncs.com/editable.zip)) |
|
||||
| emoji | 解析 emoji |
|
||||
| highlight | 代码块高亮显示 |
|
||||
| markdown | 渲染 markdown |
|
||||
| search | 关键词搜索 |
|
||||
| style | 匹配 style 标签中的样式 |
|
||||
| txv-video | 使用腾讯视频 |
|
||||
| img-cache | 图片缓存 by [@PentaTea](https://github.com/PentaTea) |
|
||||
| latex | 渲染 latex 公式 by [@Zeng-J](https://github.com/Zeng-J) |
|
||||
|
||||
从插件市场导入的包中 **不含有** 扩展插件,使用插件需通过微信小程序 `富文本插件` 获取或参考以下方法进行打包:
|
||||
1. 获取完整组件包
|
||||
```bash
|
||||
npm install mp-html
|
||||
```
|
||||
2. 编辑 `tools/config.js` 中的 `plugins` 项,选择需要的插件
|
||||
3. 生成新的组件包
|
||||
在 `node_modules/mp-html` 目录下执行
|
||||
```bash
|
||||
npm install
|
||||
npm run build:uni-app
|
||||
```
|
||||
4. 拷贝 `dist/uni-app` 中的内容到项目根目录
|
||||
|
||||
查看 [插件](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin) 了解更多
|
||||
|
||||
## 关于 nvue
|
||||
`nvue` 使用原生渲染,不支持部分 `css` 样式,为实现和 `html` 相同的效果,组件内部通过 `web-view` 进行渲染,性能上差于原生,根据 `weex` 官方建议,`web` 标签仅应用在非常规的降级场景。因此,如果通过原生的方式(如 `richtext`)能够满足需要,则不建议使用本组件,如果有较多的富文本内容,则可以直接使用 `vue` 页面
|
||||
由于渲染方式与其他端不同,有以下限制:
|
||||
1. 不支持 `lazy-load` 属性
|
||||
2. 视频不支持全屏播放
|
||||
3. 如果在 `flex-direction: row` 的容器中使用,需要给组件设置宽度或设置 `flex: 1` 占满剩余宽度
|
||||
|
||||
纯 `nvue` 模式下,[此问题](https://ask.dcloud.net.cn/question/119678) 修复前,不支持通过 `uni_modules` 引入,需要本地引入(将 [dist/uni-app](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 中的内容拷贝到项目根目录下)
|
||||
|
||||
## 立即体验
|
||||

|
||||
|
||||
## 问题反馈
|
||||
遇到问题时,请先查阅 [常见问题](https://jin-yufeng.gitee.io/mp-html/#/question/faq) 和 [issue](https://github.com/jin-yufeng/mp-html/issues) 中是否已有相同的问题
|
||||
可通过 [issue](https://github.com/jin-yufeng/mp-html/issues/new/choose) 、插件问答或发送邮件到 [mp_html@126.com](mailto:mp_html@126.com) 提问,不建议在评论区提问(不方便回复)
|
||||
提问请严格按照 [issue 模板](https://github.com/jin-yufeng/mp-html/issues/new/choose) ,描述清楚使用环境、`html` 内容或可复现的 `demo` 项目以及复现方式,对于 **描述不清**、**无法复现** 或重复的问题将不予回复
|
||||
|
||||
欢迎加入 `QQ` 交流群:
|
||||
群1(已满):`699734691`
|
||||
群2(已满):`778239129`
|
||||
群3:`960265313`
|
||||
|
||||
查看 [问题反馈](https://jin-yufeng.gitee.io/mp-html/#/question/feedback) 了解更多
|
||||
@@ -1,150 +0,0 @@
|
||||
## v2.5.0(2024-04-22)
|
||||
1. `U` `play` 事件增加返回 `src` 等信息 [详细](https://github.com/jin-yufeng/mp-html/issues/526)
|
||||
2. `U` `preview-img` 属性支持设置为 `all` 开启 `base64` 图片预览 [详细](https://github.com/jin-yufeng/mp-html/issues/536)
|
||||
3. `U` `editable` 插件增加简易模式(点击文字直接编辑)
|
||||
4. `U` `latex` 插件支持块级公式 [详细](https://github.com/jin-yufeng/mp-html/issues/582)
|
||||
5. `F` 修复了表格部分情况下背景丢失的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/587)
|
||||
6. `F` 修复了部分 `svg` 无法显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/591)
|
||||
7. `F` 修复了 `h5` 和 `app` 端部分情况下样式无法识别的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/518)
|
||||
8. `F` 修复了 `latex` 插件部分情况下显示不正确的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/580)
|
||||
9. `F` 修复了 `editable` 插件表格无法删除的问题
|
||||
10. `F` 修复了 `editable` 插件 `vue3` `h5` 端点击图片报错的问题
|
||||
11. `F` 修复了 `editable` 插件点击表格没有菜单栏的问题
|
||||
## v2.4.3(2024-01-21)
|
||||
1. `A` 增加 [card](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#card) 插件 [详细](https://github.com/jin-yufeng/mp-html/pull/533) by [@whoooami](https://github.com/whoooami)
|
||||
2. `F` 修复了 `svg` 中包含 `foreignobject` 可能不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/523)
|
||||
3. `F` 修复了合并单元格的表格部分情况下显示不正确的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/561)
|
||||
4. `F` 修复了 `img` 标签设置 `object-fit` 无效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/567)
|
||||
5. `F` 修复了 `latex` 插件公式会换行的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/540)
|
||||
6. `F` 修复了 `editable` 和 `audio` 插件共用时点击 `audio` 无法编辑的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/529) by [@whoooami](https://github.com/whoooami)
|
||||
7. `F` 修复了微信小程序部分情况下图片会报错 `replace of undefined` 的问题
|
||||
8. `F` 修复了快手小程序图片不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/571)
|
||||
## v2.4.2(2023-05-14)
|
||||
1. `A` `editable` 插件支持修改文字颜色 [详细](https://github.com/jin-yufeng/mp-html/issues/254)
|
||||
2. `F` 修复了 `svg` 中有 `style` 不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/505)
|
||||
3. `F` 修复了使用旧版编译器可能报错 `Bad attr nodes` 的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/472)
|
||||
4. `F` 修复了 `app` 端可能出现无法读取 `lazyLoad` 的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/513)
|
||||
5. `F` 修复了 `editable` 插件在点击换图时未拼接 `domain` 的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/497) by [@TwoKe945](https://github.com/TwoKe945)
|
||||
6. `F` 修复了 `latex` 插件部分情况下不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/515)
|
||||
7. `F` 修复了 `editable` 插件点击音视频时其他标签框不消失的问题
|
||||
## v2.4.1(2022-12-25)
|
||||
1. `F` 修复了没有图片时 `ready` 事件可能不触发的问题
|
||||
2. `F` 修复了加载过程中可能出现 `Root label not found` 错误的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/470)
|
||||
3. `F` 修复了 `audio` 插件退出页面可能会报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/457)
|
||||
4. `F` 修复了 `vue3` 运行到 `app` 在 `HBuilder X 3.6.10` 以上报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/480)
|
||||
5. `F` 修复了 `nvue` 端链接中包含 `%22` 时可能无法显示的问题
|
||||
6. `F` 修复了 `vue3` 使用 `highlight` 插件可能报错的问题
|
||||
## v2.4.0(2022-08-27)
|
||||
1. `A` 增加了 [setPlaybackRate](https://jin-yufeng.gitee.io/mp-html/#/advanced/api#setPlaybackRate) 的 `api`,可以设置音视频的播放速率 [详细](https://github.com/jin-yufeng/mp-html/issues/452)
|
||||
2. `A` 示例小程序代码开源 [详细](https://github.com/jin-yufeng/mp-html-demo)
|
||||
3. `U` 优化 `ready` 事件触发时机,未设置懒加载的情况下基本可以准确触发 [详细](https://github.com/jin-yufeng/mp-html/issues/195)
|
||||
4. `U` `highlight` 插件在编辑状态下不进行高亮处理,便于编辑
|
||||
5. `F` 修复了 `flex` 布局下图片大小可能不正确的问题
|
||||
6. `F` 修复了 `selectable` 属性没有设置 `force` 也可能出现渲染异常的问题
|
||||
7. `F` 修复了表格中的图片大小可能不正确的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/448)
|
||||
8. `F` 修复了含有合并单元格的表格可能无法设置竖直对齐的问题
|
||||
9. `F` 修复了 `editable` 插件在 `scroll-view` 中使用时工具条位置可能不正确的问题
|
||||
10. `F` 修复了 `vue3` 使用 [search](advanced/plugin#search) 插件可能导致错误换行的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/449)
|
||||
## v2.3.2(2022-08-13)
|
||||
1. `A` 增加 [latex](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#latex) 插件,可以渲染数学公式 [详细](https://github.com/jin-yufeng/mp-html/pull/447) by [@Zeng-J](https://github.com/Zeng-J)
|
||||
2. `U` 优化根节点下有很多标签的长内容渲染速度
|
||||
3. `U` `highlight` 插件适配 `lang-xxx` 格式
|
||||
4. `F` 修复了 `table` 标签设置 `border` 属性后可能无法修改边框样式的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/439) by [@zouxingjie](https://github.com/zouxingjie)
|
||||
5. `F` 修复了 `editable` 插件输入连续空格无效的问题
|
||||
6. `F` 修复了 `vue3` 图片设置 `inline` 会报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/438)
|
||||
7. `F` 修复了 `vue3` 使用 `table` 可能报错的问题
|
||||
## v2.3.1(2022-05-20)
|
||||
1. `U` `app` 端支持使用本地图片
|
||||
2. `U` 优化了微信小程序 `selectable` 属性在 `ios` 端的处理 [详细](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#selectable)
|
||||
3. `F` 修复了 `editable` 插件不在顶部时 `tooltip` 位置可能错误的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/430)
|
||||
4. `F` 修复了 `vue3` 运行到微信小程序可能报错丢失内容的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/414)
|
||||
5. `F` 修复了 `vue3` 部分标签可能被错误换行的问题
|
||||
6. `F` 修复了 `editable` 插件 `app` 端插入视频无法预览的问题
|
||||
## v2.3.0(2022-04-01)
|
||||
1. `A` 增加了 `play` 事件,音视频播放时触发,可用于与页面其他音视频进行互斥播放 [详细](basic/event#play)
|
||||
2. `U` `show-img-menu` 属性支持控制预览时是否长按弹出菜单
|
||||
3. `U` 优化 `wxs` 处理,提高渲染性能 [详细](https://developers.weixin.qq.com/community/develop/article/doc/0006cc2b204740f601bd43fa25a413)
|
||||
4. `U` `video` 标签支持 `object-fit` 属性
|
||||
5. `U` 增加支持一些常用实体编码 [详细](https://github.com/jin-yufeng/mp-html/issues/418)
|
||||
6. `F` 修复了图片仅设置高度可能不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/410)
|
||||
7. `F` 修复了 `video` 标签高度设置为 `auto` 不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/411)
|
||||
8. `F` 修复了使用 `grid` 布局时可能样式错误的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/413)
|
||||
9. `F` 修复了含有合并单元格的表格部分情况下显示异常的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/417)
|
||||
10. `F` 修复了 `editable` 插件连续插入内容时顺序不正确的问题
|
||||
11. `F` 修复了 `uni-app` 包 `vue3` 使用 `audio` 插件报错的问题
|
||||
12. `F` 修复了 `uni-app` 包 `highlight` 插件使用自定义的 `prism.min.js` 报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/416)
|
||||
## v2.2.2(2022-02-26)
|
||||
1. `A` 增加了 [pauseMedia](https://jin-yufeng.gitee.io/mp-html/#/advanced/api#pauseMedia) 的 `api`,可用于暂停播放音视频 [详细](https://github.com/jin-yufeng/mp-html/issues/317)
|
||||
2. `U` 优化了长内容的加载速度
|
||||
3. `U` 适配 `vue3` [#389](https://github.com/jin-yufeng/mp-html/issues/389)、[#398](https://github.com/jin-yufeng/mp-html/pull/398) by [@zhouhuafei](https://github.com/zhouhuafei)、[#400](https://github.com/jin-yufeng/mp-html/issues/400)
|
||||
4. `F` 修复了小程序端图片高度设置为百分比时可能不显示的问题
|
||||
5. `F` 修复了 `highlight` 插件部分情况下可能显示不完整的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/403)
|
||||
## v2.2.1(2021-12-24)
|
||||
1. `A` `editable` 插件增加上下移动标签功能
|
||||
2. `U` `editable` 插件支持在文本中间光标处插入内容
|
||||
3. `F` 修复了 `nvue` 端设置 `margin` 后可能导致高度不正确的问题
|
||||
4. `F` 修复了 `highlight` 插件使用压缩版的 `prism.css` 可能导致背景失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/367)
|
||||
5. `F` 修复了编辑状态下使用 `emoji` 插件内容为空时可能报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/371)
|
||||
6. `F` 修复了使用 `editable` 插件后将 `selectable` 属性设置为 `force` 不生效的问题
|
||||
## v2.2.0(2021-10-12)
|
||||
1. `A` 增加 `customElements` 配置项,便于添加自定义功能性标签 [详细](https://github.com/jin-yufeng/mp-html/issues/350)
|
||||
2. `A` `editable` 插件增加切换音视频自动播放状态的功能 [详细](https://github.com/jin-yufeng/mp-html/pull/341) by [@leeseett](https://github.com/leeseett)
|
||||
3. `A` `editable` 插件删除媒体标签时触发 `remove` 事件,便于删除已上传的文件
|
||||
4. `U` `editable` 插件 `insertImg` 方法支持同时插入多张图片 [详细](https://github.com/jin-yufeng/mp-html/issues/342)
|
||||
5. `U` `editable` 插入图片和音视频时支持拼接 `domian` 主域名
|
||||
6. `F` 修复了内部链接参数中包含 `://` 时被认为是外部链接的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/356)
|
||||
7. `F` 修复了部分 `svg` 标签名或属性名大小写不正确时不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/351)
|
||||
8. `F` 修复了 `nvue` 页面运行到非 `app` 平台时可能样式错误的问题
|
||||
## v2.1.5(2021-08-13)
|
||||
1. `A` 增加支持标签的 `dir` 属性
|
||||
2. `F` 修复了 `ruby` 标签文字与拼音没有居中对齐的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/325)
|
||||
3. `F` 修复了音视频标签内有 `a` 标签时可能无法播放的问题
|
||||
4. `F` 修复了 `externStyle` 中的 `class` 名包含下划线或数字时可能失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/326)
|
||||
5. `F` 修复了 `h5` 端引入 `externStyle` 可能不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/326)
|
||||
## v2.1.4(2021-07-14)
|
||||
1. `F` 修复了 `rt` 标签无法设置样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/318)
|
||||
2. `F` 修复了表格中有单元格同时合并行和列时可能显示不正确的问题
|
||||
3. `F` 修复了 `app` 端无法关闭图片长按菜单的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/322)
|
||||
4. `F` 修复了 `editable` 插件只能添加图片链接不能修改的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/312) by [@leeseett](https://github.com/leeseett)
|
||||
## v2.1.3(2021-06-12)
|
||||
1. `A` `editable` 插件增加 `insertTable` 方法
|
||||
2. `U` `editable` 插件支持编辑表格中的空白单元格 [详细](https://github.com/jin-yufeng/mp-html/issues/310)
|
||||
3. `F` 修复了 `externStyle` 中使用伪类可能失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/298)
|
||||
4. `F` 修复了多个组件同时使用时 `tag-style` 属性时可能互相影响的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/305) by [@woodguoyu](https://github.com/woodguoyu)
|
||||
5. `F` 修复了包含 `linearGradient` 的 `svg` 可能无法显示的问题
|
||||
6. `F` 修复了编译到头条小程序时可能报错的问题
|
||||
7. `F` 修复了 `nvue` 端不触发 `click` 事件的问题
|
||||
8. `F` 修复了 `editable` 插件尾部插入时无法撤销的问题
|
||||
9. `F` 修复了 `editable` 插件的 `insertHtml` 方法只能在末尾插入的问题
|
||||
10. `F` 修复了 `editable` 插件插入音频不显示的问题
|
||||
## v2.1.2(2021-04-24)
|
||||
1. `A` 增加了 [img-cache](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#img-cache) 插件,可以在 `app` 端缓存图片 [详细](https://github.com/jin-yufeng/mp-html/issues/292) by [@PentaTea](https://github.com/PentaTea)
|
||||
2. `U` 支持通过 `container-style` 属性设置 `white-space` 来保留连续空格和换行符 [详细](https://jin-yufeng.gitee.io/mp-html/#/question/faq#space)
|
||||
3. `U` 代码风格符合 [standard](https://standardjs.com) 标准
|
||||
4. `U` `editable` 插件编辑状态下支持预览视频 [详细](https://github.com/jin-yufeng/mp-html/issues/286)
|
||||
5. `F` 修复了 `svg` 标签内嵌 `svg` 时无法显示的问题
|
||||
6. `F` 修复了编译到支付宝和头条小程序时部分区域不可复制的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/291)
|
||||
## v2.1.1(2021-04-09)
|
||||
1. 修复了对 `p` 标签设置 `tag-style` 可能不生效的问题
|
||||
2. 修复了 `svg` 标签中的文本无法显示的问题
|
||||
3. 修复了使用 `editable` 插件编辑表格时可能报错的问题
|
||||
4. 修复了使用 `highlight` 插件运行到头条小程序时可能没有样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/280)
|
||||
5. 修复了使用 `editable` 插件 `editable` 属性为 `false` 时会报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/284)
|
||||
6. 修复了 `style` 插件连续子选择器失效的问题
|
||||
7. 修复了 `editable` 插件无法修改图片和字体大小的问题
|
||||
## v2.1.0.2(2021-03-21)
|
||||
修复了 `nvue` 端使用可能报错的问题
|
||||
## v2.1.0(2021-03-20)
|
||||
1. `A` 增加了 [container-style](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#container-style) 属性 [详细](https://gitee.com/jin-yufeng/mp-html/pulls/1)
|
||||
2. `A` 增加支持 `strike` 标签
|
||||
3. `A` `editable` 插件增加 `placeholder` 属性 [详细](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#editable)
|
||||
4. `A` `editable` 插件增加 `insertHtml` 方法 [详细](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#editable)
|
||||
5. `U` 外部样式支持标签名选择器 [详细](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart#setting)
|
||||
6. `F` 修复了 `nvue` 端部分情况下可能不显示的问题
|
||||
## v2.0.5(2021-03-12)
|
||||
1. `U` [linktap](https://jin-yufeng.gitee.io/mp-html/#/basic/event#linktap) 事件增加返回内部文本内容 `innerText` [详细](https://github.com/jin-yufeng/mp-html/issues/271)
|
||||
2. `U` [selectable](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#selectable) 属性设置为 `force` 时能够在微信 `iOS` 端生效(文本块会变成 `inline-block`) [详细](https://github.com/jin-yufeng/mp-html/issues/267)
|
||||
3. `F` 修复了部分情况下竖向无法滚动的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/182)
|
||||
4. `F` 修复了多次修改富文本数据时部分内容可能不显示的问题
|
||||
5. `F` 修复了 [腾讯视频](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#txv-video) 插件可能无法播放的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/265)
|
||||
6. `F` 修复了 [highlight](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#highlight) 插件没有设置高亮语言时没有应用默认样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/276) by [@fuzui](https://github.com/fuzui)
|
||||
@@ -1,498 +0,0 @@
|
||||
<template>
|
||||
<view id="_root" :class="(selectable?'_select ':'')+'_root'" :style="containerStyle">
|
||||
<slot v-if="!nodes[0]" />
|
||||
<!-- #ifndef APP-PLUS-NVUE -->
|
||||
<node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu,selectable]" name="span" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-PLUS-NVUE -->
|
||||
<web-view ref="web" src="/uni_modules/mp-html/static/app-plus/mp-html/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* mp-html v2.5.0
|
||||
* @description 富文本组件
|
||||
* @tutorial https://github.com/jin-yufeng/mp-html
|
||||
* @property {String} container-style 容器的样式
|
||||
* @property {String} content 用于渲染的 html 字符串
|
||||
* @property {Boolean} copy-link 是否允许外部链接被点击时自动复制
|
||||
* @property {String} domain 主域名,用于拼接链接
|
||||
* @property {String} error-img 图片出错时的占位图链接
|
||||
* @property {Boolean} lazy-load 是否开启图片懒加载
|
||||
* @property {string} loading-img 图片加载过程中的占位图链接
|
||||
* @property {Boolean} pause-video 是否在播放一个视频时自动暂停其他视频
|
||||
* @property {Boolean} preview-img 是否允许图片被点击时自动预览
|
||||
* @property {Boolean} scroll-table 是否给每个表格添加一个滚动层使其能单独横向滚动
|
||||
* @property {Boolean | String} selectable 是否开启长按复制
|
||||
* @property {Boolean} set-title 是否将 title 标签的内容设置到页面标题
|
||||
* @property {Boolean} show-img-menu 是否允许图片被长按时显示菜单
|
||||
* @property {Object} tag-style 标签的默认样式
|
||||
* @property {Boolean | Number} use-anchor 是否使用锚点链接
|
||||
* @event {Function} load dom 结构加载完毕时触发
|
||||
* @event {Function} ready 所有图片加载完毕时触发
|
||||
* @event {Function} imgtap 图片被点击时触发
|
||||
* @event {Function} linktap 链接被点击时触发
|
||||
* @event {Function} play 音视频播放时触发
|
||||
* @event {Function} error 媒体加载出错时触发
|
||||
*/
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
import node from './node/node'
|
||||
// #endif
|
||||
import Parser from './parser'
|
||||
const plugins=[]
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
const dom = weex.requireModule('dom')
|
||||
// #endif
|
||||
export default {
|
||||
name: 'mp-html',
|
||||
data () {
|
||||
return {
|
||||
nodes: [],
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
height: 3
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
props: {
|
||||
containerStyle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
copyLink: {
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
},
|
||||
domain: String,
|
||||
errorImg: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
lazyLoad: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
loadingImg: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
pauseVideo: {
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
},
|
||||
previewImg: {
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
},
|
||||
scrollTable: [Boolean, String],
|
||||
selectable: [Boolean, String],
|
||||
setTitle: {
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
},
|
||||
showImgMenu: {
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
},
|
||||
tagStyle: Object,
|
||||
useAnchor: [Boolean, Number]
|
||||
},
|
||||
// #ifdef VUE3
|
||||
emits: ['load', 'ready', 'imgtap', 'linktap', 'play', 'error'],
|
||||
// #endif
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
components: {
|
||||
node
|
||||
},
|
||||
// #endif
|
||||
watch: {
|
||||
content (content) {
|
||||
this.setContent(content)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.plugins = []
|
||||
for (let i = plugins.length; i--;) {
|
||||
this.plugins.push(new plugins[i](this))
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.content && !this.nodes.length) {
|
||||
this.setContent(this.content)
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
this._hook('onDetached')
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @description 将锚点跳转的范围限定在一个 scroll-view 内
|
||||
* @param {Object} page scroll-view 所在页面的示例
|
||||
* @param {String} selector scroll-view 的选择器
|
||||
* @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名
|
||||
*/
|
||||
in (page, selector, scrollTop) {
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
if (page && selector && scrollTop) {
|
||||
this._in = {
|
||||
page,
|
||||
selector,
|
||||
scrollTop
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 锚点跳转
|
||||
* @param {String} id 要跳转的锚点 id
|
||||
* @param {Number} offset 跳转位置的偏移量
|
||||
* @returns {Promise}
|
||||
*/
|
||||
navigateTo (id, offset) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.useAnchor) {
|
||||
reject(Error('Anchor is disabled'))
|
||||
return
|
||||
}
|
||||
offset = offset || parseInt(this.useAnchor) || 0
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
if (!id) {
|
||||
dom.scrollToElement(this.$refs.web, {
|
||||
offset
|
||||
})
|
||||
resolve()
|
||||
} else {
|
||||
this._navigateTo = {
|
||||
resolve,
|
||||
reject,
|
||||
offset
|
||||
}
|
||||
this.$refs.web.evalJs('uni.postMessage({data:{action:"getOffset",offset:(document.getElementById(' + id + ')||{}).offsetTop}})')
|
||||
}
|
||||
// #endif
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
let deep = ' '
|
||||
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
|
||||
deep = '>>>'
|
||||
// #endif
|
||||
const selector = uni.createSelectorQuery()
|
||||
// #ifndef MP-ALIPAY
|
||||
.in(this._in ? this._in.page : this)
|
||||
// #endif
|
||||
.select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect()
|
||||
if (this._in) {
|
||||
selector.select(this._in.selector).scrollOffset()
|
||||
.select(this._in.selector).boundingClientRect()
|
||||
} else {
|
||||
// 获取 scroll-view 的位置和滚动距离
|
||||
selector.selectViewport().scrollOffset() // 获取窗口的滚动距离
|
||||
}
|
||||
selector.exec(res => {
|
||||
if (!res[0]) {
|
||||
reject(Error('Label not found'))
|
||||
return
|
||||
}
|
||||
const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + offset
|
||||
if (this._in) {
|
||||
// scroll-view 跳转
|
||||
this._in.page[this._in.scrollTop] = scrollTop
|
||||
} else {
|
||||
// 页面跳转
|
||||
uni.pageScrollTo({
|
||||
scrollTop,
|
||||
duration: 300
|
||||
})
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 获取文本内容
|
||||
* @return {String}
|
||||
*/
|
||||
getText (nodes) {
|
||||
let text = '';
|
||||
(function traversal (nodes) {
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i]
|
||||
if (node.type === 'text') {
|
||||
text += node.text.replace(/&/g, '&')
|
||||
} else if (node.name === 'br') {
|
||||
text += '\n'
|
||||
} else {
|
||||
// 块级标签前后加换行
|
||||
const isBlock = node.name === 'p' || node.name === 'div' || node.name === 'tr' || node.name === 'li' || (node.name[0] === 'h' && node.name[1] > '0' && node.name[1] < '7')
|
||||
if (isBlock && text && text[text.length - 1] !== '\n') {
|
||||
text += '\n'
|
||||
}
|
||||
// 递归获取子节点的文本
|
||||
if (node.children) {
|
||||
traversal(node.children)
|
||||
}
|
||||
if (isBlock && text[text.length - 1] !== '\n') {
|
||||
text += '\n'
|
||||
} else if (node.name === 'td' || node.name === 'th') {
|
||||
text += '\t'
|
||||
}
|
||||
}
|
||||
}
|
||||
})(nodes || this.nodes)
|
||||
return text
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 获取内容大小和位置
|
||||
* @return {Promise}
|
||||
*/
|
||||
getRect () {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.createSelectorQuery()
|
||||
// #ifndef MP-ALIPAY
|
||||
.in(this)
|
||||
// #endif
|
||||
.select('#_root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject(Error('Root label not found')))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 暂停播放媒体
|
||||
*/
|
||||
pauseMedia () {
|
||||
for (let i = (this._videos || []).length; i--;) {
|
||||
this._videos[i].pause()
|
||||
}
|
||||
// #ifdef APP-PLUS
|
||||
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].pause()'
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
let page = this.$parent
|
||||
while (!page.$scope) page = page.$parent
|
||||
page.$scope.$getAppWebview().evalJS(command)
|
||||
// #endif
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
this.$refs.web.evalJs(command)
|
||||
// #endif
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 设置媒体播放速率
|
||||
* @param {Number} rate 播放速率
|
||||
*/
|
||||
setPlaybackRate (rate) {
|
||||
this.playbackRate = rate
|
||||
for (let i = (this._videos || []).length; i--;) {
|
||||
this._videos[i].playbackRate(rate)
|
||||
}
|
||||
// #ifdef APP-PLUS
|
||||
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].playbackRate=' + rate
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
let page = this.$parent
|
||||
while (!page.$scope) page = page.$parent
|
||||
page.$scope.$getAppWebview().evalJS(command)
|
||||
// #endif
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
this.$refs.web.evalJs(command)
|
||||
// #endif
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 设置内容
|
||||
* @param {String} content html 内容
|
||||
* @param {Boolean} append 是否在尾部追加
|
||||
*/
|
||||
setContent (content, append) {
|
||||
if (!append || !this.imgList) {
|
||||
this.imgList = []
|
||||
}
|
||||
const nodes = new Parser(this).parse(content)
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
if (this._ready) {
|
||||
this._set(nodes, append)
|
||||
}
|
||||
// #endif
|
||||
this.$set(this, 'nodes', append ? (this.nodes || []).concat(nodes) : nodes)
|
||||
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
this._videos = []
|
||||
this.$nextTick(() => {
|
||||
this._hook('onLoad')
|
||||
this.$emit('load')
|
||||
})
|
||||
|
||||
if (this.lazyLoad || this.imgList._unloadimgs < this.imgList.length / 2) {
|
||||
// 设置懒加载,每 350ms 获取高度,不变则认为加载完毕
|
||||
let height = 0
|
||||
const callback = rect => {
|
||||
if (!rect || !rect.height) rect = {}
|
||||
// 350ms 总高度无变化就触发 ready 事件
|
||||
if (rect.height === height) {
|
||||
this.$emit('ready', rect)
|
||||
} else {
|
||||
height = rect.height
|
||||
setTimeout(() => {
|
||||
this.getRect().then(callback).catch(callback)
|
||||
}, 350)
|
||||
}
|
||||
}
|
||||
this.getRect().then(callback).catch(callback)
|
||||
} else {
|
||||
// 未设置懒加载,等待所有图片加载完毕
|
||||
if (!this.imgList._unloadimgs) {
|
||||
this.getRect().then(rect => {
|
||||
this.$emit('ready', rect)
|
||||
}).catch(() => {
|
||||
this.$emit('ready', {})
|
||||
})
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 调用插件钩子函数
|
||||
*/
|
||||
_hook (name) {
|
||||
for (let i = plugins.length; i--;) {
|
||||
if (this.plugins[i][name]) {
|
||||
this.plugins[i][name]()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
/**
|
||||
* @description 设置内容
|
||||
*/
|
||||
_set (nodes, append) {
|
||||
this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes).replace(/%22/g, '') + ',' + JSON.stringify([this.containerStyle.replace(/(?:margin|padding)[^;]+/g, ''), this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 接收到 web-view 消息
|
||||
*/
|
||||
_onMessage (e) {
|
||||
const message = e.detail.data[0]
|
||||
switch (message.action) {
|
||||
// web-view 初始化完毕
|
||||
case 'onJSBridgeReady':
|
||||
this._ready = true
|
||||
if (this.nodes) {
|
||||
this._set(this.nodes)
|
||||
}
|
||||
break
|
||||
// 内容 dom 加载完毕
|
||||
case 'onLoad':
|
||||
this.height = message.height
|
||||
this._hook('onLoad')
|
||||
this.$emit('load')
|
||||
break
|
||||
// 所有图片加载完毕
|
||||
case 'onReady':
|
||||
this.getRect().then(res => {
|
||||
this.$emit('ready', res)
|
||||
}).catch(() => {
|
||||
this.$emit('ready', {})
|
||||
})
|
||||
break
|
||||
// 总高度发生变化
|
||||
case 'onHeightChange':
|
||||
this.height = message.height
|
||||
break
|
||||
// 图片点击
|
||||
case 'onImgTap':
|
||||
this.$emit('imgtap', message.attrs)
|
||||
if (this.previewImg) {
|
||||
uni.previewImage({
|
||||
current: parseInt(message.attrs.i),
|
||||
urls: this.imgList
|
||||
})
|
||||
}
|
||||
break
|
||||
// 链接点击
|
||||
case 'onLinkTap': {
|
||||
const href = message.attrs.href
|
||||
this.$emit('linktap', message.attrs)
|
||||
if (href) {
|
||||
// 锚点跳转
|
||||
if (href[0] === '#') {
|
||||
if (this.useAnchor) {
|
||||
dom.scrollToElement(this.$refs.web, {
|
||||
offset: message.offset
|
||||
})
|
||||
}
|
||||
} else if (href.includes('://')) {
|
||||
// 打开外链
|
||||
if (this.copyLink) {
|
||||
plus.runtime.openWeb(href)
|
||||
}
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: href,
|
||||
fail () {
|
||||
uni.switchTab({
|
||||
url: href
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'onPlay':
|
||||
this.$emit('play')
|
||||
break
|
||||
// 获取到锚点的偏移量
|
||||
case 'getOffset':
|
||||
if (typeof message.offset === 'number') {
|
||||
dom.scrollToElement(this.$refs.web, {
|
||||
offset: message.offset + this._navigateTo.offset
|
||||
})
|
||||
this._navigateTo.resolve()
|
||||
} else {
|
||||
this._navigateTo.reject(Error('Label not found'))
|
||||
}
|
||||
break
|
||||
// 点击
|
||||
case 'onClick':
|
||||
this.$emit('tap')
|
||||
this.$emit('click')
|
||||
break
|
||||
// 出错
|
||||
case 'onError':
|
||||
this.$emit('error', {
|
||||
source: message.source,
|
||||
attrs: message.attrs
|
||||
})
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* #ifndef APP-PLUS-NVUE */
|
||||
/* 根节点样式 */
|
||||
._root {
|
||||
padding: 1px 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* 长按复制 */
|
||||
._select {
|
||||
user-select: text;
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
||||
@@ -1,587 +0,0 @@
|
||||
<template>
|
||||
<view :id="attrs.id" :class="'_block _'+name+' '+attrs.class" :style="attrs.style">
|
||||
<block v-for="(n, i) in childs" v-bind:key="i">
|
||||
<!-- 图片 -->
|
||||
<!-- 占位图 -->
|
||||
<image v-if="n.name==='img'&&!n.t&&((opts[1]&&!ctrl[i])||ctrl[i]<0)" class="_img" :style="n.attrs.style" :src="ctrl[i]<0?opts[2]:opts[1]" mode="widthFix" />
|
||||
<!-- 显示图片 -->
|
||||
<!-- #ifdef H5 || (APP-PLUS && VUE2) -->
|
||||
<img v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef H5 || (APP-PLUS && VUE2) -->
|
||||
<!-- 表格中的图片,使用 rich-text 防止大小不正确 -->
|
||||
<rich-text v-if="n.name==='img'&&n.t" :style="'display:'+n.t" :nodes="[{attrs:{style:n.attrs.style||'',src:n.attrs.src},name:'img'}]" :data-i="i" @tap.stop="imgTap" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef H5 || APP-PLUS || MP-KUAISHOU -->
|
||||
<image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style" :src="n.attrs.src" :mode="!n.h?'widthFix':(!n.w?'heightFix':(n.m||'scaleToFill'))" :lazy-load="opts[0]" :webp="n.webp" :show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-KUAISHOU -->
|
||||
<image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+n.attrs.style" :src="n.attrs.src" :lazy-load="opts[0]" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap"></image>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-PLUS && VUE3 -->
|
||||
<image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':(n.m||''))" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
|
||||
<!-- #endif -->
|
||||
<!-- 文本 -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<text v-else-if="n.text" :user-select="opts[4]=='force'&&isiOS" decode>{{n.text}}</text>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN || MP-BAIDU || MP-ALIPAY || MP-TOUTIAO -->
|
||||
<text v-else-if="n.text" decode>{{n.text}}</text>
|
||||
<!-- #endif -->
|
||||
<text v-else-if="n.name==='br'">\n</text>
|
||||
<!-- 链接 -->
|
||||
<view v-else-if="n.name==='a'" :id="n.attrs.id" :class="(n.attrs.href?'_a ':'')+n.attrs.class" hover-class="_hover" :style="'display:inline;'+n.attrs.style" :data-i="i" @tap.stop="linkTap">
|
||||
<node name="span" :childs="n.children" :opts="opts" style="display:inherit" />
|
||||
</view>
|
||||
<!-- 视频 -->
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<view v-else-if="n.html" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style" v-html="n.html" @vplay.stop="play" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<video v-else-if="n.name==='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :object-fit="n.attrs['object-fit']" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 || APP-PLUS -->
|
||||
<iframe v-else-if="n.name==='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder" :src="n.attrs.src" />
|
||||
<embed v-else-if="n.name==='embed'" :style="n.attrs.style" :src="n.attrs.src" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-TOUTIAO || ((H5 || APP-PLUS) && VUE3) -->
|
||||
<!-- 音频 -->
|
||||
<audio v-else-if="n.name==='audio'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
|
||||
<!-- #endif -->
|
||||
<view v-else-if="(n.name==='table'&&n.c)||n.name==='li'" :id="n.attrs.id" :class="'_'+n.name+' '+n.attrs.class" :style="n.attrs.style">
|
||||
<node v-if="n.name==='li'" :childs="n.children" :opts="opts" />
|
||||
<view v-else v-for="(tbody, x) in n.children" v-bind:key="x" :class="'_'+tbody.name+' '+tbody.attrs.class" :style="tbody.attrs.style">
|
||||
<node v-if="tbody.name==='td'||tbody.name==='th'" :childs="tbody.children" :opts="opts" />
|
||||
<block v-else v-for="(tr, y) in tbody.children" v-bind:key="y">
|
||||
<view v-if="tr.name==='td'||tr.name==='th'" :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
|
||||
<node :childs="tr.children" :opts="opts" />
|
||||
</view>
|
||||
<view v-else :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
|
||||
<view v-for="(td, z) in tr.children" v-bind:key="z" :class="'_'+td.name+' '+td.attrs.class" :style="td.attrs.style">
|
||||
<node :childs="td.children" :opts="opts" />
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 富文本 -->
|
||||
<!-- #ifdef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
|
||||
<rich-text v-else-if="!n.c&&!handler.isInline(n.name, n.attrs.style)" :id="n.attrs.id" :style="n.f" :user-select="opts[4]" :nodes="[n]" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
|
||||
<rich-text v-else-if="!n.c" :id="n.attrs.id" :style="'display:inline;'+n.f" :preview="false" :selectable="opts[4]" :user-select="opts[4]" :nodes="[n]" />
|
||||
<!-- #endif -->
|
||||
<!-- 继续递归 -->
|
||||
<view v-else-if="n.c===2" :id="n.attrs.id" :class="'_block _'+n.name+' '+n.attrs.class" :style="n.f+';'+n.attrs.style">
|
||||
<node v-for="(n2, j) in n.children" v-bind:key="j" :style="n2.f" :name="n2.name" :attrs="n2.attrs" :childs="n2.children" :opts="opts" />
|
||||
</view>
|
||||
<node v-else :style="n.f" :name="n.name" :attrs="n.attrs" :childs="n.children" :opts="opts" />
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
<script module="handler" lang="wxs">
|
||||
// 行内标签列表
|
||||
var inlineTags = {
|
||||
abbr: true,
|
||||
b: true,
|
||||
big: true,
|
||||
code: true,
|
||||
del: true,
|
||||
em: true,
|
||||
i: true,
|
||||
ins: true,
|
||||
label: true,
|
||||
q: true,
|
||||
small: true,
|
||||
span: true,
|
||||
strong: true,
|
||||
sub: true,
|
||||
sup: true
|
||||
}
|
||||
/**
|
||||
* @description 判断是否为行内标签
|
||||
*/
|
||||
module.exports = {
|
||||
isInline: function (tagName, style) {
|
||||
return inlineTags[tagName] || (style || '').indexOf('display:inline') !== -1
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
|
||||
import node from './node'
|
||||
export default {
|
||||
name: 'node',
|
||||
options: {
|
||||
// #ifdef MP-WEIXIN
|
||||
virtualHost: true,
|
||||
// #endif
|
||||
// #ifdef MP-TOUTIAO
|
||||
addGlobalClass: false
|
||||
// #endif
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
ctrl: {},
|
||||
// #ifdef MP-WEIXIN
|
||||
isiOS: uni.getDeviceInfo().system.includes('iOS')
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
props: {
|
||||
name: String,
|
||||
attrs: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
childs: Array,
|
||||
opts: Array
|
||||
},
|
||||
components: {
|
||||
|
||||
// #ifndef (H5 || APP-PLUS) && VUE3
|
||||
node
|
||||
// #endif
|
||||
},
|
||||
mounted () {
|
||||
this.$nextTick(() => {
|
||||
for (this.root = this.$parent; this.root.$options.name !== 'mp-html'; this.root = this.root.$parent);
|
||||
})
|
||||
// #ifdef H5 || APP-PLUS
|
||||
if (this.opts[0]) {
|
||||
let i
|
||||
for (i = this.childs.length; i--;) {
|
||||
if (this.childs[i].name === 'img') break
|
||||
}
|
||||
if (i !== -1) {
|
||||
this.observer = uni.createIntersectionObserver(this).relativeToViewport({
|
||||
top: 500,
|
||||
bottom: 500
|
||||
})
|
||||
this.observer.observe('._img', res => {
|
||||
if (res.intersectionRatio) {
|
||||
this.$set(this.ctrl, 'load', 1)
|
||||
this.observer.disconnect()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
beforeDestroy () {
|
||||
// #ifdef H5 || APP-PLUS
|
||||
if (this.observer) {
|
||||
this.observer.disconnect()
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
methods:{
|
||||
// #ifdef MP-WEIXIN
|
||||
toJSON () { return this },
|
||||
// #endif
|
||||
/**
|
||||
* @description 播放视频事件
|
||||
* @param {Event} e
|
||||
*/
|
||||
play (e) {
|
||||
const i = e.currentTarget.dataset.i
|
||||
const node = this.childs[i]
|
||||
this.root.$emit('play', {
|
||||
source: node.name,
|
||||
attrs: {
|
||||
...node.attrs,
|
||||
src: node.src[this.ctrl[i] || 0]
|
||||
}
|
||||
})
|
||||
// #ifndef APP-PLUS
|
||||
if (this.root.pauseVideo) {
|
||||
let flag = false
|
||||
const id = e.target.id
|
||||
for (let i = this.root._videos.length; i--;) {
|
||||
if (this.root._videos[i].id === id) {
|
||||
flag = true
|
||||
} else {
|
||||
this.root._videos[i].pause() // 自动暂停其他视频
|
||||
}
|
||||
}
|
||||
// 将自己加入列表
|
||||
if (!flag) {
|
||||
const ctx = uni.createVideoContext(id
|
||||
// #ifndef MP-BAIDU
|
||||
, this
|
||||
// #endif
|
||||
)
|
||||
ctx.id = id
|
||||
if (this.root.playbackRate) {
|
||||
ctx.playbackRate(this.root.playbackRate)
|
||||
}
|
||||
this.root._videos.push(ctx)
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 图片点击事件
|
||||
* @param {Event} e
|
||||
*/
|
||||
imgTap (e) {
|
||||
const node = this.childs[e.currentTarget.dataset.i]
|
||||
if (node.a) {
|
||||
this.linkTap(node.a)
|
||||
return
|
||||
}
|
||||
if (node.attrs.ignore) return
|
||||
// #ifdef H5 || APP-PLUS
|
||||
node.attrs.src = node.attrs.src || node.attrs['data-src']
|
||||
// #endif
|
||||
this.root.$emit('imgtap', node.attrs)
|
||||
// 自动预览图片
|
||||
if (this.root.previewImg) {
|
||||
uni.previewImage({
|
||||
// #ifdef MP-WEIXIN
|
||||
showmenu: this.root.showImgMenu,
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
enablesavephoto: this.root.showImgMenu,
|
||||
enableShowPhotoDownload: this.root.showImgMenu,
|
||||
// #endif
|
||||
current: parseInt(node.attrs.i),
|
||||
urls: this.root.imgList
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 图片长按
|
||||
*/
|
||||
imgLongTap (e) {
|
||||
// #ifdef APP-PLUS
|
||||
const attrs = this.childs[e.currentTarget.dataset.i].attrs
|
||||
if (this.opts[3] && !attrs.ignore) {
|
||||
uni.showActionSheet({
|
||||
itemList: ['保存图片'],
|
||||
success: () => {
|
||||
const save = path => {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: path,
|
||||
success () {
|
||||
uni.showToast({
|
||||
title: '保存成功'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
if (this.root.imgList[attrs.i].startsWith('http')) {
|
||||
uni.downloadFile({
|
||||
url: this.root.imgList[attrs.i],
|
||||
success: res => save(res.tempFilePath)
|
||||
})
|
||||
} else {
|
||||
save(this.root.imgList[attrs.i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 图片加载完成事件
|
||||
* @param {Event} e
|
||||
*/
|
||||
imgLoad (e) {
|
||||
const i = e.currentTarget.dataset.i
|
||||
/* #ifndef H5 || (APP-PLUS && VUE2) */
|
||||
if (!this.childs[i].w) {
|
||||
// 设置原宽度
|
||||
this.$set(this.ctrl, i, e.detail.width)
|
||||
} else /* #endif */ if ((this.opts[1] && !this.ctrl[i]) || this.ctrl[i] === -1) {
|
||||
// 加载完毕,取消加载中占位图
|
||||
this.$set(this.ctrl, i, 1)
|
||||
}
|
||||
this.checkReady()
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 检查是否所有图片加载完毕
|
||||
*/
|
||||
checkReady () {
|
||||
if (this.root && !this.root.lazyLoad) {
|
||||
this.root._unloadimgs -= 1
|
||||
if (!this.root._unloadimgs) {
|
||||
setTimeout(() => {
|
||||
this.root.getRect().then(rect => {
|
||||
this.root.$emit('ready', rect)
|
||||
}).catch(() => {
|
||||
this.root.$emit('ready', {})
|
||||
})
|
||||
}, 350)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 链接点击事件
|
||||
* @param {Event} e
|
||||
*/
|
||||
linkTap (e) {
|
||||
const node = e.currentTarget ? this.childs[e.currentTarget.dataset.i] : {}
|
||||
const attrs = node.attrs || e
|
||||
const href = attrs.href
|
||||
this.root.$emit('linktap', Object.assign({
|
||||
innerText: this.root.getText(node.children || []) // 链接内的文本内容
|
||||
}, attrs))
|
||||
if (href) {
|
||||
if (href[0] === '#') {
|
||||
// 跳转锚点
|
||||
this.root.navigateTo(href.substring(1)).catch(() => { })
|
||||
} else if (href.split('?')[0].includes('://')) {
|
||||
// 复制外部链接
|
||||
if (this.root.copyLink) {
|
||||
// #ifdef H5
|
||||
window.open(href)
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
uni.setClipboardData({
|
||||
data: href,
|
||||
success: () =>
|
||||
uni.showToast({
|
||||
title: '链接已复制'
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
plus.runtime.openWeb(href)
|
||||
// #endif
|
||||
}
|
||||
} else {
|
||||
// 跳转页面
|
||||
uni.navigateTo({
|
||||
url: href,
|
||||
fail () {
|
||||
uni.switchTab({
|
||||
url: href,
|
||||
fail () { }
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 错误事件
|
||||
* @param {Event} e
|
||||
*/
|
||||
mediaError (e) {
|
||||
const i = e.currentTarget.dataset.i
|
||||
const node = this.childs[i]
|
||||
// 加载其他源
|
||||
if (node.name === 'video' || node.name === 'audio') {
|
||||
let index = (this.ctrl[i] || 0) + 1
|
||||
if (index > node.src.length) {
|
||||
index = 0
|
||||
}
|
||||
if (index < node.src.length) {
|
||||
this.$set(this.ctrl, i, index)
|
||||
return
|
||||
}
|
||||
} else if (node.name === 'img') {
|
||||
// #ifdef H5 && VUE3
|
||||
if (this.opts[0] && !this.ctrl.load) return
|
||||
// #endif
|
||||
// 显示错误占位图
|
||||
if (this.opts[2]) {
|
||||
this.$set(this.ctrl, i, -1)
|
||||
}
|
||||
this.checkReady()
|
||||
}
|
||||
if (this.root) {
|
||||
this.root.$emit('error', {
|
||||
source: node.name,
|
||||
attrs: node.attrs,
|
||||
// #ifndef H5 && VUE3
|
||||
errMsg: e.detail.errMsg
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
/* a 标签默认效果 */
|
||||
._a {
|
||||
padding: 1.5px 0 1.5px 0;
|
||||
color: #366092;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* a 标签点击态效果 */
|
||||
._hover {
|
||||
text-decoration: underline;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* 图片默认效果 */
|
||||
._img {
|
||||
max-width: 100%;
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
|
||||
/* 内部样式 */
|
||||
|
||||
._block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
._b,
|
||||
._strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
._code {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
._del {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
._em,
|
||||
._i {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
._h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
._h2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
._h3 {
|
||||
font-size: 1.17em;
|
||||
}
|
||||
|
||||
._h5 {
|
||||
font-size: 0.83em;
|
||||
}
|
||||
|
||||
._h6 {
|
||||
font-size: 0.67em;
|
||||
}
|
||||
|
||||
._h1,
|
||||
._h2,
|
||||
._h3,
|
||||
._h4,
|
||||
._h5,
|
||||
._h6 {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
._image {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
._ins {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
._li {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
._ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
._ol,
|
||||
._ul {
|
||||
display: block;
|
||||
padding-left: 40px;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
._q::before {
|
||||
content: '"';
|
||||
}
|
||||
|
||||
._q::after {
|
||||
content: '"';
|
||||
}
|
||||
|
||||
._sub {
|
||||
font-size: smaller;
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
._sup {
|
||||
font-size: smaller;
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
._thead,
|
||||
._tbody,
|
||||
._tfoot {
|
||||
display: table-row-group;
|
||||
}
|
||||
|
||||
._tr {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
._td,
|
||||
._th {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
._th {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
._ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
._ul ._ul {
|
||||
margin: 0;
|
||||
list-style-type: circle;
|
||||
}
|
||||
|
||||
._ul ._ul ._ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
|
||||
._abbr,
|
||||
._b,
|
||||
._code,
|
||||
._del,
|
||||
._em,
|
||||
._i,
|
||||
._ins,
|
||||
._label,
|
||||
._q,
|
||||
._span,
|
||||
._strong,
|
||||
._sub,
|
||||
._sup {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* #ifdef APP-PLUS */
|
||||
._video {
|
||||
width: 300px;
|
||||
height: 225px;
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,76 +0,0 @@
|
||||
{
|
||||
"id": "mp-html",
|
||||
"displayName": "mp-html 富文本组件【全端支持,支持编辑、latex等扩展】",
|
||||
"version": "v2.5.0",
|
||||
"description": "一个强大的富文本组件,高效轻量,功能丰富",
|
||||
"keywords": [
|
||||
"富文本",
|
||||
"编辑器",
|
||||
"html",
|
||||
"rich-text",
|
||||
"editor"
|
||||
],
|
||||
"repository": "https://github.com/jin-yufeng/mp-html",
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/mp-html",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"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": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "y",
|
||||
"联盟": "y"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
"use strict";function t(t){for(var e=Object.create(null),n=t.attributes.length;n--;)e[t.attributes[n].name]=t.attributes[n].value;return e}function e(){a[1]&&(this.src=a[1],this.onerror=null),this.onclick=null,this.ontouchstart=null,uni.postMessage({data:{action:"onError",source:"img",attrs:t(this)}})}function n(){window.unloadimgs-=1,0===window.unloadimgs&&uni.postMessage({data:{action:"onReady"}})}function o(r,s,c){for(var d=0;d<r.length;d++)!function(d){var u=r[d],l=void 0;if(u.type&&"node"!==u.type)l=document.createTextNode(u.text.replace(/&/g,"&"));else{var g=u.name;"svg"===g&&(c="http://www.w3.org/2000/svg"),"html"!==g&&"body"!==g||(g="div"),l=c?document.createElementNS(c,g):document.createElement(g);for(var p in u.attrs)l.setAttribute(p,u.attrs[p]);if(u.children&&o(u.children,l,c),"img"===g){if(window.unloadimgs+=1,l.onload=n,l.onerror=n,!l.src&&l.getAttribute("data-src")&&(l.src=l.getAttribute("data-src")),u.attrs.ignore||(l.onclick=function(e){e.stopPropagation(),uni.postMessage({data:{action:"onImgTap",attrs:t(this)}})}),a[2]){var h=new Image;h.src=l.src,l.src=a[2],h.onload=function(){l.src=this.src},h.onerror=function(){l.onerror()}}l.onerror=e}else if("a"===g)l.addEventListener("click",function(e){e.stopPropagation(),e.preventDefault();var n,o=this.getAttribute("href");o&&"#"===o[0]&&(n=(document.getElementById(o.substr(1))||{}).offsetTop),uni.postMessage({data:{action:"onLinkTap",attrs:t(this),offset:n}})},!0);else if("video"===g||"audio"===g)i.push(l),u.attrs.autoplay||u.attrs.controls||l.setAttribute("controls","true"),l.onplay=function(){if(uni.postMessage({data:{action:"onPlay"}}),a[3])for(var t=0;t<i.length;t++)i[t]!==this&&i[t].pause()},l.onerror=function(){uni.postMessage({data:{action:"onError",source:g,attrs:t(this)}})};else if("table"===g&&a[4]&&!l.style.cssText.includes("inline")){var f=document.createElement("div");f.style.overflow="auto",f.appendChild(l),l=f}else"svg"===g&&(c=void 0)}s.appendChild(l)}(d)}document.addEventListener("UniAppJSBridgeReady",function(){document.body.onclick=function(){return uni.postMessage({data:{action:"onClick"}})},uni.postMessage({data:{action:"onJSBridgeReady"}})});var a,i=[];window.setContent=function(t,e,n){var r=document.getElementById("content");e[0]&&(document.body.style.cssText=e[0]),e[5]||(r.style.userSelect="none"),n||(r.innerHTML="",i=[]),a=e,window.unloadimgs=0;var s=document.createDocumentFragment();o(t,s),r.appendChild(s);var c=r.scrollHeight;uni.postMessage({data:{action:"onLoad",height:c}}),window.unloadimgs||uni.postMessage({data:{action:"onReady",height:c}}),clearInterval(window.timer),window.timer=setInterval(function(){r.scrollHeight!==c&&(c=r.scrollHeight,uni.postMessage({data:{action:"onHeightChange",height:c}}))},350)},window.onunload=function(){clearInterval(window.timer)};
|
||||
@@ -1 +0,0 @@
|
||||
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e=e||self).uni=n()}(this,(function(){"use strict";try{var e={};Object.defineProperty(e,"passive",{get:function(){!0}}),window.addEventListener("test-passive",null,e)}catch(e){}var n=Object.prototype.hasOwnProperty;function t(e,t){return n.call(e,t)}var i=[],a=function(e,n){var t={options:{timestamp:+new Date},name:e,arg:n};if(window.__dcloud_weex_postMessage||window.__dcloud_weex_){if("postMessage"===e){var a={data:[n]};return window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessage(a):window.__dcloud_weex_.postMessage(JSON.stringify(a))}var o={type:"WEB_INVOKE_APPSERVICE",args:{data:t,webviewIds:i}};window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessageToService(o):window.__dcloud_weex_.postMessageToService(JSON.stringify(o))}if(!window.plus)return window.parent.postMessage({type:"WEB_INVOKE_APPSERVICE",data:t,pageId:""},"*");if(0===i.length){var r=plus.webview.currentWebview();if(!r)throw new Error("plus.webview.currentWebview() is undefined");var d=r.parent(),s="";s=d?d.id:r.id,i.push(s)}if(plus.webview.getWebviewById("__uniapp__service"))plus.webview.postMessageToUniNView({type:"WEB_INVOKE_APPSERVICE",args:{data:t,webviewIds:i}},"__uniapp__service");else{var w=JSON.stringify(t);plus.webview.getLaunchWebview().evalJS('UniPlusBridge.subscribeHandler("'.concat("WEB_INVOKE_APPSERVICE",'",').concat(w,",").concat(JSON.stringify(i),");"))}},o={navigateTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("navigateTo",{url:encodeURI(n)})},navigateBack:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.delta;a("navigateBack",{delta:parseInt(n)||1})},switchTab:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("switchTab",{url:encodeURI(n)})},reLaunch:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("reLaunch",{url:encodeURI(n)})},redirectTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("redirectTo",{url:encodeURI(n)})},getEnv:function(e){window.plus?e({plus:!0}):e({h5:!0})},postMessage:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};a("postMessage",e.data||{})}},r=/uni-app/i.test(navigator.userAgent),d=/Html5Plus/i.test(navigator.userAgent),s=/complete|loaded|interactive/;var w=window.my&&navigator.userAgent.indexOf("AlipayClient")>-1;var u=window.swan&&window.swan.webView&&/swan/i.test(navigator.userAgent);var c=window.qq&&window.qq.miniProgram&&/QQ/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var g=window.tt&&window.tt.miniProgram&&/toutiaomicroapp/i.test(navigator.userAgent);var v=window.wx&&window.wx.miniProgram&&/micromessenger/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var p=window.qa&&/quickapp/i.test(navigator.userAgent);for(var l,_=function(){window.UniAppJSBridge=!0,document.dispatchEvent(new CustomEvent("UniAppJSBridgeReady",{bubbles:!0,cancelable:!0}))},f=[function(e){if(r||d)return window.__dcloud_weex_postMessage||window.__dcloud_weex_?document.addEventListener("DOMContentLoaded",e):window.plus&&s.test(document.readyState)?setTimeout(e,0):document.addEventListener("plusready",e),o},function(e){if(v)return window.WeixinJSBridge&&window.WeixinJSBridge.invoke?setTimeout(e,0):document.addEventListener("WeixinJSBridgeReady",e),window.wx.miniProgram},function(e){if(c)return window.QQJSBridge&&window.QQJSBridge.invoke?setTimeout(e,0):document.addEventListener("QQJSBridgeReady",e),window.qq.miniProgram},function(e){if(w){document.addEventListener("DOMContentLoaded",e);var n=window.my;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){if(u)return document.addEventListener("DOMContentLoaded",e),window.swan.webView},function(e){if(g)return document.addEventListener("DOMContentLoaded",e),window.tt.miniProgram},function(e){if(p){window.QaJSBridge&&window.QaJSBridge.invoke?setTimeout(e,0):document.addEventListener("QaJSBridgeReady",e);var n=window.qa;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){return document.addEventListener("DOMContentLoaded",e),o}],m=0;m<f.length&&!(l=f[m](_));m++);l||(l={});var E="undefined"!=typeof uni?uni:{};if(!E.navigateTo)for(var b in l)t(l,b)&&(E[b]=l[b]);return E.webView=l,E}));
|
||||
@@ -1 +0,0 @@
|
||||
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><style>body,html{width:100%;height:100%;overflow-x:scroll;overflow-y:hidden}body{margin:0}video{width:300px;height:225px}img{max-width:100%;-webkit-touch-callout:none}</style></head><body><div id="content" style="overflow:hidden"></div><script type="text/javascript" src="./js/uni.webview.min.js"></script><script type="text/javascript" src="./js/handler.js"></script></body>
|
||||
30
uni_modules/uni-calendar/changelog.md
Normal file
30
uni_modules/uni-calendar/changelog.md
Normal file
@@ -0,0 +1,30 @@
|
||||
## 1.4.12(2024-09-21)
|
||||
- 修复 calendar在选择日期范围后重新选择日期需要点两次的Bug
|
||||
## 1.4.11(2024-01-10)
|
||||
- 修复 回到今天时,月份显示不一致问题
|
||||
## 1.4.10(2023-04-10)
|
||||
- 修复 某些情况 monthSwitch 未触发的Bug
|
||||
## 1.4.9(2023-02-02)
|
||||
- 修复 某些情况切换月份错误的Bug
|
||||
## 1.4.8(2023-01-30)
|
||||
- 修复 某些情况切换月份错误的Bug [详情](https://ask.dcloud.net.cn/question/161964)
|
||||
## 1.4.7(2022-09-16)
|
||||
- 优化 支持使用 uni-scss 控制主题色
|
||||
## 1.4.6(2022-09-08)
|
||||
- 修复 表头年月切换,导致改变当前日期为选择月1号,且未触发change事件的Bug
|
||||
## 1.4.5(2022-02-25)
|
||||
- 修复 条件编译 nvue 不支持的 css 样式的Bug
|
||||
## 1.4.4(2022-02-25)
|
||||
- 修复 条件编译 nvue 不支持的 css 样式的Bug
|
||||
## 1.4.3(2021-09-22)
|
||||
- 修复 startDate、 endDate 属性失效的Bug
|
||||
## 1.4.2(2021-08-24)
|
||||
- 新增 支持国际化
|
||||
## 1.4.1(2021-08-05)
|
||||
- 修复 弹出层被 tabbar 遮盖的Bug
|
||||
## 1.4.0(2021-07-30)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 1.3.16(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 1.3.15(2021-02-04)
|
||||
- 调整为uni_modules目录规范
|
||||
@@ -351,10 +351,8 @@ var calendar = {
|
||||
s = '\u521d\u5341'; break
|
||||
case 20:
|
||||
s = '\u4e8c\u5341'; break
|
||||
break
|
||||
case 30:
|
||||
s = '\u4e09\u5341'; break
|
||||
break
|
||||
default :
|
||||
s = this.nStr2[Math.floor(d / 10)]
|
||||
s += this.nStr1[d % 10]
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"uni-calender.ok": "ok",
|
||||
"uni-calender.cancel": "cancel",
|
||||
"uni-calender.today": "today",
|
||||
"uni-calender.MON": "MON",
|
||||
"uni-calender.TUE": "TUE",
|
||||
"uni-calender.WED": "WED",
|
||||
"uni-calender.THU": "THU",
|
||||
"uni-calender.FRI": "FRI",
|
||||
"uni-calender.SAT": "SAT",
|
||||
"uni-calender.SUN": "SUN"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import en from './en.json'
|
||||
import zhHans from './zh-Hans.json'
|
||||
import zhHant from './zh-Hant.json'
|
||||
export default {
|
||||
en,
|
||||
'zh-Hans': zhHans,
|
||||
'zh-Hant': zhHant
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"uni-calender.ok": "确定",
|
||||
"uni-calender.cancel": "取消",
|
||||
"uni-calender.today": "今日",
|
||||
"uni-calender.SUN": "日",
|
||||
"uni-calender.MON": "一",
|
||||
"uni-calender.TUE": "二",
|
||||
"uni-calender.WED": "三",
|
||||
"uni-calender.THU": "四",
|
||||
"uni-calender.FRI": "五",
|
||||
"uni-calender.SAT": "六"
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"uni-calender.ok": "確定",
|
||||
"uni-calender.cancel": "取消",
|
||||
"uni-calender.today": "今日",
|
||||
"uni-calender.SUN": "日",
|
||||
"uni-calender.MON": "一",
|
||||
"uni-calender.TUE": "二",
|
||||
"uni-calender.WED": "三",
|
||||
"uni-calender.THU": "四",
|
||||
"uni-calender.FRI": "五",
|
||||
"uni-calender.SAT": "六"
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<view class="uni-calendar-item__weeks-box" :class="{
|
||||
'uni-calendar-item--disable':weeks.disable,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':(calendar.fullDate === weeks.fullDate && !weeks.isDay) ,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
}"
|
||||
@click="choiceDate(weeks)">
|
||||
<view class="uni-calendar-item__weeks-box-item">
|
||||
<text v-if="selected&&weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text>
|
||||
<text class="uni-calendar-item__weeks-box-text" :class="{
|
||||
'uni-calendar-item--isDay-text': weeks.isDay,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
'uni-calendar-item--disable':weeks.disable,
|
||||
}">{{weeks.date}}</text>
|
||||
<text v-if="!lunar&&!weeks.extraInfo && weeks.isDay" class="uni-calendar-item__weeks-lunar-text" :class="{
|
||||
'uni-calendar-item--isDay-text':weeks.isDay,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
}">{{todayText}}</text>
|
||||
<text v-if="lunar&&!weeks.extraInfo" class="uni-calendar-item__weeks-lunar-text" :class="{
|
||||
'uni-calendar-item--isDay-text':weeks.isDay,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
'uni-calendar-item--disable':weeks.disable,
|
||||
}">{{weeks.isDay ? todayText : (weeks.lunar.IDayCn === '初一'?weeks.lunar.IMonthCn:weeks.lunar.IDayCn)}}</text>
|
||||
<text v-if="weeks.extraInfo&&weeks.extraInfo.info" class="uni-calendar-item__weeks-lunar-text" :class="{
|
||||
'uni-calendar-item--extra':weeks.extraInfo.info,
|
||||
'uni-calendar-item--isDay-text':weeks.isDay,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
'uni-calendar-item--disable':weeks.disable,
|
||||
}">{{weeks.extraInfo.info}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { initVueI18n } from '@dcloudio/uni-i18n'
|
||||
import i18nMessages from './i18n/index.js'
|
||||
const { t } = initVueI18n(i18nMessages)
|
||||
|
||||
export default {
|
||||
emits:['change'],
|
||||
props: {
|
||||
weeks: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
calendar: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
selected: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
lunar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
todayText() {
|
||||
return t("uni-calender.today")
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
choiceDate(weeks) {
|
||||
this.$emit('change', weeks)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$uni-font-size-base:14px;
|
||||
$uni-text-color:#333;
|
||||
$uni-font-size-sm:12px;
|
||||
$uni-color-error: #e43d33;
|
||||
$uni-opacity-disabled: 0.3;
|
||||
$uni-text-color-disable:#c0c0c0;
|
||||
$uni-primary: #2979ff !default;
|
||||
.uni-calendar-item__weeks-box {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-box-text {
|
||||
font-size: $uni-font-size-base;
|
||||
color: $uni-text-color;
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-lunar-text {
|
||||
font-size: $uni-font-size-sm;
|
||||
color: $uni-text-color;
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-box-item {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-box-circle {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 8px;
|
||||
background-color: $uni-color-error;
|
||||
|
||||
}
|
||||
|
||||
.uni-calendar-item--disable {
|
||||
background-color: rgba(249, 249, 249, $uni-opacity-disabled);
|
||||
color: $uni-text-color-disable;
|
||||
}
|
||||
|
||||
.uni-calendar-item--isDay-text {
|
||||
color: $uni-primary;
|
||||
}
|
||||
|
||||
.uni-calendar-item--isDay {
|
||||
background-color: $uni-primary;
|
||||
opacity: 0.8;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.uni-calendar-item--extra {
|
||||
color: $uni-color-error;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.uni-calendar-item--checked {
|
||||
background-color: $uni-primary;
|
||||
color: #fff;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.uni-calendar-item--multiple {
|
||||
background-color: $uni-primary;
|
||||
color: #fff;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.uni-calendar-item--before-checked {
|
||||
background-color: #ff5a5f;
|
||||
color: #fff;
|
||||
}
|
||||
.uni-calendar-item--after-checked {
|
||||
background-color: #ff5a5f;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,567 @@
|
||||
<template>
|
||||
<view class="uni-calendar">
|
||||
<view v-if="!insert&&show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}" @click="clean"></view>
|
||||
<view v-if="insert || show" class="uni-calendar__content" :class="{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow}">
|
||||
<view v-if="!insert" class="uni-calendar__header uni-calendar--fixed-top">
|
||||
<view class="uni-calendar__header-btn-box" @click="close">
|
||||
<text class="uni-calendar__header-text uni-calendar--fixed-width">{{cancelText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__header-btn-box" @click="confirm">
|
||||
<text class="uni-calendar__header-text uni-calendar--fixed-width">{{okText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-calendar__header">
|
||||
<view class="uni-calendar__header-btn-box" @click.stop="pre">
|
||||
<view class="uni-calendar__header-btn uni-calendar--left"></view>
|
||||
</view>
|
||||
<picker mode="date" :value="date" fields="month" @change="bindDateChange">
|
||||
<text class="uni-calendar__header-text">{{ (nowDate.year||'') +' / '+( nowDate.month||'')}}</text>
|
||||
</picker>
|
||||
<view class="uni-calendar__header-btn-box" @click.stop="next">
|
||||
<view class="uni-calendar__header-btn uni-calendar--right"></view>
|
||||
</view>
|
||||
<text class="uni-calendar__backtoday" @click="backToday">{{todayText}}</text>
|
||||
|
||||
</view>
|
||||
<view class="uni-calendar__box">
|
||||
<view v-if="showMonth" class="uni-calendar__box-bg">
|
||||
<text class="uni-calendar__box-bg-text">{{nowDate.month}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks">
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{SUNText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{monText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{TUEText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{WEDText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{THUText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{FRIText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{SATText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex">
|
||||
<view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex">
|
||||
<calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar" :selected="selected" :lunar="lunar" @change="choiceDate"></calendar-item>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Calendar from './util.js';
|
||||
import CalendarItem from './uni-calendar-item.vue'
|
||||
|
||||
import { initVueI18n } from '@dcloudio/uni-i18n'
|
||||
import i18nMessages from './i18n/index.js'
|
||||
const { t } = initVueI18n(i18nMessages)
|
||||
|
||||
/**
|
||||
* Calendar 日历
|
||||
* @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=56
|
||||
* @property {String} date 自定义当前时间,默认为今天
|
||||
* @property {Boolean} lunar 显示农历
|
||||
* @property {String} startDate 日期选择范围-开始日期
|
||||
* @property {String} endDate 日期选择范围-结束日期
|
||||
* @property {Boolean} range 范围选择
|
||||
* @property {Boolean} insert = [true|false] 插入模式,默认为false
|
||||
* @value true 弹窗模式
|
||||
* @value false 插入模式
|
||||
* @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容
|
||||
* @property {Array} selected 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]
|
||||
* @property {Boolean} showMonth 是否选择月份为背景
|
||||
* @event {Function} change 日期改变,`insert :ture` 时生效
|
||||
* @event {Function} confirm 确认选择`insert :false` 时生效
|
||||
* @event {Function} monthSwitch 切换月份时触发
|
||||
* @example <uni-calendar :insert="true":lunar="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
|
||||
*/
|
||||
export default {
|
||||
components: {
|
||||
CalendarItem
|
||||
},
|
||||
emits:['close','confirm','change','monthSwitch'],
|
||||
props: {
|
||||
date: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
selected: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
lunar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
startDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
endDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
range: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
insert: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showMonth: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
clearDate: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
weeks: [],
|
||||
calendar: {},
|
||||
nowDate: '',
|
||||
aniMaskShow: false
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
/**
|
||||
* for i18n
|
||||
*/
|
||||
|
||||
okText() {
|
||||
return t("uni-calender.ok")
|
||||
},
|
||||
cancelText() {
|
||||
return t("uni-calender.cancel")
|
||||
},
|
||||
todayText() {
|
||||
return t("uni-calender.today")
|
||||
},
|
||||
monText() {
|
||||
return t("uni-calender.MON")
|
||||
},
|
||||
TUEText() {
|
||||
return t("uni-calender.TUE")
|
||||
},
|
||||
WEDText() {
|
||||
return t("uni-calender.WED")
|
||||
},
|
||||
THUText() {
|
||||
return t("uni-calender.THU")
|
||||
},
|
||||
FRIText() {
|
||||
return t("uni-calender.FRI")
|
||||
},
|
||||
SATText() {
|
||||
return t("uni-calender.SAT")
|
||||
},
|
||||
SUNText() {
|
||||
return t("uni-calender.SUN")
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
date(newVal) {
|
||||
// this.cale.setDate(newVal)
|
||||
this.init(newVal)
|
||||
},
|
||||
startDate(val){
|
||||
this.cale.resetSatrtDate(val)
|
||||
this.cale.setDate(this.nowDate.fullDate)
|
||||
this.weeks = this.cale.weeks
|
||||
},
|
||||
endDate(val){
|
||||
this.cale.resetEndDate(val)
|
||||
this.cale.setDate(this.nowDate.fullDate)
|
||||
this.weeks = this.cale.weeks
|
||||
},
|
||||
selected(newVal) {
|
||||
this.cale.setSelectInfo(this.nowDate.fullDate, newVal)
|
||||
this.weeks = this.cale.weeks
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.cale = new Calendar({
|
||||
selected: this.selected,
|
||||
startDate: this.startDate,
|
||||
endDate: this.endDate,
|
||||
range: this.range,
|
||||
})
|
||||
this.init(this.date)
|
||||
},
|
||||
methods: {
|
||||
// 取消穿透
|
||||
clean() {},
|
||||
bindDateChange(e) {
|
||||
const value = e.detail.value + '-1'
|
||||
this.setDate(value)
|
||||
|
||||
const { year,month } = this.cale.getDate(value)
|
||||
this.$emit('monthSwitch', {
|
||||
year,
|
||||
month
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 初始化日期显示
|
||||
* @param {Object} date
|
||||
*/
|
||||
init(date) {
|
||||
this.cale.setDate(date)
|
||||
this.weeks = this.cale.weeks
|
||||
this.nowDate = this.calendar = this.cale.getInfo(date)
|
||||
},
|
||||
/**
|
||||
* 打开日历弹窗
|
||||
*/
|
||||
open() {
|
||||
// 弹窗模式并且清理数据
|
||||
if (this.clearDate && !this.insert) {
|
||||
this.cale.cleanMultipleStatus()
|
||||
// this.cale.setDate(this.date)
|
||||
this.init(this.date)
|
||||
}
|
||||
this.show = true
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.aniMaskShow = true
|
||||
}, 50)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 关闭日历弹窗
|
||||
*/
|
||||
close() {
|
||||
this.aniMaskShow = false
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.show = false
|
||||
this.$emit('close')
|
||||
}, 300)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 确认按钮
|
||||
*/
|
||||
confirm() {
|
||||
this.setEmit('confirm')
|
||||
this.close()
|
||||
},
|
||||
/**
|
||||
* 变化触发
|
||||
*/
|
||||
change() {
|
||||
if (!this.insert) return
|
||||
this.setEmit('change')
|
||||
},
|
||||
/**
|
||||
* 选择月份触发
|
||||
*/
|
||||
monthSwitch() {
|
||||
let {
|
||||
year,
|
||||
month
|
||||
} = this.nowDate
|
||||
this.$emit('monthSwitch', {
|
||||
year,
|
||||
month: Number(month)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 派发事件
|
||||
* @param {Object} name
|
||||
*/
|
||||
setEmit(name) {
|
||||
let {
|
||||
year,
|
||||
month,
|
||||
date,
|
||||
fullDate,
|
||||
lunar,
|
||||
extraInfo
|
||||
} = this.calendar
|
||||
this.$emit(name, {
|
||||
range: this.cale.multipleStatus,
|
||||
year,
|
||||
month,
|
||||
date,
|
||||
fulldate: fullDate,
|
||||
lunar,
|
||||
extraInfo: extraInfo || {}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 选择天触发
|
||||
* @param {Object} weeks
|
||||
*/
|
||||
choiceDate(weeks) {
|
||||
if (weeks.disable) return
|
||||
this.calendar = weeks
|
||||
// 设置多选
|
||||
this.cale.setMultiple(this.calendar.fullDate)
|
||||
this.weeks = this.cale.weeks
|
||||
this.change()
|
||||
},
|
||||
/**
|
||||
* 回到今天
|
||||
*/
|
||||
backToday() {
|
||||
const nowYearMonth = `${this.nowDate.year}-${this.nowDate.month}`
|
||||
const date = this.cale.getDate(new Date())
|
||||
const todayYearMonth = `${date.year}-${date.month}`
|
||||
|
||||
this.init(date.fullDate)
|
||||
|
||||
if(nowYearMonth !== todayYearMonth) {
|
||||
this.monthSwitch()
|
||||
}
|
||||
|
||||
this.change()
|
||||
},
|
||||
/**
|
||||
* 上个月
|
||||
*/
|
||||
pre() {
|
||||
const preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate
|
||||
this.setDate(preDate)
|
||||
this.monthSwitch()
|
||||
|
||||
},
|
||||
/**
|
||||
* 下个月
|
||||
*/
|
||||
next() {
|
||||
const nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate
|
||||
this.setDate(nextDate)
|
||||
this.monthSwitch()
|
||||
},
|
||||
/**
|
||||
* 设置日期
|
||||
* @param {Object} date
|
||||
*/
|
||||
setDate(date) {
|
||||
this.cale.setDate(date)
|
||||
this.weeks = this.cale.weeks
|
||||
this.nowDate = this.cale.getInfo(date)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$uni-bg-color-mask: rgba($color: #000000, $alpha: 0.4);
|
||||
$uni-border-color: #EDEDED;
|
||||
$uni-text-color: #333;
|
||||
$uni-bg-color-hover:#f1f1f1;
|
||||
$uni-font-size-base:14px;
|
||||
$uni-text-color-placeholder: #808080;
|
||||
$uni-color-subtitle: #555555;
|
||||
$uni-text-color-grey:#999;
|
||||
.uni-calendar {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.uni-calendar__mask {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: $uni-bg-color-mask;
|
||||
transition-property: opacity;
|
||||
transition-duration: 0.3s;
|
||||
opacity: 0;
|
||||
/* #ifndef APP-NVUE */
|
||||
z-index: 99;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-calendar--mask-show {
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
.uni-calendar--fixed {
|
||||
position: fixed;
|
||||
/* #ifdef APP-NVUE */
|
||||
bottom: 0;
|
||||
/* #endif */
|
||||
left: 0;
|
||||
right: 0;
|
||||
transition-property: transform;
|
||||
transition-duration: 0.3s;
|
||||
transform: translateY(460px);
|
||||
/* #ifndef APP-NVUE */
|
||||
bottom: calc(var(--window-bottom));
|
||||
z-index: 99;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-calendar--ani-show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.uni-calendar__content {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.uni-calendar__header {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 50px;
|
||||
border-bottom-color: $uni-border-color;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.uni-calendar--fixed-top {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
border-top-color: $uni-border-color;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
}
|
||||
|
||||
.uni-calendar--fixed-width {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.uni-calendar__backtoday {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 25rpx;
|
||||
padding: 0 5px;
|
||||
padding-left: 10px;
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
font-size: 12px;
|
||||
border-top-left-radius: 25px;
|
||||
border-bottom-left-radius: 25px;
|
||||
color: $uni-text-color;
|
||||
background-color: $uni-bg-color-hover;
|
||||
}
|
||||
|
||||
.uni-calendar__header-text {
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
font-size: $uni-font-size-base;
|
||||
color: $uni-text-color;
|
||||
}
|
||||
|
||||
.uni-calendar__header-btn-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.uni-calendar__header-btn {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-left-color: $uni-text-color-placeholder;
|
||||
border-left-style: solid;
|
||||
border-left-width: 2px;
|
||||
border-top-color: $uni-color-subtitle;
|
||||
border-top-style: solid;
|
||||
border-top-width: 2px;
|
||||
}
|
||||
|
||||
.uni-calendar--left {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.uni-calendar--right {
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
|
||||
|
||||
.uni-calendar__weeks {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.uni-calendar__weeks-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.uni-calendar__weeks-day {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 45px;
|
||||
border-bottom-color: #F5F5F5;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.uni-calendar__weeks-day-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.uni-calendar__box {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.uni-calendar__box-bg {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.uni-calendar__box-bg-text {
|
||||
font-size: 200px;
|
||||
font-weight: bold;
|
||||
color: $uni-text-color-grey;
|
||||
opacity: 0.1;
|
||||
text-align: center;
|
||||
/* #ifndef APP-NVUE */
|
||||
line-height: 1;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
360
uni_modules/uni-calendar/components/uni-calendar/util.js
Normal file
360
uni_modules/uni-calendar/components/uni-calendar/util.js
Normal file
@@ -0,0 +1,360 @@
|
||||
import CALENDAR from './calendar.js'
|
||||
|
||||
class Calendar {
|
||||
constructor({
|
||||
date,
|
||||
selected,
|
||||
startDate,
|
||||
endDate,
|
||||
range
|
||||
} = {}) {
|
||||
// 当前日期
|
||||
this.date = this.getDate(new Date()) // 当前初入日期
|
||||
// 打点信息
|
||||
this.selected = selected || [];
|
||||
// 范围开始
|
||||
this.startDate = startDate
|
||||
// 范围结束
|
||||
this.endDate = endDate
|
||||
this.range = range
|
||||
// 多选状态
|
||||
this.cleanMultipleStatus()
|
||||
// 每周日期
|
||||
this.weeks = {}
|
||||
// this._getWeek(this.date.fullDate)
|
||||
}
|
||||
/**
|
||||
* 设置日期
|
||||
* @param {Object} date
|
||||
*/
|
||||
setDate(date) {
|
||||
this.selectDate = this.getDate(date)
|
||||
this._getWeek(this.selectDate.fullDate)
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理多选状态
|
||||
*/
|
||||
cleanMultipleStatus() {
|
||||
this.multipleStatus = {
|
||||
before: '',
|
||||
after: '',
|
||||
data: []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置开始日期
|
||||
*/
|
||||
resetSatrtDate(startDate) {
|
||||
// 范围开始
|
||||
this.startDate = startDate
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置结束日期
|
||||
*/
|
||||
resetEndDate(endDate) {
|
||||
// 范围结束
|
||||
this.endDate = endDate
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任意时间
|
||||
*/
|
||||
getDate(date, AddDayCount = 0, str = 'day') {
|
||||
if (!date) {
|
||||
date = new Date()
|
||||
}
|
||||
if (typeof date !== 'object') {
|
||||
date = date.replace(/-/g, '/')
|
||||
}
|
||||
const dd = new Date(date)
|
||||
switch (str) {
|
||||
case 'day':
|
||||
dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
|
||||
break
|
||||
case 'month':
|
||||
if (dd.getDate() === 31 && AddDayCount>0) {
|
||||
dd.setDate(dd.getDate() + AddDayCount)
|
||||
} else {
|
||||
const preMonth = dd.getMonth()
|
||||
dd.setMonth(preMonth + AddDayCount) // 获取AddDayCount天后的日期
|
||||
const nextMonth = dd.getMonth()
|
||||
// 处理 pre 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
|
||||
if(AddDayCount<0 && preMonth!==0 && nextMonth-preMonth>AddDayCount){
|
||||
dd.setMonth(nextMonth+(nextMonth-preMonth+AddDayCount))
|
||||
}
|
||||
// 处理 next 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
|
||||
if(AddDayCount>0 && nextMonth-preMonth>AddDayCount){
|
||||
dd.setMonth(nextMonth-(nextMonth-preMonth-AddDayCount))
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'year':
|
||||
dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期
|
||||
break
|
||||
}
|
||||
const y = dd.getFullYear()
|
||||
const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0
|
||||
const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0
|
||||
return {
|
||||
fullDate: y + '-' + m + '-' + d,
|
||||
year: y,
|
||||
month: m,
|
||||
date: d,
|
||||
day: dd.getDay()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取上月剩余天数
|
||||
*/
|
||||
_getLastMonthDays(firstDay, full) {
|
||||
let dateArr = []
|
||||
for (let i = firstDay; i > 0; i--) {
|
||||
const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()
|
||||
dateArr.push({
|
||||
date: beforeDate,
|
||||
month: full.month - 1,
|
||||
lunar: this.getlunar(full.year, full.month - 1, beforeDate),
|
||||
disable: true
|
||||
})
|
||||
}
|
||||
return dateArr
|
||||
}
|
||||
/**
|
||||
* 获取本月天数
|
||||
*/
|
||||
_currentMonthDys(dateData, full) {
|
||||
let dateArr = []
|
||||
let fullDate = this.date.fullDate
|
||||
for (let i = 1; i <= dateData; i++) {
|
||||
let nowDate = full.year + '-' + (full.month < 10 ?
|
||||
full.month : full.month) + '-' + (i < 10 ?
|
||||
'0' + i : i)
|
||||
// 是否今天
|
||||
let isDay = fullDate === nowDate
|
||||
// 获取打点信息
|
||||
let info = this.selected && this.selected.find((item) => {
|
||||
if (this.dateEqual(nowDate, item.date)) {
|
||||
return item
|
||||
}
|
||||
})
|
||||
|
||||
// 日期禁用
|
||||
let disableBefore = true
|
||||
let disableAfter = true
|
||||
if (this.startDate) {
|
||||
// let dateCompBefore = this.dateCompare(this.startDate, fullDate)
|
||||
// disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)
|
||||
disableBefore = this.dateCompare(this.startDate, nowDate)
|
||||
}
|
||||
|
||||
if (this.endDate) {
|
||||
// let dateCompAfter = this.dateCompare(fullDate, this.endDate)
|
||||
// disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
|
||||
disableAfter = this.dateCompare(nowDate, this.endDate)
|
||||
}
|
||||
let multiples = this.multipleStatus.data
|
||||
let checked = false
|
||||
let multiplesStatus = -1
|
||||
if (this.range) {
|
||||
if (multiples) {
|
||||
multiplesStatus = multiples.findIndex((item) => {
|
||||
return this.dateEqual(item, nowDate)
|
||||
})
|
||||
}
|
||||
if (multiplesStatus !== -1) {
|
||||
checked = true
|
||||
}
|
||||
}
|
||||
let data = {
|
||||
fullDate: nowDate,
|
||||
year: full.year,
|
||||
date: i,
|
||||
multiple: this.range ? checked : false,
|
||||
beforeMultiple: this.dateEqual(this.multipleStatus.before, nowDate),
|
||||
afterMultiple: this.dateEqual(this.multipleStatus.after, nowDate),
|
||||
month: full.month,
|
||||
lunar: this.getlunar(full.year, full.month, i),
|
||||
disable: !(disableBefore && disableAfter),
|
||||
isDay
|
||||
}
|
||||
if (info) {
|
||||
data.extraInfo = info
|
||||
}
|
||||
|
||||
dateArr.push(data)
|
||||
}
|
||||
return dateArr
|
||||
}
|
||||
/**
|
||||
* 获取下月天数
|
||||
*/
|
||||
_getNextMonthDays(surplus, full) {
|
||||
let dateArr = []
|
||||
for (let i = 1; i < surplus + 1; i++) {
|
||||
dateArr.push({
|
||||
date: i,
|
||||
month: Number(full.month) + 1,
|
||||
lunar: this.getlunar(full.year, Number(full.month) + 1, i),
|
||||
disable: true
|
||||
})
|
||||
}
|
||||
return dateArr
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前日期详情
|
||||
* @param {Object} date
|
||||
*/
|
||||
getInfo(date) {
|
||||
if (!date) {
|
||||
date = new Date()
|
||||
}
|
||||
const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)
|
||||
return dateInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较时间大小
|
||||
*/
|
||||
dateCompare(startDate, endDate) {
|
||||
// 计算截止时间
|
||||
startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
|
||||
// 计算详细项的截止时间
|
||||
endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
|
||||
if (startDate <= endDate) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较时间是否相等
|
||||
*/
|
||||
dateEqual(before, after) {
|
||||
// 计算截止时间
|
||||
before = new Date(before.replace('-', '/').replace('-', '/'))
|
||||
// 计算详细项的截止时间
|
||||
after = new Date(after.replace('-', '/').replace('-', '/'))
|
||||
if (before.getTime() - after.getTime() === 0) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取日期范围内所有日期
|
||||
* @param {Object} begin
|
||||
* @param {Object} end
|
||||
*/
|
||||
geDateAll(begin, end) {
|
||||
var arr = []
|
||||
var ab = begin.split('-')
|
||||
var ae = end.split('-')
|
||||
var db = new Date()
|
||||
db.setFullYear(ab[0], ab[1] - 1, ab[2])
|
||||
var de = new Date()
|
||||
de.setFullYear(ae[0], ae[1] - 1, ae[2])
|
||||
var unixDb = db.getTime() - 24 * 60 * 60 * 1000
|
||||
var unixDe = de.getTime() - 24 * 60 * 60 * 1000
|
||||
for (var k = unixDb; k <= unixDe;) {
|
||||
k = k + 24 * 60 * 60 * 1000
|
||||
arr.push(this.getDate(new Date(parseInt(k))).fullDate)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
/**
|
||||
* 计算阴历日期显示
|
||||
*/
|
||||
getlunar(year, month, date) {
|
||||
return CALENDAR.solar2lunar(year, month, date)
|
||||
}
|
||||
/**
|
||||
* 设置打点
|
||||
*/
|
||||
setSelectInfo(data, value) {
|
||||
this.selected = value
|
||||
this._getWeek(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多选状态
|
||||
*/
|
||||
setMultiple(fullDate) {
|
||||
let {
|
||||
before,
|
||||
after
|
||||
} = this.multipleStatus
|
||||
|
||||
if (!this.range) return
|
||||
if (before && after) {
|
||||
this.multipleStatus.before = fullDate
|
||||
this.multipleStatus.after = ''
|
||||
this.multipleStatus.data = []
|
||||
} else {
|
||||
if (!before) {
|
||||
this.multipleStatus.before = fullDate
|
||||
} else {
|
||||
this.multipleStatus.after = fullDate
|
||||
if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
|
||||
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);
|
||||
} else {
|
||||
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._getWeek(fullDate)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取每周数据
|
||||
* @param {Object} dateData
|
||||
*/
|
||||
_getWeek(dateData) {
|
||||
const {
|
||||
year,
|
||||
month
|
||||
} = this.getDate(dateData)
|
||||
let firstDay = new Date(year, month - 1, 1).getDay()
|
||||
let currentDay = new Date(year, month, 0).getDate()
|
||||
let dates = {
|
||||
lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
|
||||
currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数
|
||||
nextMonthDays: [], // 下个月开始几天
|
||||
weeks: []
|
||||
}
|
||||
let canlender = []
|
||||
const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)
|
||||
dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))
|
||||
canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)
|
||||
let weeks = {}
|
||||
// 拼接数组 上个月开始几天 + 本月天数+ 下个月开始几天
|
||||
for (let i = 0; i < canlender.length; i++) {
|
||||
if (i % 7 === 0) {
|
||||
weeks[parseInt(i / 7)] = new Array(7)
|
||||
}
|
||||
weeks[parseInt(i / 7)][i % 7] = canlender[i]
|
||||
}
|
||||
this.canlender = canlender
|
||||
this.weeks = weeks
|
||||
}
|
||||
|
||||
//静态方法
|
||||
// static init(date) {
|
||||
// if (!this.instance) {
|
||||
// this.instance = new Calendar(date);
|
||||
// }
|
||||
// return this.instance;
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
export default Calendar
|
||||
@@ -1,18 +1,22 @@
|
||||
{
|
||||
"id": "lime-painter",
|
||||
"displayName": "海报画板",
|
||||
"version": "1.9.6.6",
|
||||
"description": "一款canvas海报组件,更优雅的海报生成方案,有限的支持富文本",
|
||||
"id": "uni-calendar",
|
||||
"displayName": "uni-calendar 日历",
|
||||
"version": "1.4.12",
|
||||
"description": "日历组件",
|
||||
"keywords": [
|
||||
"海报",
|
||||
"富文本",
|
||||
"生成海报",
|
||||
"生成二维码",
|
||||
"JSON"
|
||||
"uni-ui",
|
||||
"uniui",
|
||||
"日历",
|
||||
"",
|
||||
"打卡",
|
||||
"日历选择"
|
||||
],
|
||||
"repository": "https://gitee.com/liangei/lime-painter",
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.4.14"
|
||||
"HBuilderX": ""
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
@@ -24,14 +28,14 @@
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": "305716444"
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "",
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
@@ -56,9 +60,9 @@
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "u",
|
||||
"Edge": "u",
|
||||
"Firefox": "u",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
@@ -66,11 +70,7 @@
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y",
|
||||
"钉钉": "u",
|
||||
"快手": "u",
|
||||
"飞书": "u",
|
||||
"京东": "u"
|
||||
"QQ": "y"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
@@ -82,12 +82,5 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "lime-painter",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
}
|
||||
103
uni_modules/uni-calendar/readme.md
Normal file
103
uni_modules/uni-calendar/readme.md
Normal file
@@ -0,0 +1,103 @@
|
||||
|
||||
|
||||
## Calendar 日历
|
||||
> **组件名:uni-calendar**
|
||||
> 代码块: `uCalendar`
|
||||
|
||||
|
||||
日历组件
|
||||
|
||||
> **注意事项**
|
||||
> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。
|
||||
> - 本组件农历转换使用的js是 [@1900-2100区间内的公历、农历互转](https://github.com/jjonline/calendar.js)
|
||||
> - 仅支持自定义组件模式
|
||||
> - `date`属性传入的应该是一个 String ,如: 2019-06-27 ,而不是 new Date()
|
||||
> - 通过 `insert` 属性来确定当前的事件是 @change 还是 @confirm 。理应合并为一个事件,但是为了区分模式,现使用两个事件,这里需要注意
|
||||
> - 弹窗模式下无法阻止后面的元素滚动,如有需要阻止,请在弹窗弹出后,手动设置滚动元素为不可滚动
|
||||
|
||||
|
||||
### 安装方式
|
||||
|
||||
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
|
||||
|
||||
如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
|
||||
|
||||
### 基本用法
|
||||
|
||||
在 ``template`` 中使用组件
|
||||
|
||||
```html
|
||||
<view>
|
||||
<uni-calendar
|
||||
:insert="true"
|
||||
:lunar="true"
|
||||
:start-date="'2019-3-2'"
|
||||
:end-date="'2019-5-20'"
|
||||
@change="change"
|
||||
/>
|
||||
</view>
|
||||
```
|
||||
|
||||
### 通过方法打开日历
|
||||
|
||||
需要设置 `insert` 为 `false`
|
||||
|
||||
```html
|
||||
<view>
|
||||
<uni-calendar
|
||||
ref="calendar"
|
||||
:insert="false"
|
||||
@confirm="confirm"
|
||||
/>
|
||||
<button @click="open">打开日历</button>
|
||||
</view>
|
||||
```
|
||||
|
||||
```javascript
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
open(){
|
||||
this.$refs.calendar.open();
|
||||
},
|
||||
confirm(e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### Calendar Props
|
||||
|
||||
| 属性名 | 类型 | 默认值| 说明 |
|
||||
| - | - | - | - |
|
||||
| date | String |- | 自定义当前时间,默认为今天 |
|
||||
| lunar | Boolean | false | 显示农历 |
|
||||
| startDate | String |- | 日期选择范围-开始日期 |
|
||||
| endDate | String |- | 日期选择范围-结束日期 |
|
||||
| range | Boolean | false | 范围选择 |
|
||||
| insert | Boolean | false | 插入模式,可选值,ture:插入模式;false:弹窗模式;默认为插入模式 |
|
||||
|clearDate |Boolean |true |弹窗模式是否清空上次选择内容 |
|
||||
| selected | Array |- | 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}] |
|
||||
|showMonth | Boolean | true | 是否显示月份为背景 |
|
||||
|
||||
### Calendar Events
|
||||
|
||||
| 事件名 | 说明 |返回值|
|
||||
| - | - | - |
|
||||
| open | 弹出日历组件,`insert :false` 时生效|- |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 组件示例
|
||||
|
||||
点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar](https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar)
|
||||
@@ -1,3 +1,5 @@
|
||||
## 1.4.4(2024-03-20)
|
||||
- 修复 titleBorder类型修正
|
||||
## 1.4.3(2022-01-25)
|
||||
- 修复 初始化的时候 ,open 属性失效的bug
|
||||
## 1.4.2(2022-01-21)
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
* @property {String} name 唯一标志符
|
||||
* @property {Boolean} open = [true|false] 是否展开组件
|
||||
* @property {Boolean} titleBorder = [true|false] 是否显示标题分隔线
|
||||
* @property {Boolean} border = [true|false] 是否显示分隔线
|
||||
* @property {String} border = ['auto'|'show'|'none'] 是否显示分隔线
|
||||
* @property {Boolean} disabled = [true|false] 是否展开面板
|
||||
* @property {Boolean} showAnimation = [true|false] 开启动画
|
||||
* @property {Boolean} showArrow = [true|false] 是否显示右侧箭头
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "uni-collapse",
|
||||
"displayName": "uni-collapse 折叠面板",
|
||||
"version": "1.4.3",
|
||||
"version": "1.4.4",
|
||||
"description": "Collapse 组件,可以折叠 / 展开的内容区域。",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
@@ -16,11 +16,7 @@
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
],
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
@@ -37,7 +33,8 @@
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
## 1.0.2(2024-09-21)
|
||||
- 新增 clearAble属性
|
||||
## 1.0.1(2021-11-23)
|
||||
- 优化 label、label-width 属性
|
||||
## 1.0.0(2021-11-19)
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
<text>{{label}}</text>
|
||||
</view>
|
||||
<view class="uni-combox__input-box">
|
||||
<input class="uni-combox__input" type="text" :placeholder="placeholder"
|
||||
placeholder-class="uni-combox__input-plac" v-model="inputVal" @input="onInput" @focus="onFocus"
|
||||
@blur="onBlur" />
|
||||
<uni-icons :type="showSelector? 'top' : 'bottom'" size="14" color="#999" @click="toggleSelector">
|
||||
<input class="uni-combox__input" type="text" :placeholder="placeholder" placeholder-class="uni-combox__input-plac"
|
||||
v-model="inputVal" @input="onInput" @focus="onFocus" @blur="onBlur" />
|
||||
<uni-icons v-if="!inputVal || !clearAble" :type="showSelector? 'top' : 'bottom'" size="14" color="#999" @click="toggleSelector">
|
||||
</uni-icons>
|
||||
<uni-icons v-if="inputVal && clearAble" type="clear" size="24" color="#999" @click="clean">
|
||||
</uni-icons>
|
||||
</view>
|
||||
<view class="uni-combox__selector" v-if="showSelector">
|
||||
@@ -16,8 +17,8 @@
|
||||
<view class="uni-combox__selector-empty" v-if="filterCandidatesLength === 0">
|
||||
<text>{{emptyTips}}</text>
|
||||
</view>
|
||||
<view class="uni-combox__selector-item" v-for="(item,index) in filterCandidates" :key="index"
|
||||
@click="onSelectorClick(index)">
|
||||
<view class="uni-combox__selector-item" v-for="(item,index) in filterCandidates" :key="index"
|
||||
@click="onSelectorClick(index)">
|
||||
<text>{{item}}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
@@ -41,6 +42,10 @@
|
||||
name: 'uniCombox',
|
||||
emits: ['input', 'update:modelValue'],
|
||||
props: {
|
||||
clearAble: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
@@ -143,6 +148,10 @@
|
||||
this.$emit('input', this.inputVal)
|
||||
this.$emit('update:modelValue', this.inputVal)
|
||||
})
|
||||
},
|
||||
clean() {
|
||||
this.inputVal = ''
|
||||
this.onInput()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "uni-combox",
|
||||
"displayName": "uni-combox 组合框",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"description": "可以选择也可以输入的表单项 ",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
@@ -17,11 +17,7 @@
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
],
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
@@ -38,7 +34,8 @@
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [
|
||||
@@ -49,7 +46,8 @@
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
"aliyun": "y",
|
||||
"alipay": "n"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
@@ -87,4 +85,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 1.2.4(2024-09-21)
|
||||
- 新增 支持控制显示位数 默认显示2位
|
||||
## 1.2.3(2024-02-20)
|
||||
- 新增 支持控制小时,分钟的显隐:showHour showMinute
|
||||
## 1.2.2(2022-01-19)
|
||||
- 修复 在微信小程序中样式不生效的bug
|
||||
## 1.2.1(2022-01-18)
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<view class="uni-countdown">
|
||||
<text v-if="showDay" :style="[timeStyle]" class="uni-countdown__number">{{ d }}</text>
|
||||
<text v-if="showDay" :style="[splitorStyle]" class="uni-countdown__splitor">{{dayText}}</text>
|
||||
<text :style="[timeStyle]" class="uni-countdown__number">{{ h }}</text>
|
||||
<text :style="[splitorStyle]" class="uni-countdown__splitor">{{ showColon ? ':' : hourText }}</text>
|
||||
<text :style="[timeStyle]" class="uni-countdown__number">{{ i }}</text>
|
||||
<text :style="[splitorStyle]" class="uni-countdown__splitor">{{ showColon ? ':' : minuteText }}</text>
|
||||
<text v-if="showHour" :style="[timeStyle]" class="uni-countdown__number">{{ h }}</text>
|
||||
<text v-if="showHour" :style="[splitorStyle]" class="uni-countdown__splitor">{{ showColon ? ':' : hourText }}</text>
|
||||
<text v-if="showMinute" :style="[timeStyle]" class="uni-countdown__number">{{ i }}</text>
|
||||
<text v-if="showMinute" :style="[splitorStyle]" class="uni-countdown__splitor">{{ showColon ? ':' : minuteText }}</text>
|
||||
<text :style="[timeStyle]" class="uni-countdown__number">{{ s }}</text>
|
||||
<text v-if="!showColon" :style="[splitorStyle]" class="uni-countdown__splitor">{{secondText}}</text>
|
||||
</view>
|
||||
@@ -30,6 +30,8 @@
|
||||
* @property {Number} second 秒
|
||||
* @property {Number} timestamp 时间戳
|
||||
* @property {Boolean} showDay = [true|false] 是否显示天数
|
||||
* @property {Boolean} showHour = [true|false] 是否显示小时
|
||||
* @property {Boolean} showMinute = [true|false] 是否显示分钟
|
||||
* @property {Boolean} show-colon = [true|false] 是否以冒号为分隔符
|
||||
* @property {String} splitorColor 分割符号颜色
|
||||
* @event {Function} timeup 倒计时时间到触发事件
|
||||
@@ -43,6 +45,14 @@
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showHour: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showMinute: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showColon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
@@ -86,6 +96,10 @@
|
||||
timestamp: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
filterShow : {
|
||||
type:Object,
|
||||
default:{}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -199,22 +213,13 @@
|
||||
} else {
|
||||
this.timeUp()
|
||||
}
|
||||
if (day < 10) {
|
||||
day = '0' + day
|
||||
}
|
||||
if (hour < 10) {
|
||||
hour = '0' + hour
|
||||
}
|
||||
if (minute < 10) {
|
||||
minute = '0' + minute
|
||||
}
|
||||
if (second < 10) {
|
||||
second = '0' + second
|
||||
}
|
||||
this.d = day
|
||||
this.h = hour
|
||||
this.i = minute
|
||||
this.s = second
|
||||
this.d = String(day).padStart(this.validFilterShow(this.filterShow.d), '0')
|
||||
this.h = String(hour).padStart(this.validFilterShow(this.filterShow.h), '0')
|
||||
this.i = String(minute).padStart(this.validFilterShow(this.filterShow.m), '0')
|
||||
this.s = String(second).padStart(this.validFilterShow(this.filterShow.s), '0')
|
||||
},
|
||||
validFilterShow(filter){
|
||||
return (filter && filter > 0) ? filter : 2;
|
||||
},
|
||||
startData() {
|
||||
this.seconds = this.toSeconds(this.timestamp, this.day, this.hour, this.minute, this.second)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "uni-countdown",
|
||||
"displayName": "uni-countdown 倒计时",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"description": "CountDown 倒计时组件",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
@@ -16,11 +16,7 @@
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
],
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
@@ -37,7 +33,8 @@
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": ["uni-scss"],
|
||||
@@ -45,7 +42,8 @@
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
"aliyun": "y",
|
||||
"alipay": "n"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
@@ -83,4 +81,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
## 1.0.6(2024-10-22)
|
||||
- 新增 当 multiple 为 false 且传递的 value 为 数组时,使用数组第一项用作反显
|
||||
## 1.0.5(2024-03-20)
|
||||
- 修复 单选模式下选中样式不生效的bug
|
||||
## 1.0.4(2024-01-27)
|
||||
- 修复 修复错别字chagne为change
|
||||
## 1.0.3(2022-09-16)
|
||||
- 可以使用 uni-scss 控制主题色
|
||||
## 1.0.2(2022-06-30)
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
|
||||
const events = {
|
||||
load: 'load',
|
||||
error: 'error'
|
||||
}
|
||||
const pageMode = {
|
||||
add: 'add',
|
||||
replace: 'replace'
|
||||
}
|
||||
|
||||
const attrs = [
|
||||
'pageCurrent',
|
||||
'pageSize',
|
||||
'collection',
|
||||
'action',
|
||||
'field',
|
||||
'getcount',
|
||||
'orderby',
|
||||
'where'
|
||||
]
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
listData: this.getone ? {} : [],
|
||||
paginationInternal: {
|
||||
current: this.pageCurrent,
|
||||
size: this.pageSize,
|
||||
count: 0
|
||||
},
|
||||
errorMessage: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
let db = null;
|
||||
let dbCmd = null;
|
||||
|
||||
if(this.collection){
|
||||
this.db = uniCloud.database();
|
||||
this.dbCmd = this.db.command;
|
||||
}
|
||||
|
||||
this._isEnded = false
|
||||
|
||||
this.$watch(() => {
|
||||
let al = []
|
||||
attrs.forEach(key => {
|
||||
al.push(this[key])
|
||||
})
|
||||
return al
|
||||
}, (newValue, oldValue) => {
|
||||
this.paginationInternal.pageSize = this.pageSize
|
||||
|
||||
let needReset = false
|
||||
for (let i = 2; i < newValue.length; i++) {
|
||||
if (newValue[i] != oldValue[i]) {
|
||||
needReset = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (needReset) {
|
||||
this.clear()
|
||||
this.reset()
|
||||
}
|
||||
if (newValue[0] != oldValue[0]) {
|
||||
this.paginationInternal.current = this.pageCurrent
|
||||
}
|
||||
|
||||
this._execLoadData()
|
||||
})
|
||||
|
||||
// #ifdef H5
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
this._debugDataList = []
|
||||
if (!window.unidev) {
|
||||
window.unidev = {
|
||||
clientDB: {
|
||||
data: []
|
||||
}
|
||||
}
|
||||
}
|
||||
unidev.clientDB.data.push(this._debugDataList)
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-TOUTIAO
|
||||
let changeName
|
||||
let events = this.$scope.dataset.eventOpts
|
||||
for (let i = 0; i < events.length; i++) {
|
||||
let event = events[i]
|
||||
if (event[0].includes('^load')) {
|
||||
changeName = event[1][0][0]
|
||||
}
|
||||
}
|
||||
if (changeName) {
|
||||
let parent = this.$parent
|
||||
let maxDepth = 16
|
||||
this._changeDataFunction = null
|
||||
while (parent && maxDepth > 0) {
|
||||
let fun = parent[changeName]
|
||||
if (fun && typeof fun === 'function') {
|
||||
this._changeDataFunction = fun
|
||||
maxDepth = 0
|
||||
break
|
||||
}
|
||||
parent = parent.$parent
|
||||
maxDepth--;
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// if (!this.manual) {
|
||||
// this.loadData()
|
||||
// }
|
||||
},
|
||||
// #ifdef H5
|
||||
beforeDestroy() {
|
||||
if (process.env.NODE_ENV === 'development' && window.unidev) {
|
||||
let cd = this._debugDataList
|
||||
let dl = unidev.clientDB.data
|
||||
for (let i = dl.length - 1; i >= 0; i--) {
|
||||
if (dl[i] === cd) {
|
||||
dl.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
loadData(args1, args2) {
|
||||
let callback = null
|
||||
if (typeof args1 === 'object') {
|
||||
if (args1.clear) {
|
||||
this.clear()
|
||||
this.reset()
|
||||
}
|
||||
if (args1.current !== undefined) {
|
||||
this.paginationInternal.current = args1.current
|
||||
}
|
||||
if (typeof args2 === 'function') {
|
||||
callback = args2
|
||||
}
|
||||
} else if (typeof args1 === 'function') {
|
||||
callback = args1
|
||||
}
|
||||
|
||||
this._execLoadData(callback)
|
||||
},
|
||||
loadMore() {
|
||||
if (this._isEnded) {
|
||||
return
|
||||
}
|
||||
this._execLoadData()
|
||||
},
|
||||
refresh() {
|
||||
this.clear()
|
||||
this._execLoadData()
|
||||
},
|
||||
clear() {
|
||||
this._isEnded = false
|
||||
this.listData = []
|
||||
},
|
||||
reset() {
|
||||
this.paginationInternal.current = 1
|
||||
},
|
||||
remove(id, {
|
||||
action,
|
||||
callback,
|
||||
confirmTitle,
|
||||
confirmContent
|
||||
} = {}) {
|
||||
if (!id || !id.length) {
|
||||
return
|
||||
}
|
||||
uni.showModal({
|
||||
title: confirmTitle || '提示',
|
||||
content: confirmContent || '是否删除该数据',
|
||||
showCancel: true,
|
||||
success: (res) => {
|
||||
if (!res.confirm) {
|
||||
return
|
||||
}
|
||||
this._execRemove(id, action, callback)
|
||||
}
|
||||
})
|
||||
},
|
||||
_execLoadData(callback) {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
this.errorMessage = ''
|
||||
|
||||
this._getExec().then((res) => {
|
||||
this.loading = false
|
||||
const {
|
||||
data,
|
||||
count
|
||||
} = res.result
|
||||
this._isEnded = data.length < this.pageSize
|
||||
|
||||
callback && callback(data, this._isEnded)
|
||||
this._dispatchEvent(events.load, data)
|
||||
|
||||
if (this.getone) {
|
||||
this.listData = data.length ? data[0] : undefined
|
||||
} else if (this.pageData === pageMode.add) {
|
||||
this.listData.push(...data)
|
||||
if (this.listData.length) {
|
||||
this.paginationInternal.current++
|
||||
}
|
||||
} else if (this.pageData === pageMode.replace) {
|
||||
this.listData = data
|
||||
this.paginationInternal.count = count
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
this._debugDataList.length = 0
|
||||
this._debugDataList.push(...JSON.parse(JSON.stringify(this.listData)))
|
||||
}
|
||||
// #endif
|
||||
}).catch((err) => {
|
||||
this.loading = false
|
||||
this.errorMessage = err
|
||||
callback && callback()
|
||||
this.$emit(events.error, err)
|
||||
})
|
||||
},
|
||||
_getExec() {
|
||||
let exec = this.db
|
||||
if (this.action) {
|
||||
exec = exec.action(this.action)
|
||||
}
|
||||
|
||||
exec = exec.collection(this.collection)
|
||||
|
||||
if (!(!this.where || !Object.keys(this.where).length)) {
|
||||
exec = exec.where(this.where)
|
||||
}
|
||||
if (this.field) {
|
||||
exec = exec.field(this.field)
|
||||
}
|
||||
if (this.orderby) {
|
||||
exec = exec.orderBy(this.orderby)
|
||||
}
|
||||
|
||||
const {
|
||||
current,
|
||||
size
|
||||
} = this.paginationInternal
|
||||
exec = exec.skip(size * (current - 1)).limit(size).get({
|
||||
getCount: this.getcount
|
||||
})
|
||||
|
||||
return exec
|
||||
},
|
||||
_execRemove(id, action, callback) {
|
||||
if (!this.collection || !id) {
|
||||
return
|
||||
}
|
||||
|
||||
const ids = Array.isArray(id) ? id : [id]
|
||||
if (!ids.length) {
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({
|
||||
mask: true
|
||||
})
|
||||
|
||||
let exec = this.db
|
||||
if (action) {
|
||||
exec = exec.action(action)
|
||||
}
|
||||
|
||||
exec.collection(this.collection).where({
|
||||
_id: dbCmd.in(ids)
|
||||
}).remove().then((res) => {
|
||||
callback && callback(res.result)
|
||||
if (this.pageData === pageMode.replace) {
|
||||
this.refresh()
|
||||
} else {
|
||||
this.removeData(ids)
|
||||
}
|
||||
}).catch((err) => {
|
||||
uni.showModal({
|
||||
content: err.message,
|
||||
showCancel: false
|
||||
})
|
||||
}).finally(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
},
|
||||
removeData(ids) {
|
||||
let il = ids.slice(0)
|
||||
let dl = this.listData
|
||||
for (let i = dl.length - 1; i >= 0; i--) {
|
||||
let index = il.indexOf(dl[i]._id)
|
||||
if (index >= 0) {
|
||||
dl.splice(i, 1)
|
||||
il.splice(index, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
_dispatchEvent(type, data) {
|
||||
if (this._changeDataFunction) {
|
||||
this._changeDataFunction(data, this._isEnded)
|
||||
} else {
|
||||
this.$emit(type, data, this._isEnded)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,21 @@
|
||||
<view class="uni-data-checklist" :style="{'margin-top':isTop+'px'}">
|
||||
<template v-if="!isLocal">
|
||||
<view class="uni-data-loading">
|
||||
<uni-load-more v-if="!mixinDatacomErrorMessage" status="loading" iconType="snow" :iconSize="18" :content-text="contentText"></uni-load-more>
|
||||
<uni-load-more v-if="!mixinDatacomErrorMessage" status="loading" iconType="snow" :iconSize="18"
|
||||
:content-text="contentText"></uni-load-more>
|
||||
<text v-else>{{mixinDatacomErrorMessage}}</text>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<checkbox-group v-if="multiple" class="checklist-group" :class="{'is-list':mode==='list' || wrap}" @change="chagne">
|
||||
<label class="checklist-box" :class="['is--'+mode,item.selected?'is-checked':'',(disabled || !!item.disabled)?'is-disable':'',index!==0&&mode==='list'?'is-list-border':'']"
|
||||
:style="item.styleBackgroud" v-for="(item,index) in dataList" :key="index">
|
||||
<checkbox class="hidden" hidden :disabled="disabled || !!item.disabled" :value="item[map.value]+''" :checked="item.selected" />
|
||||
<view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')" class="checkbox__inner" :style="item.styleIcon">
|
||||
<checkbox-group v-if="multiple" class="checklist-group" :class="{'is-list':mode==='list' || wrap}"
|
||||
@change="change">
|
||||
<label class="checklist-box"
|
||||
:class="['is--'+mode,item.selected?'is-checked':'',(disabled || !!item.disabled)?'is-disable':'',index!==0&&mode==='list'?'is-list-border':'']"
|
||||
:style="item.styleBackgroud" v-for="(item,index) in dataList" :key="index">
|
||||
<checkbox class="hidden" hidden :disabled="disabled || !!item.disabled" :value="item[map.value]+''"
|
||||
:checked="item.selected" />
|
||||
<view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')"
|
||||
class="checkbox__inner" :style="item.styleIcon">
|
||||
<view class="checkbox__inner-icon"></view>
|
||||
</view>
|
||||
<view class="checklist-content" :class="{'list-content':mode === 'list' && icon ==='left'}">
|
||||
@@ -20,13 +25,14 @@
|
||||
</view>
|
||||
</label>
|
||||
</checkbox-group>
|
||||
<radio-group v-else class="checklist-group" :class="{'is-list':mode==='list','is-wrap':wrap}" @change="chagne">
|
||||
<!-- -->
|
||||
<label class="checklist-box" :class="['is--'+mode,item.selected?'is-checked':'',(disabled || !!item.disabled)?'is-disable':'',index!==0&&mode==='list'?'is-list-border':'']"
|
||||
:style="item.styleBackgroud" v-for="(item,index) in dataList" :key="index">
|
||||
<radio class="hidden" hidden :disabled="disabled || item.disabled" :value="item[map.value]+''" :checked="item.selected" />
|
||||
<radio-group v-else class="checklist-group" :class="{'is-list':mode==='list','is-wrap':wrap}" @change="change">
|
||||
<label class="checklist-box"
|
||||
:class="['is--'+mode,item.selected?'is-checked':'',(disabled || !!item.disabled)?'is-disable':'',index!==0&&mode==='list'?'is-list-border':'']"
|
||||
:style="item.styleBackgroud" v-for="(item,index) in dataList" :key="index">
|
||||
<radio class="hidden" hidden :disabled="disabled || item.disabled" :value="item[map.value]+''"
|
||||
:checked="item.selected" />
|
||||
<view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')" class="radio__inner"
|
||||
:style="item.styleBackgroud">
|
||||
:style="item.styleBackgroud">
|
||||
<view class="radio__inner-icon" :style="item.styleIcon"></view>
|
||||
</view>
|
||||
<view class="checklist-content" :class="{'list-content':mode === 'list' && icon ==='left'}">
|
||||
@@ -68,7 +74,7 @@
|
||||
export default {
|
||||
name: 'uniDataChecklist',
|
||||
mixins: [uniCloud.mixinDatacom || {}],
|
||||
emits:['input','update:modelValue','change'],
|
||||
emits: ['input', 'update:modelValue', 'change'],
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
@@ -88,7 +94,7 @@
|
||||
// TODO vue3
|
||||
modelValue: {
|
||||
type: [Array, String, Number],
|
||||
default() {
|
||||
default () {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
@@ -122,20 +128,20 @@
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
emptyText:{
|
||||
emptyText: {
|
||||
type: String,
|
||||
default: '暂无数据'
|
||||
},
|
||||
disabled:{
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
map:{
|
||||
map: {
|
||||
type: Object,
|
||||
default(){
|
||||
default () {
|
||||
return {
|
||||
text:'text',
|
||||
value:'value'
|
||||
text: 'text',
|
||||
value: 'value'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,17 +161,17 @@
|
||||
value(newVal) {
|
||||
this.dataList = this.getDataList(newVal)
|
||||
// fix by mehaotian is_reset 在 uni-forms 中定义
|
||||
if(!this.is_reset){
|
||||
this.is_reset = false
|
||||
this.formItem && this.formItem.setValue(newVal)
|
||||
}
|
||||
// if(!this.is_reset){
|
||||
// this.is_reset = false
|
||||
// this.formItem && this.formItem.setValue(newVal)
|
||||
// }
|
||||
},
|
||||
modelValue(newVal) {
|
||||
this.dataList = this.getDataList(newVal);
|
||||
if(!this.is_reset){
|
||||
this.is_reset = false
|
||||
this.formItem && this.formItem.setValue(newVal)
|
||||
}
|
||||
// if(!this.is_reset){
|
||||
// this.is_reset = false
|
||||
// this.formItem && this.formItem.setValue(newVal)
|
||||
// }
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -177,38 +183,38 @@
|
||||
contentrefresh: '加载中',
|
||||
contentnomore: '没有更多'
|
||||
},
|
||||
isLocal:true,
|
||||
isLocal: true,
|
||||
styles: {
|
||||
selectedColor: '#2979ff',
|
||||
selectedTextColor: '#666',
|
||||
},
|
||||
isTop:0
|
||||
isTop: 0
|
||||
};
|
||||
},
|
||||
computed:{
|
||||
dataValue(){
|
||||
if(this.value === '')return this.modelValue
|
||||
if(this.modelValue === '') return this.value
|
||||
computed: {
|
||||
dataValue() {
|
||||
if (this.value === '') return this.modelValue
|
||||
if (this.modelValue === '') return this.value
|
||||
return this.value
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.form = this.getForm('uniForms')
|
||||
this.formItem = this.getForm('uniFormsItem')
|
||||
// this.form = this.getForm('uniForms')
|
||||
// this.formItem = this.getForm('uniFormsItem')
|
||||
// this.formItem && this.formItem.setValue(this.value)
|
||||
|
||||
if (this.formItem) {
|
||||
this.isTop = 6
|
||||
if (this.formItem.name) {
|
||||
// 如果存在name添加默认值,否则formData 中不存在这个字段不校验
|
||||
if(!this.is_reset){
|
||||
this.is_reset = false
|
||||
this.formItem.setValue(this.dataValue)
|
||||
}
|
||||
this.rename = this.formItem.name
|
||||
this.form.inputChildrens.push(this)
|
||||
}
|
||||
}
|
||||
// if (this.formItem) {
|
||||
// this.isTop = 6
|
||||
// if (this.formItem.name) {
|
||||
// // 如果存在name添加默认值,否则formData 中不存在这个字段不校验
|
||||
// if(!this.is_reset){
|
||||
// this.is_reset = false
|
||||
// this.formItem.setValue(this.dataValue)
|
||||
// }
|
||||
// this.rename = this.formItem.name
|
||||
// this.form.inputChildrens.push(this)
|
||||
// }
|
||||
// }
|
||||
|
||||
if (this.localdata && this.localdata.length !== 0) {
|
||||
this.isLocal = true
|
||||
@@ -223,15 +229,15 @@
|
||||
},
|
||||
methods: {
|
||||
loadData() {
|
||||
this.mixinDatacomGet().then(res=>{
|
||||
this.mixinDatacomGet().then(res => {
|
||||
this.mixinDatacomResData = res.result.data
|
||||
if(this.mixinDatacomResData.length === 0){
|
||||
if (this.mixinDatacomResData.length === 0) {
|
||||
this.isLocal = false
|
||||
this.mixinDatacomErrorMessage = this.emptyText
|
||||
}else{
|
||||
} else {
|
||||
this.isLocal = true
|
||||
}
|
||||
}).catch(err=>{
|
||||
}).catch(err => {
|
||||
this.mixinDatacomErrorMessage = err.message
|
||||
})
|
||||
},
|
||||
@@ -248,7 +254,7 @@
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
chagne(e) {
|
||||
change(e) {
|
||||
const values = e.detail.value
|
||||
|
||||
let detail = {
|
||||
@@ -273,7 +279,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
this.formItem && this.formItem.setValue(detail.value)
|
||||
// this.formItem && this.formItem.setValue(detail.value)
|
||||
// TODO 兼容 vue2
|
||||
this.$emit('input', detail.value);
|
||||
// // TOTO 兼容 vue3
|
||||
@@ -303,6 +309,10 @@
|
||||
if (!Array.isArray(value)) {
|
||||
value = []
|
||||
}
|
||||
} else {
|
||||
if (Array.isArray(value) && value.length) {
|
||||
value = value[0]
|
||||
}
|
||||
}
|
||||
dataList.forEach((item, index) => {
|
||||
item.disabled = item.disable || item.disabled || false
|
||||
@@ -375,7 +385,7 @@
|
||||
selectedArr.push(item[this.map.value])
|
||||
}
|
||||
})
|
||||
return this.dataValue && this.dataValue.length > 0 ? this.dataValue : selectedArr
|
||||
return this.dataValue.length > 0 ? this.dataValue : selectedArr
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -383,12 +393,14 @@
|
||||
*/
|
||||
setStyleBackgroud(item) {
|
||||
let styles = {}
|
||||
let selectedColor = this.selectedColor?this.selectedColor:'#2979ff'
|
||||
if (this.mode !== 'list') {
|
||||
styles['border-color'] = item.selected?selectedColor:'#DCDFE6'
|
||||
}
|
||||
if (this.mode === 'tag') {
|
||||
styles['background-color'] = item.selected? selectedColor:'#f5f5f5'
|
||||
let selectedColor = this.selectedColor ? this.selectedColor : '#2979ff'
|
||||
if (this.selectedColor) {
|
||||
if (this.mode !== 'list') {
|
||||
styles['border-color'] = item.selected ? selectedColor : '#DCDFE6'
|
||||
}
|
||||
if (this.mode === 'tag') {
|
||||
styles['background-color'] = item.selected ? selectedColor : '#f5f5f5'
|
||||
}
|
||||
}
|
||||
let classles = ''
|
||||
for (let i in styles) {
|
||||
@@ -399,15 +411,16 @@
|
||||
setStyleIcon(item) {
|
||||
let styles = {}
|
||||
let classles = ''
|
||||
let selectedColor = this.selectedColor?this.selectedColor:'#2979ff'
|
||||
styles['background-color'] = item.selected?selectedColor:'#fff'
|
||||
styles['border-color'] = item.selected?selectedColor:'#DCDFE6'
|
||||
if (this.selectedColor) {
|
||||
let selectedColor = this.selectedColor ? this.selectedColor : '#2979ff'
|
||||
styles['background-color'] = item.selected ? selectedColor : '#fff'
|
||||
styles['border-color'] = item.selected ? selectedColor : '#DCDFE6'
|
||||
|
||||
if(!item.selected && item.disabled){
|
||||
styles['background-color'] = '#F2F6FC'
|
||||
styles['border-color'] = item.selected?selectedColor:'#DCDFE6'
|
||||
if (!item.selected && item.disabled) {
|
||||
styles['background-color'] = '#F2F6FC'
|
||||
styles['border-color'] = item.selected ? selectedColor : '#DCDFE6'
|
||||
}
|
||||
}
|
||||
|
||||
for (let i in styles) {
|
||||
classles += `${i}:${styles[i]};`
|
||||
}
|
||||
@@ -416,16 +429,17 @@
|
||||
setStyleIconText(item) {
|
||||
let styles = {}
|
||||
let classles = ''
|
||||
let selectedColor = this.selectedColor?this.selectedColor:'#2979ff'
|
||||
if (this.mode === 'tag') {
|
||||
styles.color = item.selected?(this.selectedTextColor?this.selectedTextColor:'#fff'):'#666'
|
||||
} else {
|
||||
styles.color = item.selected?(this.selectedTextColor?this.selectedTextColor:selectedColor):'#666'
|
||||
if (this.selectedColor) {
|
||||
let selectedColor = this.selectedColor ? this.selectedColor : '#2979ff'
|
||||
if (this.mode === 'tag') {
|
||||
styles.color = item.selected ? (this.selectedTextColor ? this.selectedTextColor : '#fff') : '#666'
|
||||
} else {
|
||||
styles.color = item.selected ? (this.selectedTextColor ? this.selectedTextColor : selectedColor) : '#666'
|
||||
}
|
||||
if (!item.selected && item.disabled) {
|
||||
styles.color = '#999'
|
||||
}
|
||||
}
|
||||
if(!item.selected && item.disabled){
|
||||
styles.color = '#999'
|
||||
}
|
||||
|
||||
for (let i in styles) {
|
||||
classles += `${i}:${styles[i]};`
|
||||
}
|
||||
@@ -435,7 +449,7 @@
|
||||
let styles = {}
|
||||
let classles = ''
|
||||
if (this.mode === 'list') {
|
||||
styles['border-color'] = item.selected?this.styles.selectedColor:'#DCDFE6'
|
||||
styles['border-color'] = item.selected ? this.styles.selectedColor : '#DCDFE6'
|
||||
}
|
||||
for (let i in styles) {
|
||||
classles += `${i}:${styles[i]};`
|
||||
@@ -448,9 +462,9 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
$checked-color: #2979ff;
|
||||
$uni-primary: #2979ff !default;
|
||||
$border-color: #DCDFE6;
|
||||
$disable:0.4;
|
||||
$disable: 0.4;
|
||||
|
||||
@mixin flex {
|
||||
/* #ifndef APP-NVUE */
|
||||
@@ -472,6 +486,7 @@
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
flex: 1;
|
||||
|
||||
// 多选样式
|
||||
.checklist-group {
|
||||
@include flex;
|
||||
@@ -502,6 +517,7 @@
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.checklist-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
@@ -513,7 +529,7 @@
|
||||
border-right-width: 1px;
|
||||
border-right-color: #007aff;
|
||||
border-right-style: solid;
|
||||
border-bottom-width:1px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: #007aff;
|
||||
border-bottom-style: solid;
|
||||
height: 12px;
|
||||
@@ -538,6 +554,7 @@
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
z-index: 1;
|
||||
|
||||
.checkbox__inner-icon {
|
||||
position: absolute;
|
||||
/* #ifdef APP-NVUE */
|
||||
@@ -552,7 +569,7 @@
|
||||
border-right-width: 1px;
|
||||
border-right-color: #fff;
|
||||
border-right-style: solid;
|
||||
border-bottom-width:1px ;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: #fff;
|
||||
border-bottom-style: solid;
|
||||
opacity: 0;
|
||||
@@ -593,6 +610,7 @@
|
||||
&.is-disable {
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
|
||||
/* #endif */
|
||||
.checkbox__inner {
|
||||
background-color: #F2F6FC;
|
||||
@@ -606,6 +624,7 @@
|
||||
background-color: #F2F6FC;
|
||||
border-color: $border-color;
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: #999;
|
||||
}
|
||||
@@ -614,24 +633,28 @@
|
||||
// 选中
|
||||
&.is-checked {
|
||||
.checkbox__inner {
|
||||
border-color: $checked-color;
|
||||
background-color: $checked-color;
|
||||
border-color: $uni-primary;
|
||||
background-color: $uni-primary;
|
||||
|
||||
.checkbox__inner-icon {
|
||||
opacity: 1;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
border-color: $checked-color;
|
||||
border-color: $uni-primary;
|
||||
|
||||
.radio__inner-icon {
|
||||
opacity: 1;
|
||||
background-color: $checked-color;
|
||||
background-color: $uni-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: $checked-color;
|
||||
color: $uni-primary;
|
||||
}
|
||||
|
||||
// 选中禁用
|
||||
&.is-disable {
|
||||
.checkbox__inner {
|
||||
@@ -641,6 +664,7 @@
|
||||
.checklist-text {
|
||||
opacity: $disable;
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
opacity: $disable;
|
||||
}
|
||||
@@ -663,6 +687,7 @@
|
||||
/* #endif */
|
||||
border: 1px #eee solid;
|
||||
opacity: $disable;
|
||||
|
||||
.checkbox__inner {
|
||||
background-color: #F2F6FC;
|
||||
border-color: $border-color;
|
||||
@@ -670,6 +695,7 @@
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
background-color: #F2F6FC;
|
||||
border-color: $border-color;
|
||||
@@ -677,16 +703,19 @@
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-checked {
|
||||
border-color: $checked-color;
|
||||
border-color: $uni-primary;
|
||||
|
||||
.checkbox__inner {
|
||||
border-color: $checked-color;
|
||||
background-color: $checked-color;
|
||||
border-color: $uni-primary;
|
||||
background-color: $uni-primary;
|
||||
|
||||
.checkbox__inner-icon {
|
||||
opacity: 1;
|
||||
transform: rotate(45deg);
|
||||
@@ -694,16 +723,16 @@
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
border-color: $checked-color;
|
||||
border-color: $uni-primary;
|
||||
|
||||
.radio__inner-icon {
|
||||
opacity: 1;
|
||||
background-color: $checked-color;
|
||||
background-color: $uni-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: $checked-color;
|
||||
color: $uni-primary;
|
||||
}
|
||||
|
||||
// 选中禁用
|
||||
@@ -735,14 +764,15 @@
|
||||
}
|
||||
|
||||
&.is-checked {
|
||||
background-color: $checked-color;
|
||||
border-color: $checked-color;
|
||||
background-color: $uni-primary;
|
||||
border-color: $uni-primary;
|
||||
|
||||
.checklist-text {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 列表样式
|
||||
&.is--list {
|
||||
/* #ifndef APP-NVUE */
|
||||
@@ -760,6 +790,7 @@
|
||||
&.is-disable {
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
|
||||
/* #endif */
|
||||
.checkbox__inner {
|
||||
background-color: #F2F6FC;
|
||||
@@ -768,6 +799,7 @@
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: #999;
|
||||
}
|
||||
@@ -775,27 +807,31 @@
|
||||
|
||||
&.is-checked {
|
||||
.checkbox__inner {
|
||||
border-color: $checked-color;
|
||||
background-color: $checked-color;
|
||||
border-color: $uni-primary;
|
||||
background-color: $uni-primary;
|
||||
|
||||
.checkbox__inner-icon {
|
||||
opacity: 1;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
border-color: $uni-primary;
|
||||
.radio__inner-icon {
|
||||
opacity: 1;
|
||||
background-color: $uni-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: $checked-color;
|
||||
color: $uni-primary;
|
||||
}
|
||||
|
||||
.checklist-content {
|
||||
.checkobx__list {
|
||||
opacity: 1;
|
||||
border-color: $checked-color;
|
||||
border-color: $uni-primary;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "uni-data-checkbox",
|
||||
"displayName": "uni-data-checkbox 数据选择器",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.6",
|
||||
"description": "通过数据驱动的单选框和复选框",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
@@ -43,12 +43,15 @@
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
"aliyun": "y",
|
||||
"alipay": "n"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y"
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y",
|
||||
"app-harmony": "u",
|
||||
"app-uvue": "u"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
## 2.0.1(2024-08-22)
|
||||
- 修复 uni-app-x v-model 没有更新传入值的 bug
|
||||
## 2.0.0(2023-12-11)
|
||||
- 新增 支持 uni-app-x
|
||||
## 1.1.2(2023-04-11)
|
||||
- 修复 更改 modelValue 报错的 bug
|
||||
- 修复 v-for 未使用 key 值控制台 warning
|
||||
## 1.1.1(2023-02-21)
|
||||
- 修复代码合并时引发 value 属性为空时不渲染数据的问题
|
||||
## 1.1.0(2023-02-15)
|
||||
- 修复 localdata 不支持动态更新的bug
|
||||
## 1.0.9(2023-02-15)
|
||||
- 修复 localdata 不支持动态更新的bug
|
||||
## 1.0.8(2022-09-16)
|
||||
- 可以使用 uni-scss 控制主题色
|
||||
## 1.0.7(2022-07-06)
|
||||
- 优化 pc端图标位置不正确的问题
|
||||
## 1.0.6(2022-07-05)
|
||||
- 优化 显示样式
|
||||
## 1.0.5(2022-07-04)
|
||||
- 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug
|
||||
## 1.0.4(2022-04-19)
|
||||
- 修复 字节小程序 本地数据无法选择下一级的Bug
|
||||
## 1.0.3(2022-02-25)
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
<template>
|
||||
<view class="uni-data-tree">
|
||||
<view class="uni-data-tree-input" @click="handleInput">
|
||||
<slot :data="selectedPaths" :error="error">
|
||||
<view class="input-value" :class="{'input-value-border': border}">
|
||||
<text v-if="error!=null" class="error-text">{{error!.errMsg}}</text>
|
||||
<scroll-view v-if="selectedPaths.length" class="selected-path" scroll-x="true">
|
||||
<view class="selected-list">
|
||||
<template v-for="(item, index) in selectedPaths">
|
||||
<text class="text-color">{{item[mappingTextName]}}</text>
|
||||
<text v-if="index<selectedPaths.length-1" class="input-split-line">{{split}}</text>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<text v-else-if="error==null&&!loading" class="placeholder">{{placeholder}}</text>
|
||||
<view v-if="!readonly" class="arrow-area">
|
||||
<view class="input-arrow"></view>
|
||||
</view>
|
||||
</view>
|
||||
</slot>
|
||||
<view v-if="loading && !isOpened" class="selected-loading">
|
||||
<slot name="picker-loading" :loading="loading"></slot>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-data-tree-cover" v-if="isOpened" @click="handleClose"></view>
|
||||
<view class="uni-data-tree-dialog" v-if="isOpened">
|
||||
<view class="uni-popper__arrow"></view>
|
||||
<view class="dialog-caption">
|
||||
<view class="dialog-title-view">
|
||||
<text class="dialog-title">{{popupTitle}}</text>
|
||||
</view>
|
||||
<view class="dialog-close" @click="handleClose">
|
||||
<view class="dialog-close-plus" data-id="close"></view>
|
||||
<view class="dialog-close-plus dialog-close-rotate" data-id="close"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view ref="pickerView" class="uni-data-pickerview">
|
||||
<view v-if="error!=null" class="error">
|
||||
<text class="error-text">{{error!.errMsg}}</text>
|
||||
</view>
|
||||
<scroll-view v-if="!isCloudDataList" :scroll-x="true">
|
||||
<view class="selected-node-list">
|
||||
<template v-for="(item, index) in selectedNodes">
|
||||
<text class="selected-node-item" :class="{'selected-node-item-active':index==selectedIndex}"
|
||||
@click="onTabSelect(index)">
|
||||
{{item[mappingTextName]}}
|
||||
</text>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<list-view class="list-view" :scroll-y="true">
|
||||
<list-item class="list-item" v-for="(item, _) in currentDataList" @click="onNodeClick(item)">
|
||||
<text class="item-text" :class="{'item-text-disabled': item['disable']}">{{item[mappingTextName]}}</text>
|
||||
<text class="check" v-if="item[mappingValueName] == selectedNodes[selectedIndex][mappingValueName]"></text>
|
||||
</list-item>
|
||||
</list-view>
|
||||
<view class="loading-cover" v-if="loading">
|
||||
<slot name="pickerview-loading" :loading="loading"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { dataPicker } from "../uni-data-pickerview/uni-data-picker.uts"
|
||||
|
||||
/**
|
||||
* DataPicker 级联选择
|
||||
* @description 支持单列、和多列级联选择。列数没有限制,如果屏幕显示不全,顶部tab区域会左右滚动。
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=3796
|
||||
* @property {String} popup-title 弹出窗口标题
|
||||
* @property {Array} localdata 本地数据,参考
|
||||
* @property {Boolean} border = [true|false] 是否有边框
|
||||
* @property {Boolean} readonly = [true|false] 是否仅读
|
||||
* @property {Boolean} preload = [true|false] 是否预加载数据
|
||||
* @value true 开启预加载数据,点击弹出窗口后显示已加载数据
|
||||
* @value false 关闭预加载数据,点击弹出窗口后开始加载数据
|
||||
* @property {Boolean} step-searh = [true|false] 是否分布查询
|
||||
* @value true 启用分布查询,仅查询当前选中节点
|
||||
* @value false 关闭分布查询,一次查询出所有数据
|
||||
* @property {String|DBFieldString} self-field 分布查询当前字段名称
|
||||
* @property {String|DBFieldString} parent-field 分布查询父字段名称
|
||||
* @property {String|DBCollectionString} collection 表名
|
||||
* @property {String|DBFieldString} field 查询字段,多个字段用 `,` 分割
|
||||
* @property {String} orderby 排序字段及正序倒叙设置
|
||||
* @property {String|JQLString} where 查询条件
|
||||
* @event {Function} popupshow 弹出的选择窗口打开时触发此事件
|
||||
* @event {Function} popuphide 弹出的选择窗口关闭时触发此事件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniDataPicker',
|
||||
emits: ['popupopened', 'popupclosed', 'nodeclick', 'change', 'input', 'update:modelValue', 'inputclick'],
|
||||
mixins: [dataPicker],
|
||||
props: {
|
||||
popupTitle: {
|
||||
type: String,
|
||||
default: '请选择'
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择'
|
||||
},
|
||||
heightMobile: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
clearIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
split: {
|
||||
type: String,
|
||||
default: '/'
|
||||
},
|
||||
ellipsis: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isOpened: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isShowClearIcon() : boolean {
|
||||
if (this.readonly) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.clearIcon && this.selectedPaths.length > 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.load()
|
||||
},
|
||||
methods: {
|
||||
clear() {
|
||||
},
|
||||
load() {
|
||||
if (this.isLocalData) {
|
||||
this.loadLocalData()
|
||||
} else if (this.isCloudDataList || this.isCloudDataTree) {
|
||||
this.loadCloudDataPath()
|
||||
}
|
||||
},
|
||||
show() {
|
||||
this.isOpened = true
|
||||
this.$emit('popupopened')
|
||||
if (!this.hasCloudTreeData) {
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
hide() {
|
||||
this.isOpened = false
|
||||
this.$emit('popupclosed')
|
||||
},
|
||||
handleInput() {
|
||||
if (this.readonly) {
|
||||
this.$emit('inputclick')
|
||||
} else {
|
||||
this.show()
|
||||
}
|
||||
},
|
||||
handleClose() {
|
||||
this.hide()
|
||||
},
|
||||
onFinish() {
|
||||
this.selectedPaths = this.getChangeNodes()
|
||||
this.$emit('update:modelValue', this.selectedPaths)
|
||||
this.$emit('change', this.selectedPaths)
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import url("../uni-data-pickerview/uni-data-pickerview.css");
|
||||
|
||||
.uni-data-tree {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.uni-data-tree-input {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.selected-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
color: #DD524D;
|
||||
}
|
||||
|
||||
.input-value {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
padding: 5px 5px;
|
||||
padding-right: 5px;
|
||||
overflow: hidden;
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.input-value-border {
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.selected-path {
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.selected-list {
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.selected-item {
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.text-color {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: grey;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.input-split-line {
|
||||
opacity: .5;
|
||||
margin-left: 1px;
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
||||
.arrow-area {
|
||||
position: relative;
|
||||
padding: 0 12px;
|
||||
margin-left: auto;
|
||||
justify-content: center;
|
||||
transform: rotate(-45deg);
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.input-arrow {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-left: 2px solid #999;
|
||||
border-bottom: 2px solid #999;
|
||||
}
|
||||
|
||||
.uni-data-tree-cover {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, .4);
|
||||
flex-direction: column;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.uni-data-tree-dialog {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 20%;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #FFFFFF;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
flex-direction: column;
|
||||
z-index: 102;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dialog-caption {
|
||||
position: relative;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.dialog-title-view {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
align-self: center;
|
||||
padding: 0 10px;
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
.dialog-close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.dialog-close-plus {
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
background-color: #666;
|
||||
border-radius: 2px;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.dialog-close-rotate {
|
||||
position: absolute;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.uni-data-pickerview {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.icon-clear {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* #ifdef H5 */
|
||||
@media all and (min-width: 768px) {
|
||||
.uni-data-tree-cover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.uni-data-tree-dialog {
|
||||
position: absolute;
|
||||
top: 55px;
|
||||
height: auto;
|
||||
min-height: 400px;
|
||||
max-height: 50vh;
|
||||
background-color: #fff;
|
||||
border: 1px solid #EBEEF5;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
overflow: unset;
|
||||
}
|
||||
|
||||
.dialog-caption {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,7 +42,7 @@ export default {
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 20
|
||||
default: 500
|
||||
},
|
||||
getcount: {
|
||||
type: [Boolean, String],
|
||||
@@ -94,7 +94,7 @@ export default {
|
||||
},
|
||||
map: {
|
||||
type: Object,
|
||||
default() {
|
||||
default () {
|
||||
return {
|
||||
text: "text",
|
||||
value: "value"
|
||||
@@ -122,19 +122,22 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isLocaldata() {
|
||||
return !this.collection.length
|
||||
isLocalData() {
|
||||
return !this.collection.length;
|
||||
},
|
||||
postField() {
|
||||
let fields = [this.field];
|
||||
if (this.parentField) {
|
||||
fields.push(`${this.parentField} as parent_value`);
|
||||
}
|
||||
return fields.join(',');
|
||||
isCloudData() {
|
||||
return this.collection.length > 0;
|
||||
},
|
||||
isCloudDataList() {
|
||||
return (this.isCloudData && (!this.parentField && !this.selfField));
|
||||
},
|
||||
isCloudDataTree() {
|
||||
return (this.isCloudData && this.parentField && this.selfField);
|
||||
},
|
||||
dataValue() {
|
||||
let isModelValue = Array.isArray(this.modelValue) ? (this.modelValue.length > 0) : (this.modelValue !== null || this.modelValue !== undefined)
|
||||
return isModelValue ? this.modelValue : this.value
|
||||
let isModelValue = Array.isArray(this.modelValue) ? (this.modelValue.length > 0) : (this.modelValue !== null ||
|
||||
this.modelValue !== undefined);
|
||||
return isModelValue ? this.modelValue : this.value;
|
||||
},
|
||||
hasValue() {
|
||||
if (typeof this.dataValue === 'number') {
|
||||
@@ -183,8 +186,169 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
onPropsChange() {
|
||||
this._treeData = []
|
||||
this._treeData = [];
|
||||
},
|
||||
|
||||
// 填充 pickview 数据
|
||||
async loadData() {
|
||||
if (this.isLocalData) {
|
||||
this.loadLocalData();
|
||||
} else if (this.isCloudDataList) {
|
||||
this.loadCloudDataList();
|
||||
} else if (this.isCloudDataTree) {
|
||||
this.loadCloudDataTree();
|
||||
}
|
||||
},
|
||||
|
||||
// 加载本地数据
|
||||
async loadLocalData() {
|
||||
this._treeData = [];
|
||||
this._extractTree(this.localdata, this._treeData);
|
||||
|
||||
let inputValue = this.dataValue;
|
||||
if (inputValue === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(inputValue)) {
|
||||
inputValue = inputValue[inputValue.length - 1];
|
||||
if (typeof inputValue === 'object' && inputValue[this.map.value]) {
|
||||
inputValue = inputValue[this.map.value];
|
||||
}
|
||||
}
|
||||
|
||||
this.selected = this._findNodePath(inputValue, this.localdata);
|
||||
},
|
||||
|
||||
// 加载 Cloud 数据 (单列)
|
||||
async loadCloudDataList() {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
let response = await this.getCommand();
|
||||
let responseData = response.result.data;
|
||||
|
||||
this._treeData = responseData;
|
||||
|
||||
this._updateBindData();
|
||||
this._updateSelected();
|
||||
|
||||
this.onDataChange();
|
||||
} catch (e) {
|
||||
this.errorMessage = e;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载 Cloud 数据 (树形)
|
||||
async loadCloudDataTree() {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
let commandOptions = {
|
||||
field: this._cloudDataPostField(),
|
||||
where: this._cloudDataTreeWhere()
|
||||
};
|
||||
if (this.gettree) {
|
||||
commandOptions.startwith = `${this.selfField}=='${this.dataValue}'`;
|
||||
}
|
||||
|
||||
let response = await this.getCommand(commandOptions);
|
||||
let responseData = response.result.data;
|
||||
|
||||
this._treeData = responseData;
|
||||
this._updateBindData();
|
||||
this._updateSelected();
|
||||
|
||||
this.onDataChange();
|
||||
} catch (e) {
|
||||
this.errorMessage = e;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载 Cloud 数据 (节点)
|
||||
async loadCloudDataNode(callback) {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
let commandOptions = {
|
||||
field: this._cloudDataPostField(),
|
||||
where: this._cloudDataNodeWhere()
|
||||
};
|
||||
|
||||
let response = await this.getCommand(commandOptions);
|
||||
let responseData = response.result.data;
|
||||
|
||||
callback(responseData);
|
||||
} catch (e) {
|
||||
this.errorMessage = e;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 回显 Cloud 数据
|
||||
getCloudDataValue() {
|
||||
if (this.isCloudDataList) {
|
||||
return this.getCloudDataListValue();
|
||||
}
|
||||
|
||||
if (this.isCloudDataTree) {
|
||||
return this.getCloudDataTreeValue();
|
||||
}
|
||||
},
|
||||
|
||||
// 回显 Cloud 数据 (单列)
|
||||
getCloudDataListValue() {
|
||||
// 根据 field's as value标识匹配 where 条件
|
||||
let where = [];
|
||||
let whereField = this._getForeignKeyByField();
|
||||
if (whereField) {
|
||||
where.push(`${whereField} == '${this.dataValue}'`)
|
||||
}
|
||||
|
||||
where = where.join(' || ');
|
||||
|
||||
if (this.where) {
|
||||
where = `(${this.where}) && (${where})`
|
||||
}
|
||||
|
||||
return this.getCommand({
|
||||
field: this._cloudDataPostField(),
|
||||
where
|
||||
}).then((res) => {
|
||||
this.selected = res.result.data;
|
||||
return res.result.data;
|
||||
});
|
||||
},
|
||||
|
||||
// 回显 Cloud 数据 (树形)
|
||||
getCloudDataTreeValue() {
|
||||
return this.getCommand({
|
||||
field: this._cloudDataPostField(),
|
||||
getTreePath: {
|
||||
startWith: `${this.selfField}=='${this.dataValue}'`
|
||||
}
|
||||
}).then((res) => {
|
||||
let treePath = [];
|
||||
this._extractTreePath(res.result.data, treePath);
|
||||
this.selected = treePath;
|
||||
return treePath;
|
||||
});
|
||||
},
|
||||
|
||||
getCommand(options = {}) {
|
||||
/* eslint-disable no-undef */
|
||||
let db = uniCloud.database(this.spaceInfo)
|
||||
@@ -229,125 +393,16 @@ export default {
|
||||
|
||||
return db
|
||||
},
|
||||
getNodeData(callback) {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
this.getCommand({
|
||||
field: this.postField,
|
||||
where: this._pathWhere()
|
||||
}).then((res) => {
|
||||
this.loading = false
|
||||
this.selected = res.result.data
|
||||
callback && callback()
|
||||
}).catch((err) => {
|
||||
this.loading = false
|
||||
this.errorMessage = err
|
||||
})
|
||||
},
|
||||
getTreePath(callback) {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
|
||||
this.getCommand({
|
||||
field: this.postField,
|
||||
getTreePath: {
|
||||
startWith: `${this.selfField}=='${this.dataValue}'`
|
||||
}
|
||||
}).then((res) => {
|
||||
this.loading = false
|
||||
let treePath = []
|
||||
this._extractTreePath(res.result.data, treePath)
|
||||
this.selected = treePath
|
||||
callback && callback()
|
||||
}).catch((err) => {
|
||||
this.loading = false
|
||||
this.errorMessage = err
|
||||
})
|
||||
_cloudDataPostField() {
|
||||
let fields = [this.field];
|
||||
if (this.parentField) {
|
||||
fields.push(`${this.parentField} as parent_value`);
|
||||
}
|
||||
return fields.join(',');
|
||||
},
|
||||
loadData() {
|
||||
if (this.isLocaldata) {
|
||||
this._processLocalData()
|
||||
return
|
||||
}
|
||||
|
||||
if (this.dataValue != null) {
|
||||
this._loadNodeData((data) => {
|
||||
this._treeData = data
|
||||
this._updateBindData()
|
||||
this._updateSelected()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (this.stepSearh) {
|
||||
this._loadNodeData((data) => {
|
||||
this._treeData = data
|
||||
this._updateBindData()
|
||||
})
|
||||
} else {
|
||||
this._loadAllData((data) => {
|
||||
this._treeData = []
|
||||
this._extractTree(data, this._treeData, null)
|
||||
this._updateBindData()
|
||||
})
|
||||
}
|
||||
},
|
||||
_loadAllData(callback) {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
|
||||
this.getCommand({
|
||||
field: this.postField,
|
||||
gettree: true,
|
||||
startwith: `${this.selfField}=='${this.dataValue}'`
|
||||
}).then((res) => {
|
||||
this.loading = false
|
||||
callback(res.result.data)
|
||||
this.onDataChange()
|
||||
}).catch((err) => {
|
||||
this.loading = false
|
||||
this.errorMessage = err
|
||||
})
|
||||
},
|
||||
_loadNodeData(callback, pw) {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
|
||||
this.getCommand({
|
||||
field: this.postField,
|
||||
where: pw || this._postWhere(),
|
||||
pageSize: 500
|
||||
}).then((res) => {
|
||||
this.loading = false
|
||||
callback(res.result.data)
|
||||
this.onDataChange()
|
||||
}).catch((err) => {
|
||||
this.loading = false
|
||||
this.errorMessage = err
|
||||
})
|
||||
},
|
||||
_pathWhere() {
|
||||
let result = []
|
||||
let where_field = this._getParentNameByField();
|
||||
if (where_field) {
|
||||
result.push(`${where_field} == '${this.dataValue}'`)
|
||||
}
|
||||
|
||||
if (this.where) {
|
||||
return `(${this.where}) && (${result.join(' || ')})`
|
||||
}
|
||||
|
||||
return result.join(' || ')
|
||||
},
|
||||
_postWhere() {
|
||||
_cloudDataTreeWhere() {
|
||||
let result = []
|
||||
let selected = this.selected
|
||||
let parentField = this.parentField
|
||||
@@ -364,17 +419,35 @@ export default {
|
||||
if (this.where) {
|
||||
where.push(`(${this.where})`)
|
||||
}
|
||||
|
||||
if (result.length) {
|
||||
where.push(`(${result.join(' || ')})`)
|
||||
}
|
||||
|
||||
return where.join(' && ')
|
||||
},
|
||||
_nodeWhere() {
|
||||
let result = []
|
||||
let selected = this.selected
|
||||
|
||||
_cloudDataNodeWhere() {
|
||||
let where = []
|
||||
let selected = this.selected;
|
||||
if (selected.length) {
|
||||
result.push(`${this.parentField} == '${selected[selected.length - 1].value}'`)
|
||||
where.push(`${this.parentField} == '${selected[selected.length - 1].value}'`);
|
||||
}
|
||||
|
||||
where = where.join(' || ');
|
||||
|
||||
if (this.where) {
|
||||
return `(${this.where}) && (${where})`
|
||||
}
|
||||
|
||||
return where
|
||||
},
|
||||
|
||||
_getWhereByForeignKey() {
|
||||
let result = []
|
||||
let whereField = this._getForeignKeyByField();
|
||||
if (whereField) {
|
||||
result.push(`${whereField} == '${this.dataValue}'`)
|
||||
}
|
||||
|
||||
if (this.where) {
|
||||
@@ -383,41 +456,23 @@ export default {
|
||||
|
||||
return result.join(' || ')
|
||||
},
|
||||
_getParentNameByField() {
|
||||
const fields = this.field.split(',');
|
||||
let where_field = null;
|
||||
|
||||
_getForeignKeyByField() {
|
||||
let fields = this.field.split(',');
|
||||
let whereField = null;
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
const items = fields[i].split('as');
|
||||
if (items.length < 2) {
|
||||
continue;
|
||||
}
|
||||
if (items[1].trim() === 'value') {
|
||||
where_field = items[0].trim();
|
||||
whereField = items[0].trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return where_field
|
||||
},
|
||||
_isTreeView() {
|
||||
return (this.parentField && this.selfField)
|
||||
},
|
||||
_updateSelected() {
|
||||
var dl = this.dataList
|
||||
var sl = this.selected
|
||||
let textField = this.map.text
|
||||
let valueField = this.map.value
|
||||
for (var i = 0; i < sl.length; i++) {
|
||||
var value = sl[i].value
|
||||
var dl2 = dl[i]
|
||||
for (var j = 0; j < dl2.length; j++) {
|
||||
var item2 = dl2[j]
|
||||
if (item2[valueField] === value) {
|
||||
sl[i].text = item2[textField]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return whereField;
|
||||
},
|
||||
|
||||
_updateBindData(node) {
|
||||
const {
|
||||
dataList,
|
||||
@@ -445,6 +500,25 @@ export default {
|
||||
hasNodes
|
||||
}
|
||||
},
|
||||
|
||||
_updateSelected() {
|
||||
let dl = this.dataList
|
||||
let sl = this.selected
|
||||
let textField = this.map.text
|
||||
let valueField = this.map.value
|
||||
for (let i = 0; i < sl.length; i++) {
|
||||
let value = sl[i].value
|
||||
let dl2 = dl[i]
|
||||
for (let j = 0; j < dl2.length; j++) {
|
||||
let item2 = dl2[j]
|
||||
if (item2[valueField] === value) {
|
||||
sl[i].text = item2[textField]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_filterData(data, paths) {
|
||||
let dataList = []
|
||||
let hasNodes = true
|
||||
@@ -453,8 +527,8 @@ export default {
|
||||
return (item.parent_value === null || item.parent_value === undefined || item.parent_value === '')
|
||||
}))
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
var value = paths[i].value
|
||||
var nodes = data.filter((item) => {
|
||||
let value = paths[i].value
|
||||
let nodes = data.filter((item) => {
|
||||
return item.parent_value === value
|
||||
})
|
||||
|
||||
@@ -470,6 +544,7 @@ export default {
|
||||
hasNodes
|
||||
}
|
||||
},
|
||||
|
||||
_extractTree(nodes, result, parent_value) {
|
||||
let list = result || []
|
||||
let valueField = this.map.value
|
||||
@@ -493,6 +568,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_extractTreePath(nodes, result) {
|
||||
let list = result || []
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
@@ -512,6 +588,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_findNodePath(key, nodes, path = []) {
|
||||
let textField = this.map.text
|
||||
let valueField = this.map.value
|
||||
@@ -540,24 +617,6 @@ export default {
|
||||
path.pop()
|
||||
}
|
||||
return []
|
||||
},
|
||||
_processLocalData() {
|
||||
this._treeData = []
|
||||
this._extractTree(this.localdata, this._treeData)
|
||||
|
||||
var inputValue = this.dataValue
|
||||
if (inputValue === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (Array.isArray(inputValue)) {
|
||||
inputValue = inputValue[inputValue.length - 1]
|
||||
if (typeof inputValue === 'object' && inputValue[this.map.value]) {
|
||||
inputValue = inputValue[this.map.value]
|
||||
}
|
||||
}
|
||||
|
||||
this.selected = this._findNodePath(inputValue, this.localdata)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,692 @@
|
||||
export type PaginationType = {
|
||||
current : number,
|
||||
size : number,
|
||||
count : number
|
||||
}
|
||||
|
||||
export type LoadMoreType = {
|
||||
contentdown : string,
|
||||
contentrefresh : string,
|
||||
contentnomore : string
|
||||
}
|
||||
|
||||
export type SelectedItemType = {
|
||||
name : string,
|
||||
value : string,
|
||||
}
|
||||
|
||||
export type GetCommandOptions = {
|
||||
collection ?: UTSJSONObject,
|
||||
field ?: string,
|
||||
orderby ?: string,
|
||||
where ?: any,
|
||||
pageData ?: string,
|
||||
pageCurrent ?: number,
|
||||
pageSize ?: number,
|
||||
getCount ?: boolean,
|
||||
getTree ?: any,
|
||||
getTreePath ?: UTSJSONObject,
|
||||
startwith ?: string,
|
||||
limitlevel ?: number,
|
||||
groupby ?: string,
|
||||
groupField ?: string,
|
||||
distinct ?: boolean,
|
||||
pageIndistinct ?: boolean,
|
||||
foreignKey ?: string,
|
||||
loadtime ?: string,
|
||||
manual ?: boolean
|
||||
}
|
||||
|
||||
const DefaultSelectedNode = {
|
||||
text: '请选择',
|
||||
value: ''
|
||||
}
|
||||
|
||||
export const dataPicker = defineMixin({
|
||||
props: {
|
||||
localdata: {
|
||||
type: Array as PropType<Array<UTSJSONObject>>,
|
||||
default: [] as Array<UTSJSONObject>
|
||||
},
|
||||
collection: {
|
||||
type: Object,
|
||||
default: ''
|
||||
},
|
||||
field: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
orderby: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
where: {
|
||||
type: Object,
|
||||
default: ''
|
||||
},
|
||||
pageData: {
|
||||
type: String,
|
||||
default: 'add'
|
||||
},
|
||||
pageCurrent: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
getcount: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
gettree: {
|
||||
type: Object,
|
||||
default: ''
|
||||
},
|
||||
gettreepath: {
|
||||
type: Object,
|
||||
default: ''
|
||||
},
|
||||
startwith: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
limitlevel: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
groupby: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
groupField: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
distinct: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
pageIndistinct: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
foreignKey: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
loadtime: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
manual: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
preload: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
stepSearh: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
selfField: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
parentField: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
default: ''
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: ''
|
||||
},
|
||||
defaultProps: {
|
||||
type: Object as PropType<UTSJSONObject>,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
error: null as UniCloudError | null,
|
||||
treeData: [] as Array<UTSJSONObject>,
|
||||
selectedIndex: 0,
|
||||
selectedNodes: [] as Array<UTSJSONObject>,
|
||||
selectedPages: [] as Array<UTSJSONObject>[],
|
||||
selectedValue: '',
|
||||
selectedPaths: [] as Array<UTSJSONObject>,
|
||||
pagination: {
|
||||
current: 1,
|
||||
size: 20,
|
||||
count: 0
|
||||
} as PaginationType
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
mappingTextName() : string {
|
||||
// TODO
|
||||
return (this.defaultProps != null) ? this.defaultProps!.getString('text', 'text') : 'text'
|
||||
},
|
||||
mappingValueName() : string {
|
||||
// TODO
|
||||
return (this.defaultProps != null) ? this.defaultProps!.getString('value', 'value') : 'value'
|
||||
},
|
||||
currentDataList() : Array<UTSJSONObject> {
|
||||
if (this.selectedIndex > this.selectedPages.length - 1) {
|
||||
return [] as Array<UTSJSONObject>
|
||||
}
|
||||
return this.selectedPages[this.selectedIndex]
|
||||
},
|
||||
isLocalData() : boolean {
|
||||
return this.localdata.length > 0
|
||||
},
|
||||
isCloudData() : boolean {
|
||||
return this._checkIsNotNull(this.collection)
|
||||
},
|
||||
isCloudDataList() : boolean {
|
||||
return (this.isCloudData && (this.parentField.length == 0 && this.selfField.length == 0))
|
||||
},
|
||||
isCloudDataTree() : boolean {
|
||||
return (this.isCloudData && this.parentField.length > 0 && this.selfField.length > 0)
|
||||
},
|
||||
dataValue() : any {
|
||||
return this.hasModelValue ? this.modelValue : this.value
|
||||
},
|
||||
hasCloudTreeData() : boolean {
|
||||
return this.treeData.length > 0
|
||||
},
|
||||
hasModelValue() : boolean {
|
||||
if (typeof this.modelValue == 'string') {
|
||||
const valueString = this.modelValue as string
|
||||
return (valueString.length > 0)
|
||||
} else if (Array.isArray(this.modelValue)) {
|
||||
const valueArray = this.modelValue as Array<string>
|
||||
return (valueArray.length > 0)
|
||||
}
|
||||
return false
|
||||
},
|
||||
hasCloudDataValue() : boolean {
|
||||
if (typeof this.dataValue == 'string') {
|
||||
const valueString = this.dataValue as string
|
||||
return (valueString.length > 0)
|
||||
}
|
||||
return false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pagination.current = this.pageCurrent
|
||||
this.pagination.size = this.pageSize
|
||||
|
||||
this.$watch(
|
||||
() : any => [
|
||||
this.pageCurrent,
|
||||
this.pageSize,
|
||||
this.localdata,
|
||||
this.value,
|
||||
this.collection,
|
||||
this.field,
|
||||
this.getcount,
|
||||
this.orderby,
|
||||
this.where,
|
||||
this.groupby,
|
||||
this.groupField,
|
||||
this.distinct
|
||||
],
|
||||
(newValue : Array<any>, oldValue : Array<any>) => {
|
||||
this.pagination.size = this.pageSize
|
||||
if (newValue[0] !== oldValue[0]) {
|
||||
this.pagination.current = this.pageCurrent
|
||||
}
|
||||
|
||||
this.onPropsChange()
|
||||
}
|
||||
)
|
||||
},
|
||||
methods: {
|
||||
onPropsChange() {
|
||||
this.selectedIndex = 0
|
||||
this.selectedNodes.length = 0
|
||||
this.selectedPages.length = 0
|
||||
this.selectedPaths.length = 0
|
||||
|
||||
// 加载数据
|
||||
this.$nextTick(() => {
|
||||
this.loadData()
|
||||
})
|
||||
},
|
||||
|
||||
onTabSelect(index : number) {
|
||||
this.selectedIndex = index
|
||||
},
|
||||
|
||||
onNodeClick(nodeData : UTSJSONObject) {
|
||||
if (nodeData.getBoolean('disable', false)) {
|
||||
return
|
||||
}
|
||||
|
||||
const isLeaf = this._checkIsLeafNode(nodeData)
|
||||
|
||||
this._trimSelectedNodes(nodeData)
|
||||
|
||||
this.$emit('nodeclick', nodeData)
|
||||
|
||||
if (this.isLocalData) {
|
||||
if (isLeaf || !this._checkHasChildren(nodeData)) {
|
||||
this.onFinish()
|
||||
}
|
||||
} else if (this.isCloudDataList) {
|
||||
this.onFinish()
|
||||
} else if (this.isCloudDataTree) {
|
||||
if (isLeaf) {
|
||||
this.onFinish()
|
||||
} else if (!this._checkHasChildren(nodeData)) {
|
||||
// 尝试请求一次,如果没有返回数据标记为叶子节点
|
||||
this.loadCloudDataNode(nodeData)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getChangeNodes(): Array<UTSJSONObject> {
|
||||
const nodes: Array<UTSJSONObject> = []
|
||||
this.selectedNodes.forEach((node : UTSJSONObject) => {
|
||||
const newNode: UTSJSONObject = {}
|
||||
newNode[this.mappingTextName] = node.getString(this.mappingTextName)
|
||||
newNode[this.mappingValueName] = node.getString(this.mappingValueName)
|
||||
nodes.push(newNode)
|
||||
})
|
||||
return nodes
|
||||
},
|
||||
|
||||
onFinish() { },
|
||||
|
||||
// 加载数据(自动判定环境)
|
||||
loadData() {
|
||||
if (this.isLocalData) {
|
||||
this.loadLocalData()
|
||||
} else if (this.isCloudDataList) {
|
||||
this.loadCloudDataList()
|
||||
} else if (this.isCloudDataTree) {
|
||||
this.loadCloudDataTree()
|
||||
}
|
||||
},
|
||||
|
||||
// 加载本地数据
|
||||
loadLocalData() {
|
||||
this.treeData = this.localdata
|
||||
if (Array.isArray(this.dataValue)) {
|
||||
const value = this.dataValue as Array<UTSJSONObject>
|
||||
this.selectedPaths = value.slice(0)
|
||||
this._pushSelectedTreeNodes(value, this.localdata)
|
||||
} else {
|
||||
this._pushSelectedNodes(this.localdata)
|
||||
}
|
||||
},
|
||||
|
||||
// 加载 Cloud 数据 (单列)
|
||||
loadCloudDataList() {
|
||||
this._loadCloudData(null, (data : Array<UTSJSONObject>) => {
|
||||
this.treeData = data
|
||||
this._pushSelectedNodes(data)
|
||||
})
|
||||
},
|
||||
|
||||
// 加载 Cloud 数据 (树形)
|
||||
loadCloudDataTree() {
|
||||
let commandOptions = {
|
||||
field: this._cloudDataPostField(),
|
||||
where: this._cloudDataTreeWhere(),
|
||||
getTree: true
|
||||
} as GetCommandOptions
|
||||
if (this._checkIsNotNull(this.gettree)) {
|
||||
commandOptions.startwith = `${this.selfField}=='${this.dataValue as string}'`
|
||||
}
|
||||
this._loadCloudData(commandOptions, (data : Array<UTSJSONObject>) => {
|
||||
this.treeData = data
|
||||
if (this.selectedPaths.length > 0) {
|
||||
this._pushSelectedTreeNodes(this.selectedPaths, data)
|
||||
} else {
|
||||
this._pushSelectedNodes(data)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 加载 Cloud 数据 (节点)
|
||||
loadCloudDataNode(nodeData : UTSJSONObject) {
|
||||
const commandOptions = {
|
||||
field: this._cloudDataPostField(),
|
||||
where: this._cloudDataNodeWhere()
|
||||
} as GetCommandOptions
|
||||
this._loadCloudData(commandOptions, (data : Array<UTSJSONObject>) => {
|
||||
nodeData['children'] = data
|
||||
if (data.length == 0) {
|
||||
nodeData['isleaf'] = true
|
||||
this.onFinish()
|
||||
} else {
|
||||
this._pushSelectedNodes(data)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 回显 Cloud Tree Path
|
||||
loadCloudDataPath() {
|
||||
if (!this.hasCloudDataValue) {
|
||||
return
|
||||
}
|
||||
|
||||
const command : GetCommandOptions = {}
|
||||
|
||||
// 单列
|
||||
if (this.isCloudDataList) {
|
||||
// 根据 field's as value标识匹配 where 条件
|
||||
let where : Array<string> = [];
|
||||
let whereField = this._getForeignKeyByField();
|
||||
if (whereField.length > 0) {
|
||||
where.push(`${whereField} == '${this.dataValue as string}'`)
|
||||
}
|
||||
|
||||
let whereString = where.join(' || ')
|
||||
if (this._checkIsNotNull(this.where)) {
|
||||
whereString = `(${this.where}) && (${whereString})`
|
||||
}
|
||||
|
||||
command.field = this._cloudDataPostField()
|
||||
command.where = whereString
|
||||
}
|
||||
|
||||
// 树形
|
||||
if (this.isCloudDataTree) {
|
||||
command.field = this._cloudDataPostField()
|
||||
command.getTreePath = {
|
||||
startWith: `${this.selfField}=='${this.dataValue as string}'`
|
||||
}
|
||||
}
|
||||
|
||||
this._loadCloudData(command, (data : Array<UTSJSONObject>) => {
|
||||
this._extractTreePath(data, this.selectedPaths)
|
||||
})
|
||||
},
|
||||
|
||||
_loadCloudData(options ?: GetCommandOptions, callback ?: ((data : Array<UTSJSONObject>) => void)) {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
|
||||
this.error = null
|
||||
|
||||
this._getCommand(options).then((response : UniCloudDBGetResult) => {
|
||||
callback?.(response.data)
|
||||
}).catch((err : any | null) => {
|
||||
this.error = err as UniCloudError
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
_cloudDataPostField() : string {
|
||||
let fields = [this.field];
|
||||
if (this.parentField.length > 0) {
|
||||
fields.push(`${this.parentField} as parent_value`)
|
||||
}
|
||||
return fields.join(',')
|
||||
},
|
||||
|
||||
_cloudDataTreeWhere() : string {
|
||||
let result : Array<string> = []
|
||||
let selectedNodes = this.selectedNodes.length > 0 ? this.selectedNodes : this.selectedPaths
|
||||
let parentField = this.parentField
|
||||
if (parentField.length > 0) {
|
||||
result.push(`${parentField} == null || ${parentField} == ""`)
|
||||
}
|
||||
if (selectedNodes.length > 0) {
|
||||
for (var i = 0; i < selectedNodes.length - 1; i++) {
|
||||
const parentFieldValue = selectedNodes[i].getString('value', '')
|
||||
result.push(`${parentField} == '${parentFieldValue}'`)
|
||||
}
|
||||
}
|
||||
|
||||
let where : Array<string> = []
|
||||
if (this._checkIsNotNull(this.where)) {
|
||||
where.push(`(${this.where as string})`)
|
||||
}
|
||||
|
||||
if (result.length > 0) {
|
||||
where.push(`(${result.join(' || ')})`)
|
||||
}
|
||||
|
||||
return where.join(' && ')
|
||||
},
|
||||
|
||||
_cloudDataNodeWhere() : string {
|
||||
const where : Array<string> = []
|
||||
if (this.selectedNodes.length > 0) {
|
||||
const value = this.selectedNodes[this.selectedNodes.length - 1].getString('value', '')
|
||||
where.push(`${this.parentField} == '${value}'`)
|
||||
}
|
||||
|
||||
let whereString = where.join(' || ')
|
||||
if (this._checkIsNotNull(this.where)) {
|
||||
return `(${this.where as string}) && (${whereString})`
|
||||
}
|
||||
|
||||
return whereString
|
||||
},
|
||||
|
||||
_getWhereByForeignKey() : string {
|
||||
let result : Array<string> = []
|
||||
let whereField = this._getForeignKeyByField();
|
||||
if (whereField.length > 0) {
|
||||
result.push(`${whereField} == '${this.dataValue as string}'`)
|
||||
}
|
||||
|
||||
if (this._checkIsNotNull(this.where)) {
|
||||
return `(${this.where}) && (${result.join(' || ')})`
|
||||
}
|
||||
|
||||
return result.join(' || ')
|
||||
},
|
||||
|
||||
_getForeignKeyByField() : string {
|
||||
const fields = this.field.split(',')
|
||||
let whereField = ''
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
const items = fields[i].split('as')
|
||||
if (items.length < 2) {
|
||||
continue
|
||||
}
|
||||
if (items[1].trim() === 'value') {
|
||||
whereField = items[0].trim()
|
||||
break
|
||||
}
|
||||
}
|
||||
return whereField
|
||||
},
|
||||
|
||||
_getCommand(options ?: GetCommandOptions) : Promise<UniCloudDBGetResult> {
|
||||
let db = uniCloud.databaseForJQL()
|
||||
|
||||
let collection = Array.isArray(this.collection) ? db.collection(...(this.collection as Array<any>)) : db.collection(this.collection)
|
||||
|
||||
let filter : UniCloudDBFilter | null = null
|
||||
if (this.foreignKey.length > 0) {
|
||||
filter = collection.foreignKey(this.foreignKey)
|
||||
}
|
||||
|
||||
const where : any = options?.where ?? this.where
|
||||
if (typeof where == 'string') {
|
||||
const whereString = where as string
|
||||
if (whereString.length > 0) {
|
||||
filter = (filter != null) ? filter.where(where) : collection.where(where)
|
||||
}
|
||||
} else {
|
||||
filter = (filter != null) ? filter.where(where) : collection.where(where)
|
||||
}
|
||||
|
||||
let query : UniCloudDBQuery | null = null
|
||||
if (this.field.length > 0) {
|
||||
query = (filter != null) ? filter.field(this.field) : collection.field(this.field)
|
||||
}
|
||||
if (this.groupby.length > 0) {
|
||||
if (query != null) {
|
||||
query = query.groupBy(this.groupby)
|
||||
} else if (filter != null) {
|
||||
query = filter.groupBy(this.groupby)
|
||||
}
|
||||
}
|
||||
if (this.groupField.length > 0) {
|
||||
if (query != null) {
|
||||
query = query.groupField(this.groupField)
|
||||
} else if (filter != null) {
|
||||
query = filter.groupField(this.groupField)
|
||||
}
|
||||
}
|
||||
if (this.distinct == true) {
|
||||
if (query != null) {
|
||||
query = query.distinct(this.field)
|
||||
} else if (filter != null) {
|
||||
query = filter.distinct(this.field)
|
||||
}
|
||||
}
|
||||
if (this.orderby.length > 0) {
|
||||
if (query != null) {
|
||||
query = query.orderBy(this.orderby)
|
||||
} else if (filter != null) {
|
||||
query = filter.orderBy(this.orderby)
|
||||
}
|
||||
}
|
||||
|
||||
const size = this.pagination.size
|
||||
const current = this.pagination.current
|
||||
if (query != null) {
|
||||
query = query.skip(size * (current - 1)).limit(size)
|
||||
} else if (filter != null) {
|
||||
query = filter.skip(size * (current - 1)).limit(size)
|
||||
} else {
|
||||
query = collection.skip(size * (current - 1)).limit(size)
|
||||
}
|
||||
|
||||
const getOptions = {}
|
||||
const treeOptions = {
|
||||
limitLevel: this.limitlevel,
|
||||
startWith: this.startwith
|
||||
}
|
||||
if (this.getcount == true) {
|
||||
getOptions['getCount'] = this.getcount
|
||||
}
|
||||
|
||||
const getTree : any = options?.getTree ?? this.gettree
|
||||
if (typeof getTree == 'string') {
|
||||
const getTreeString = getTree as string
|
||||
if (getTreeString.length > 0) {
|
||||
getOptions['getTree'] = treeOptions
|
||||
}
|
||||
} else if (typeof getTree == 'object') {
|
||||
getOptions['getTree'] = treeOptions
|
||||
} else {
|
||||
getOptions['getTree'] = getTree
|
||||
}
|
||||
|
||||
const getTreePath = options?.getTreePath ?? this.gettreepath
|
||||
if (typeof getTreePath == 'string') {
|
||||
const getTreePathString = getTreePath as string
|
||||
if (getTreePathString.length > 0) {
|
||||
getOptions['getTreePath'] = getTreePath
|
||||
}
|
||||
} else {
|
||||
getOptions['getTreePath'] = getTreePath
|
||||
}
|
||||
|
||||
return query.get(getOptions)
|
||||
},
|
||||
|
||||
_checkIsNotNull(value : any) : boolean {
|
||||
if (typeof value == 'string') {
|
||||
const valueString = value as string
|
||||
return (valueString.length > 0)
|
||||
} else if (value instanceof UTSJSONObject) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
_checkIsLeafNode(nodeData : UTSJSONObject) : boolean {
|
||||
if (this.selectedIndex >= this.limitlevel) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (nodeData.getBoolean('isleaf', false)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
_checkHasChildren(nodeData : UTSJSONObject) : boolean {
|
||||
const children = nodeData.getArray('children') ?? ([] as Array<any>)
|
||||
return children.length > 0
|
||||
},
|
||||
|
||||
_pushSelectedNodes(nodes : Array<UTSJSONObject>) {
|
||||
this.selectedNodes.push(DefaultSelectedNode)
|
||||
this.selectedPages.push(nodes)
|
||||
this.selectedIndex = this.selectedPages.length - 1
|
||||
},
|
||||
|
||||
_trimSelectedNodes(nodeData : UTSJSONObject) {
|
||||
this.selectedNodes.splice(this.selectedIndex)
|
||||
this.selectedNodes.push(nodeData)
|
||||
|
||||
if (this.selectedPages.length > 0) {
|
||||
this.selectedPages.splice(this.selectedIndex + 1)
|
||||
}
|
||||
|
||||
const children = nodeData.getArray<UTSJSONObject>('children') ?? ([] as Array<UTSJSONObject>)
|
||||
if (children.length > 0) {
|
||||
this.selectedNodes.push(DefaultSelectedNode)
|
||||
this.selectedPages.push(children)
|
||||
}
|
||||
|
||||
this.selectedIndex = this.selectedPages.length - 1
|
||||
},
|
||||
|
||||
_pushSelectedTreeNodes(paths : Array<UTSJSONObject>, nodes : Array<UTSJSONObject>) {
|
||||
let children : Array<UTSJSONObject> = nodes
|
||||
paths.forEach((node : UTSJSONObject) => {
|
||||
const findNode = children.find((item : UTSJSONObject) : boolean => {
|
||||
return (item.getString(this.mappingValueName) == node.getString(this.mappingValueName))
|
||||
})
|
||||
if (findNode != null) {
|
||||
this.selectedPages.push(children)
|
||||
this.selectedNodes.push(node)
|
||||
children = findNode.getArray<UTSJSONObject>('children') ?? ([] as Array<UTSJSONObject>)
|
||||
}
|
||||
})
|
||||
this.selectedIndex = this.selectedPages.length - 1
|
||||
},
|
||||
|
||||
_extractTreePath(nodes : Array<UTSJSONObject>, result : Array<UTSJSONObject>) {
|
||||
if (nodes.length == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const node = nodes[0]
|
||||
result.push(node)
|
||||
|
||||
const children = node.getArray<UTSJSONObject>('children')
|
||||
if (Array.isArray(children) && children!.length > 0) {
|
||||
this._extractTreePath(children, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,76 @@
|
||||
.uni-data-pickerview {
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.loading-cover {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(150, 150, 150, .1);
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: #fff;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: #DD524D;
|
||||
}
|
||||
|
||||
.selected-node-list {
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.selected-node-item {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
padding: 8px 10px 8px 10px;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.selected-node-item-active {
|
||||
color: #007aff;
|
||||
border-bottom-color: #007aff;
|
||||
}
|
||||
|
||||
.list-view {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 12px 15px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.item-text {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.item-text-disabled {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.item-text-overflow {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.check {
|
||||
margin-right: 5px;
|
||||
border: 2px solid #007aff;
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
height: 12px;
|
||||
width: 6px;
|
||||
transform-origin: center;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<view class="uni-data-pickerview">
|
||||
<view v-if="error!=null" class="error">
|
||||
<text class="error-text">{{error!.errMsg}}</text>
|
||||
</view>
|
||||
<scroll-view v-if="!isCloudDataList" :scroll-x="true">
|
||||
<view class="selected-node-list">
|
||||
<template v-for="(item, index) in selectedNodes">
|
||||
<text class="selected-node-item" :class="{'selected-node-item-active':index==selectedIndex}"
|
||||
@click="onTabSelect(index)">
|
||||
{{item[mappingTextName]}}
|
||||
</text>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<list-view class="list-view" :scroll-y="true">
|
||||
<list-item class="list-item" v-for="(item, _) in currentDataList" @click="onNodeClick(item)">
|
||||
<text class="item-text" :class="{'item-text-disabled': item['disable']}">{{item[mappingTextName]}}</text>
|
||||
<text class="check" v-if="item[mappingValueName] == selectedNodes[selectedIndex][mappingValueName]"></text>
|
||||
</list-item>
|
||||
</list-view>
|
||||
<view class="loading-cover" v-if="loading">
|
||||
<slot name="pickerview-loading" :loading="loading"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { dataPicker } from "./uni-data-picker.uts"
|
||||
|
||||
/**
|
||||
* DataPickerview
|
||||
* @description uni-data-pickerview
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=3796
|
||||
* @property {Array} localdata 本地数据,参考
|
||||
* @property {Boolean} step-searh = [true|false] 是否分布查询
|
||||
* @value true 启用分布查询,仅查询当前选中节点
|
||||
* @value false 关闭分布查询,一次查询出所有数据
|
||||
* @property {String|DBFieldString} self-field 分布查询当前字段名称
|
||||
* @property {String|DBFieldString} parent-field 分布查询父字段名称
|
||||
* @property {String|DBCollectionString} collection 表名
|
||||
* @property {String|DBFieldString} field 查询字段,多个字段用 `,` 分割
|
||||
* @property {String} orderby 排序字段及正序倒叙设置
|
||||
* @property {String|JQLString} where 查询条件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniDataPickerView',
|
||||
emits: ['nodeclick', 'change', 'update:modelValue'],
|
||||
mixins: [dataPicker],
|
||||
props: {
|
||||
ellipsis: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
onFinish() {
|
||||
this.$emit('change', this.getChangeNodes())
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import url("uni-data-pickerview.css");
|
||||
</style>
|
||||
@@ -1,183 +1,174 @@
|
||||
<template>
|
||||
<view class="uni-data-pickerview">
|
||||
<scroll-view class="selected-area" scroll-x="true" scroll-y="false" :show-scrollbar="false">
|
||||
<view class="selected-list">
|
||||
<template v-for="(item,index) in selected">
|
||||
<view class="selected-item"
|
||||
:class="{'selected-item-active':index==selectedIndex, 'selected-item-text-overflow': ellipsis}"
|
||||
:key="index" v-if="item.text" @click="handleSelect(index)">
|
||||
<text class="">{{item.text}}</text>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="tab-c">
|
||||
<template v-for="(child, i) in dataList">
|
||||
<scroll-view class="list" :key="i" v-if="i==selectedIndex" :scroll-y="true">
|
||||
<view class="item" :class="{'is-disabled': !!item.disable}" v-for="(item, j) in child" :key="j"
|
||||
@click="handleNodeClick(item, i, j)">
|
||||
<text class="item-text item-text-overflow">{{item[map.text]}}</text>
|
||||
<view class="check" v-if="selected.length > i && item[map.value] == selected[i].value"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
<view class="uni-data-pickerview">
|
||||
<scroll-view v-if="!isCloudDataList" class="selected-area" scroll-x="true">
|
||||
<view class="selected-list">
|
||||
<view
|
||||
class="selected-item"
|
||||
v-for="(item,index) in selected"
|
||||
:key="index"
|
||||
:class="{
|
||||
'selected-item-active':index == selectedIndex
|
||||
}"
|
||||
@click="handleSelect(index)"
|
||||
>
|
||||
<text>{{item.text || ''}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="tab-c">
|
||||
<scroll-view class="list" :scroll-y="true">
|
||||
<view class="item" :class="{'is-disabled': !!item.disable}" v-for="(item, j) in dataList[selectedIndex]" :key="j"
|
||||
@click="handleNodeClick(item, selectedIndex, j)">
|
||||
<text class="item-text">{{item[map.text]}}</text>
|
||||
<view class="check" v-if="selected.length > selectedIndex && item[map.value] == selected[selectedIndex].value"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="loading-cover" v-if="loading">
|
||||
<uni-load-more class="load-more" :contentText="loadMore" status="loading"></uni-load-more>
|
||||
</view>
|
||||
<view class="error-message" v-if="errorMessage">
|
||||
<text class="error-text">{{errorMessage}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="loading-cover" v-if="loading">
|
||||
<uni-load-more class="load-more" :contentText="loadMore" status="loading"></uni-load-more>
|
||||
</view>
|
||||
<view class="error-message" v-if="errorMessage">
|
||||
<text class="error-text">{{errorMessage}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dataPicker from "./uni-data-picker.js"
|
||||
import dataPicker from "./uni-data-picker.js"
|
||||
|
||||
/**
|
||||
* DataPickerview
|
||||
* @description uni-data-pickerview
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=3796
|
||||
* @property {Array} localdata 本地数据,参考
|
||||
* @property {Boolean} step-searh = [true|false] 是否分布查询
|
||||
* @value true 启用分布查询,仅查询当前选中节点
|
||||
* @value false 关闭分布查询,一次查询出所有数据
|
||||
* @property {String|DBFieldString} self-field 分布查询当前字段名称
|
||||
* @property {String|DBFieldString} parent-field 分布查询父字段名称
|
||||
* @property {String|DBCollectionString} collection 表名
|
||||
* @property {String|DBFieldString} field 查询字段,多个字段用 `,` 分割
|
||||
* @property {String} orderby 排序字段及正序倒叙设置
|
||||
* @property {String|JQLString} where 查询条件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniDataPickerView',
|
||||
emits: ['nodeclick', 'change', 'datachange', 'update:modelValue'],
|
||||
mixins: [dataPicker],
|
||||
props: {
|
||||
managedMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
ellipsis: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
created() {
|
||||
if (this.managedMode) {
|
||||
return
|
||||
}
|
||||
/**
|
||||
* DataPickerview
|
||||
* @description uni-data-pickerview
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=3796
|
||||
* @property {Array} localdata 本地数据,参考
|
||||
* @property {Boolean} step-searh = [true|false] 是否分布查询
|
||||
* @value true 启用分布查询,仅查询当前选中节点
|
||||
* @value false 关闭分布查询,一次查询出所有数据
|
||||
* @property {String|DBFieldString} self-field 分布查询当前字段名称
|
||||
* @property {String|DBFieldString} parent-field 分布查询父字段名称
|
||||
* @property {String|DBCollectionString} collection 表名
|
||||
* @property {String|DBFieldString} field 查询字段,多个字段用 `,` 分割
|
||||
* @property {String} orderby 排序字段及正序倒叙设置
|
||||
* @property {String|JQLString} where 查询条件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniDataPickerView',
|
||||
emits: ['nodeclick', 'change', 'datachange', 'update:modelValue'],
|
||||
mixins: [dataPicker],
|
||||
props: {
|
||||
managedMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
ellipsis: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (!this.managedMode) {
|
||||
this.$nextTick(() => {
|
||||
this.loadData();
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onPropsChange() {
|
||||
this._treeData = [];
|
||||
this.selectedIndex = 0;
|
||||
this.$nextTick(() => {
|
||||
this.loadData();
|
||||
})
|
||||
},
|
||||
handleSelect(index) {
|
||||
this.selectedIndex = index;
|
||||
},
|
||||
handleNodeClick(item, i, j) {
|
||||
if (item.disable) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.load()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
onPropsChange() {
|
||||
this._treeData = []
|
||||
this.selectedIndex = 0
|
||||
this.load()
|
||||
},
|
||||
load() {
|
||||
if (this.isLocaldata) {
|
||||
this.loadData()
|
||||
} else if (this.dataValue.length) {
|
||||
this.getTreePath((res) => {
|
||||
this.loadData()
|
||||
})
|
||||
}
|
||||
},
|
||||
handleSelect(index) {
|
||||
this.selectedIndex = index
|
||||
},
|
||||
handleNodeClick(item, i, j) {
|
||||
if (item.disable) {
|
||||
return
|
||||
}
|
||||
const node = this.dataList[i][j]
|
||||
const text = node[this.map.text]
|
||||
const value = node[this.map.value]
|
||||
if (i < this.selected.length - 1) {
|
||||
this.selected.splice(i, this.selected.length - i)
|
||||
this.selected.push({
|
||||
text,
|
||||
value
|
||||
})
|
||||
} else if (i === this.selected.length - 1) {
|
||||
this.selected.splice(i, 1, {
|
||||
text,
|
||||
value
|
||||
})
|
||||
}
|
||||
const node = this.dataList[i][j];
|
||||
const text = node[this.map.text];
|
||||
const value = node[this.map.value];
|
||||
|
||||
if (node.isleaf) {
|
||||
this.onSelectedChange(node, node.isleaf)
|
||||
return
|
||||
}
|
||||
if (i < this.selected.length - 1) {
|
||||
this.selected.splice(i, this.selected.length - i)
|
||||
this.selected.push({
|
||||
text,
|
||||
value
|
||||
})
|
||||
} else if (i === this.selected.length - 1) {
|
||||
this.selected.splice(i, 1, {
|
||||
text,
|
||||
value
|
||||
})
|
||||
}
|
||||
|
||||
const {
|
||||
isleaf,
|
||||
hasNodes
|
||||
} = this._updateBindData()
|
||||
if (node.isleaf) {
|
||||
this.onSelectedChange(node, node.isleaf)
|
||||
return
|
||||
}
|
||||
|
||||
if (!this._isTreeView() && !hasNodes) {
|
||||
this.onSelectedChange(node, true)
|
||||
return
|
||||
}
|
||||
const {
|
||||
isleaf,
|
||||
hasNodes
|
||||
} = this._updateBindData()
|
||||
|
||||
if (this.isLocaldata && (!hasNodes || isleaf)) {
|
||||
this.onSelectedChange(node, true)
|
||||
return
|
||||
}
|
||||
// 本地数据
|
||||
if (this.isLocalData) {
|
||||
this.onSelectedChange(node, (!hasNodes || isleaf))
|
||||
} else if (this.isCloudDataList) { // Cloud 数据 (单列)
|
||||
this.onSelectedChange(node, true)
|
||||
} else if (this.isCloudDataTree) { // Cloud 数据 (树形)
|
||||
if (isleaf) {
|
||||
this.onSelectedChange(node, node.isleaf)
|
||||
} else if (!hasNodes) { // 请求一次服务器以确定是否为叶子节点
|
||||
this.loadCloudDataNode((data) => {
|
||||
if (!data.length) {
|
||||
node.isleaf = true
|
||||
} else {
|
||||
this._treeData.push(...data)
|
||||
this._updateBindData(node)
|
||||
}
|
||||
this.onSelectedChange(node, node.isleaf)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
updateData(data) {
|
||||
this._treeData = data.treeData
|
||||
this.selected = data.selected
|
||||
if (!this._treeData.length) {
|
||||
this.loadData()
|
||||
} else {
|
||||
//this.selected = data.selected
|
||||
this._updateBindData()
|
||||
}
|
||||
},
|
||||
onDataChange() {
|
||||
this.$emit('datachange');
|
||||
},
|
||||
onSelectedChange(node, isleaf) {
|
||||
if (isleaf) {
|
||||
this._dispatchEvent()
|
||||
}
|
||||
|
||||
if (!isleaf && !hasNodes) {
|
||||
this._loadNodeData((data) => {
|
||||
if (!data.length) {
|
||||
node.isleaf = true
|
||||
} else {
|
||||
this._treeData.push(...data)
|
||||
this._updateBindData(node)
|
||||
}
|
||||
this.onSelectedChange(node, node.isleaf)
|
||||
}, this._nodeWhere())
|
||||
return
|
||||
}
|
||||
|
||||
this.onSelectedChange(node, false)
|
||||
},
|
||||
updateData(data) {
|
||||
this._treeData = data.treeData
|
||||
this.selected = data.selected
|
||||
if (!this._treeData.length) {
|
||||
this.loadData()
|
||||
} else {
|
||||
//this.selected = data.selected
|
||||
this._updateBindData()
|
||||
}
|
||||
},
|
||||
onDataChange() {
|
||||
this.$emit('datachange')
|
||||
},
|
||||
onSelectedChange(node, isleaf) {
|
||||
if (isleaf) {
|
||||
this._dispatchEvent()
|
||||
}
|
||||
|
||||
if (node) {
|
||||
this.$emit('nodeclick', node)
|
||||
}
|
||||
},
|
||||
_dispatchEvent() {
|
||||
this.$emit('change', this.selected.slice(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node) {
|
||||
this.$emit('nodeclick', node)
|
||||
}
|
||||
},
|
||||
_dispatchEvent() {
|
||||
this.$emit('change', this.selected.slice(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style >
|
||||
|
||||
<style lang="scss">
|
||||
$uni-primary: #007aff !default;
|
||||
|
||||
.uni-data-pickerview {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
@@ -188,138 +179,137 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: #DD524D;
|
||||
}
|
||||
.error-text {
|
||||
color: #DD524D;
|
||||
}
|
||||
|
||||
.loading-cover {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, .5);
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 1001;
|
||||
}
|
||||
.loading-cover {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, .5);
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
/* #ifndef APP-NVUE */
|
||||
margin: auto;
|
||||
/* #endif */
|
||||
}
|
||||
.load-more {
|
||||
/* #ifndef APP-NVUE */
|
||||
margin: auto;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #fff;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 15px;
|
||||
opacity: .9;
|
||||
z-index: 102;
|
||||
}
|
||||
.error-message {
|
||||
background-color: #fff;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 15px;
|
||||
opacity: .9;
|
||||
z-index: 102;
|
||||
}
|
||||
|
||||
/* #ifdef APP-NVUE */
|
||||
.selected-area {
|
||||
width: 750rpx;
|
||||
}
|
||||
/* #ifdef APP-NVUE */
|
||||
.selected-area {
|
||||
width: 750rpx;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* #endif */
|
||||
.selected-list {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
padding: 0 5px;
|
||||
border-bottom: 1px solid #f8f8f8;
|
||||
}
|
||||
|
||||
.selected-list {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
padding: 0 5px;
|
||||
border-bottom: 1px solid #f8f8f8;
|
||||
}
|
||||
.selected-item {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
padding: 12px 0;
|
||||
text-align: center;
|
||||
/* #ifndef APP-NVUE */
|
||||
white-space: nowrap;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.selected-item {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
padding: 12px 0;
|
||||
text-align: center;
|
||||
/* #ifndef APP-NVUE */
|
||||
white-space: nowrap;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.selected-item-text-overflow {
|
||||
width: 168px;
|
||||
/* fix nvue */
|
||||
overflow: hidden;
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 6em;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
-o-text-overflow: ellipsis;
|
||||
/* #endif */
|
||||
}
|
||||
.selected-item-text-overflow {
|
||||
width: 168px;
|
||||
/* fix nvue */
|
||||
overflow: hidden;
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 6em;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
-o-text-overflow: ellipsis;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.selected-item-active {
|
||||
border-bottom: 2px solid #007aff;
|
||||
border-bottom: 2px solid $uni-primary;
|
||||
}
|
||||
|
||||
.selected-item-text {
|
||||
color: #007aff;
|
||||
color: $uni-primary;
|
||||
}
|
||||
|
||||
.tab-c {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
}
|
||||
.tab-c {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list {
|
||||
flex: 1;
|
||||
}
|
||||
.list {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: 12px 15px;
|
||||
/* border-bottom: 1px solid #f0f0f0; */
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.item {
|
||||
padding: 12px 15px;
|
||||
/* border-bottom: 1px solid #f0f0f0; */
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.is-disabled {
|
||||
opacity: .5;
|
||||
}
|
||||
.is-disabled {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.item-text {
|
||||
/* flex: 1; */
|
||||
color: #333333;
|
||||
}
|
||||
.item-text {
|
||||
/* flex: 1; */
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.item-text-overflow {
|
||||
width: 280px;
|
||||
/* fix nvue */
|
||||
overflow: hidden;
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 20em;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
-o-text-overflow: ellipsis;
|
||||
/* #endif */
|
||||
}
|
||||
.item-text-overflow {
|
||||
width: 280px;
|
||||
/* fix nvue */
|
||||
overflow: hidden;
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 20em;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
-o-text-overflow: ellipsis;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.check {
|
||||
margin-right: 5px;
|
||||
border: 2px solid #007aff;
|
||||
border: 2px solid $uni-primary;
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
height: 12px;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "uni-data-picker",
|
||||
"displayName": "uni-data-picker 数据驱动的picker选择器",
|
||||
"version": "1.0.4",
|
||||
"version": "2.0.1",
|
||||
"description": "单列、多列级联选择器,常用于省市区城市选择、公司部门选择、多级分类等场景",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
@@ -18,11 +18,7 @@
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
],
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
@@ -39,7 +35,8 @@
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [
|
||||
@@ -51,7 +48,8 @@
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
"aliyun": "y",
|
||||
"alipay": "n"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
@@ -76,7 +74,8 @@
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y"
|
||||
"QQ": "y",
|
||||
"京东": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
|
||||
@@ -19,4 +19,4 @@
|
||||
在uniCloud数据表中新建表“uni-id-address”和“opendb-city-china”,这2个表的schema自带foreignKey关联。在“uni-id-address”表的表结构页面使用schema2code生成前端页面,会自动生成地址管理的维护页面,自动从“opendb-city-china”表包含的中国所有省市区信息里选择地址。
|
||||
|
||||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-data-picker)
|
||||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||
39
uni_modules/uni-data-select/changelog.md
Normal file
39
uni_modules/uni-data-select/changelog.md
Normal file
@@ -0,0 +1,39 @@
|
||||
## 1.0.8(2024-03-28)
|
||||
- 修复 在vue2下:style动态绑定导致编译失败的bug
|
||||
## 1.0.7(2024-01-20)
|
||||
- 修复 长文本回显超过容器的bug,超过容器部分显示省略号
|
||||
## 1.0.6(2023-04-12)
|
||||
- 修复 微信小程序点击时会改变背景颜色的 bug
|
||||
## 1.0.5(2023-02-03)
|
||||
- 修复 禁用时会显示清空按钮
|
||||
## 1.0.4(2023-02-02)
|
||||
- 优化 查询条件短期内多次变更只查询最后一次变更后的结果
|
||||
- 调整 内部缓存键名调整为 uni-data-select-lastSelectedValue
|
||||
## 1.0.3(2023-01-16)
|
||||
- 修复 不关联服务空间报错的问题
|
||||
## 1.0.2(2023-01-14)
|
||||
- 新增 属性 `format` 可用于格式化显示选项内容
|
||||
## 1.0.1(2022-12-06)
|
||||
- 修复 当where变化时,数据不会自动更新的问题
|
||||
## 0.1.9(2022-09-05)
|
||||
- 修复 微信小程序下拉框出现后选择会点击到蒙板后面的输入框
|
||||
## 0.1.8(2022-08-29)
|
||||
- 修复 点击的位置不准确
|
||||
## 0.1.7(2022-08-12)
|
||||
- 新增 支持 disabled 属性
|
||||
## 0.1.6(2022-07-06)
|
||||
- 修复 pc端宽度异常的bug
|
||||
## 0.1.5
|
||||
- 修复 pc端宽度异常的bug
|
||||
## 0.1.4(2022-07-05)
|
||||
- 优化 显示样式
|
||||
## 0.1.3(2022-06-02)
|
||||
- 修复 localdata 赋值不生效的 bug
|
||||
- 新增 支持 uni.scss 修改颜色
|
||||
- 新增 支持选项禁用(数据选项设置 disabled: true 即禁用)
|
||||
## 0.1.2(2022-05-08)
|
||||
- 修复 当 value 为 0 时选择不生效的 bug
|
||||
## 0.1.1(2022-05-07)
|
||||
- 新增 记住上次的选项(仅 collection 存在时有效)
|
||||
## 0.1.0(2022-04-22)
|
||||
- 初始化
|
||||
@@ -0,0 +1,562 @@
|
||||
<template>
|
||||
<view class="uni-stat__select">
|
||||
<span v-if="label" class="uni-label-text hide-on-phone">{{label + ':'}}</span>
|
||||
<view class="uni-stat-box" :class="{'uni-stat__actived': current}">
|
||||
<view class="uni-select" :class="{'uni-select--disabled':disabled}">
|
||||
<view class="uni-select__input-box" @click="toggleSelector">
|
||||
<view v-if="current" class="uni-select__input-text">{{textShow}}</view>
|
||||
<view v-else class="uni-select__input-text uni-select__input-placeholder">{{typePlaceholder}}</view>
|
||||
<view v-if="current && clear && !disabled" @click.stop="clearVal">
|
||||
<uni-icons type="clear" color="#c0c4cc" size="24" />
|
||||
</view>
|
||||
<view v-else>
|
||||
<uni-icons :type="showSelector? 'top' : 'bottom'" size="14" color="#999" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-select--mask" v-if="showSelector" @click="toggleSelector" />
|
||||
<view class="uni-select__selector" :style="getOffsetByPlacement" v-if="showSelector">
|
||||
<view :class="placement=='bottom'?'uni-popper__arrow_bottom':'uni-popper__arrow_top'"></view>
|
||||
<scroll-view scroll-y="true" class="uni-select__selector-scroll">
|
||||
<view class="uni-select__selector-empty" v-if="mixinDatacomResData.length === 0">
|
||||
<text>{{emptyTips}}</text>
|
||||
</view>
|
||||
<view v-else class="uni-select__selector-item" v-for="(item,index) in mixinDatacomResData" :key="index"
|
||||
@click="change(item)">
|
||||
<text :class="{'uni-select__selector__disabled': item.disable}">{{formatItemName(item)}}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* DataChecklist 数据选择器
|
||||
* @description 通过数据渲染的下拉框组件
|
||||
* @tutorial https://uniapp.dcloud.io/component/uniui/uni-data-select
|
||||
* @property {String} value 默认值
|
||||
* @property {Array} localdata 本地数据 ,格式 [{text:'',value:''}]
|
||||
* @property {Boolean} clear 是否可以清空已选项
|
||||
* @property {Boolean} emptyText 没有数据时显示的文字 ,本地数据无效
|
||||
* @property {String} label 左侧标题
|
||||
* @property {String} placeholder 输入框的提示文字
|
||||
* @property {Boolean} disabled 是否禁用
|
||||
* @property {String} placement 弹出位置
|
||||
* @value top 顶部弹出
|
||||
* @value bottom 底部弹出(default)
|
||||
* @event {Function} change 选中发生变化触发
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: "uni-data-select",
|
||||
mixins: [uniCloud.mixinDatacom || {}],
|
||||
props: {
|
||||
localdata: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择'
|
||||
},
|
||||
emptyTips: {
|
||||
type: String,
|
||||
default: '无选项'
|
||||
},
|
||||
clear: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
defItem: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 格式化输出 用法 field="_id as value, version as text, uni_platform as label" format="{label} - {text}"
|
||||
format: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottom'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showSelector: false,
|
||||
current: '',
|
||||
mixinDatacomResData: [],
|
||||
apps: [],
|
||||
channels: [],
|
||||
cacheKey: "uni-data-select-lastSelectedValue",
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.debounceGet = this.debounce(() => {
|
||||
this.query();
|
||||
}, 300);
|
||||
if (this.collection && !this.localdata.length) {
|
||||
this.debounceGet();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
typePlaceholder() {
|
||||
const text = {
|
||||
'opendb-stat-app-versions': '版本',
|
||||
'opendb-app-channels': '渠道',
|
||||
'opendb-app-list': '应用'
|
||||
}
|
||||
const common = this.placeholder
|
||||
const placeholder = text[this.collection]
|
||||
return placeholder ?
|
||||
common + placeholder :
|
||||
common
|
||||
},
|
||||
valueCom() {
|
||||
// #ifdef VUE3
|
||||
return this.modelValue;
|
||||
// #endif
|
||||
// #ifndef VUE3
|
||||
return this.value;
|
||||
// #endif
|
||||
},
|
||||
textShow() {
|
||||
// 长文本显示
|
||||
let text = this.current;
|
||||
if (text.length > 10) {
|
||||
return text.slice(0, 25) + '...';
|
||||
}
|
||||
return text;
|
||||
},
|
||||
getOffsetByPlacement() {
|
||||
switch (this.placement) {
|
||||
case 'top':
|
||||
return "bottom:calc(100% + 12px);";
|
||||
case 'bottom':
|
||||
return "top:calc(100% + 12px);";
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
localdata: {
|
||||
immediate: true,
|
||||
handler(val, old) {
|
||||
if (Array.isArray(val) && old !== val) {
|
||||
this.mixinDatacomResData = val
|
||||
}
|
||||
}
|
||||
},
|
||||
valueCom(val, old) {
|
||||
this.initDefVal()
|
||||
},
|
||||
mixinDatacomResData: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
if (val.length) {
|
||||
this.initDefVal()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
methods: {
|
||||
debounce(fn, time = 100) {
|
||||
let timer = null
|
||||
return function(...args) {
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(() => {
|
||||
fn.apply(this, args)
|
||||
}, time)
|
||||
}
|
||||
},
|
||||
// 执行数据库查询
|
||||
query() {
|
||||
this.mixinDatacomEasyGet();
|
||||
},
|
||||
// 监听查询条件变更事件
|
||||
onMixinDatacomPropsChange() {
|
||||
if (this.collection) {
|
||||
this.debounceGet();
|
||||
}
|
||||
},
|
||||
initDefVal() {
|
||||
let defValue = ''
|
||||
if ((this.valueCom || this.valueCom === 0) && !this.isDisabled(this.valueCom)) {
|
||||
defValue = this.valueCom
|
||||
} else {
|
||||
let strogeValue
|
||||
if (this.collection) {
|
||||
strogeValue = this.getCache()
|
||||
}
|
||||
if (strogeValue || strogeValue === 0) {
|
||||
defValue = strogeValue
|
||||
} else {
|
||||
let defItem = ''
|
||||
if (this.defItem > 0 && this.defItem <= this.mixinDatacomResData.length) {
|
||||
defItem = this.mixinDatacomResData[this.defItem - 1].value
|
||||
}
|
||||
defValue = defItem
|
||||
}
|
||||
if (defValue || defValue === 0) {
|
||||
this.emit(defValue)
|
||||
}
|
||||
}
|
||||
const def = this.mixinDatacomResData.find(item => item.value === defValue)
|
||||
this.current = def ? this.formatItemName(def) : ''
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {[String, Number]} value
|
||||
* 判断用户给的 value 是否同时为禁用状态
|
||||
*/
|
||||
isDisabled(value) {
|
||||
let isDisabled = false;
|
||||
|
||||
this.mixinDatacomResData.forEach(item => {
|
||||
if (item.value === value) {
|
||||
isDisabled = item.disable
|
||||
}
|
||||
})
|
||||
|
||||
return isDisabled;
|
||||
},
|
||||
|
||||
clearVal() {
|
||||
this.emit('')
|
||||
if (this.collection) {
|
||||
this.removeCache()
|
||||
}
|
||||
},
|
||||
change(item) {
|
||||
if (!item.disable) {
|
||||
this.showSelector = false
|
||||
this.current = this.formatItemName(item)
|
||||
this.emit(item.value)
|
||||
}
|
||||
},
|
||||
emit(val) {
|
||||
this.$emit('input', val)
|
||||
this.$emit('update:modelValue', val)
|
||||
this.$emit('change', val)
|
||||
if (this.collection) {
|
||||
this.setCache(val);
|
||||
}
|
||||
},
|
||||
toggleSelector() {
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
this.showSelector = !this.showSelector
|
||||
},
|
||||
formatItemName(item) {
|
||||
let {
|
||||
text,
|
||||
value,
|
||||
channel_code
|
||||
} = item
|
||||
channel_code = channel_code ? `(${channel_code})` : ''
|
||||
|
||||
if (this.format) {
|
||||
// 格式化输出
|
||||
let str = "";
|
||||
str = this.format;
|
||||
for (let key in item) {
|
||||
str = str.replace(new RegExp(`{${key}}`, "g"), item[key]);
|
||||
}
|
||||
return str;
|
||||
} else {
|
||||
return this.collection.indexOf('app-list') > 0 ?
|
||||
`${text}(${value})` :
|
||||
(
|
||||
text ?
|
||||
text :
|
||||
`未命名${channel_code}`
|
||||
)
|
||||
}
|
||||
},
|
||||
// 获取当前加载的数据
|
||||
getLoadData() {
|
||||
return this.mixinDatacomResData;
|
||||
},
|
||||
// 获取当前缓存key
|
||||
getCurrentCacheKey() {
|
||||
return this.collection;
|
||||
},
|
||||
// 获取缓存
|
||||
getCache(name = this.getCurrentCacheKey()) {
|
||||
let cacheData = uni.getStorageSync(this.cacheKey) || {};
|
||||
return cacheData[name];
|
||||
},
|
||||
// 设置缓存
|
||||
setCache(value, name = this.getCurrentCacheKey()) {
|
||||
let cacheData = uni.getStorageSync(this.cacheKey) || {};
|
||||
cacheData[name] = value;
|
||||
uni.setStorageSync(this.cacheKey, cacheData);
|
||||
},
|
||||
// 删除缓存
|
||||
removeCache(name = this.getCurrentCacheKey()) {
|
||||
let cacheData = uni.getStorageSync(this.cacheKey) || {};
|
||||
delete cacheData[name];
|
||||
uni.setStorageSync(this.cacheKey, cacheData);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
$uni-base-color: #6a6a6a !default;
|
||||
$uni-main-color: #333 !default;
|
||||
$uni-secondary-color: #909399 !default;
|
||||
$uni-border-3: #e5e5e5;
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
@media screen and (max-width: 500px) {
|
||||
.hide-on-phone {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.uni-stat__select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// padding: 15px;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.uni-stat-box {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.uni-stat__actived {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
// outline: 1px solid #2979ff;
|
||||
}
|
||||
|
||||
.uni-label-text {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: $uni-base-color;
|
||||
margin: auto 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.uni-select {
|
||||
font-size: 14px;
|
||||
border: 1px solid $uni-border-3;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
padding: 0 5px;
|
||||
padding-left: 10px;
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
user-select: none;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-bottom: solid 1px $uni-border-3;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
height: 35px;
|
||||
|
||||
&--disabled {
|
||||
background-color: #f5f7fa;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.uni-select__label {
|
||||
font-size: 16px;
|
||||
// line-height: 22px;
|
||||
height: 35px;
|
||||
padding-right: 10px;
|
||||
color: $uni-secondary-color;
|
||||
}
|
||||
|
||||
.uni-select__input-box {
|
||||
height: 35px;
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-select__input {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.uni-select__input-plac {
|
||||
font-size: 14px;
|
||||
color: $uni-secondary-color;
|
||||
}
|
||||
|
||||
.uni-select__selector {
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #EBEEF5;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
z-index: 3;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.uni-select__selector-scroll {
|
||||
/* #ifndef APP-NVUE */
|
||||
max-height: 200px;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
/* #ifdef H5 */
|
||||
@media (min-width: 768px) {
|
||||
.uni-select__selector-scroll {
|
||||
max-height: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
.uni-select__selector-empty,
|
||||
.uni-select__selector-item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
line-height: 35px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
/* border-bottom: solid 1px $uni-border-3; */
|
||||
padding: 0px 10px;
|
||||
}
|
||||
|
||||
.uni-select__selector-item:hover {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.uni-select__selector-empty:last-child,
|
||||
.uni-select__selector-item:last-child {
|
||||
/* #ifndef APP-NVUE */
|
||||
border-bottom: none;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-select__selector__disabled {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* picker 弹出层通用的指示小三角 */
|
||||
.uni-popper__arrow_bottom,
|
||||
.uni-popper__arrow_bottom::after,
|
||||
.uni-popper__arrow_top,
|
||||
.uni-popper__arrow_top::after,
|
||||
{
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-color: transparent;
|
||||
border-style: solid;
|
||||
border-width: 6px;
|
||||
}
|
||||
|
||||
.uni-popper__arrow_bottom {
|
||||
filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
|
||||
top: -6px;
|
||||
left: 10%;
|
||||
margin-right: 3px;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: #EBEEF5;
|
||||
}
|
||||
|
||||
.uni-popper__arrow_bottom::after {
|
||||
content: " ";
|
||||
top: 1px;
|
||||
margin-left: -6px;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
|
||||
.uni-popper__arrow_top {
|
||||
filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
|
||||
bottom: -6px;
|
||||
left: 10%;
|
||||
margin-right: 3px;
|
||||
border-bottom-width: 0;
|
||||
border-top-color: #EBEEF5;
|
||||
}
|
||||
|
||||
.uni-popper__arrow_top::after {
|
||||
content: " ";
|
||||
bottom: 1px;
|
||||
margin-left: -6px;
|
||||
border-bottom-width: 0;
|
||||
border-top-color: #fff;
|
||||
}
|
||||
|
||||
|
||||
.uni-select__input-text {
|
||||
// width: 280px;
|
||||
width: 100%;
|
||||
color: $uni-main-color;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
-o-text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-select__input-placeholder {
|
||||
color: $uni-base-color;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.uni-select--mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
</style>
|
||||
86
uni_modules/uni-data-select/package.json
Normal file
86
uni_modules/uni-data-select/package.json
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"id": "uni-data-select",
|
||||
"displayName": "uni-data-select 下拉框选择器",
|
||||
"version": "1.0.8",
|
||||
"description": "通过数据驱动的下拉框选择器",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
"select",
|
||||
"uni-data-select",
|
||||
"下拉框",
|
||||
"下拉选"
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.1"
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": ["uni-load-more"],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y",
|
||||
"alipay": "n"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "u",
|
||||
"app-nvue": "n"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "u",
|
||||
"百度": "u",
|
||||
"字节跳动": "u",
|
||||
"QQ": "u",
|
||||
"京东": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
uni_modules/uni-data-select/readme.md
Normal file
8
uni_modules/uni-data-select/readme.md
Normal file
@@ -0,0 +1,8 @@
|
||||
## DataSelect 下拉框选择器
|
||||
> **组件名:uni-data-select**
|
||||
> 代码块: `uDataSelect`
|
||||
|
||||
当选项过多时,使用下拉菜单展示并选择内容
|
||||
|
||||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-data-select)
|
||||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||
@@ -1,27 +1,106 @@
|
||||
## 2.2.38(2024-10-15)
|
||||
- 修复 微信小程序中的getSystemInfo警告
|
||||
## 2.2.37(2024-10-12)
|
||||
- 修复 微信小程序中的getSystemInfo警告
|
||||
## 2.2.36(2024-10-12)
|
||||
- 修复 微信小程序中的getSystemInfo警告
|
||||
## 2.2.35(2024-09-21)
|
||||
- 修复 没有选中日期时点击确定直接报错的Bug [详情](https://ask.dcloud.net.cn/question/198168)
|
||||
## 2.2.34(2024-04-24)
|
||||
- 新增 日期点击事件,在点击日期时会触发该事件。
|
||||
## 2.2.33(2024-04-15)
|
||||
- 修复 抖音小程序事件传递失效bug
|
||||
## 2.2.32(2024-02-20)
|
||||
- 修复 日历的close事件触发异常的bug [详情](https://github.com/dcloudio/uni-ui/issues/844)
|
||||
## 2.2.31(2024-02-20)
|
||||
- 修复 h5平台 右边日历的月份默认+1的bug [详情](https://github.com/dcloudio/uni-ui/issues/841)
|
||||
## 2.2.30(2024-01-31)
|
||||
- 修复 隐藏“秒”时,在IOS15及以下版本时出现 结束时间在开始时间之前 的bug [详情](https://github.com/dcloudio/uni-ui/issues/788)
|
||||
## 2.2.29(2024-01-20)
|
||||
- 新增 show事件,弹窗弹出时触发该事件 [详情](https://github.com/dcloudio/uni-app/issues/4694)
|
||||
## 2.2.28(2024-01-18)
|
||||
- 去除 noChange事件,当进行日期范围选择时,若只选了一天,则开始结束日期都为同一天 [详情](https://github.com/dcloudio/uni-ui/issues/815)
|
||||
## 2.2.27(2024-01-10)
|
||||
- 优化 增加noChange事件,当进行日期范围选择时,若有空值,则触发该事件 [详情](https://github.com/dcloudio/uni-ui/issues/815)
|
||||
## 2.2.26(2024-01-08)
|
||||
- 修复 字节小程序时间选择范围器失效问题 [详情](https://github.com/dcloudio/uni-ui/issues/834)
|
||||
## 2.2.25(2023-10-18)
|
||||
- 修复 PC端初次修改时间,开始时间未更新的Bug [详情](https://github.com/dcloudio/uni-ui/issues/737)
|
||||
## 2.2.24(2023-06-02)
|
||||
- 修复 部分情况修改时间,开始、结束时间显示异常的Bug [详情](https://ask.dcloud.net.cn/question/171146)
|
||||
- 优化 当前月可以选择上月、下月的日期的Bug
|
||||
## 2.2.23(2023-05-02)
|
||||
- 修复 部分情况修改时间,开始时间未更新的Bug [详情](https://github.com/dcloudio/uni-ui/issues/737)
|
||||
- 修复 部分平台及设备第一次点击无法显示弹框的Bug
|
||||
- 修复 ios 日期格式未补零显示及使用异常的Bug [详情](https://ask.dcloud.net.cn/question/162979)
|
||||
## 2.2.22(2023-03-30)
|
||||
- 修复 日历 picker 修改年月后,自动选中当月1日的Bug [详情](https://ask.dcloud.net.cn/question/165937)
|
||||
- 修复 小程序端 低版本 ios NaN的Bug [详情](https://ask.dcloud.net.cn/question/162979)
|
||||
## 2.2.21(2023-02-20)
|
||||
- 修复 firefox 浏览器显示区域点击无法拉起日历弹框的Bug [详情](https://ask.dcloud.net.cn/question/163362)
|
||||
## 2.2.20(2023-02-17)
|
||||
- 优化 值为空依然选中当天问题
|
||||
- 优化 提供 default-value 属性支持配置选择器打开时默认显示的时间
|
||||
- 优化 非范围选择未选择日期时间,点击确认按钮选中当前日期时间
|
||||
- 优化 字节小程序日期时间范围选择,底部日期换行的Bug
|
||||
## 2.2.19(2023-02-09)
|
||||
- 修复 2.2.18 引起范围选择配置 end 选择无效的Bug [详情](https://github.com/dcloudio/uni-ui/issues/686)
|
||||
## 2.2.18(2023-02-08)
|
||||
- 修复 移动端范围选择change事件触发异常的Bug [详情](https://github.com/dcloudio/uni-ui/issues/684)
|
||||
- 优化 PC端输入日期格式错误时返回当前日期时间
|
||||
- 优化 PC端输入日期时间超出 start、end 限制的Bug
|
||||
- 优化 移动端日期时间范围用法时间展示不完整问题
|
||||
## 2.2.17(2023-02-04)
|
||||
- 修复 小程序端绑定 Date 类型报错的Bug [详情](https://github.com/dcloudio/uni-ui/issues/679)
|
||||
- 修复 vue3 time-picker 无法显示绑定时分秒的Bug
|
||||
## 2.2.16(2023-02-02)
|
||||
- 修复 字节小程序报错的Bug
|
||||
## 2.2.15(2023-02-02)
|
||||
- 修复 某些情况切换月份错误的Bug
|
||||
## 2.2.14(2023-01-30)
|
||||
- 修复 某些情况切换月份错误的Bug [详情](https://ask.dcloud.net.cn/question/162033)
|
||||
## 2.2.13(2023-01-10)
|
||||
- 修复 多次加载组件造成内存占用的Bug
|
||||
## 2.2.12(2022-12-01)
|
||||
- 修复 vue3 下 i18n 国际化初始值不正确的Bug
|
||||
## 2.2.11(2022-09-19)
|
||||
- 修复 支付宝小程序样式错乱的Bug [详情](https://github.com/dcloudio/uni-app/issues/3861)
|
||||
## 2.2.10(2022-09-19)
|
||||
- 修复 反向选择日期范围,日期显示异常的Bug [详情](https://ask.dcloud.net.cn/question/153401?item_id=212892&rf=false)
|
||||
## 2.2.9(2022-09-16)
|
||||
- 可以使用 uni-scss 控制主题色
|
||||
## 2.2.8(2022-09-08)
|
||||
- 修复 close事件无效的Bug
|
||||
## 2.2.7(2022-09-05)
|
||||
- 修复 移动端 maskClick 无效的Bug [详情](https://ask.dcloud.net.cn/question/140824)
|
||||
## 2.2.6(2022-06-30)
|
||||
- 优化 组件样式,调整了组件图标大小、高度、颜色等,与uni-ui风格保持一致
|
||||
## 2.2.5(2022-06-24)
|
||||
- 修复 日历顶部年月及底部确认未国际化的Bug
|
||||
## 2.2.4(2022-03-31)
|
||||
- 修复 Vue3 下动态赋值,单选类型未响应的 bug
|
||||
- 修复 Vue3 下动态赋值,单选类型未响应的Bug
|
||||
## 2.2.3(2022-03-28)
|
||||
- 修复 Vue3 下动态赋值未响应的 bug
|
||||
- 修复 Vue3 下动态赋值未响应的Bug
|
||||
## 2.2.2(2021-12-10)
|
||||
- 修复 clear-icon 属性在小程序平台不生效的 bug
|
||||
- 修复 clear-icon 属性在小程序平台不生效的Bug
|
||||
## 2.2.1(2021-12-10)
|
||||
- 修复 日期范围选在小程序平台,必须多点击一次才能取消选中状态的 bug
|
||||
- 修复 日期范围选在小程序平台,必须多点击一次才能取消选中状态的Bug
|
||||
## 2.2.0(2021-11-19)
|
||||
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-datetime-picker](https://uniapp.dcloud.io/component/uniui/uni-datetime-picker)
|
||||
## 2.1.5(2021-11-09)
|
||||
- 优化 组件UI,并提供设计资源 [详情](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
- 文档迁移 [https://uniapp.dcloud.io/component/uniui/uni-datetime-picker](https://uniapp.dcloud.io/component/uniui/uni-datetime-picker)
|
||||
## 2.1.5(2021-11-09)
|
||||
- 新增 提供组件设计资源,组件样式调整
|
||||
## 2.1.4(2021-09-10)
|
||||
- 修复 hide-second 在移动端的 bug
|
||||
- 修复 单选赋默认值时,赋值日期未高亮的 bug
|
||||
- 修复 赋默认值时,移动端未正确显示时间的 bug
|
||||
- 修复 hide-second 在移动端的Bug
|
||||
- 修复 单选赋默认值时,赋值日期未高亮的Bug
|
||||
- 修复 赋默认值时,移动端未正确显示时间的Bug
|
||||
## 2.1.3(2021-09-09)
|
||||
- 新增 hide-second 属性,支持只使用时分,隐藏秒
|
||||
## 2.1.2(2021-09-03)
|
||||
- 优化 取消选中时(范围选)直接开始下一次选择, 避免多点一次
|
||||
- 优化 移动端支持清除按钮,同时支持通过 ref 调用组件的 clear 方法
|
||||
- 优化 调整字号大小,美化日历界面
|
||||
- 修复 因国际化导致的 placeholder 失效的 bug
|
||||
- 修复 因国际化导致的 placeholder 失效的Bug
|
||||
## 2.1.1(2021-08-24)
|
||||
- 新增 支持国际化
|
||||
- 优化 范围选择器在 pc 端过宽的问题
|
||||
@@ -29,50 +108,50 @@
|
||||
- 新增 适配 vue3
|
||||
## 2.0.19(2021-08-09)
|
||||
- 新增 支持作为 uni-forms 子组件相关功能
|
||||
- 修复 在 uni-forms 中使用时,选择时间报 NAN 错误的 bug
|
||||
- 修复 在 uni-forms 中使用时,选择时间报 NAN 错误的Bug
|
||||
## 2.0.18(2021-08-05)
|
||||
- 修复 type 属性动态赋值无效的 bug
|
||||
- 修复 type 属性动态赋值无效的Bug
|
||||
- 修复 ‘确认’按钮被 tabbar 遮盖 bug
|
||||
- 修复 组件未赋值时范围选左、右日历相同的 bug
|
||||
- 修复 组件未赋值时范围选左、右日历相同的Bug
|
||||
## 2.0.17(2021-08-04)
|
||||
- 修复 范围选未正确显示当前值的 bug
|
||||
- 修复 h5 平台(移动端)报错 'cale' of undefined 的 bug
|
||||
- 修复 范围选未正确显示当前值的Bug
|
||||
- 修复 h5 平台(移动端)报错 'cale' of undefined 的Bug
|
||||
## 2.0.16(2021-07-21)
|
||||
- 新增 return-type 属性支持返回 date 日期对象
|
||||
## 2.0.15(2021-07-14)
|
||||
- 修复 单选日期类型,初始赋值后不在当前日历的 bug
|
||||
- 修复 单选日期类型,初始赋值后不在当前日历的Bug
|
||||
- 新增 clearIcon 属性,显示框的清空按钮可配置显示隐藏(仅 pc 有效)
|
||||
- 优化 移动端移除显示框的清空按钮,无实际用途
|
||||
## 2.0.14(2021-07-14)
|
||||
- 修复 组件赋值为空,界面未更新的 bug
|
||||
- 修复 start 和 end 不能动态赋值的 bug
|
||||
- 修复 范围选类型,用户选择后再次选择右侧日历(结束日期)显示不正确的 bug
|
||||
- 修复 组件赋值为空,界面未更新的Bug
|
||||
- 修复 start 和 end 不能动态赋值的Bug
|
||||
- 修复 范围选类型,用户选择后再次选择右侧日历(结束日期)显示不正确的Bug
|
||||
## 2.0.13(2021-07-08)
|
||||
- 修复 范围选择不能动态赋值的 bug
|
||||
- 修复 范围选择不能动态赋值的Bug
|
||||
## 2.0.12(2021-07-08)
|
||||
- 修复 范围选择的初始时间在一个月内时,造成无法选择的bug
|
||||
## 2.0.11(2021-07-08)
|
||||
- 优化 弹出层在超出视窗边缘定位不准确的问题
|
||||
## 2.0.10(2021-07-08)
|
||||
- 修复 范围起始点样式的背景色与今日样式的字体前景色融合,导致日期字体看不清的 bug
|
||||
- 修复 范围起始点样式的背景色与今日样式的字体前景色融合,导致日期字体看不清的Bug
|
||||
- 优化 弹出层在超出视窗边缘被遮盖的问题
|
||||
## 2.0.9(2021-07-07)
|
||||
- 新增 maskClick 事件
|
||||
- 修复 特殊情况日历 rpx 布局错误的 bug,rpx -> px
|
||||
- 修复 特殊情况日历 rpx 布局错误的Bug,rpx -> px
|
||||
- 修复 范围选择时清空返回值不合理的bug,['', ''] -> []
|
||||
## 2.0.8(2021-07-07)
|
||||
- 新增 日期时间显示框支持插槽
|
||||
## 2.0.7(2021-07-01)
|
||||
- 优化 添加 uni-icons 依赖
|
||||
## 2.0.6(2021-05-22)
|
||||
- 修复 图标在小程序上不显示的 bug
|
||||
- 修复 图标在小程序上不显示的Bug
|
||||
- 优化 重命名引用组件,避免潜在组件命名冲突
|
||||
## 2.0.5(2021-05-20)
|
||||
- 优化 代码目录扁平化
|
||||
## 2.0.4(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 2.0.3(2021-05-10)
|
||||
- 修复 ios 下不识别 '-' 日期格式的 bug
|
||||
- 修复 ios 下不识别 '-' 日期格式的Bug
|
||||
- 优化 pc 下弹出层添加边框和阴影
|
||||
## 2.0.2(2021-05-08)
|
||||
- 修复 在 admin 中获取弹出层定位错误的bug
|
||||
@@ -83,7 +162,7 @@
|
||||
> 注意:此版本不向后兼容,不再支持单独时间选择(type=time)及相关的 hide-second 属性(时间选可使用内置组件 picker)
|
||||
## 1.0.6(2021-03-18)
|
||||
- 新增 hide-second 属性,时间支持仅选择时、分
|
||||
- 修复 选择跟显示的日期不一样的 bug
|
||||
- 修复 chang事件触发2次的 bug
|
||||
- 修复 分、秒 end 范围错误的 bug
|
||||
- 修复 选择跟显示的日期不一样的Bug
|
||||
- 修复 chang事件触发2次的Bug
|
||||
- 修复 分、秒 end 范围错误的Bug
|
||||
- 优化 更好的 nvue 适配
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
'uni-calendar-item--disable':weeks.disable,
|
||||
}">
|
||||
<text v-if="selected&&weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text>
|
||||
<text v-if="selected && weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text>
|
||||
<text class="uni-calendar-item__weeks-box-text uni-calendar-item__weeks-box-text-disable uni-calendar-item--checked-text">{{weeks.date}}</text>
|
||||
</view>
|
||||
<view :class="{'uni-calendar-item--isDay': weeks.isDay}"></view>
|
||||
<view :class="{'uni-calendar-item--today': weeks.isToday}"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -41,10 +41,6 @@
|
||||
return []
|
||||
}
|
||||
},
|
||||
lunar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
checkHover: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -62,6 +58,8 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" >
|
||||
$uni-primary: #007aff !default;
|
||||
|
||||
.uni-calendar-item__weeks-box {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
@@ -78,12 +76,7 @@
|
||||
font-size: 14px;
|
||||
// font-family: Lato-Bold, Lato;
|
||||
font-weight: bold;
|
||||
color: #455997;
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-lunar-text {
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
color: darken($color: $uni-primary, $amount: 40%);
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-box-item {
|
||||
@@ -114,7 +107,6 @@
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-box .uni-calendar-item--disable {
|
||||
// background-color: rgba(249, 249, 249, $uni-opacity-disabled);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -122,7 +114,7 @@
|
||||
color: #D1D1D1;
|
||||
}
|
||||
|
||||
.uni-calendar-item--isDay {
|
||||
.uni-calendar-item--today {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 17%;
|
||||
@@ -138,7 +130,7 @@
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-box .uni-calendar-item--checked {
|
||||
background-color: #007aff;
|
||||
background-color: $uni-primary;
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
border: 3px solid #fff;
|
||||
@@ -159,7 +151,7 @@
|
||||
|
||||
.uni-calendar-item--multiple .uni-calendar-item--before-checked,
|
||||
.uni-calendar-item--multiple .uni-calendar-item--after-checked {
|
||||
background-color: #409eff;
|
||||
background-color: $uni-primary;
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
border: 3px solid #F6F7FC;
|
||||
|
||||
@@ -1,37 +1,43 @@
|
||||
<template>
|
||||
<view class="uni-calendar" @mouseleave="leaveCale">
|
||||
<view v-if="!insert&&show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}"
|
||||
@click="clean"></view>
|
||||
|
||||
<view v-if="!insert && show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}"
|
||||
@click="maskClick"></view>
|
||||
|
||||
<view v-if="insert || show" class="uni-calendar__content"
|
||||
:class="{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow, 'uni-calendar__content-mobile': aniMaskShow}">
|
||||
<view class="uni-calendar__header" :class="{'uni-calendar__header-mobile' :!insert}">
|
||||
<view v-if="left" class="uni-calendar__header-btn-box" @click.stop="pre">
|
||||
|
||||
<view class="uni-calendar__header-btn-box" @click.stop="changeMonth('pre')">
|
||||
<view class="uni-calendar__header-btn uni-calendar--left"></view>
|
||||
</view>
|
||||
|
||||
<picker mode="date" :value="date" fields="month" @change="bindDateChange">
|
||||
<text
|
||||
class="uni-calendar__header-text">{{ (nowDate.year||'') + ' 年 ' + ( nowDate.month||'') +' 月'}}</text>
|
||||
class="uni-calendar__header-text">{{ (nowDate.year||'') + yearText + ( nowDate.month||'') + monthText}}</text>
|
||||
</picker>
|
||||
<view v-if="right" class="uni-calendar__header-btn-box" @click.stop="next">
|
||||
|
||||
<view class="uni-calendar__header-btn-box" @click.stop="changeMonth('next')">
|
||||
<view class="uni-calendar__header-btn uni-calendar--right"></view>
|
||||
</view>
|
||||
<view v-if="!insert" class="dialog-close" @click="clean">
|
||||
|
||||
<view v-if="!insert" class="dialog-close" @click="maskClick">
|
||||
<view class="dialog-close-plus" data-id="close"></view>
|
||||
<view class="dialog-close-plus dialog-close-rotate" data-id="close"></view>
|
||||
</view>
|
||||
|
||||
<!-- <text class="uni-calendar__backtoday" @click="backtoday">回到今天</text> -->
|
||||
</view>
|
||||
<view class="uni-calendar__box">
|
||||
|
||||
<view v-if="showMonth" class="uni-calendar__box-bg">
|
||||
<text class="uni-calendar__box-bg-text">{{nowDate.month}}</text>
|
||||
</view>
|
||||
|
||||
<view class="uni-calendar__weeks" style="padding-bottom: 7px;">
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{SUNText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{monText}}</text>
|
||||
<text class="uni-calendar__weeks-day-text">{{MONText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{TUEText}}</text>
|
||||
@@ -49,66 +55,72 @@
|
||||
<text class="uni-calendar__weeks-day-text">{{SATText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex">
|
||||
<view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex">
|
||||
<calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar"
|
||||
:selected="selected" :lunar="lunar" :checkHover="range" @change="choiceDate"
|
||||
@handleMouse="handleMouse">
|
||||
<calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar" :selected="selected"
|
||||
:checkHover="range" @change="choiceDate" @handleMouse="handleMouse">
|
||||
</calendar-item>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="!insert && !range && typeHasTime" class="uni-date-changed uni-calendar--fixed-top"
|
||||
|
||||
<view v-if="!insert && !range && hasTime" class="uni-date-changed uni-calendar--fixed-top"
|
||||
style="padding: 0 80px;">
|
||||
<view class="uni-date-changed--time-date">{{tempSingleDate ? tempSingleDate : selectDateText}}</view>
|
||||
<time-picker type="time" :start="reactStartTime" :end="reactEndTime" v-model="time"
|
||||
<time-picker type="time" :start="timepickerStartTime" :end="timepickerEndTime" v-model="time"
|
||||
:disabled="!tempSingleDate" :border="false" :hide-second="hideSecond" class="time-picker-style">
|
||||
</time-picker>
|
||||
</view>
|
||||
|
||||
<view v-if="!insert && range && typeHasTime" class="uni-date-changed uni-calendar--fixed-top">
|
||||
<view v-if="!insert && range && hasTime" class="uni-date-changed uni-calendar--fixed-top">
|
||||
<view class="uni-date-changed--time-start">
|
||||
<view class="uni-date-changed--time-date">{{tempRange.before ? tempRange.before : startDateText}}
|
||||
</view>
|
||||
<time-picker type="time" :start="reactStartTime" v-model="timeRange.startTime" :border="false"
|
||||
<time-picker type="time" :start="timepickerStartTime" v-model="timeRange.startTime" :border="false"
|
||||
:hide-second="hideSecond" :disabled="!tempRange.before" class="time-picker-style">
|
||||
</time-picker>
|
||||
</view>
|
||||
<uni-icons type="arrowthinright" color="#999" style="line-height: 50px;"></uni-icons>
|
||||
<view style="line-height: 50px;">
|
||||
<uni-icons type="arrowthinright" color="#999"></uni-icons>
|
||||
</view>
|
||||
<view class="uni-date-changed--time-end">
|
||||
<view class="uni-date-changed--time-date">{{tempRange.after ? tempRange.after : endDateText}}</view>
|
||||
<time-picker type="time" :end="reactEndTime" v-model="timeRange.endTime" :border="false"
|
||||
<time-picker type="time" :end="timepickerEndTime" v-model="timeRange.endTime" :border="false"
|
||||
:hide-second="hideSecond" :disabled="!tempRange.after" class="time-picker-style">
|
||||
</time-picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="!insert" class="uni-date-changed uni-date-btn--ok">
|
||||
<!-- <view class="uni-calendar__header-btn-box">
|
||||
<text class="uni-calendar__button-text uni-calendar--fixed-width">{{okText}}</text>
|
||||
</view> -->
|
||||
<view class="uni-datetime-picker--btn" @click="confirm">确认</view>
|
||||
<view class="uni-datetime-picker--btn" @click="confirm">{{confirmText}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Calendar from './util.js';
|
||||
import {
|
||||
Calendar,
|
||||
getDate,
|
||||
getTime
|
||||
} from './util.js';
|
||||
import calendarItem from './calendar-item.vue'
|
||||
import timePicker from './time-picker.vue'
|
||||
|
||||
import {
|
||||
initVueI18n
|
||||
} from '@dcloudio/uni-i18n'
|
||||
import messages from './i18n/index.js'
|
||||
import i18nMessages from './i18n/index.js'
|
||||
const {
|
||||
t
|
||||
} = initVueI18n(messages)
|
||||
} = initVueI18n(i18nMessages)
|
||||
|
||||
/**
|
||||
* Calendar 日历
|
||||
* @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=56
|
||||
* @property {String} date 自定义当前时间,默认为今天
|
||||
* @property {Boolean} lunar 显示农历
|
||||
* @property {String} startDate 日期选择范围-开始日期
|
||||
* @property {String} endDate 日期选择范围-结束日期
|
||||
* @property {Boolean} range 范围选择
|
||||
@@ -118,16 +130,26 @@
|
||||
* @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容
|
||||
* @property {Array} selected 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]
|
||||
* @property {Boolean} showMonth 是否选择月份为背景
|
||||
* @property {[String} defaultValue 选择器打开时默认显示的时间
|
||||
* @event {Function} change 日期改变,`insert :ture` 时生效
|
||||
* @event {Function} confirm 确认选择`insert :false` 时生效
|
||||
* @event {Function} monthSwitch 切换月份时触发
|
||||
* @example <uni-calendar :insert="true":lunar="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
|
||||
* @example <uni-calendar :insert="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
|
||||
*/
|
||||
export default {
|
||||
components: {
|
||||
calendarItem,
|
||||
timePicker
|
||||
},
|
||||
|
||||
options: {
|
||||
// #ifdef MP-TOUTIAO
|
||||
virtualHost: false,
|
||||
// #endif
|
||||
// #ifndef MP-TOUTIAO
|
||||
virtualHost: true
|
||||
// #endif
|
||||
},
|
||||
props: {
|
||||
date: {
|
||||
type: String,
|
||||
@@ -149,10 +171,6 @@
|
||||
return []
|
||||
}
|
||||
},
|
||||
lunar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
startDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
@@ -161,11 +179,19 @@
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
startPlaceholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
endPlaceholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
range: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
typeHasTime: {
|
||||
hasTime: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
@@ -181,14 +207,6 @@
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
left: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
right: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
checkHover: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
@@ -207,6 +225,10 @@
|
||||
fulldate: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
defaultValue: {
|
||||
type: [String, Object, Array],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -214,7 +236,7 @@
|
||||
show: false,
|
||||
weeks: [],
|
||||
calendar: {},
|
||||
nowDate: '',
|
||||
nowDate: {},
|
||||
aniMaskShow: false,
|
||||
firstEnter: true,
|
||||
time: '',
|
||||
@@ -232,7 +254,7 @@
|
||||
watch: {
|
||||
date: {
|
||||
immediate: true,
|
||||
handler(newVal, oldVal) {
|
||||
handler(newVal) {
|
||||
if (!this.range) {
|
||||
this.tempSingleDate = newVal
|
||||
setTimeout(() => {
|
||||
@@ -243,7 +265,7 @@
|
||||
},
|
||||
defTime: {
|
||||
immediate: true,
|
||||
handler(newVal, oldVal) {
|
||||
handler(newVal) {
|
||||
if (!this.range) {
|
||||
this.time = newVal
|
||||
} else {
|
||||
@@ -253,22 +275,34 @@
|
||||
}
|
||||
},
|
||||
startDate(val) {
|
||||
this.cale.resetSatrtDate(val)
|
||||
// 字节小程序 watch 早于 created
|
||||
if (!this.cale) {
|
||||
return
|
||||
}
|
||||
this.cale.setStartDate(val)
|
||||
this.cale.setDate(this.nowDate.fullDate)
|
||||
this.weeks = this.cale.weeks
|
||||
},
|
||||
endDate(val) {
|
||||
this.cale.resetEndDate(val)
|
||||
// 字节小程序 watch 早于 created
|
||||
if (!this.cale) {
|
||||
return
|
||||
}
|
||||
this.cale.setEndDate(val)
|
||||
this.cale.setDate(this.nowDate.fullDate)
|
||||
this.weeks = this.cale.weeks
|
||||
},
|
||||
selected(newVal) {
|
||||
// 字节小程序 watch 早于 created
|
||||
if (!this.cale) {
|
||||
return
|
||||
}
|
||||
this.cale.setSelectInfo(this.nowDate.fullDate, newVal)
|
||||
this.weeks = this.cale.weeks
|
||||
},
|
||||
pleStatus: {
|
||||
immediate: true,
|
||||
handler(newVal, oldVal) {
|
||||
handler(newVal) {
|
||||
const {
|
||||
before,
|
||||
after,
|
||||
@@ -291,11 +325,16 @@
|
||||
this.cale.lastHover = false
|
||||
}
|
||||
} else {
|
||||
// 字节小程序 watch 早于 created
|
||||
if (!this.cale) {
|
||||
return
|
||||
}
|
||||
|
||||
this.cale.setDefaultMultiple(before, after)
|
||||
if (which === 'left') {
|
||||
if (which === 'left' && before) {
|
||||
this.setDate(before)
|
||||
this.weeks = this.cale.weeks
|
||||
} else {
|
||||
} else if (after) {
|
||||
this.setDate(after)
|
||||
this.weeks = this.cale.weeks
|
||||
}
|
||||
@@ -306,15 +345,13 @@
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
reactStartTime() {
|
||||
timepickerStartTime() {
|
||||
const activeDate = this.range ? this.tempRange.before : this.calendar.fullDate
|
||||
const res = activeDate === this.startDate ? this.selectableTimes.start : ''
|
||||
return res
|
||||
return activeDate === this.startDate ? this.selectableTimes.start : ''
|
||||
},
|
||||
reactEndTime() {
|
||||
timepickerEndTime() {
|
||||
const activeDate = this.range ? this.tempRange.after : this.calendar.fullDate
|
||||
const res = activeDate === this.endDate ? this.selectableTimes.end : ''
|
||||
return res
|
||||
return activeDate === this.endDate ? this.selectableTimes.end : ''
|
||||
},
|
||||
/**
|
||||
* for i18n
|
||||
@@ -331,7 +368,13 @@
|
||||
okText() {
|
||||
return t("uni-datetime-picker.ok")
|
||||
},
|
||||
monText() {
|
||||
yearText() {
|
||||
return t("uni-datetime-picker.year")
|
||||
},
|
||||
monthText() {
|
||||
return t("uni-datetime-picker.month")
|
||||
},
|
||||
MONText() {
|
||||
return t("uni-calender.MON")
|
||||
},
|
||||
TUEText() {
|
||||
@@ -352,21 +395,20 @@
|
||||
SUNText() {
|
||||
return t("uni-calender.SUN")
|
||||
},
|
||||
confirmText() {
|
||||
return t("uni-calender.confirm")
|
||||
},
|
||||
},
|
||||
created() {
|
||||
// 获取日历方法实例
|
||||
this.cale = new Calendar({
|
||||
// date: new Date(),
|
||||
selected: this.selected,
|
||||
startDate: this.startDate,
|
||||
endDate: this.endDate,
|
||||
range: this.range,
|
||||
// multipleStatus: this.pleStatus
|
||||
})
|
||||
// 选中某一天
|
||||
// this.cale.setDate(this.date)
|
||||
this.init(this.date)
|
||||
// this.setDay
|
||||
},
|
||||
methods: {
|
||||
leaveCale() {
|
||||
@@ -395,10 +437,10 @@
|
||||
const [yearB, monthB] = B.split('-')
|
||||
return yearA === yearB && monthA === monthB
|
||||
},
|
||||
|
||||
// 取消穿透
|
||||
clean() {
|
||||
// 蒙版点击事件
|
||||
maskClick() {
|
||||
this.close()
|
||||
this.$emit('maskClose')
|
||||
},
|
||||
|
||||
clearCalender() {
|
||||
@@ -416,33 +458,51 @@
|
||||
this.tempSingleDate = ''
|
||||
}
|
||||
this.calendar.fullDate = ''
|
||||
this.setDate()
|
||||
this.setDate(new Date())
|
||||
},
|
||||
|
||||
bindDateChange(e) {
|
||||
const value = e.detail.value + '-1'
|
||||
this.init(value)
|
||||
this.setDate(value)
|
||||
},
|
||||
/**
|
||||
* 初始化日期显示
|
||||
* @param {Object} date
|
||||
*/
|
||||
init(date) {
|
||||
this.cale.setDate(date)
|
||||
// 字节小程序 watch 早于 created
|
||||
if (!this.cale) {
|
||||
return
|
||||
}
|
||||
this.cale.setDate(date || new Date())
|
||||
this.weeks = this.cale.weeks
|
||||
this.nowDate = this.calendar = this.cale.getInfo(date)
|
||||
this.nowDate = this.cale.getInfo(date)
|
||||
this.calendar = {
|
||||
...this.nowDate
|
||||
}
|
||||
if (!date) {
|
||||
// 优化date为空默认不选中今天
|
||||
this.calendar.fullDate = ''
|
||||
if (this.defaultValue && !this.range) {
|
||||
// 暂时只支持移动端非范围选择
|
||||
const defaultDate = new Date(this.defaultValue)
|
||||
const fullDate = getDate(defaultDate)
|
||||
const year = defaultDate.getFullYear()
|
||||
const month = defaultDate.getMonth() + 1
|
||||
const date = defaultDate.getDate()
|
||||
const day = defaultDate.getDay()
|
||||
this.calendar = {
|
||||
fullDate,
|
||||
year,
|
||||
month,
|
||||
date,
|
||||
day
|
||||
},
|
||||
this.tempSingleDate = fullDate
|
||||
this.time = getTime(defaultDate, this.hideSecond)
|
||||
}
|
||||
}
|
||||
},
|
||||
// choiceDate(weeks) {
|
||||
// if (weeks.disable) return
|
||||
// this.calendar = weeks
|
||||
// // 设置多选
|
||||
// this.cale.setMultiple(this.calendar.fullDate, true)
|
||||
// this.weeks = this.cale.weeks
|
||||
// this.tempSingleDate = this.calendar.fullDate
|
||||
// this.tempRange.before = this.cale.multipleStatus.before
|
||||
// this.tempRange.after = this.cale.multipleStatus.after
|
||||
// this.change()
|
||||
// },
|
||||
/**
|
||||
* 打开日历弹窗
|
||||
*/
|
||||
@@ -450,7 +510,6 @@
|
||||
// 弹窗模式并且清理数据
|
||||
if (this.clearDate && !this.insert) {
|
||||
this.cale.cleanMultipleStatus()
|
||||
// this.cale.setDate(this.date)
|
||||
this.init(this.date)
|
||||
}
|
||||
this.show = true
|
||||
@@ -482,8 +541,8 @@
|
||||
/**
|
||||
* 变化触发
|
||||
*/
|
||||
change() {
|
||||
if (!this.insert) return
|
||||
change(isSingleChange) {
|
||||
if (!this.insert && !isSingleChange) return
|
||||
this.setEmit('change')
|
||||
},
|
||||
/**
|
||||
@@ -504,12 +563,20 @@
|
||||
* @param {Object} name
|
||||
*/
|
||||
setEmit(name) {
|
||||
if (!this.range) {
|
||||
if (!this.calendar.fullDate) {
|
||||
this.calendar = this.cale.getInfo(new Date())
|
||||
this.tempSingleDate = this.calendar.fullDate
|
||||
}
|
||||
if (this.hasTime && !this.time) {
|
||||
this.time = getTime(new Date(), this.hideSecond)
|
||||
}
|
||||
}
|
||||
let {
|
||||
year,
|
||||
month,
|
||||
date,
|
||||
fullDate,
|
||||
lunar,
|
||||
extraInfo
|
||||
} = this.calendar
|
||||
this.$emit(name, {
|
||||
@@ -520,7 +587,6 @@
|
||||
time: this.time,
|
||||
timeRange: this.timeRange,
|
||||
fulldate: fullDate,
|
||||
lunar,
|
||||
extraInfo: extraInfo || {}
|
||||
})
|
||||
},
|
||||
@@ -536,48 +602,26 @@
|
||||
this.cale.setMultiple(this.calendar.fullDate, true)
|
||||
this.weeks = this.cale.weeks
|
||||
this.tempSingleDate = this.calendar.fullDate
|
||||
this.tempRange.before = this.cale.multipleStatus.before
|
||||
this.tempRange.after = this.cale.multipleStatus.after
|
||||
this.change()
|
||||
},
|
||||
/**
|
||||
* 回到今天
|
||||
*/
|
||||
backtoday() {
|
||||
let date = this.cale.getDate(new Date()).fullDate
|
||||
// this.cale.setDate(date)
|
||||
this.init(date)
|
||||
this.change()
|
||||
},
|
||||
/**
|
||||
* 比较时间大小
|
||||
*/
|
||||
dateCompare(startDate, endDate) {
|
||||
// 计算截止时间
|
||||
startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
|
||||
// 计算详细项的截止时间
|
||||
endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
|
||||
if (startDate <= endDate) {
|
||||
return true
|
||||
const beforeDate = new Date(this.cale.multipleStatus.before).getTime()
|
||||
const afterDate = new Date(this.cale.multipleStatus.after).getTime()
|
||||
if (beforeDate > afterDate && afterDate) {
|
||||
this.tempRange.before = this.cale.multipleStatus.after
|
||||
this.tempRange.after = this.cale.multipleStatus.before
|
||||
} else {
|
||||
return false
|
||||
this.tempRange.before = this.cale.multipleStatus.before
|
||||
this.tempRange.after = this.cale.multipleStatus.after
|
||||
}
|
||||
this.change(true)
|
||||
},
|
||||
/**
|
||||
* 上个月
|
||||
*/
|
||||
pre() {
|
||||
const preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate
|
||||
this.setDate(preDate)
|
||||
this.monthSwitch()
|
||||
changeMonth(type) {
|
||||
let newDate
|
||||
if (type === 'pre') {
|
||||
newDate = this.cale.getPreMonthObj(this.nowDate.fullDate).fullDate
|
||||
} else if (type === 'next') {
|
||||
newDate = this.cale.getNextMonthObj(this.nowDate.fullDate).fullDate
|
||||
}
|
||||
|
||||
},
|
||||
/**
|
||||
* 下个月
|
||||
*/
|
||||
next() {
|
||||
const nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate
|
||||
this.setDate(nextDate)
|
||||
this.setDate(newDate)
|
||||
this.monthSwitch()
|
||||
},
|
||||
/**
|
||||
@@ -593,7 +637,9 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" >
|
||||
<style lang="scss">
|
||||
$uni-primary: #007aff !default;
|
||||
|
||||
.uni-calendar {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
@@ -704,7 +750,7 @@
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
font-size: 14px;
|
||||
color: #007aff;
|
||||
color: $uni-primary;
|
||||
/* #ifndef APP-NVUE */
|
||||
letter-spacing: 3px;
|
||||
/* #endif */
|
||||
@@ -835,6 +881,9 @@
|
||||
.uni-date-changed--time-date {
|
||||
color: #999;
|
||||
line-height: 50px;
|
||||
/* #ifdef MP-TOUTIAO */
|
||||
font-size: 16px;
|
||||
/* #endif */
|
||||
margin-right: 5px;
|
||||
// opacity: 0.6;
|
||||
}
|
||||
@@ -883,15 +932,16 @@
|
||||
border-radius: 100px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
background-color: #007aff;
|
||||
background-color: $uni-primary;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
letter-spacing: 5px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-datetime-picker--btn:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"uni-datetime-picker.selectDate": "select date",
|
||||
"uni-datetime-picker.selectTime": "select time",
|
||||
"uni-datetime-picker.selectDateTime": "select datetime",
|
||||
"uni-datetime-picker.selectDateTime": "select date and time",
|
||||
"uni-datetime-picker.startDate": "start date",
|
||||
"uni-datetime-picker.endDate": "end date",
|
||||
"uni-datetime-picker.startTime": "start time",
|
||||
@@ -9,11 +9,14 @@
|
||||
"uni-datetime-picker.ok": "ok",
|
||||
"uni-datetime-picker.clear": "clear",
|
||||
"uni-datetime-picker.cancel": "cancel",
|
||||
"uni-datetime-picker.year": "-",
|
||||
"uni-datetime-picker.month": "",
|
||||
"uni-calender.MON": "MON",
|
||||
"uni-calender.TUE": "TUE",
|
||||
"uni-calender.WED": "WED",
|
||||
"uni-calender.THU": "THU",
|
||||
"uni-calender.FRI": "FRI",
|
||||
"uni-calender.SAT": "SAT",
|
||||
"uni-calender.SUN": "SUN"
|
||||
"uni-calender.SUN": "SUN",
|
||||
"uni-calender.confirm": "confirm"
|
||||
}
|
||||
|
||||
@@ -9,11 +9,14 @@
|
||||
"uni-datetime-picker.ok": "确定",
|
||||
"uni-datetime-picker.clear": "清除",
|
||||
"uni-datetime-picker.cancel": "取消",
|
||||
"uni-datetime-picker.year": "年",
|
||||
"uni-datetime-picker.month": "月",
|
||||
"uni-calender.SUN": "日",
|
||||
"uni-calender.MON": "一",
|
||||
"uni-calender.TUE": "二",
|
||||
"uni-calender.WED": "三",
|
||||
"uni-calender.THU": "四",
|
||||
"uni-calender.FRI": "五",
|
||||
"uni-calender.SAT": "六"
|
||||
}
|
||||
"uni-calender.SAT": "六",
|
||||
"uni-calender.confirm": "确认"
|
||||
}
|
||||
@@ -1,19 +1,22 @@
|
||||
{
|
||||
"uni-datetime-picker.selectDate": "選擇日期",
|
||||
"uni-datetime-picker.selectTime": "選擇時間",
|
||||
"uni-datetime-picker.selectDateTime": "選擇日期時間",
|
||||
"uni-datetime-picker.startDate": "開始日期",
|
||||
"uni-datetime-picker.endDate": "結束日期",
|
||||
"uni-datetime-picker.startTime": "開始时间",
|
||||
"uni-datetime-picker.endTime": "結束时间",
|
||||
"uni-datetime-picker.ok": "確定",
|
||||
"uni-datetime-picker.clear": "清除",
|
||||
"uni-datetime-picker.cancel": "取消",
|
||||
"uni-calender.SUN": "日",
|
||||
"uni-calender.MON": "一",
|
||||
"uni-calender.TUE": "二",
|
||||
"uni-calender.WED": "三",
|
||||
"uni-calender.THU": "四",
|
||||
"uni-calender.FRI": "五",
|
||||
"uni-calender.SAT": "六"
|
||||
}
|
||||
"uni-datetime-picker.selectDate": "選擇日期",
|
||||
"uni-datetime-picker.selectTime": "選擇時間",
|
||||
"uni-datetime-picker.selectDateTime": "選擇日期時間",
|
||||
"uni-datetime-picker.startDate": "開始日期",
|
||||
"uni-datetime-picker.endDate": "結束日期",
|
||||
"uni-datetime-picker.startTime": "開始时间",
|
||||
"uni-datetime-picker.endTime": "結束时间",
|
||||
"uni-datetime-picker.ok": "確定",
|
||||
"uni-datetime-picker.clear": "清除",
|
||||
"uni-datetime-picker.cancel": "取消",
|
||||
"uni-datetime-picker.year": "年",
|
||||
"uni-datetime-picker.month": "月",
|
||||
"uni-calender.SUN": "日",
|
||||
"uni-calender.MON": "一",
|
||||
"uni-calender.TUE": "二",
|
||||
"uni-calender.WED": "三",
|
||||
"uni-calender.THU": "四",
|
||||
"uni-calender.FRI": "五",
|
||||
"uni-calender.SAT": "六",
|
||||
"uni-calender.confirm": "確認"
|
||||
}
|
||||
@@ -77,21 +77,20 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #ifdef H5 -->
|
||||
<!-- <keypress v-if="visible" @esc="tiggerTimePicker" @enter="setTime" /> -->
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifdef H5
|
||||
import keypress from './keypress'
|
||||
// #endif
|
||||
import {
|
||||
initVueI18n
|
||||
} from '@dcloudio/uni-i18n'
|
||||
import messages from './i18n/index.js'
|
||||
const { t } = initVueI18n(messages)
|
||||
import i18nMessages from './i18n/index.js'
|
||||
const {
|
||||
t
|
||||
} = initVueI18n(i18nMessages)
|
||||
import {
|
||||
fixIosDateFormat
|
||||
} from './util'
|
||||
|
||||
/**
|
||||
* DatetimePicker 时间选择器
|
||||
@@ -108,11 +107,6 @@
|
||||
|
||||
export default {
|
||||
name: 'UniDatetimePicker',
|
||||
components: {
|
||||
// #ifdef H5
|
||||
keypress
|
||||
// #endif
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
indicatorStyle: `height: 50px;`,
|
||||
@@ -146,6 +140,14 @@
|
||||
endSecond: 59,
|
||||
}
|
||||
},
|
||||
options: {
|
||||
// #ifdef MP-TOUTIAO
|
||||
virtualHost: false,
|
||||
// #endif
|
||||
// #ifndef MP-TOUTIAO
|
||||
virtualHost: true
|
||||
// #endif
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
@@ -185,10 +187,11 @@
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// #ifndef VUE3
|
||||
value: {
|
||||
handler(newVal, oldVal) {
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.parseValue(this.fixIosDateFormat(newVal)) //兼容 iOS、safari 日期格式
|
||||
this.parseValue(fixIosDateFormat(newVal))
|
||||
this.initTime(false)
|
||||
} else {
|
||||
this.time = ''
|
||||
@@ -197,6 +200,21 @@
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
modelValue: {
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.parseValue(fixIosDateFormat(newVal))
|
||||
this.initTime(false)
|
||||
} else {
|
||||
this.time = ''
|
||||
this.parseValue(Date.now())
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
// #endif
|
||||
type: {
|
||||
handler(newValue) {
|
||||
if (newValue === 'date') {
|
||||
@@ -217,13 +235,13 @@
|
||||
},
|
||||
start: {
|
||||
handler(newVal) {
|
||||
this.parseDatetimeRange(this.fixIosDateFormat(newVal), 'start') //兼容 iOS、safari 日期格式
|
||||
this.parseDatetimeRange(fixIosDateFormat(newVal), 'start')
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
end: {
|
||||
handler(newVal) {
|
||||
this.parseDatetimeRange(this.fixIosDateFormat(newVal), 'end') //兼容 iOS、safari 日期格式
|
||||
this.parseDatetimeRange(fixIosDateFormat(newVal), 'end')
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
@@ -432,7 +450,7 @@
|
||||
|
||||
mounted() {
|
||||
// #ifdef APP-NVUE
|
||||
const res = uni.getWindowInfo();
|
||||
const res = uni.getSystemInfoSync();
|
||||
this.fixNvueBug = {
|
||||
top: res.windowHeight / 2,
|
||||
left: res.windowWidth / 2
|
||||
@@ -527,7 +545,7 @@
|
||||
const day = now.getDate()
|
||||
dateBase = year + '/' + month + '/' + day + ' '
|
||||
}
|
||||
if (Number(value) && typeof value !== NaN) {
|
||||
if (Number(value)) {
|
||||
value = parseInt(value)
|
||||
dateBase = 0
|
||||
}
|
||||
@@ -598,7 +616,7 @@
|
||||
pointType === 'start' ? this.startYear = this.year - 60 : this.endYear = this.year + 60
|
||||
return
|
||||
}
|
||||
if (Number(point) && Number(point) !== NaN) {
|
||||
if (Number(point)) {
|
||||
point = parseInt(point)
|
||||
}
|
||||
// datetime 的 end 没有时分秒, 则不限制
|
||||
@@ -645,14 +663,6 @@
|
||||
return new Date(year, month, 0).getDate();
|
||||
},
|
||||
|
||||
//兼容 iOS、safari 日期格式
|
||||
fixIosDateFormat(value) {
|
||||
if (typeof value === 'string') {
|
||||
value = value.replace(/-/g, '/')
|
||||
}
|
||||
return value
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成时间戳
|
||||
* @param {Object} time
|
||||
@@ -736,7 +746,7 @@
|
||||
*/
|
||||
initTimePicker() {
|
||||
if (this.disabled) return
|
||||
const value = this.fixIosDateFormat(this.value)
|
||||
const value = fixIosDateFormat(this.time)
|
||||
this.initPickerValue(value)
|
||||
this.visible = !this.visible
|
||||
},
|
||||
@@ -770,7 +780,9 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
$uni-primary: #007aff !default;
|
||||
|
||||
.uni-datetime-picker {
|
||||
/* #ifndef APP-NVUE */
|
||||
/* width: 100%; */
|
||||
@@ -804,7 +816,7 @@
|
||||
|
||||
.uni-datetime-picker-btn-text {
|
||||
font-size: 14px;
|
||||
color: #007AFF;
|
||||
color: $uni-primary;
|
||||
}
|
||||
|
||||
.uni-datetime-picker-btn-group {
|
||||
@@ -889,6 +901,7 @@
|
||||
|
||||
.uni-datetime-picker-text {
|
||||
font-size: 14px;
|
||||
line-height: 50px
|
||||
}
|
||||
|
||||
.uni-datetime-picker-sign {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,27 +1,24 @@
|
||||
class Calendar {
|
||||
constructor({
|
||||
date,
|
||||
selected,
|
||||
startDate,
|
||||
endDate,
|
||||
range,
|
||||
// multipleStatus
|
||||
} = {}) {
|
||||
// 当前日期
|
||||
this.date = this.getDate(new Date()) // 当前初入日期
|
||||
this.date = this.getDateObj(new Date()) // 当前初入日期
|
||||
// 打点信息
|
||||
this.selected = selected || [];
|
||||
// 范围开始
|
||||
// 起始时间
|
||||
this.startDate = startDate
|
||||
// 范围结束
|
||||
// 终止时间
|
||||
this.endDate = endDate
|
||||
// 是否范围选择
|
||||
this.range = range
|
||||
// 多选状态
|
||||
this.cleanMultipleStatus()
|
||||
// 每周日期
|
||||
this.weeks = {}
|
||||
// this._getWeek(this.date.fullDate)
|
||||
// this.multipleStatus = multipleStatus
|
||||
this.lastHover = false
|
||||
}
|
||||
/**
|
||||
@@ -29,8 +26,8 @@ class Calendar {
|
||||
* @param {Object} date
|
||||
*/
|
||||
setDate(date) {
|
||||
this.selectDate = this.getDate(date)
|
||||
this._getWeek(this.selectDate.fullDate)
|
||||
const selectDate = this.getDateObj(date)
|
||||
this.getWeeks(selectDate.fullDate)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,93 +41,82 @@ class Calendar {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置开始日期
|
||||
*/
|
||||
resetSatrtDate(startDate) {
|
||||
// 范围开始
|
||||
setStartDate(startDate) {
|
||||
this.startDate = startDate
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置结束日期
|
||||
*/
|
||||
resetEndDate(endDate) {
|
||||
// 范围结束
|
||||
setEndDate(endDate) {
|
||||
this.endDate = endDate
|
||||
}
|
||||
|
||||
getPreMonthObj(date) {
|
||||
date = fixIosDateFormat(date)
|
||||
date = new Date(date)
|
||||
|
||||
const oldMonth = date.getMonth()
|
||||
date.setMonth(oldMonth - 1)
|
||||
const newMonth = date.getMonth()
|
||||
if (oldMonth !== 0 && newMonth - oldMonth === 0) {
|
||||
date.setMonth(newMonth - 1)
|
||||
}
|
||||
return this.getDateObj(date)
|
||||
}
|
||||
getNextMonthObj(date) {
|
||||
date = fixIosDateFormat(date)
|
||||
date = new Date(date)
|
||||
|
||||
const oldMonth = date.getMonth()
|
||||
date.setMonth(oldMonth + 1)
|
||||
const newMonth = date.getMonth()
|
||||
if (newMonth - oldMonth > 1) {
|
||||
date.setMonth(newMonth - 1)
|
||||
}
|
||||
return this.getDateObj(date)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任意时间
|
||||
* 获取指定格式Date对象
|
||||
*/
|
||||
getDate(date, AddDayCount = 0, str = 'day') {
|
||||
if (!date) {
|
||||
date = new Date()
|
||||
}
|
||||
if (typeof date !== 'object') {
|
||||
date = date.replace(/-/g, '/')
|
||||
}
|
||||
const dd = new Date(date)
|
||||
switch (str) {
|
||||
case 'day':
|
||||
dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
|
||||
break
|
||||
case 'month':
|
||||
if (dd.getDate() === 31) {
|
||||
dd.setDate(dd.getDate() + AddDayCount)
|
||||
} else {
|
||||
dd.setMonth(dd.getMonth() + AddDayCount) // 获取AddDayCount天后的日期
|
||||
}
|
||||
break
|
||||
case 'year':
|
||||
dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期
|
||||
break
|
||||
}
|
||||
const y = dd.getFullYear()
|
||||
const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0
|
||||
const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0
|
||||
getDateObj(date) {
|
||||
date = fixIosDateFormat(date)
|
||||
date = new Date(date)
|
||||
|
||||
return {
|
||||
fullDate: y + '-' + m + '-' + d,
|
||||
year: y,
|
||||
month: m,
|
||||
date: d,
|
||||
day: dd.getDay()
|
||||
fullDate: getDate(date),
|
||||
year: date.getFullYear(),
|
||||
month: addZero(date.getMonth() + 1),
|
||||
date: addZero(date.getDate()),
|
||||
day: date.getDay()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取上月剩余天数
|
||||
* 获取上一个月日期集合
|
||||
*/
|
||||
_getLastMonthDays(firstDay, full) {
|
||||
let dateArr = []
|
||||
for (let i = firstDay; i > 0; i--) {
|
||||
const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()
|
||||
dateArr.push({
|
||||
date: beforeDate,
|
||||
month: full.month - 1,
|
||||
getPreMonthDays(amount, dateObj) {
|
||||
const result = []
|
||||
for (let i = amount - 1; i >= 0; i--) {
|
||||
const month = dateObj.month - 1
|
||||
result.push({
|
||||
date: new Date(dateObj.year, month, -i).getDate(),
|
||||
month,
|
||||
disable: true
|
||||
})
|
||||
}
|
||||
return dateArr
|
||||
return result
|
||||
}
|
||||
/**
|
||||
* 获取本月天数
|
||||
* 获取本月日期集合
|
||||
*/
|
||||
_currentMonthDys(dateData, full) {
|
||||
let dateArr = []
|
||||
let fullDate = this.date.fullDate
|
||||
for (let i = 1; i <= dateData; i++) {
|
||||
let isinfo = false
|
||||
let nowDate = full.year + '-' + (full.month < 10 ?
|
||||
full.month : full.month) + '-' + (i < 10 ?
|
||||
'0' + i : i)
|
||||
// 是否今天
|
||||
let isDay = fullDate === nowDate
|
||||
getCurrentMonthDays(amount, dateObj) {
|
||||
const result = []
|
||||
const fullDate = this.date.fullDate
|
||||
for (let i = 1; i <= amount; i++) {
|
||||
const currentDate = `${dateObj.year}-${dateObj.month}-${addZero(i)}`
|
||||
const isToday = fullDate === currentDate
|
||||
// 获取打点信息
|
||||
let info = this.selected && this.selected.find((item) => {
|
||||
if (this.dateEqual(nowDate, item.date)) {
|
||||
const info = this.selected && this.selected.find((item) => {
|
||||
if (this.dateEqual(currentDate, item.date)) {
|
||||
return item
|
||||
}
|
||||
})
|
||||
@@ -139,62 +125,53 @@ class Calendar {
|
||||
let disableBefore = true
|
||||
let disableAfter = true
|
||||
if (this.startDate) {
|
||||
// let dateCompBefore = this.dateCompare(this.startDate, fullDate)
|
||||
// disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)
|
||||
disableBefore = this.dateCompare(this.startDate, nowDate)
|
||||
disableBefore = dateCompare(this.startDate, currentDate)
|
||||
}
|
||||
|
||||
if (this.endDate) {
|
||||
// let dateCompAfter = this.dateCompare(fullDate, this.endDate)
|
||||
// disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
|
||||
disableAfter = this.dateCompare(nowDate, this.endDate)
|
||||
}
|
||||
let multiples = this.multipleStatus.data
|
||||
let checked = false
|
||||
let multiplesStatus = -1
|
||||
if (this.range) {
|
||||
if (multiples) {
|
||||
multiplesStatus = multiples.findIndex((item) => {
|
||||
return this.dateEqual(item, nowDate)
|
||||
})
|
||||
}
|
||||
if (multiplesStatus !== -1) {
|
||||
checked = true
|
||||
}
|
||||
}
|
||||
let data = {
|
||||
fullDate: nowDate,
|
||||
year: full.year,
|
||||
date: i,
|
||||
multiple: this.range ? checked : false,
|
||||
beforeMultiple: this.isLogicBefore(nowDate, this.multipleStatus.before, this.multipleStatus.after),
|
||||
afterMultiple: this.isLogicAfter(nowDate, this.multipleStatus.before, this.multipleStatus.after),
|
||||
month: full.month,
|
||||
disable: !(disableBefore && disableAfter),
|
||||
isDay,
|
||||
userChecked: false
|
||||
}
|
||||
if (info) {
|
||||
data.extraInfo = info
|
||||
disableAfter = dateCompare(currentDate, this.endDate)
|
||||
}
|
||||
|
||||
dateArr.push(data)
|
||||
let multiples = this.multipleStatus.data
|
||||
let multiplesStatus = -1
|
||||
if (this.range && multiples) {
|
||||
multiplesStatus = multiples.findIndex((item) => {
|
||||
return this.dateEqual(item, currentDate)
|
||||
})
|
||||
}
|
||||
const checked = multiplesStatus !== -1
|
||||
|
||||
result.push({
|
||||
fullDate: currentDate,
|
||||
year: dateObj.year,
|
||||
date: i,
|
||||
multiple: this.range ? checked : false,
|
||||
beforeMultiple: this.isLogicBefore(currentDate, this.multipleStatus.before, this.multipleStatus.after),
|
||||
afterMultiple: this.isLogicAfter(currentDate, this.multipleStatus.before, this.multipleStatus.after),
|
||||
month: dateObj.month,
|
||||
disable: (this.startDate && !dateCompare(this.startDate, currentDate)) || (this.endDate && !dateCompare(
|
||||
currentDate, this.endDate)),
|
||||
isToday,
|
||||
userChecked: false,
|
||||
extraInfo: info
|
||||
})
|
||||
}
|
||||
return dateArr
|
||||
return result
|
||||
}
|
||||
/**
|
||||
* 获取下月天数
|
||||
* 获取下一个月日期集合
|
||||
*/
|
||||
_getNextMonthDays(surplus, full) {
|
||||
let dateArr = []
|
||||
for (let i = 1; i < surplus + 1; i++) {
|
||||
dateArr.push({
|
||||
_getNextMonthDays(amount, dateObj) {
|
||||
const result = []
|
||||
const month = dateObj.month + 1
|
||||
for (let i = 1; i <= amount; i++) {
|
||||
result.push({
|
||||
date: i,
|
||||
month: Number(full.month) + 1,
|
||||
month,
|
||||
disable: true
|
||||
})
|
||||
}
|
||||
return dateArr
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,58 +182,37 @@ class Calendar {
|
||||
if (!date) {
|
||||
date = new Date()
|
||||
}
|
||||
const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)
|
||||
return dateInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较时间大小
|
||||
*/
|
||||
dateCompare(startDate, endDate) {
|
||||
// 计算截止时间
|
||||
startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
|
||||
// 计算详细项的截止时间
|
||||
endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
|
||||
if (startDate <= endDate) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
const res = this.calendar.find(item => item.fullDate === this.getDateObj(date).fullDate)
|
||||
return res ? res : this.getDateObj(date)
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较时间是否相等
|
||||
*/
|
||||
dateEqual(before, after) {
|
||||
// 计算截止时间
|
||||
before = new Date(before.replace('-', '/').replace('-', '/'))
|
||||
// 计算详细项的截止时间
|
||||
after = new Date(after.replace('-', '/').replace('-', '/'))
|
||||
if (before.getTime() - after.getTime() === 0) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
before = new Date(fixIosDateFormat(before))
|
||||
after = new Date(fixIosDateFormat(after))
|
||||
return before.valueOf() === after.valueOf()
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较真实起始日期
|
||||
*/
|
||||
|
||||
isLogicBefore(currentDay, before, after) {
|
||||
isLogicBefore(currentDate, before, after) {
|
||||
let logicBefore = before
|
||||
if (before && after) {
|
||||
logicBefore = this.dateCompare(before, after) ? before : after
|
||||
logicBefore = dateCompare(before, after) ? before : after
|
||||
}
|
||||
return this.dateEqual(logicBefore, currentDay)
|
||||
return this.dateEqual(logicBefore, currentDate)
|
||||
}
|
||||
|
||||
isLogicAfter(currentDay, before, after) {
|
||||
isLogicAfter(currentDate, before, after) {
|
||||
let logicAfter = after
|
||||
if (before && after) {
|
||||
logicAfter = this.dateCompare(before, after) ? after : before
|
||||
logicAfter = dateCompare(before, after) ? after : before
|
||||
}
|
||||
return this.dateEqual(logicAfter, currentDay)
|
||||
return this.dateEqual(logicAfter, currentDate)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -276,7 +232,7 @@ class Calendar {
|
||||
var unixDe = de.getTime() - 24 * 60 * 60 * 1000
|
||||
for (var k = unixDb; k <= unixDe;) {
|
||||
k = k + 24 * 60 * 60 * 1000
|
||||
arr.push(this.getDate(new Date(parseInt(k))).fullDate)
|
||||
arr.push(this.getDateObj(new Date(parseInt(k))).fullDate)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
@@ -285,11 +241,12 @@ class Calendar {
|
||||
* 获取多选状态
|
||||
*/
|
||||
setMultiple(fullDate) {
|
||||
if (!this.range) return
|
||||
|
||||
let {
|
||||
before,
|
||||
after
|
||||
} = this.multipleStatus
|
||||
if (!this.range) return
|
||||
if (before && after) {
|
||||
if (!this.lastHover) {
|
||||
this.lastHover = true
|
||||
@@ -303,10 +260,11 @@ class Calendar {
|
||||
} else {
|
||||
if (!before) {
|
||||
this.multipleStatus.before = fullDate
|
||||
this.multipleStatus.after = undefined;
|
||||
this.lastHover = false
|
||||
} else {
|
||||
this.multipleStatus.after = fullDate
|
||||
if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
|
||||
if (dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
|
||||
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus
|
||||
.after);
|
||||
} else {
|
||||
@@ -316,32 +274,33 @@ class Calendar {
|
||||
this.lastHover = true
|
||||
}
|
||||
}
|
||||
this._getWeek(fullDate)
|
||||
this.getWeeks(fullDate)
|
||||
}
|
||||
|
||||
/**
|
||||
* 鼠标 hover 更新多选状态
|
||||
*/
|
||||
setHoverMultiple(fullDate) {
|
||||
let {
|
||||
before,
|
||||
after
|
||||
//抖音小程序点击会触发hover事件,需要避免一下
|
||||
// #ifndef MP-TOUTIAO
|
||||
if (!this.range || this.lastHover) return
|
||||
const {
|
||||
before
|
||||
} = this.multipleStatus
|
||||
|
||||
if (!this.range) return
|
||||
if (this.lastHover) return
|
||||
|
||||
if (!before) {
|
||||
this.multipleStatus.before = fullDate
|
||||
} else {
|
||||
this.multipleStatus.after = fullDate
|
||||
if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
|
||||
if (dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
|
||||
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);
|
||||
} else {
|
||||
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);
|
||||
}
|
||||
}
|
||||
this._getWeek(fullDate)
|
||||
this.getWeeks(fullDate)
|
||||
// #endif
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -351,12 +310,12 @@ class Calendar {
|
||||
this.multipleStatus.before = before
|
||||
this.multipleStatus.after = after
|
||||
if (before && after) {
|
||||
if (this.dateCompare(before, after)) {
|
||||
if (dateCompare(before, after)) {
|
||||
this.multipleStatus.data = this.geDateAll(before, after);
|
||||
this._getWeek(after)
|
||||
this.getWeeks(after)
|
||||
} else {
|
||||
this.multipleStatus.data = this.geDateAll(after, before);
|
||||
this._getWeek(before)
|
||||
this.getWeeks(before)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -365,46 +324,98 @@ class Calendar {
|
||||
* 获取每周数据
|
||||
* @param {Object} dateData
|
||||
*/
|
||||
_getWeek(dateData) {
|
||||
getWeeks(dateData) {
|
||||
const {
|
||||
fullDate,
|
||||
year,
|
||||
month,
|
||||
date,
|
||||
day
|
||||
} = this.getDate(dateData)
|
||||
let firstDay = new Date(year, month - 1, 1).getDay()
|
||||
let currentDay = new Date(year, month, 0).getDate()
|
||||
let dates = {
|
||||
lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
|
||||
currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数
|
||||
nextMonthDays: [], // 下个月开始几天
|
||||
weeks: []
|
||||
}
|
||||
let canlender = []
|
||||
const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)
|
||||
dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))
|
||||
canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)
|
||||
let weeks = {}
|
||||
// 拼接数组 上个月开始几天 + 本月天数+ 下个月开始几天
|
||||
for (let i = 0; i < canlender.length; i++) {
|
||||
if (i % 7 === 0) {
|
||||
weeks[parseInt(i / 7)] = new Array(7)
|
||||
} = this.getDateObj(dateData)
|
||||
|
||||
const preMonthDayAmount = new Date(year, month - 1, 1).getDay()
|
||||
const preMonthDays = this.getPreMonthDays(preMonthDayAmount, this.getDateObj(dateData))
|
||||
|
||||
const currentMonthDayAmount = new Date(year, month, 0).getDate()
|
||||
const currentMonthDays = this.getCurrentMonthDays(currentMonthDayAmount, this.getDateObj(dateData))
|
||||
|
||||
const nextMonthDayAmount = 42 - preMonthDayAmount - currentMonthDayAmount
|
||||
const nextMonthDays = this._getNextMonthDays(nextMonthDayAmount, this.getDateObj(dateData))
|
||||
|
||||
const calendarDays = [...preMonthDays, ...currentMonthDays, ...nextMonthDays]
|
||||
|
||||
const weeks = new Array(6)
|
||||
for (let i = 0; i < calendarDays.length; i++) {
|
||||
const index = Math.floor(i / 7)
|
||||
if (!weeks[index]) {
|
||||
weeks[index] = new Array(7)
|
||||
}
|
||||
weeks[parseInt(i / 7)][i % 7] = canlender[i]
|
||||
weeks[index][i % 7] = calendarDays[i]
|
||||
}
|
||||
this.canlender = canlender
|
||||
|
||||
this.calendar = calendarDays
|
||||
this.weeks = weeks
|
||||
}
|
||||
|
||||
//静态方法
|
||||
// static init(date) {
|
||||
// if (!this.instance) {
|
||||
// this.instance = new Calendar(date);
|
||||
// }
|
||||
// return this.instance;
|
||||
// }
|
||||
}
|
||||
|
||||
function getDateTime(date, hideSecond) {
|
||||
return `${getDate(date)} ${getTime(date, hideSecond)}`
|
||||
}
|
||||
|
||||
export default Calendar
|
||||
function getDate(date) {
|
||||
date = fixIosDateFormat(date)
|
||||
date = new Date(date)
|
||||
const year = date.getFullYear()
|
||||
const month = date.getMonth() + 1
|
||||
const day = date.getDate()
|
||||
return `${year}-${addZero(month)}-${addZero(day)}`
|
||||
}
|
||||
|
||||
function getTime(date, hideSecond) {
|
||||
date = fixIosDateFormat(date)
|
||||
date = new Date(date)
|
||||
const hour = date.getHours()
|
||||
const minute = date.getMinutes()
|
||||
const second = date.getSeconds()
|
||||
return hideSecond ? `${addZero(hour)}:${addZero(minute)}` : `${addZero(hour)}:${addZero(minute)}:${addZero(second)}`
|
||||
}
|
||||
|
||||
function addZero(num) {
|
||||
if (num < 10) {
|
||||
num = `0${num}`
|
||||
}
|
||||
return num
|
||||
}
|
||||
|
||||
function getDefaultSecond(hideSecond) {
|
||||
return hideSecond ? '00:00' : '00:00:00'
|
||||
}
|
||||
|
||||
function dateCompare(startDate, endDate) {
|
||||
startDate = new Date(fixIosDateFormat(startDate))
|
||||
endDate = new Date(fixIosDateFormat(endDate))
|
||||
return startDate <= endDate
|
||||
}
|
||||
|
||||
function checkDate(date) {
|
||||
const dateReg = /((19|20)\d{2})(-|\/)\d{1,2}(-|\/)\d{1,2}/g
|
||||
return date.match(dateReg)
|
||||
}
|
||||
//ios低版本15及以下,无法匹配 没有 ’秒‘ 时的情况,所以需要在末尾 秒 加上 问号
|
||||
const dateTimeReg = /^\d{4}-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])( [0-5]?[0-9]:[0-5]?[0-9](:[0-5]?[0-9])?)?$/;
|
||||
|
||||
function fixIosDateFormat(value) {
|
||||
if (typeof value === 'string' && dateTimeReg.test(value)) {
|
||||
value = value.replace(/-/g, '/')
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
export {
|
||||
Calendar,
|
||||
getDateTime,
|
||||
getDate,
|
||||
getTime,
|
||||
addZero,
|
||||
getDefaultSecond,
|
||||
dateCompare,
|
||||
checkDate,
|
||||
fixIosDateFormat
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "uni-datetime-picker",
|
||||
"displayName": "uni-datetime-picker 日期选择器",
|
||||
"version": "2.2.4",
|
||||
"version": "2.2.38",
|
||||
"description": "uni-datetime-picker 日期时间选择器,支持日历,支持范围选择",
|
||||
"keywords": [
|
||||
"uni-datetime-picker",
|
||||
@@ -17,11 +17,7 @@
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
],
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
@@ -38,7 +34,8 @@
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [
|
||||
@@ -49,7 +46,8 @@
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
"aliyun": "y",
|
||||
"alipay": "n"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
|
||||
@@ -1,41 +1,115 @@
|
||||
## 1.1.19(2024-07-18)
|
||||
- 修复 初始值传入 null 导致input报错的bug
|
||||
## 1.1.18(2024-04-11)
|
||||
- 修复 easyinput组件双向绑定问题
|
||||
## 1.1.17(2024-03-28)
|
||||
- 修复 在头条小程序下丢失事件绑定的问题
|
||||
## 1.1.16(2024-03-20)
|
||||
- 修复 在密码输入情况下 清除和小眼睛覆盖bug 在edge浏览器下显示双眼睛bug
|
||||
## 1.1.15(2024-02-21)
|
||||
- 新增 左侧插槽:left
|
||||
## 1.1.14(2024-02-19)
|
||||
- 修复 onBlur的emit传值错误
|
||||
## 1.1.12(2024-01-29)
|
||||
- 补充 adjust-position文档属性补充
|
||||
## 1.1.11(2024-01-29)
|
||||
- 补充 adjust-position属性传递值:(Boolean)当键盘弹起时,是否自动上推页面
|
||||
## 1.1.10(2024-01-22)
|
||||
- 去除 移除无用的log输出
|
||||
## 1.1.9(2023-04-11)
|
||||
- 修复 vue3 下 keyboardheightchange 事件报错的bug
|
||||
## 1.1.8(2023-03-29)
|
||||
- 优化 trim 属性默认值
|
||||
## 1.1.7(2023-03-29)
|
||||
- 新增 cursor-spacing 属性
|
||||
## 1.1.6(2023-01-28)
|
||||
- 新增 keyboardheightchange 事件,可监听键盘高度变化
|
||||
## 1.1.5(2022-11-29)
|
||||
- 优化 主题样式
|
||||
## 1.1.4(2022-10-27)
|
||||
- 修复 props 中背景颜色无默认值的bug
|
||||
## 1.1.0(2022-06-30)
|
||||
|
||||
- 新增 在 uni-forms 1.4.0 中使用可以在 blur 时校验内容
|
||||
- 新增 clear 事件,点击右侧叉号图标触发
|
||||
- 新增 change 事件 ,仅在输入框失去焦点或用户按下回车时触发
|
||||
- 优化 组件样式,组件获取焦点时高亮显示,图标颜色调整等
|
||||
|
||||
## 1.0.5(2022-06-07)
|
||||
|
||||
- 优化 clearable 显示策略
|
||||
|
||||
## 1.0.4(2022-06-07)
|
||||
|
||||
- 优化 clearable 显示策略
|
||||
|
||||
## 1.0.3(2022-05-20)
|
||||
- 修复 关闭图标某些情况下无法取消的bug
|
||||
|
||||
- 修复 关闭图标某些情况下无法取消的 bug
|
||||
|
||||
## 1.0.2(2022-04-12)
|
||||
- 修复 默认值不生效的bug
|
||||
|
||||
- 修复 默认值不生效的 bug
|
||||
|
||||
## 1.0.1(2022-04-02)
|
||||
- 修复 value不能为0的bug
|
||||
|
||||
- 修复 value 不能为 0 的 bug
|
||||
|
||||
## 1.0.0(2021-11-19)
|
||||
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
|
||||
- 优化 组件 UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-easyinput](https://uniapp.dcloud.io/component/uniui/uni-easyinput)
|
||||
|
||||
## 0.1.4(2021-08-20)
|
||||
|
||||
- 修复 在 uni-forms 的动态表单中默认值校验不通过的 bug
|
||||
|
||||
## 0.1.3(2021-08-11)
|
||||
|
||||
- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题
|
||||
|
||||
## 0.1.2(2021-07-30)
|
||||
- 优化 vue3下事件警告的问题
|
||||
|
||||
- 优化 vue3 下事件警告的问题
|
||||
|
||||
## 0.1.1
|
||||
|
||||
- 优化 errorMessage 属性支持 Boolean 类型
|
||||
|
||||
## 0.1.0(2021-07-13)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
|
||||
- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
|
||||
## 0.0.16(2021-06-29)
|
||||
|
||||
- 修复 confirmType 属性(仅 type="text" 生效)导致多行文本框无法换行的 bug
|
||||
|
||||
## 0.0.15(2021-06-21)
|
||||
|
||||
- 修复 passwordIcon 属性拼写错误的 bug
|
||||
|
||||
## 0.0.14(2021-06-18)
|
||||
- 新增 passwordIcon 属性,当type=password时是否显示小眼睛图标
|
||||
|
||||
- 新增 passwordIcon 属性,当 type=password 时是否显示小眼睛图标
|
||||
- 修复 confirmType 属性不生效的问题
|
||||
|
||||
## 0.0.13(2021-06-04)
|
||||
|
||||
- 修复 disabled 状态可清出内容的 bug
|
||||
|
||||
## 0.0.12(2021-05-12)
|
||||
|
||||
- 新增 组件示例地址
|
||||
|
||||
## 0.0.11(2021-05-07)
|
||||
|
||||
- 修复 input-border 属性不生效的问题
|
||||
|
||||
## 0.0.10(2021-04-30)
|
||||
|
||||
- 修复 ios 遮挡文字、显示一半的问题
|
||||
|
||||
## 0.0.9(2021-02-05)
|
||||
- 调整为uni_modules目录规范
|
||||
|
||||
- 调整为 uni_modules 目录规范
|
||||
- 优化 兼容 nvue 页面
|
||||
|
||||
@@ -5,50 +5,50 @@
|
||||
* @param immediate true - 立即执行, false - 延迟执行
|
||||
*/
|
||||
export const debounce = function(func, wait = 1000, immediate = true) {
|
||||
let timer;
|
||||
return function() {
|
||||
let context = this,
|
||||
args = arguments;
|
||||
if (timer) clearTimeout(timer);
|
||||
if (immediate) {
|
||||
let callNow = !timer;
|
||||
timer = setTimeout(() => {
|
||||
timer = null;
|
||||
}, wait);
|
||||
if (callNow) func.apply(context, args);
|
||||
} else {
|
||||
timer = setTimeout(() => {
|
||||
func.apply(context, args);
|
||||
}, wait)
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @desc 函数节流
|
||||
* @param func 函数
|
||||
* @param wait 延迟执行毫秒数
|
||||
* @param type 1 使用表时间戳,在时间段开始的时候触发 2 使用表定时器,在时间段结束的时候触发
|
||||
*/
|
||||
let timer;
|
||||
return function() {
|
||||
let context = this,
|
||||
args = arguments;
|
||||
if (timer) clearTimeout(timer);
|
||||
if (immediate) {
|
||||
let callNow = !timer;
|
||||
timer = setTimeout(() => {
|
||||
timer = null;
|
||||
}, wait);
|
||||
if (callNow) func.apply(context, args);
|
||||
} else {
|
||||
timer = setTimeout(() => {
|
||||
func.apply(context, args);
|
||||
}, wait)
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @desc 函数节流
|
||||
* @param func 函数
|
||||
* @param wait 延迟执行毫秒数
|
||||
* @param type 1 使用表时间戳,在时间段开始的时候触发 2 使用表定时器,在时间段结束的时候触发
|
||||
*/
|
||||
export const throttle = (func, wait = 1000, type = 1) => {
|
||||
let previous = 0;
|
||||
let timeout;
|
||||
return function() {
|
||||
let context = this;
|
||||
let args = arguments;
|
||||
if (type === 1) {
|
||||
let now = Date.now();
|
||||
let previous = 0;
|
||||
let timeout;
|
||||
return function() {
|
||||
let context = this;
|
||||
let args = arguments;
|
||||
if (type === 1) {
|
||||
let now = Date.now();
|
||||
|
||||
if (now - previous > wait) {
|
||||
func.apply(context, args);
|
||||
previous = now;
|
||||
}
|
||||
} else if (type === 2) {
|
||||
if (!timeout) {
|
||||
timeout = setTimeout(() => {
|
||||
timeout = null;
|
||||
func.apply(context, args)
|
||||
}, wait)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (now - previous > wait) {
|
||||
func.apply(context, args);
|
||||
previous = now;
|
||||
}
|
||||
} else if (type === 2) {
|
||||
if (!timeout) {
|
||||
timeout = setTimeout(() => {
|
||||
timeout = null;
|
||||
func.apply(context, args)
|
||||
}, wait)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "uni-easyinput",
|
||||
"displayName": "uni-easyinput 增强输入框",
|
||||
"version": "1.0.5",
|
||||
"version": "1.1.19",
|
||||
"description": "Easyinput 组件是对原生input组件的增强",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
@@ -17,11 +17,7 @@
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
],
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
@@ -38,7 +34,8 @@
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [
|
||||
@@ -49,7 +46,8 @@
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
"aliyun": "y",
|
||||
"alipay": "n"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
## 1.2.6(2024-10-12)
|
||||
- 修复 微信小程序中的getSystemInfo警告
|
||||
## 1.2.5(2023-03-29)
|
||||
- 新增 pattern.icon 属性,可自定义图标
|
||||
## 1.2.4(2022-09-07)
|
||||
小程序端由于 style 使用了对象导致报错,[详情](https://ask.dcloud.net.cn/question/152790?item_id=211778&rf=false)
|
||||
## 1.2.3(2022-09-05)
|
||||
- 修复 nvue 环境下,具有 tabBar 时,fab 组件下部位置无法正常获取 --window-bottom 的bug,详见:[https://ask.dcloud.net.cn/question/110638?notification_id=826310](https://ask.dcloud.net.cn/question/110638?notification_id=826310)
|
||||
## 1.2.2(2021-12-29)
|
||||
- 更新 组件依赖
|
||||
## 1.2.1(2021-11-19)
|
||||
|
||||
@@ -1,528 +1,491 @@
|
||||
<template>
|
||||
<view class="uni-cursor-point">
|
||||
<view
|
||||
v-if="popMenu && (leftBottom || rightBottom || leftTop || rightTop) && content.length > 0"
|
||||
:class="{
|
||||
<view class="uni-cursor-point">
|
||||
<view v-if="popMenu && (leftBottom||rightBottom||leftTop||rightTop) && content.length > 0" :class="{
|
||||
'uni-fab--leftBottom': leftBottom,
|
||||
'uni-fab--rightBottom': rightBottom,
|
||||
'uni-fab--leftTop': leftTop,
|
||||
'uni-fab--rightTop': rightTop,
|
||||
}"
|
||||
class="uni-fab"
|
||||
>
|
||||
<view
|
||||
:class="{
|
||||
'uni-fab--rightTop': rightTop
|
||||
}" class="uni-fab"
|
||||
:style="nvueBottom"
|
||||
>
|
||||
<view :class="{
|
||||
'uni-fab__content--left': horizontal === 'left',
|
||||
'uni-fab__content--right': horizontal === 'right',
|
||||
'uni-fab__content--flexDirection': direction === 'vertical',
|
||||
'uni-fab__content--flexDirectionStart': flexDirectionStart,
|
||||
'uni-fab__content--flexDirectionEnd': flexDirectionEnd,
|
||||
'uni-fab__content--other-platform': !isAndroidNvue,
|
||||
}"
|
||||
:style="{ width: boxWidth, height: boxHeight }"
|
||||
class="uni-fab__content"
|
||||
elevation="5"
|
||||
>
|
||||
<view
|
||||
v-if="flexDirectionStart || horizontalLeft"
|
||||
class="uni-fab__item uni-fab__item--first"
|
||||
/>
|
||||
<view
|
||||
v-for="(item, index) in content"
|
||||
:key="index"
|
||||
:class="{
|
||||
'uni-fab__item--active': isShow,
|
||||
'horizontal-margin': direction == 'horizontal',
|
||||
}"
|
||||
class="uni-fab__item"
|
||||
@click="_onItemClick(index, item)"
|
||||
>
|
||||
<image
|
||||
:src="item.active ? item.selectedIconPath : item.iconPath"
|
||||
class="uni-fab__item-image"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<text
|
||||
class="uni-fab__item-text"
|
||||
:class="{ 'vertical-margin': direction == 'vertical' }"
|
||||
:style="{ color: pattern[index].color }"
|
||||
>{{ item.text }}</text
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
v-if="flexDirectionEnd || horizontalRight"
|
||||
class="uni-fab__item uni-fab__item--first"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
:class="{
|
||||
'uni-fab__circle--leftBottom': leftBottom,
|
||||
'uni-fab__circle--rightBottom': rightBottom,
|
||||
'uni-fab__circle--leftTop': leftTop,
|
||||
'uni-fab__circle--rightTop': rightTop,
|
||||
'uni-fab__content--other-platform': !isAndroidNvue,
|
||||
}"
|
||||
class="uni-fab__circle uni-fab__plus"
|
||||
:style="{ 'background-color': 'var(--ui-BG-Main)' }"
|
||||
@click="_onClick"
|
||||
>
|
||||
<uni-icons
|
||||
class="fab-circle-icon"
|
||||
type="plusempty"
|
||||
color="#fff"
|
||||
size="20"
|
||||
:class="{ 'uni-fab__plus--active': isShow && content.length > 0 }"
|
||||
></uni-icons>
|
||||
<!-- <view class="fab-circle-v" :class="{'uni-fab__plus--active': isShow && content.length > 0}"></view>
|
||||
'uni-fab__content--other-platform': !isAndroidNvue
|
||||
}" :style="{ width: boxWidth, height: boxHeight, backgroundColor: styles.backgroundColor }"
|
||||
class="uni-fab__content" elevation="5">
|
||||
<view v-if="flexDirectionStart || horizontalLeft" class="uni-fab__item uni-fab__item--first" />
|
||||
<view v-for="(item, index) in content" :key="index" :class="{ 'uni-fab__item--active': isShow }"
|
||||
class="uni-fab__item" @click="_onItemClick(index, item)">
|
||||
<image :src="item.active ? item.selectedIconPath : item.iconPath" class="uni-fab__item-image"
|
||||
mode="aspectFit" />
|
||||
<text class="uni-fab__item-text"
|
||||
:style="{ color: item.active ? styles.selectedColor : styles.color }">{{ item.text }}</text>
|
||||
</view>
|
||||
<view v-if="flexDirectionEnd || horizontalRight" class="uni-fab__item uni-fab__item--first" />
|
||||
</view>
|
||||
</view>
|
||||
<view :class="{
|
||||
'uni-fab__circle--leftBottom': leftBottom,
|
||||
'uni-fab__circle--rightBottom': rightBottom,
|
||||
'uni-fab__circle--leftTop': leftTop,
|
||||
'uni-fab__circle--rightTop': rightTop,
|
||||
'uni-fab__content--other-platform': !isAndroidNvue
|
||||
}" class="uni-fab__circle uni-fab__plus" :style="{ 'background-color': styles.buttonColor, 'bottom': nvueBottom }" @click="_onClick">
|
||||
<uni-icons class="fab-circle-icon" :type="styles.icon" :color="styles.iconColor" size="32"
|
||||
:class="{'uni-fab__plus--active': isShow && content.length > 0}"></uni-icons>
|
||||
<!-- <view class="fab-circle-v" :class="{'uni-fab__plus--active': isShow && content.length > 0}"></view>
|
||||
<view class="fab-circle-h" :class="{'uni-fab__plus--active': isShow && content.length > 0}"></view> -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import sheep from '@/sheep';
|
||||
let platform = 'other';
|
||||
// #ifdef APP-NVUE
|
||||
platform = uni.getDeviceInfo().platform;
|
||||
// #endif
|
||||
let platform = 'other'
|
||||
// #ifdef APP-NVUE
|
||||
platform = uni.getSystemInfoSync().platform
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* Fab 悬浮按钮
|
||||
* @description 点击可展开一个图形按钮菜单
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=144
|
||||
* @property {Object} pattern 可选样式配置项
|
||||
* @property {Object} horizontal = [left | right] 水平对齐方式
|
||||
* @value left 左对齐
|
||||
* @value right 右对齐
|
||||
* @property {Object} vertical = [bottom | top] 垂直对齐方式
|
||||
* @value bottom 下对齐
|
||||
* @value top 上对齐
|
||||
* @property {Object} direction = [horizontal | vertical] 展开菜单显示方式
|
||||
* @value horizontal 水平显示
|
||||
* @value vertical 垂直显示
|
||||
* @property {Array} content 展开菜单内容配置项
|
||||
* @property {Boolean} popMenu 是否使用弹出菜单
|
||||
* @event {Function} trigger 展开菜单点击事件,返回点击信息
|
||||
* @event {Function} fabClick 悬浮按钮点击事件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniFab',
|
||||
emits: ['fabClick', 'trigger'],
|
||||
props: {
|
||||
pattern: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
horizontal: {
|
||||
type: String,
|
||||
default: 'left',
|
||||
},
|
||||
vertical: {
|
||||
type: String,
|
||||
default: 'bottom',
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'horizontal',
|
||||
},
|
||||
content: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
popMenu: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fabShow: false,
|
||||
isShow: false,
|
||||
isAndroidNvue: platform === 'android',
|
||||
styles: [
|
||||
{
|
||||
// color: '#3c3e49',
|
||||
// selectedColor: '#007AFF',
|
||||
// backgroundColor: '#fff',
|
||||
// buttonColor: '#007AFF',
|
||||
// iconColor: '#fff'
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
contentWidth(e) {
|
||||
return (this.content.length + 1) * 130 + 'rpx';
|
||||
},
|
||||
contentWidthMin() {
|
||||
return '100rpx';
|
||||
},
|
||||
// 动态计算宽度
|
||||
boxWidth() {
|
||||
return this.getPosition(3, 'horizontal');
|
||||
},
|
||||
// 动态计算高度
|
||||
boxHeight() {
|
||||
return this.getPosition(3, 'vertical');
|
||||
},
|
||||
// 计算左下位置
|
||||
leftBottom() {
|
||||
return this.getPosition(0, 'left', 'bottom');
|
||||
},
|
||||
// 计算右下位置
|
||||
rightBottom() {
|
||||
return this.getPosition(0, 'right', 'bottom');
|
||||
},
|
||||
// 计算左上位置
|
||||
leftTop() {
|
||||
return this.getPosition(0, 'left', 'top');
|
||||
},
|
||||
rightTop() {
|
||||
return this.getPosition(0, 'right', 'top');
|
||||
},
|
||||
flexDirectionStart() {
|
||||
return this.getPosition(1, 'vertical', 'top');
|
||||
},
|
||||
flexDirectionEnd() {
|
||||
return this.getPosition(1, 'vertical', 'bottom');
|
||||
},
|
||||
horizontalLeft() {
|
||||
return this.getPosition(2, 'horizontal', 'left');
|
||||
},
|
||||
horizontalRight() {
|
||||
return this.getPosition(2, 'horizontal', 'right');
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// pattern: {
|
||||
// handler(val, oldVal) {
|
||||
// this.styles = Object.assign({}, this.styles, val)
|
||||
// },
|
||||
// deep: true
|
||||
// }
|
||||
},
|
||||
created() {
|
||||
this.isShow = this.show;
|
||||
if (this.top === 0) {
|
||||
this.fabShow = true;
|
||||
}
|
||||
// 初始化样式
|
||||
// this.styles = Object.assign({}, this.styles, this.pattern)
|
||||
},
|
||||
methods: {
|
||||
_onClick() {
|
||||
this.$emit('fabClick');
|
||||
if (!this.popMenu) {
|
||||
return;
|
||||
}
|
||||
this.isShow = !this.isShow;
|
||||
},
|
||||
open() {
|
||||
this.isShow = true;
|
||||
},
|
||||
close() {
|
||||
this.isShow = false;
|
||||
},
|
||||
/**
|
||||
* 按钮点击事件
|
||||
*/
|
||||
_onItemClick(index, item) {
|
||||
this.$emit('trigger', {
|
||||
index,
|
||||
item,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 获取 位置信息
|
||||
*/
|
||||
getPosition(types, paramA, paramB) {
|
||||
if (types === 0) {
|
||||
return this.horizontal === paramA && this.vertical === paramB;
|
||||
} else if (types === 1) {
|
||||
return this.direction === paramA && this.vertical === paramB;
|
||||
} else if (types === 2) {
|
||||
return this.direction === paramA && this.horizontal === paramB;
|
||||
} else {
|
||||
return this.isShow && this.direction === paramA ? this.contentWidth : this.contentWidthMin;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
/**
|
||||
* Fab 悬浮按钮
|
||||
* @description 点击可展开一个图形按钮菜单
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=144
|
||||
* @property {Object} pattern 可选样式配置项
|
||||
* @property {Object} horizontal = [left | right] 水平对齐方式
|
||||
* @value left 左对齐
|
||||
* @value right 右对齐
|
||||
* @property {Object} vertical = [bottom | top] 垂直对齐方式
|
||||
* @value bottom 下对齐
|
||||
* @value top 上对齐
|
||||
* @property {Object} direction = [horizontal | vertical] 展开菜单显示方式
|
||||
* @value horizontal 水平显示
|
||||
* @value vertical 垂直显示
|
||||
* @property {Array} content 展开菜单内容配置项
|
||||
* @property {Boolean} popMenu 是否使用弹出菜单
|
||||
* @event {Function} trigger 展开菜单点击事件,返回点击信息
|
||||
* @event {Function} fabClick 悬浮按钮点击事件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniFab',
|
||||
emits: ['fabClick', 'trigger'],
|
||||
props: {
|
||||
pattern: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
horizontal: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
vertical: {
|
||||
type: String,
|
||||
default: 'bottom'
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'horizontal'
|
||||
},
|
||||
content: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
popMenu: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fabShow: false,
|
||||
isShow: false,
|
||||
isAndroidNvue: platform === 'android',
|
||||
styles: {
|
||||
color: '#3c3e49',
|
||||
selectedColor: '#007AFF',
|
||||
backgroundColor: '#fff',
|
||||
buttonColor: '#007AFF',
|
||||
iconColor: '#fff',
|
||||
icon: 'plusempty'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
contentWidth(e) {
|
||||
return (this.content.length + 1) * 55 + 15 + 'px'
|
||||
},
|
||||
contentWidthMin() {
|
||||
return '55px'
|
||||
},
|
||||
// 动态计算宽度
|
||||
boxWidth() {
|
||||
return this.getPosition(3, 'horizontal')
|
||||
},
|
||||
// 动态计算高度
|
||||
boxHeight() {
|
||||
return this.getPosition(3, 'vertical')
|
||||
},
|
||||
// 计算左下位置
|
||||
leftBottom() {
|
||||
return this.getPosition(0, 'left', 'bottom')
|
||||
},
|
||||
// 计算右下位置
|
||||
rightBottom() {
|
||||
return this.getPosition(0, 'right', 'bottom')
|
||||
},
|
||||
// 计算左上位置
|
||||
leftTop() {
|
||||
return this.getPosition(0, 'left', 'top')
|
||||
},
|
||||
rightTop() {
|
||||
return this.getPosition(0, 'right', 'top')
|
||||
},
|
||||
flexDirectionStart() {
|
||||
return this.getPosition(1, 'vertical', 'top')
|
||||
},
|
||||
flexDirectionEnd() {
|
||||
return this.getPosition(1, 'vertical', 'bottom')
|
||||
},
|
||||
horizontalLeft() {
|
||||
return this.getPosition(2, 'horizontal', 'left')
|
||||
},
|
||||
horizontalRight() {
|
||||
return this.getPosition(2, 'horizontal', 'right')
|
||||
},
|
||||
// 计算 nvue bottom
|
||||
nvueBottom() {
|
||||
// #ifdef APP-NVUE
|
||||
const safeBottom = uni.getSystemInfoSync().windowBottom;
|
||||
return 30 + safeBottom
|
||||
// #endif
|
||||
// #ifndef APP-NVUE
|
||||
return 30
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
pattern: {
|
||||
handler(val, oldVal) {
|
||||
this.styles = Object.assign({}, this.styles, val)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.isShow = this.show
|
||||
if (this.top === 0) {
|
||||
this.fabShow = true
|
||||
}
|
||||
// 初始化样式
|
||||
this.styles = Object.assign({}, this.styles, this.pattern)
|
||||
},
|
||||
methods: {
|
||||
_onClick() {
|
||||
this.$emit('fabClick')
|
||||
if (!this.popMenu) {
|
||||
return
|
||||
}
|
||||
this.isShow = !this.isShow
|
||||
},
|
||||
open() {
|
||||
this.isShow = true
|
||||
},
|
||||
close() {
|
||||
this.isShow = false
|
||||
},
|
||||
/**
|
||||
* 按钮点击事件
|
||||
*/
|
||||
_onItemClick(index, item) {
|
||||
if (!this.isShow) {
|
||||
return
|
||||
}
|
||||
this.$emit('trigger', {
|
||||
index,
|
||||
item
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 获取 位置信息
|
||||
*/
|
||||
getPosition(types, paramA, paramB) {
|
||||
if (types === 0) {
|
||||
return this.horizontal === paramA && this.vertical === paramB
|
||||
} else if (types === 1) {
|
||||
return this.direction === paramA && this.vertical === paramB
|
||||
} else if (types === 2) {
|
||||
return this.direction === paramA && this.horizontal === paramB
|
||||
} else {
|
||||
return this.isShow && this.direction === paramA ? this.contentWidth : this.contentWidthMin
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" >
|
||||
$uni-shadow-base: 0 1px 5px 2px
|
||||
rgba(
|
||||
$color: #000000,
|
||||
$alpha: 0.3,
|
||||
) !default;
|
||||
$uni-shadow-base:0 1px 5px 2px rgba($color: #000000, $alpha: 0.3) !default;
|
||||
|
||||
.horizontal-margin {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
.vertical-margin {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.uni-fab {
|
||||
position: fixed;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 12;
|
||||
border-radius: 45px;
|
||||
// box-shadow: $uni-shadow-base;
|
||||
}
|
||||
.uni-fab {
|
||||
position: fixed;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
border-radius: 45px;
|
||||
box-shadow: $uni-shadow-base;
|
||||
}
|
||||
|
||||
.uni-cursor-point {
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
.uni-cursor-point {
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-fab--active {
|
||||
opacity: 1;
|
||||
}
|
||||
.uni-fab--active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.uni-fab--leftBottom {
|
||||
left: 15px;
|
||||
bottom: 30px;
|
||||
/* #ifdef H5 */
|
||||
left: calc(30px + var(--window-left));
|
||||
bottom: calc(60px + var(--window-bottom));
|
||||
/* #endif */
|
||||
// padding: 10px;
|
||||
}
|
||||
.uni-fab--leftBottom {
|
||||
left: 15px;
|
||||
bottom: 30px;
|
||||
/* #ifdef H5 */
|
||||
left: calc(15px + var(--window-left));
|
||||
bottom: calc(30px + var(--window-bottom));
|
||||
/* #endif */
|
||||
// padding: 10px;
|
||||
}
|
||||
|
||||
.uni-fab--leftTop {
|
||||
left: 15px;
|
||||
top: 30px;
|
||||
/* #ifdef H5 */
|
||||
left: calc(15px + var(--window-left));
|
||||
top: calc(30px + var(--window-top));
|
||||
/* #endif */
|
||||
// padding: 10px;
|
||||
}
|
||||
.uni-fab--leftTop {
|
||||
left: 15px;
|
||||
top: 30px;
|
||||
/* #ifdef H5 */
|
||||
left: calc(15px + var(--window-left));
|
||||
top: calc(30px + var(--window-top));
|
||||
/* #endif */
|
||||
// padding: 10px;
|
||||
}
|
||||
|
||||
.uni-fab--rightBottom {
|
||||
right: 24rpx;
|
||||
bottom: calc(100rpx + env(safe-area-inset-bottom));
|
||||
/* #ifdef H5 */
|
||||
right: calc(24rpx + var(--window-right));
|
||||
bottom: calc(100rpx + var(--window-bottom));
|
||||
/* #endif */
|
||||
// padding: 10px;
|
||||
}
|
||||
.uni-fab--rightBottom {
|
||||
right: 15px;
|
||||
bottom: 30px;
|
||||
/* #ifdef H5 */
|
||||
right: calc(15px + var(--window-right));
|
||||
bottom: calc(30px + var(--window-bottom));
|
||||
/* #endif */
|
||||
// padding: 10px;
|
||||
}
|
||||
|
||||
.uni-fab--rightTop {
|
||||
right: 15px;
|
||||
top: 30px;
|
||||
/* #ifdef H5 */
|
||||
right: calc(15px + var(--window-right));
|
||||
top: calc(30px + var(--window-top));
|
||||
/* #endif */
|
||||
// padding: 10px;
|
||||
}
|
||||
.uni-fab--rightTop {
|
||||
right: 15px;
|
||||
top: 30px;
|
||||
/* #ifdef H5 */
|
||||
right: calc(15px + var(--window-right));
|
||||
top: calc(30px + var(--window-top));
|
||||
/* #endif */
|
||||
// padding: 10px;
|
||||
}
|
||||
|
||||
.uni-fab__circle {
|
||||
position: fixed;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
background-color: #3c3e49;
|
||||
border-radius: 50%;
|
||||
z-index: 13;
|
||||
// box-shadow: $uni-shadow-base;
|
||||
}
|
||||
.uni-fab__circle {
|
||||
position: fixed;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
background-color: #3c3e49;
|
||||
border-radius: 45px;
|
||||
z-index: 11;
|
||||
// box-shadow: $uni-shadow-base;
|
||||
}
|
||||
|
||||
.uni-fab__circle--leftBottom {
|
||||
left: 15px;
|
||||
bottom: 30px;
|
||||
/* #ifdef H5 */
|
||||
left: calc(15px + var(--window-left));
|
||||
bottom: calc(30px + var(--window-bottom));
|
||||
/* #endif */
|
||||
}
|
||||
.uni-fab__circle--leftBottom {
|
||||
left: 15px;
|
||||
bottom: 30px;
|
||||
/* #ifdef H5 */
|
||||
left: calc(15px + var(--window-left));
|
||||
bottom: calc(30px + var(--window-bottom));
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-fab__circle--leftTop {
|
||||
left: 15px;
|
||||
top: 30px;
|
||||
/* #ifdef H5 */
|
||||
left: calc(15px + var(--window-left));
|
||||
top: calc(30px + var(--window-top));
|
||||
/* #endif */
|
||||
}
|
||||
.uni-fab__circle--leftTop {
|
||||
left: 15px;
|
||||
top: 30px;
|
||||
/* #ifdef H5 */
|
||||
left: calc(15px + var(--window-left));
|
||||
top: calc(30px + var(--window-top));
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-fab__circle--rightBottom {
|
||||
right: 40rpx;
|
||||
bottom: calc(120rpx + env(safe-area-inset-bottom));
|
||||
/* #ifdef H5 */
|
||||
right: calc(40rpx + var(--window-right));
|
||||
bottom: calc(120rpx + var(--window-bottom));
|
||||
/* #endif */
|
||||
}
|
||||
.uni-fab__circle--rightBottom {
|
||||
right: 15px;
|
||||
bottom: 30px;
|
||||
/* #ifdef H5 */
|
||||
right: calc(15px + var(--window-right));
|
||||
bottom: calc(30px + var(--window-bottom));
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-fab__circle--rightTop {
|
||||
right: 15px;
|
||||
top: 30px;
|
||||
/* #ifdef H5 */
|
||||
right: calc(15px + var(--window-right));
|
||||
top: calc(30px + var(--window-top));
|
||||
/* #endif */
|
||||
}
|
||||
.uni-fab__circle--rightTop {
|
||||
right: 15px;
|
||||
top: 30px;
|
||||
/* #ifdef H5 */
|
||||
right: calc(15px + var(--window-right));
|
||||
top: calc(30px + var(--window-top));
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-fab__circle--left {
|
||||
left: 0;
|
||||
}
|
||||
.uni-fab__circle--left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.uni-fab__circle--right {
|
||||
right: 0;
|
||||
}
|
||||
.uni-fab__circle--right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.uni-fab__circle--top {
|
||||
top: 0;
|
||||
}
|
||||
.uni-fab__circle--top {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.uni-fab__circle--bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
.uni-fab__circle--bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.uni-fab__plus {
|
||||
font-weight: bold;
|
||||
}
|
||||
.uni-fab__plus {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
// .fab-circle-v {
|
||||
// position: absolute;
|
||||
// width: 2px;
|
||||
// height: 24px;
|
||||
// left: 0;
|
||||
// top: 0;
|
||||
// right: 0;
|
||||
// bottom: 0;
|
||||
// /* #ifndef APP-NVUE */
|
||||
// margin: auto;
|
||||
// /* #endif */
|
||||
// background-color: white;
|
||||
// transform: rotate(0deg);
|
||||
// transition: transform 0.3s;
|
||||
// }
|
||||
// .fab-circle-v {
|
||||
// position: absolute;
|
||||
// width: 2px;
|
||||
// height: 24px;
|
||||
// left: 0;
|
||||
// top: 0;
|
||||
// right: 0;
|
||||
// bottom: 0;
|
||||
// /* #ifndef APP-NVUE */
|
||||
// margin: auto;
|
||||
// /* #endif */
|
||||
// background-color: white;
|
||||
// transform: rotate(0deg);
|
||||
// transition: transform 0.3s;
|
||||
// }
|
||||
|
||||
// .fab-circle-h {
|
||||
// position: absolute;
|
||||
// width: 24px;
|
||||
// height: 2px;
|
||||
// left: 0;
|
||||
// top: 0;
|
||||
// right: 0;
|
||||
// bottom: 0;
|
||||
// /* #ifndef APP-NVUE */
|
||||
// margin: auto;
|
||||
// /* #endif */
|
||||
// background-color: white;
|
||||
// transform: rotate(0deg);
|
||||
// transition: transform 0.3s;
|
||||
// }
|
||||
// .fab-circle-h {
|
||||
// position: absolute;
|
||||
// width: 24px;
|
||||
// height: 2px;
|
||||
// left: 0;
|
||||
// top: 0;
|
||||
// right: 0;
|
||||
// bottom: 0;
|
||||
// /* #ifndef APP-NVUE */
|
||||
// margin: auto;
|
||||
// /* #endif */
|
||||
// background-color: white;
|
||||
// transform: rotate(0deg);
|
||||
// transition: transform 0.3s;
|
||||
// }
|
||||
|
||||
.fab-circle-icon {
|
||||
transform: rotate(0deg);
|
||||
transition: transform 0.3s;
|
||||
font-weight: 200;
|
||||
}
|
||||
.fab-circle-icon {
|
||||
transform: rotate(0deg);
|
||||
transition: transform 0.3s;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
.uni-fab__plus--active {
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
.uni-fab__plus--active {
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
|
||||
.uni-fab__content {
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
border-radius: 55px;
|
||||
overflow: hidden;
|
||||
transition-property: width, height;
|
||||
transition-duration: 0.2s;
|
||||
width: 64rpx;
|
||||
border-color: #dddddd;
|
||||
border-width: 1rpx;
|
||||
border-style: solid;
|
||||
}
|
||||
.uni-fab__content {
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
border-radius: 55px;
|
||||
overflow: hidden;
|
||||
transition-property: width, height;
|
||||
transition-duration: 0.2s;
|
||||
width: 55px;
|
||||
border-color: #DDDDDD;
|
||||
border-width: 1rpx;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.uni-fab__content--other-platform {
|
||||
border-width: 0px;
|
||||
// box-shadow: $uni-shadow-base;
|
||||
}
|
||||
.uni-fab__content--other-platform {
|
||||
border-width: 0px;
|
||||
box-shadow: $uni-shadow-base;
|
||||
}
|
||||
|
||||
.uni-fab__content--left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.uni-fab__content--left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.uni-fab__content--right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.uni-fab__content--right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.uni-fab__content--flexDirection {
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.uni-fab__content--flexDirection {
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.uni-fab__content--flexDirectionStart {
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.uni-fab__content--flexDirectionStart {
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.uni-fab__content--flexDirectionEnd {
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.uni-fab__content--flexDirectionEnd {
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.uni-fab__item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100rpx;
|
||||
// height: 84rpx;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.uni-fab__item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.uni-fab__item--active {
|
||||
opacity: 1;
|
||||
}
|
||||
.uni-fab__item--active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.uni-fab__item-image {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
}
|
||||
.uni-fab__item-image {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.uni-fab__item-text {
|
||||
color: #ffffff;
|
||||
display: table;
|
||||
font-size: 24rpx;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.uni-fab__item-text {
|
||||
color: #FFFFFF;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.uni-fab__item--first {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.uni-fab__item--first {
|
||||
width: 55px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "uni-fab",
|
||||
"displayName": "uni-fab 悬浮按钮",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.6",
|
||||
"description": "悬浮按钮 fab button ,点击可展开一个图标按钮菜单。",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
@@ -17,11 +17,7 @@
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
],
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
@@ -38,7 +34,8 @@
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": ["uni-scss","uni-icons"],
|
||||
@@ -46,7 +43,8 @@
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
"aliyun": "y",
|
||||
"alipay": "n"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
|
||||
81
uni_modules/uni-file-picker/changelog.md
Normal file
81
uni_modules/uni-file-picker/changelog.md
Normal file
@@ -0,0 +1,81 @@
|
||||
## 1.0.11(2024-07-19)
|
||||
- 修复 vue3 使用value报错的bug
|
||||
## 1.0.10(2024-07-09)
|
||||
- 优化 vue3兼容性
|
||||
## 1.0.9(2024-07-09)
|
||||
- 修复 value 属性不兼容vue3的bug
|
||||
## 1.0.8(2024-03-20)
|
||||
- 补充 删除文件时返回文件下标
|
||||
## 1.0.7(2024-02-21)
|
||||
- 新增 微信小程序选择视频时改用chooseMedia,并返回视频缩略图
|
||||
## 1.0.6(2024-01-06)
|
||||
- 新增 微信小程序不再调用chooseImage,而是调用chooseMedia
|
||||
## 1.0.5(2024-01-03)
|
||||
- 新增 上传文件至云存储携带本地文件名称
|
||||
## 1.0.4(2023-03-29)
|
||||
- 修复 手动上传删除一个文件后不能再上传的bug
|
||||
## 1.0.3(2022-12-19)
|
||||
- 新增 sourceType 属性, 可以自定义图片和视频选择的来源
|
||||
## 1.0.2(2022-07-04)
|
||||
- 修复 在uni-forms下样式不生效的bug
|
||||
## 1.0.1(2021-11-23)
|
||||
- 修复 参数为对象的情况下,url在某些情况显示错误的bug
|
||||
## 1.0.0(2021-11-19)
|
||||
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-file-picker](https://uniapp.dcloud.io/component/uniui/uni-file-picker)
|
||||
## 0.2.16(2021-11-08)
|
||||
- 修复 传入空对象 ,显示错误的Bug
|
||||
## 0.2.15(2021-08-30)
|
||||
- 修复 return-type="object" 时且存在v-model时,无法删除文件的Bug
|
||||
## 0.2.14(2021-08-23)
|
||||
- 新增 参数中返回 fileID 字段
|
||||
## 0.2.13(2021-08-23)
|
||||
- 修复 腾讯云传入fileID 不能回显的bug
|
||||
- 修复 选择图片后,不能放大的问题
|
||||
## 0.2.12(2021-08-17)
|
||||
- 修复 由于 0.2.11 版本引起的不能回显图片的Bug
|
||||
## 0.2.11(2021-08-16)
|
||||
- 新增 clearFiles(index) 方法,可以手动删除指定文件
|
||||
- 修复 v-model 值设为 null 报错的Bug
|
||||
## 0.2.10(2021-08-13)
|
||||
- 修复 return-type="object" 时,无法删除文件的Bug
|
||||
## 0.2.9(2021-08-03)
|
||||
- 修复 auto-upload 属性失效的Bug
|
||||
## 0.2.8(2021-07-31)
|
||||
- 修复 fileExtname属性不指定值报错的Bug
|
||||
## 0.2.7(2021-07-31)
|
||||
- 修复 在某种场景下图片不回显的Bug
|
||||
## 0.2.6(2021-07-30)
|
||||
- 修复 return-type为object下,返回值不正确的Bug
|
||||
## 0.2.5(2021-07-30)
|
||||
- 修复(重要) H5 平台下如果和uni-forms组件一同使用导致页面卡死的问题
|
||||
## 0.2.3(2021-07-28)
|
||||
- 优化 调整示例代码
|
||||
## 0.2.2(2021-07-27)
|
||||
- 修复 vue3 下赋值错误的Bug
|
||||
- 优化 h5平台下上传文件导致页面卡死的问题
|
||||
## 0.2.0(2021-07-13)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 0.1.1(2021-07-02)
|
||||
- 修复 sourceType 缺少默认值导致 ios 无法选择文件
|
||||
## 0.1.0(2021-06-30)
|
||||
- 优化 解耦与uniCloud的强绑定关系 ,如不绑定服务空间,默认autoUpload为false且不可更改
|
||||
## 0.0.11(2021-06-30)
|
||||
- 修复 由 0.0.10 版本引发的 returnType 属性失效的问题
|
||||
## 0.0.10(2021-06-29)
|
||||
- 优化 文件上传后进度条消失时机
|
||||
## 0.0.9(2021-06-29)
|
||||
- 修复 在uni-forms 中,删除文件 ,获取的值不对的Bug
|
||||
## 0.0.8(2021-06-15)
|
||||
- 修复 删除文件时无法触发 v-model 的Bug
|
||||
## 0.0.7(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 0.0.6(2021-04-09)
|
||||
- 修复 选择的文件非 file-extname 字段指定的扩展名报错的Bug
|
||||
## 0.0.5(2021-04-09)
|
||||
- 优化 更新组件示例
|
||||
## 0.0.4(2021-04-09)
|
||||
- 优化 file-extname 字段支持字符串写法,多个扩展名需要用逗号分隔
|
||||
## 0.0.3(2021-02-05)
|
||||
- 调整为uni_modules目录规范
|
||||
- 修复 微信小程序不指定 fileExtname 属性选择失败的Bug
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user