新增插件

master
于肖磊 2025-11-26 16:51:13 +08:00
parent b51cb05497
commit 3f294b0b7a
13 changed files with 5646 additions and 0 deletions

View File

@ -0,0 +1,106 @@
<template>
<!-- #ifdef APP-NVUE -->
<text :style="[{ color: color, 'font-size': iconSize, 'font-family': 'iconfont' }]" class="icon-font" :class="'cr-' + type" @tap="_onClick">{{ iconfontCode }}</text>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<text :style="{ color: color, 'font-size': iconSize }" class="icon-font" :class="'icon-' + name + ' cr-' + type" @tap="_onClick"></text>
<!-- #endif -->
</template>
<script>
//#ifdef APP-NVUE
import dataIconfont from '@/static/icon/iconfont.json';
import iconUrl from '@/static/icon/iconfont.ttf';
//#endif
/**
* Icons 图标
* @description 用于展示 icons 图标
* @property {Number} size 图标大小
* @property {String} name 图标图案参考示例
* @property {String} color 图标颜色
* @property {String} type 图标常规颜色 info / primary / error/ warning / success
*/
export default {
name: 'u-icon',
props: {
name: {
type: String,
default: '',
},
type: {
type: String,
default: 'info',
},
size: {
type: [Number, String],
default: 32,
},
color: {
type: String,
default: '',
}
},
computed: {
//#region
iconSize() {
return this.getVal(this.size);
},
//#ifdef APP-NVUE
// appnvue
iconfontCode() {
const code = this.dataIconfont.glyphs.find(v => v.font_class === this.name);
if (code != null) {
return unescape(`%u${code.unicode}`);
}
return ''
}
//#endif
//#endregion
},
//#ifdef APP-NVUE
data() {
return {
dataIconfont: dataIconfont,
iconUrl: iconUrl
}
},
mounted() {
const domModule = weex.requireModule("dom");
domModule.addRule('fontFace', {
'fontFamily': 'iconfont',
'src': `url('${this.iconUrl}')`
})
},
//#endif
methods: {
//#region
getVal(val) {
const reg = /^[0-9]*$/g;
return typeof val === 'number' || reg.test(val) ? val.toString() + 'rpx' : val;
},
//#endregion
//#region
_onClick() {
this.$emit('click');
}
//#endregion
}
}
</script>
<style lang="scss">
/* #ifndef APP-NVUE */
@import url('@/static/icon/iconfont.css');
/* #ifndef MP-WEIXIN */
@font-face {
font-family: "iconfont";
src: url('@/static/icon/iconfont.ttf');
}
/* #endif */
/* #endif */
.icon-font {
font-family: 'iconfont' !important;
text-decoration: none;
text-align: center;
}
</style>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,45 @@
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
// this.$once('hook:beforeDestroy', () => {
// document.removeEventListener('keyup', listener)
// })
},
render: () => { }
}
// #endif

View File

@ -0,0 +1,698 @@
<template>
<view v-if="showPopup" class="uni-popup" :class="popupstyle + (isDesktop ? ' fixforpc-z-index' : '')">
<view @touchstart="touchstart">
<u-transition key="1" v-if="maskShow" name="mask" mode="fade" :customStyle="maskClass" :duration="duration" :show="showTrans" @click="onTap" />
<u-transition key="2" :mode="ani" name="content" :customStyle="transClass" :duration="duration" :show="showTrans">
<view class="re" :style="'border-radius:' + round + 'px'">
<view v-if="closeType == 'icon' && closeable" class="popup-close pa-14 box-border-box" :class="closeIconPos" :style="closeIconStyle" @tap="close">
<u-icon :name="closeIcon" :type="closeIconType" :size="closeIconSize + 'rpx'"></u-icon>
</view>
<view v-if="closeType == 'text' && closeable" class="flex-row jc-sb align-c w abs top-0 pa-14 z-i">
<text class="cr-info" @click="close"></text>
<view class="re">
<text v-if="isCustomBtn" class="inline-block" :style="customBtnStyle" @click="custom_change">{{ customBtnText }}</text>
<text class="cr-primary" @click="comfirm"></text>
</view>
</view>
<view v-if="title != ''" class="re">
<view class="popup-close pa-14 box-border-box" :class="titleBorder ? 'br-b-e' : ''">
<view v-if="title != ''" class="title">
<text class="fw">{{ title }}222</text>
</view>
</view>
</view>
<view class="uni-popup__wrapper radius-lg" :style="{ backgroundColor: bg, maxHeight: height }" :class="[popupstyle]" @click="clear">
<slot />
</view>
</view>
</u-transition>
</view>
<!-- #ifdef H5 -->
<keypress v-if="maskShow" @esc="onTap" />
<!-- #endif -->
</view>
</template>
<script>
// #ifdef H5
import keypress from './keypress.js';
// #endif
/**
* PopUp 弹出层
* @description 弹出层组件为了解决遮罩弹层的问题
* @property {String} type = [top|center|bottom|left|right|message|dialog|share] 弹出方式
* @value top 顶部弹出
* @value center 中间弹出
* @value bottom 底部弹出
* @value left 左侧弹出
* @value right 右侧弹出
* @value message 消息提示
* @value dialog 对话框
* @value share 底部分享示例
* @property {Boolean} animation = [true|false] 是否开启动画
* @property {Boolean} maskClick = [true|false] 蒙版点击是否关闭弹窗(废弃)
* @property {Boolean} isMaskClick = [true|false] 蒙版点击是否关闭弹窗
* @property {String} backgroundColor 主窗口背景色
* @property {String} maskBackgroundColor 蒙版颜色
* @property {Boolean} safeArea 是否适配底部安全区
* @property {Boolean} closeable Boolean | 是否显示关闭图标默认true
* @property {String} closeIcon String | 关闭iconfont默认close ---还有close-o或者其他自定义icon
* @property {String} closeIconType String | 关闭颜色默认6 参照组件u-icon
* @property {String} closeIconPos = [top-left|top-right|top-center|bottom-left|bottom-right|bottom-center] String | 关闭图标位置
* @value top-left 左上角
* @value top-right 右上角
* @value bottom-left 左下角
* @value bottom-right 右下角
* @property {Number} closeIconSize Number | 关闭图标大小默认32(单位rpx)
* @property {Boolean} closeType Boolean | 显示关闭图标还是文本(文本包含确定和取消)默认icon,------icon,text,
* @property {Boolean} isCustomBtn Boolean | 自定义按钮
* @property {String} customBtnText String | 自定义按钮文本
* @property {Object} customBtnStyle Object | 自定义按钮样式
* @property {String} title String | 弹窗标题
* @property {String} titleBorder String | 弹窗底部边框
* @property {String} height String | 弹窗内容高度
* @property {String} round String | 弹窗圆角
* @property {Boolean} isConfirmClose Boolean | 是否点击确认按钮时主动关闭弹窗
* @event {Function} change 打开关闭弹窗触发e={show: false}
* @event {Function} maskClick 点击遮罩触发
* @event {Function} callBack 确定按钮回调方法
* @event {Function} custom_change 自定义按钮回调方法
*/
export default {
name: 'uniPopup',
components: {
// #ifdef H5
keypress,
// #endif
},
emits: ['change', 'maskClick', 'callBack', 'callBackCustom'],
props: {
//
animation: {
type: Boolean,
default: true,
},
// top: bottomcenter
// message: ; dialog :
type: {
type: String,
default: 'bottom',
},
// maskClick
isMaskClick: {
type: Boolean,
default: null,
},
// TODO 2 使 isMaskClick
maskClick: {
type: Boolean,
default: null,
},
backgroundColor: {
type: String,
default: 'none',
},
safeArea: {
type: Boolean,
default: true,
},
maskBackgroundColor: {
type: String,
default: 'rgba(0, 0, 0, 0.4)',
},
//
closeable: {
type: Boolean,
default: true,
},
// icon
closeIcon: {
type: String,
default: 'close-line',
},
// icon
closeIconType: {
type: String,
default: '6',
},
//
closeIconSize: {
type: Number,
default: 32,
},
// top-lefttop-rightbottom-leftbottom-righttop-center bottom-center
closeIconPos: {
type: String,
validator: (value) => ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'top-center', 'bottom-center'].includes(value),
},
closeType: {
type: String,
default: 'icon',
},
isCustomBtn: {
type: Boolean,
default: false,
},
customBtnText: {
type: String,
default: '自定义',
},
customBtnStyle: {
type: String,
default: '',
},
//
title: {
type: String,
default: '',
},
titleBorder: {
type: Boolean,
default: false,
},
height: {
type: String,
default: 'auto',
},
round: {
type: Number,
default: 0,
},
isConfirmClose: {
type: Boolean,
default: true,
},
},
watch: {
/**
* 监听type类型
*/
type: {
handler: function (type) {
if (!this.config[type]) return;
this[this.config[type]](true);
},
immediate: true,
},
isDesktop: {
handler: function (newVal) {
if (!this.config[newVal]) return;
this[this.config[this.type]](true);
},
immediate: true,
},
/**
* 监听遮罩是否可点击
* @param {Object} val
*/
maskClick: {
handler: function (val) {
this.mkclick = val;
},
immediate: true,
},
isMaskClick: {
handler: function (val) {
this.mkclick = val;
},
immediate: true,
},
// H5
showPopup(show) {
// #ifdef H5
// fix by mehaotian h5 穿
document.getElementsByTagName('body')[0].style.overflow = show ? 'hidden' : 'visible';
// #endif
},
},
data() {
return {
duration: 300,
ani: [],
showPopup: false,
showTrans: false,
popupWidth: 0,
popupHeight: 0,
config: {
top: 'top',
bottom: 'bottom',
center: 'center',
left: 'left',
right: 'right',
message: 'top',
dialog: 'center',
share: 'bottom',
},
maskClass: {
position: 'fixed',
bottom: 0,
top: '-1000%',
left: 0,
right: 0,
backgroundColor: 'rgba(0, 0, 0, 0.4)',
},
transClass: {
position: 'fixed',
left: 0,
right: 0,
},
maskShow: true,
mkclick: true,
popupstyle: this.isDesktop ? 'fixforpc-top' : 'top',
};
},
computed: {
isDesktop() {
return this.popupWidth >= 500 && this.popupHeight >= 500;
},
bg() {
if (this.backgroundColor === '' || this.backgroundColor === 'none') {
return '#fff';
}
return this.backgroundColor;
},
// transition
transitionFixedStyle() {
let style = {
backgroundColor: this.bgColor,
zIndex: this.zIndex,
};
switch (this.mode) {
case 'top':
style['borderBottomLeftRadius'] = this.round + 'px';
style['borderBottomRightRadius'] = this.round + 'px';
break;
case 'bottom':
style['borderTopLeftRadius'] = this.round + 'px';
style['borderTopRightRadius'] = this.round + 'px';
break;
case 'left':
style['borderTopRightRadius'] = this.round + 'px';
style['borderBottomRightRadius'] = this.round + 'px';
break;
case 'right':
style['borderTopLeftRadius'] = this.round + 'px';
style['borderBottomLeftRadius'] = this.round + 'px';
break;
case 'center':
style['borderRadius'] = this.round + 'px';
break;
}
return style;
},
// Icon
closeIconStyle() {
let style = {
zIndex: 999,
};
let posSize = 28;
switch (this.closeIconPos) {
case 'top-left':
style['position'] = 'absolute';
style['top'] = '0rpx';
style['left'] = '0rpx';
break;
case 'top-right':
style['position'] = 'absolute';
style['top'] = '0rpx';
style['right'] = '0rpx';
break;
case 'top-center':
style['position'] = 'absolute';
style['top'] = -(posSize * 4) + 'rpx';
style['left'] = '0rpx';
style['right'] = '0rpx';
break;
case 'bottom-left':
style['position'] = 'absolute';
style['bottom'] = '0rpx';
style['left'] = '0rpx';
break;
case 'bottom-right':
style['position'] = 'absolute';
style['bottom'] = '0rpx';
style['right'] = '0rpx';
break;
case 'bottom-center':
style['position'] = 'absolute';
style['bottom'] = -(posSize * 4) + 'rpx';
style['left'] = '0rpx';
style['right'] = '0rpx';
break;
default:
if (this.title != '') {
style['position'] = 'absolute';
style['top'] = '0rpx';
style['right'] = '0rpx';
}
break;
}
return style;
},
},
mounted() {
const fixSize = () => {
const { windowWidth, windowHeight, windowTop, safeArea, screenHeight, safeAreaInsets } = uni.getSystemInfoSync();
this.popupWidth = windowWidth;
this.popupHeight = windowHeight + (windowTop || 0);
// TODO fix by mehaotian ,ios app ios
if (safeArea && this.safeArea) {
// #ifdef MP-WEIXIN
this.safeAreaInsets = screenHeight - safeArea.bottom;
// #endif
// #ifndef MP-WEIXIN
this.safeAreaInsets = safeAreaInsets.bottom;
// #endif
} else {
this.safeAreaInsets = 0;
}
};
fixSize();
// #ifdef H5
// window.addEventListener('resize', fixSize)
// this.$once('hook:beforeDestroy', () => {
// window.removeEventListener('resize', fixSize)
// })
// #endif
// #ifdef APP-NVUE
// nvue
const data = uni.getWindowInfo();
this.maskClass.top = - data.windowHeight + 'px;';
// #endif
},
// #ifndef VUE3
// TODO vue2
destroyed() {
this.setH5Visible();
},
// #endif
// #ifdef VUE3
// TODO vue3
unmounted() {
this.setH5Visible();
},
// #endif
created() {
// this.mkclick = this.isMaskClick || this.maskClick
if (this.isMaskClick === null && this.maskClick === null) {
this.mkclick = true;
} else {
this.mkclick = this.isMaskClick != null ? this.isMaskClick : this.maskClick;
}
if (this.animation) {
this.duration = 300;
} else {
this.duration = 0;
}
// TODO message
this.messageChild = null;
// TODO
this.clearPropagation = false;
this.maskClass.backgroundColor = this.maskBackgroundColor;
},
methods: {
setH5Visible() {
// #ifdef H5
// fix by mehaotian h5 穿
document.getElementsByTagName('body')[0].style.overflow = 'visible';
// #endif
},
/**
* 公用方法不显示遮罩层
*/
closeMask() {
this.maskShow = false;
},
/**
* 公用方法遮罩层禁止点击
*/
disableMask() {
this.mkclick = false;
},
// TODO nvue
clear(e) {
// #ifndef APP-NVUE
e.stopPropagation();
// #endif
this.clearPropagation = true;
},
open(direction) {
// fix by mehaotian
if (this.showPopup) {
clearTimeout(this.timer);
this.showPopup = false;
}
let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share'];
if (!(direction && innerType.indexOf(direction) != -1)) {
direction = this.type;
}
if (!this.config[direction]) {
return;
}
this[this.config[direction]]();
this.$emit('change', {
show: true,
type: direction,
});
},
close(type) {
this.showTrans = false;
this.$emit('change', {
show: false,
type: this.type,
});
clearTimeout(this.timer);
// //
// this.customOpen && this.customClose()
this.timer = setTimeout(() => {
this.showPopup = false;
}, 300);
},
// TODO
touchstart() {
this.clearPropagation = false;
},
onTap() {
if (this.clearPropagation) {
// fix by mehaotian nvue
this.clearPropagation = false;
return;
}
this.$emit('maskClick');
if (!this.mkclick) return;
this.close();
},
/**
* 顶部弹出样式处理
*/
top(type) {
this.popupstyle = this.isDesktop ? 'fixforpc-top' : 'top';
this.ani = ['slide-top'];
this.transClass = {
position: 'fixed',
left: 0,
right: 0,
backgroundColor: this.bg,
};
// TODO type
if (type) return;
this.showPopup = true;
this.showTrans = true;
this.$nextTick(() => {
if (this.messageChild && this.type === 'message') {
this.messageChild.timerClose();
}
});
},
/**
* 底部弹出样式处理
*/
bottom(type) {
this.popupstyle = 'bottom';
this.ani = ['slide-bottom'];
this.transClass = {
position: 'fixed',
left: 0,
right: 0,
bottom: 0,
paddingBottom: this.safeAreaInsets + 'px',
backgroundColor: this.bg,
};
// TODO type
if (type) return;
this.showPopup = true;
this.showTrans = true;
},
/**
* 中间弹出样式处理
*/
center(type) {
this.popupstyle = 'center';
this.ani = ['zoom-out', 'fade'];
this.transClass = {
position: 'fixed',
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column',
/* #endif */
bottom: 0,
left: 0,
right: 0,
top: 0,
justifyContent: 'center',
alignItems: 'center',
};
// TODO type
if (type) return;
this.showPopup = true;
this.showTrans = true;
},
left(type) {
this.popupstyle = 'left';
this.ani = ['slide-left'];
this.transClass = {
position: 'fixed',
left: 0,
bottom: 0,
top: 0,
backgroundColor: this.bg,
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column',
/* #endif */
};
// TODO type
if (type) return;
this.showPopup = true;
this.showTrans = true;
},
right(type) {
this.popupstyle = 'right';
this.ani = ['slide-right'];
this.transClass = {
position: 'fixed',
bottom: 0,
right: 0,
top: 0,
backgroundColor: this.bg,
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column',
/* #endif */
};
// TODO type
if (type) return;
this.showPopup = true;
this.showTrans = true;
},
comfirm() {
if (this.isConfirmClose) {
this.close();
}
this.$emit('callBack');
},
//
custom_change() {
if (this.isConfirmClose) {
this.close();
}
this.$emit('callBackCustom');
},
},
};
</script>
<style lang="scss">
.uni-popup {
position: fixed;
/* #ifndef APP-NVUE */
z-index: 99;
/* #endif */
&.top,
&.left,
&.right {
/* #ifdef H5 */
top: var(--window-top);
/* #endif */
/* #ifndef H5 */
top: 0;
/* #endif */
}
&.top {
.popup-close {
display: none;
}
}
&.center,
&.bottom,
&.left {
.popup-close {
display: flex;
justify-content: flex-end;
}
}
&.right {
.popup-close {
display: flex;
justify-content: flex-start;
}
}
.uni-popup__wrapper {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
position: relative;
overflow-y: auto;
/* iphonex 等安全区设置,底部安全区适配 */
/* #ifndef APP-NVUE */
// padding-bottom: constant(safe-area-inset-bottom);
// padding-bottom: env(safe-area-inset-bottom);
/* #endif */
&.left,
&.right {
/* #ifdef H5 */
padding-top: var(--window-top);
/* #endif */
/* #ifndef H5 */
padding-top: 0;
/* #endif */
flex: 1;
}
}
.popup-close {
padding: 28rpx;
&.top-center,
&.bottom-center {
justify-content: center;
}
}
.title {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
}
.fixforpc-z-index {
/* #ifndef APP-NVUE */
z-index: 999;
/* #endif */
}
.fixforpc-top {
top: 0;
}
</style>

View File

@ -0,0 +1,133 @@
// const defaultOption = {
// duration: 300,
// timingFunction: 'linear',
// delay: 0,
// transformOrigin: '50% 50% 0'
// }
// #ifdef APP-NVUE
const nvueAnimation = uni.requireNativePlugin('animation')
// #endif
class MPAnimation {
constructor(options, _this) {
this.options = options
// #ifndef APP-NVUE
// 在iOS10+QQ小程序平台下传给原生的对象一定是个普通对象而不是Proxy对象否则会报parameter should be Object instead of ProxyObject的错误
this.animation = uni.createAnimation({
...options
})
//#endif
this.currentStepAnimates = {}
this.next = 0
this.$ = _this
}
_nvuePushAnimates (type, args) {
let aniObj = this.currentStepAnimates[this.next]
let styles = {}
if (!aniObj) {
styles = {
styles: {},
config: {}
}
} else {
styles = aniObj
}
if (animateTypes1.includes(type)) {
if (!styles.styles.transform) {
styles.styles.transform = ''
}
let unit = ''
if (type === 'rotate') {
unit = 'deg'
}
styles.styles.transform += `${type}(${args + unit}) `
} else {
styles.styles[type] = `${args}`
}
this.currentStepAnimates[this.next] = styles
}
_animateRun (styles = {}, config = {}) {
let ref = this.$.$refs['ani'].ref
if (!ref) return
return new Promise((resolve, reject) => {
nvueAnimation.transition(ref, {
styles,
...config
}, res => {
resolve()
})
})
}
_nvueNextAnimate (animates, step = 0, fn) {
let obj = animates[step]
if (obj) {
let {
styles,
config
} = obj
this._animateRun(styles, config).then(() => {
step += 1
this._nvueNextAnimate(animates, step, fn)
})
} else {
this.currentStepAnimates = {}
typeof fn === 'function' && fn()
this.isEnd = true
}
}
step (config = {}) {
// #ifndef APP-NVUE
this.animation.step(config)
// #endif
// #ifdef APP-NVUE
this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
this.next++
// #endif
return this
}
run (fn) {
// #ifndef APP-NVUE
this.$.animationData = this.animation.export()
this.$.timer = setTimeout(() => {
typeof fn === 'function' && fn()
}, this.$.durationTime)
// #endif
// #ifdef APP-NVUE
this.isEnd = false
let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref
if (!ref) return
this._nvueNextAnimate(this.currentStepAnimates, 0, fn)
this.next = 0
// #endif
}
}
const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
'translateZ'
]
const animateTypes2 = ['opacity', 'backgroundColor']
const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']
animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
MPAnimation.prototype[type] = function (...args) {
// #ifndef APP-NVUE
this.animation[type](...args)
// #endif
// #ifdef APP-NVUE
this._nvuePushAnimates(type, args)
// #endif
return this
}
})
export function createAnimation (option, _this) {
if (!_this) return
clearTimeout(_this.timer)
return new MPAnimation(option, _this)
}

View File

@ -0,0 +1,395 @@
<template>
<!-- #ifndef APP-NVUE -->
<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick">
<slot></slot>
</view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick">
<slot></slot>
</view>
<!-- #endif -->
</template>
<script>
import { createAnimation } from './createAnimation';
/**
* transition 动画组件
* @description
* @tutorial
* @property {Boolean} show 控制组件显示或关闭 默认 false
* @property {Array | String} mode 内置过渡动画类型 默认 'fade'
* @value fade 渐隐渐出过渡
* @value slide-top 由上至下过渡
* @value slide-bottom 由下至上过渡
* @value slide-left 由左至右过渡
* @value slide-right 由右至左过渡
* @value zoom-in 由小到大过渡
* @value zoom-out 由大到小过渡
* @property {String | Number} duration 动画的执行时间单位ms 默认 300
* @property {Object} customStyle 组件样式 css 样式注意带-连接符的属性需要使用小驼峰写法如`backgroundColor:red`
* @property {String} timingFunction 使用的动画过渡函数 默认 'ease-out'
* @property {String} customClass 自定义类名
* @event {Function} click 点击组件触发
* @event {Function} change 过渡动画结束时触发
* @example
*/
export default {
name: 'u-transition',
emits: ['click', 'change'],
props: {
//
show: {
type: Boolean,
default: false,
},
// 使
mode: {
type: [Array, String, null],
default() {
return 'fade';
},
},
// ms
duration: {
type: [String, Number],
default: 300,
},
// 使
timingFunction: {
type: String,
default: 'ease-out',
},
customStyle: {
type: Object,
default() {
return {};
},
},
customClass: {
type: String,
default: '',
},
// nvue uv-listcell使
cellChild: {
type: Boolean,
default: false,
},
},
data() {
return {
isShow: false,
transform: '',
opacity: 1,
animationData: {},
durationTime: 300,
config: {},
};
},
watch: {
show: {
handler(newVal) {
if (newVal) {
this.open();
} else {
// close,
if (this.isShow) {
this.close();
}
}
},
immediate: true,
},
},
computed: {
//
transformStyles() {
const style = {
transform: this.transform,
opacity: this.opacity,
...this.addStyle(this.customStyle),
'transition-duration': `${this.duration / 1000}s`,
};
return this.addStyle(style, 'string');
},
},
created() {
//
this.config = {
duration: this.duration,
timingFunction: this.timingFunction,
transformOrigin: '50% 50%',
delay: 0,
};
this.durationTime = this.duration;
},
methods: {
/**
* @description 样式转换
* 对象转字符串或者字符串转对象
* @param {object | string} customStyle 需要转换的目标
* @param {String} target 转换的目的object-转为对象string-转为字符串
* @returns {object|string}
*/
addStyle (customStyle, target = 'object') {
//
if (this.empty(customStyle) || typeof (customStyle) === 'object' && target === 'object' || target === 'string' &&
typeof (customStyle) === 'string') {
return customStyle
}
//
if (target === 'object') {
// (padding: 20px 0)
customStyle = this.trim(customStyle)
// ";"
const styleArray = customStyle.split(';')
const style = {}
//
for (let i = 0; i < styleArray.length; i++) {
// 'font-size:20px;color:red;'";"styleArray
if (styleArray[i]) {
const item = styleArray[i].split(':')
style[this.trim(item[0])] = this.trim(item[1])
}
}
return style
}
//
let string = ''
for (const i in customStyle) {
// 线css
const key = i.replace(/([A-Z])/g, '-$1').toLowerCase()
string += `${key}:${customStyle[i]};`
}
//
return this.trim(string)
},
/**
* 判断是否为空
*/
empty (value) {
switch (typeof value) {
case 'undefined':
return true
case 'string':
if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true
break
case 'boolean':
if (!value) return true
break
case 'number':
if (value === 0 || isNaN(value)) return true
break
case 'object':
if (value === null || value.length === 0) return true
for (const i in value) {
return false
}
return true
}
return false
},
/**
* @description 去除空格
* @param {String} str 需要去除空格的字符串
* @param {String} pos both(左右)|left|right|all 默认both
*/
trim (str, pos = 'both') {
str = String(str)
if (pos == 'both') {
return str.replace(/^\s+|\s+$/g, '')
}
if (pos == 'left') {
return str.replace(/^\s*/, '')
}
if (pos == 'right') {
return str.replace(/(\s*$)/g, '')
}
if (pos == 'all') {
return str.replace(/\s+/g, '')
}
return str
},
/**
* ref 触发 初始化动画
*/
init(obj = {}) {
if (obj.duration) {
this.durationTime = obj.duration;
}
this.animation = createAnimation(Object.assign(this.config, obj), this);
},
/**
* 点击组件触发回调
*/
onClick() {
this.$emit('click', {
detail: this.isShow,
});
},
/**
* ref 触发 动画分组
* @param {Object} obj
*/
step(obj, config = {}) {
if (!this.animation) return;
for (let i in obj) {
try {
if (typeof obj[i] === 'object') {
this.animation[i](...obj[i]);
} else {
this.animation[i](obj[i]);
}
} catch (e) {}
}
this.animation.step(config);
return this;
},
/**
* ref 触发 执行动画
*/
run(fn) {
if (!this.animation) return;
this.animation.run(fn);
},
//
open() {
clearTimeout(this.timer);
this.transform = '';
this.isShow = true;
let { opacity, transform } = this.styleInit(false);
if (typeof opacity != 'undefined') {
this.opacity = opacity;
}
this.transform = transform;
// nextTick wx
this.$nextTick(() => {
// TODO
this.timer = setTimeout(() => {
this.animation = createAnimation(this.config, this);
this.tranfromInit(false).step();
// #ifdef APP-NVUE
if (this.cellChild) {
this.opacity = 1;
} else {
this.animation.run();
}
// #endif
// #ifndef APP-NVUE
this.animation.run();
// #endif
// #ifdef VUE3
// #ifdef H5
this.opacity = 1;
// #endif
// #endif
this.$emit('change', {
detail: this.isShow,
});
// #ifdef H5
// #ifdef VUE3
this.transform = '';
// #endif
// #endif
}, 20);
});
},
//
close(type) {
if (!this.animation) return;
this.tranfromInit(true)
.step()
.run(() => {
this.isShow = false;
this.animationData = null;
this.animation = null;
let { opacity, transform } = this.styleInit(false);
this.opacity = opacity || 1;
this.transform = transform;
this.$emit('change', {
detail: this.isShow,
});
});
},
//
styleInit(type) {
let styles = {
transform: '',
};
let buildStyle = (type, mode) => {
if (mode === 'fade') {
styles.opacity = this.animationType(type)[mode];
} else {
styles.transform += this.animationType(type)[mode] + ' ';
}
};
if (typeof this.mode === 'string') {
buildStyle(type, this.mode);
} else {
this.mode.forEach((mode) => {
buildStyle(type, mode);
});
}
return styles;
},
//
tranfromInit(type) {
let buildTranfrom = (type, mode) => {
let aniNum = null;
if (mode === 'fade') {
aniNum = type ? 0 : 1;
} else {
aniNum = type ? '-100%' : '0';
if (mode === 'zoom-in') {
aniNum = type ? 0.8 : 1;
}
if (mode === 'zoom-out') {
aniNum = type ? 1.2 : 1;
}
if (mode === 'slide-right') {
aniNum = type ? '100%' : '0';
}
if (mode === 'slide-bottom') {
aniNum = type ? '100%' : '0';
}
}
this.animation[this.animationMode()[mode]](aniNum);
};
if (typeof this.mode === 'string') {
buildTranfrom(type, this.mode);
} else {
this.mode.forEach((mode) => {
buildTranfrom(type, mode);
});
}
return this.animation;
},
animationType(type) {
return {
fade: type ? 1 : 0,
'slide-top': `translateY(${type ? '0' : '-100%'})`,
'slide-right': `translateX(${type ? '0' : '100%'})`,
'slide-bottom': `translateY(${type ? '0' : '100%'})`,
'slide-left': `translateX(${type ? '0' : '-100%'})`,
'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`,
};
},
//
animationMode() {
return {
fade: 'opacity',
'slide-top': 'translateY',
'slide-right': 'translateX',
'slide-bottom': 'translateY',
'slide-left': 'translateX',
'zoom-in': 'scale',
'zoom-out': 'scale',
};
},
// 线
toLine(name) {
return name.replace(/([A-Z])/g, '-$1').toLowerCase();
},
},
};
</script>

View File

@ -0,0 +1,58 @@
<template>
<view :class="theme_view + ' bg-0 flex-row'" :style="'width:' + windowWidth + 'px;height:' + windowHeight + 'px;'">
<view class="flex-1">
<live-video src="http://live-pull-all.shopxo.vip/68f764013572f9240ca7ce6c/shopxo122.m3u8"></live-video>
</view>
<view class="live-content" :style="'width:' + windowWidth + 'px;height:' + windowHeight + 'px;'">
<live-content></live-content>
</view>
</view>
</template>
<script>
import liveVideo from './video.vue';
import liveContent from './components/live-content.vue';
const app = getApp();
export default {
components: {
liveVideo,
liveContent
},
data() {
return {
windowWidth: 0,
windowHeight: 0,
}
},
onLoad(params) {
// 调用公共事件方法
app.globalData.page_event_onload_handle(params);
// 设置参数
this.params = app.globalData.launch_params_handle(params);
},
onShow() {
// 调用公共事件方法
app.globalData.page_event_onshow_handle();
// 分享菜单处理
app.globalData.page_share_handle();
const data = uni.getWindowInfo();
this.windowWidth = data.windowWidth;
this.windowHeight = data.windowHeight;
},
methods: {
}
}
</script>
<style scoped>
.live-content {
position: absolute;
top: 0;
left: 0;
z-index: 9;
width: 100%;
height: 100%;
}
</style>

3628
static/icon/iconfont.json Normal file

File diff suppressed because it is too large Load Diff

BIN
static/icon/iconfont.ttf Normal file

Binary file not shown.

View File

@ -0,0 +1,2 @@
## 1.2.12023-07-25
更新节流函数不执行导致DOM不会逐渐消失问题

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,75 @@
{
"id": "like-button",
"displayName": "点赞冒泡效果 like-button",
"version": "1.2.1",
"description": "uni-app 点赞冒泡效果兼容nvue。",
"keywords": [
"like-button",
"点赞",
"冒泡"
],
"repository": "https://github.com/lnice/like-button",
"engines": {
"HBuilderX": "^3.1.0"
},
"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"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "y",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@ -0,0 +1,41 @@
# like-button
#### 使用方法:
**script** 中引用组件
```javascript
import likeButton from '@/components/like-button/like-button.vue'
export default {
components: {likeButton}
}
```
**template** 中引用组件
```javascript
<like-button></like-button>
```
#### 属性说明
| 属性名 | 类型 | 默认值 | 说明 |
| ------------ | ------------ | ------------ | ------------ |
| src | String | /static/logo.png | 点击按钮图片 |
| showImgs | Array | ['//xxx', '/static/logo.png', ] | 冒泡图片 |
| duration | Number | 5000 | 动画效果时间 |
| range | Number | 50 | 冒泡图片x轴摇摆幅度 |
| high | Number | 360 | 冒泡图片y轴飘出高度 |
| width | Number | 52 | 点赞图标宽度 |
| height | Number | 52 | 点赞图标高度 |
| imgWidth | Number | 52 | 冒泡图标宽度 |
| imgHeight | Number | 52 | 冒泡图标高度 |
| throttle | Number | 200 | 点击按钮 节流 |
| site | Array,Object | [30, 160] | 冒泡图片相对窗口x y坐标 |
| large | Number,Boolean | false | 缩放冒泡为true默认放大2 |
| alone | Boolean | true | 1.0.9-新增, DOM元素逐渐消失 |
#### 事件说明
| 事件名称 | 说明 | 返回值 |
| ------------ | ------------ | ------------ |
| handleClick | 点击按钮触发事件 | 冒泡元素id |
| finished | 动画执行完成回调 | - |
PS:使用定时器触发可用 this.$refs.likeButton.handleClick({timeStamp: Date.now()})。
---
### End