This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
type callbackFunType = (x:number)=>number
let kSplineTableSize = 11;
let kSampleStepSize = 1.0 / (kSplineTableSize - 1.0);
function A(aA1:number, aA2:number):number { return 1.0 - 3.0 * aA2 + 3.0 * aA1 }
function B(aA1:number, aA2:number):number { return 3.0 * aA2 - 6.0 * aA1 }
function C(aA1:number):number { return 3.0 * aA1 }
function calcBezier(aT:number, aA1:number, aA2:number):number { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT }
function getSlope(aT:number, aA1:number, aA2:number):number { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1) }
function binarySubdivide(aX:number, aA:number, aB:number, mX1:number, mX2:number):number {
let currentX = 0;
let currentT = 0;
let i = 0;
do {
currentT = aA + (aB - aA) / 2.0;
currentX = calcBezier(currentT, mX1, mX2) - aX;
if (currentX > 0.0) { aB = currentT; } else { aA = currentT; }
} while (Math.abs(currentX) > 0.0000001 && ++i < 10);
return currentT;
}
function newtonRaphsonIterate(aX:number, aGuessT:number, mX1:number, mX2:number):number {
let pat = aGuessT;
for (let i = 0; i < 4; ++i) {
let currentSlope = getSlope(aGuessT, mX1, mX2);
if (currentSlope == 0.0) { return aGuessT; }
let currentX = calcBezier(aGuessT, mX1, mX2) - aX;
pat -= currentX / currentSlope;
}
return pat;
}
function bezier(mX1:number, mY1:number, mX2:number, mY2:number):callbackFunType|null {
if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) { return null; }
let sampleValues:number[] = [];
if (mX1 != mY1 || mX2 != mY2) {
for (let i = 0; i < kSplineTableSize; ++i) {
sampleValues.push(calcBezier(i * kSampleStepSize, mX1, mX2))
}
}
function getTForX(aX:number):number {
let intervalStart = 0;
let currentSample = 1;
let lastSample = kSplineTableSize - 1;
for (; currentSample != lastSample && sampleValues[currentSample] <= aX; ++currentSample) {
intervalStart += kSampleStepSize;
}
--currentSample;
let dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]);
let guessForT:number = intervalStart + dist * kSampleStepSize;
let initialSlope = getSlope(guessForT, mX1, mX2);
if (initialSlope >= 0.001) {
return newtonRaphsonIterate(aX, guessForT, mX1, mX2);
} else if (initialSlope == 0.0) {
return guessForT;
}
return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2);
}
return function (x:number):number {
if (mX1 == mY1 && mX2 == mY2) { return x; }
if (x == 0 || x == 1) { return x; }
return calcBezier(getTForX(x), mY1, mY2);
}
}
export default bezier
+20
View File
@@ -0,0 +1,20 @@
import {xRequestHistoryType,X_REQUEST_PRIVATE_CALL_FUN_TYPE} from "../../interface.uts"
/**
* 内部组件私有
*/
export const xRequestCall = reactive({
history:[] as xRequestHistoryType[],
authPass:true,
hostUrl:"",
dev:false,
header:null,
/** 请求前是否显示loading遮罩 */
showLoadToast:true,
/** 请求成功后是否提示,否则不提示 */
showSuccessToast:true,
/** 出错时,是否显示提示,否则不提示 */
showErrorToast:true
} as X_REQUEST_PRIVATE_CALL_FUN_TYPE)
+850
View File
@@ -0,0 +1,850 @@
/**
* xAnimate
* @author tmzdy
* @copyright tmui4.0附带核心基类
* @version 1.0.0
* @description 通过nodeElement元素来控制属性动画变得更为简单方便
*
*/
import { XANIMATE_OPIONS } from "../../interface.uts"
import { getUid } from "./xCoreUtil.uts";
import { hexToRgb,getDefaultColor,rgbToHex } from "./xCoreColorUtil.uts";
import bezier from "./bezier.uts"
// #ifdef APP-ANDROID
import Choreographer from "android.view.Choreographer";
import FrameCallback from "android.view.Choreographer.FrameCallback";
// #endif
// #ifdef APP-IOS
// import * as UIKit from 'UIKit';
// #endif
type ATTRGERTS = {
from : string,
to : string,
unit : string,
now : string,
progress : number,
name : string
}
type callbackFunType = (x:number)=>number
type easingFunType = {
x:number,
y:number,
m:number,
n:number,
}
export class xAnimate {
easingList = new Map<string,number[]>([
["linear",[0.250, 0.250, 0.750, 0.750]],
["ease",[0.250, 0.100, 0.250, 1.000]],
["easeIn",[0.420, 0.000, 1.000, 1.000]],
["easeOut",[0.000, 0.000, 0.580, 1.000]],
["easeInOut",[0.420, 0.000, 0.580, 1.000]],
["easeInQuad", [0.550, 0.085, 0.680, 0.530]],
["easeOutQuad", [0.250, 0.460, 0.450, 0.940]],
["easeInOutQuad", [0.455, 0.030, 0.515, 0.955]],
["easeInCubic", [0.550, 0.055, 0.675, 0.190]],
["easeOutCubic", [0.215, 0.610, 0.355, 1.000]],
["easeInOutCubic", [0.645, 0.045, 0.355, 1.000]],
["easeInQuart", [0.895, 0.030, 0.685, 0.220]],
["easeOutQuart", [0.165, 0.840, 0.440, 1.000]],
["easeInOutQuart", [0.770, 0.000, 0.175, 1.000]],
["easeInQuint", [0.755, 0.050, 0.855, 0.060]],
["easeOutQuint", [0.230, 1.000, 0.320, 1.000]],
["easeInOutQuint", [0.860, 0.000, 0.070, 1.000]],
["easeInSine", [0.470, 0.000, 0.745, 0.715]],
["easeOutSine", [0.390, 0.575, 0.565, 1.000]],
["easeInOutSine", [0.445, 0.050, 0.550, 0.950]],
["easeInExpo", [0.950, 0.050, 0.795, 0.035]],
["easeOutExpo", [0.190, 1.000, 0.220, 1.000]],
["easeInOutExpo", [1.000, 0.000, 0.000, 1.000]],
["easeInCirc", [0.600, 0.040, 0.980, 0.335]],
["easeOutCirc", [0.075, 0.820, 0.165, 1.000]],
["easeInOutBack", [0.680, -0.550, 0.265, 1.550]],
]);
private tid = null as null | number;
element = null as null|UniElement;
// 当前播放的动画函数
timingFunction = 'linear';
//当前设置的播放时间
duration = 500;
//元素id
ele = "";
// 当前是否播放中
running = false
//当前是否暂停中
pauseing = false
//当前的播放进度
progress = 0
reverse = false;
//设置true时,loop>1播放时会来回播放动画,而不是从起始开始而是
// 始-终-始这样的模式播放。
tyty = false;
_tyty = false;
// 循环播放次数-1表示无限循环.
loop = 1
// 播放计数
private _loop = 0
// 是否停止动画。
private _isStopping = true
private attrIndex = 0;
private completeCallBack = () => { }
private startCallBack = () => { }
private doCallBack = (propress:number) => { }
private tagetsAttr = [] as ATTRGERTS[]
private startTime = 0
private easing = null as null | callbackFunType;
/** 是否让动画顺序挨过按attr添加的属性顺序执行动画,而不是统一一起执行。 */
private isDescPlay = false
private enterCallFun = ()=>{}
// #ifdef APP-ANDROID
private ChoreographerDemo = null as Choreographer|null
private FrameCallbackCallFun = null as FrameCallback|null
// #endif
// #ifdef APP-IOS
// private CADisplayLink = null as UIKit.CADisplayLink|null
private dhid = 0
// #endif
// #ifdef WEB || APP-IOS
private canFrameIds:number|null = null
// #endif
/**
* 创建xAnimate动画实例
* @param ele 元素UniElement
* @param options 动画参数 XANIMATE_OPIONS
*/
constructor(ele:UniElement|null,options : XANIMATE_OPIONS) {
// this.element = uni.getElementById(options.ele);
this.element = ele;
this.duration = options.duration == null?this.duration:options.duration!
this.loop = options.loop == null?this.loop:options.loop!
this.tyty = options.tyty == null?this.tyty:options.tyty!
this.isDescPlay = options.isDescPlay == null?this.isDescPlay:options.isDescPlay!
let easingName = options.timingFunction == null?'linear':options.timingFunction!
let ecall = this.easingList.get(easingName);
if(ecall!=null){
let ecallps = ecall!
this.easing = bezier(ecallps[0],ecallps[1],ecallps[2],ecallps[3]);
}
if(options.bezier!=null){
let ecallps = options.bezier!
this.easing = bezier(ecallps[0],ecallps[1],ecallps[2],ecallps[3]);
}
if(options.complete!=null){
this.completeCallBack = options.complete!
}
if(options.start!=null){
this.startCallBack = options.start!
}
if(options.frame!=null){
this.doCallBack = options.frame!
}
}
private getUnit(n ?: string) : string {
if (n == null) return 'px';
let unit = n.replace(/[\d|\-|\+]/g, '');
if(unit=="."){
unit = ""
}
return unit;
}
/**
* 添加自定义动画
*/
addTimingFunction(name:string,nubs:number[]){
this.easingList.set(name,nubs)
}
/**
* 设置动画反转状态
*/
setAniReverse(n:boolean|null=null){
if(n!=null){
this.reverse = n!
}else{
this.reverse = !this.reverse
}
}
/**
* 设置播放动画的次数
*/
setLoops(n:number|null = null){
if(n!=null){
this.loop = n!;
}
}
/**
* 设置单次动画播放的持续时间
*/
setDurations(n:number|null = null){
if(n!=null){
this.duration = n!;
}
}
/**
* 连续播放的模式切换
*/
setTytys(n:boolean|null = null){
if(n!=null){
this.tyty = n!;
}
}
/**
* 添加动画属性
* @param {string} name 属性名,目前支持官方的css属性,相同的name会被覆盖
* @param {string} from 起始值,允许数字字符,带单位2px,20%这样的,但必须和to相同单位
* @param {string} to 结束值,允许数字字符,带单位2px,20%这样的,但必须和from相同单位
* @param {boolean} only 是否让其是唯一性,重复添加相同的name会被最后一次替换。
*/
attr(name:string,from : string, to : string ,only:boolean = true):xAnimate {
let unit = this.isColorStyle(name)?'':this.getUnit(from);
let from_n = this.isColorStyle(name)?getDefaultColor(from):parseFloat(from).toString()
let to_n = this.isColorStyle(name)?getDefaultColor(to):parseFloat(to).toString()
let index = this.tagetsAttr.findIndex((item:ATTRGERTS):boolean => item.name == name);
if(!only){
index=-1;
}
if(index==-1){
this.tagetsAttr.push({
from: from_n.toString(),
to: to_n.toString(),
unit,
progress: 0,
now: from_n,
name: name
} as ATTRGERTS)
}else{
this.tagetsAttr[index] = {
from: from_n.toString(),
to: to_n.toString(),
unit,
progress: 0,
now: from_n,
name: name
} as ATTRGERTS
}
return this;
}
private interpolate(startValue:number, endValue:number, progress:number):number {
return startValue + (endValue - startValue) * progress;
}
private isColorStyle(val:string):boolean{
return val.indexOf('background')>-1||val.indexOf('color')>-1;
}
private _setAttr(name:string,current:number,unit:string,progress:number,item:ATTRGERTS){
if(this.element==null) return
if(name=='scaleX'){
this.element!.style!.setProperty("transform",`scaleX(${current})`)
}else if(name=='scaleY'){
this.element!.style!.setProperty("transform",`scaleY(${current})`)
}else if(name=='scale'){
this.element!.style!.setProperty("transform",`scale(${current})`)
}else if(name=='rotateX'){
this.element!.style!.setProperty("transform",`rotateX(${(current).toString()+unit})`)
}else if(name=='rotateY'){
this.element!.style!.setProperty("transform",`rotateY(${(current).toString()+unit})`)
}else if(name=='rotate'){
this.element!.style!.setProperty("transform",`rotate(${(current).toString()+unit})`)
}else if(name=='translateX'){
this.element!.style!.setProperty("transform",`translateX(${(current).toString()+unit})`)
}else if(name=='translateY'){
this.element!.style!.setProperty("transform",`translateY(${(current).toString()+unit})`)
}else if(name=='translate'){
this.element!.style!.setProperty("transform",`translate(${(current).toString()+unit},${(current).toString()+unit})`)
}else if(this.isColorStyle(name)){
let startRgba = hexToRgb(item.from)
let dndRgba = hexToRgb(item.to)
let r = this.interpolate(startRgba.getNumber('r')!,dndRgba.getNumber('r')!,progress);
let g = this.interpolate(startRgba.getNumber('g')!,dndRgba.getNumber('g')!,progress);
let b = this.interpolate(startRgba.getNumber('b')!,dndRgba.getNumber('b')!,progress);
let a = this.interpolate(startRgba.getNumber('a')!,dndRgba.getNumber('a')!,progress);
this.element!.style!
.setProperty(name,`rgba(${r.toFixed(0)},${g.toFixed(0)},${b.toFixed(0)},${a.toFixed(1)})`)
}else{
this.element!.style!.setProperty(name,current.toFixed(2)+unit)
}
}
private _run_web() {
// #ifdef H5 || APP-IOS || APP-HARMONY
let _this = this;
_this.startTime = 0
if(_this.canFrameIds!=null){
cancelAnimationFrame(_this.canFrameIds)
}
function run(){
if (_this.startTime<=0) {
_this.startTime = Date.now(); // 记录动画开始时间
}
const progress = Math.min((Date.now() - _this.startTime) / _this.duration + _this.progress, 1); // 计算当前进度
if(_this.element!=null){
if(!_this.isDescPlay){
for(let i=0;i<_this.tagetsAttr.length;i++){
let item = _this.tagetsAttr[i]
item.progress = progress
if(!_this.isColorStyle(item.name)){
let fromN = parseFloat(item.from)
let toN = parseFloat(item.to)
let easeInt = 1
if(_this.easing!=null){
let eas = _this.easing!
easeInt = eas(progress)
}
let current = fromN + (toN - fromN) * (easeInt==1?progress:easeInt); // 根据进度计算当前值
if(_this.reverse||_this._tyty ){
current = toN + (fromN - toN) * (easeInt==1?progress:easeInt);
}
if(_this.element!=null){
_this._setAttr(item.name,current,item.unit,progress,item)
}
}else{
if(_this.element!=null){
_this._setAttr(item.name,0,item.unit,progress,item)
}
}
}
}else{
if(_this.attrIndex<_this.tagetsAttr.length){
let item = _this.tagetsAttr[_this.attrIndex]
item.progress = progress
if(!_this.isColorStyle(item.name)){
let fromN = parseFloat(item.from)
let toN = parseFloat(item.to)
let easeInt = 1
if(_this.easing!=null){
let eas = _this.easing!
easeInt = eas(progress)
}
let current = fromN + (toN - fromN) * (easeInt==1?progress:easeInt); // 根据进度计算当前值
if(_this.reverse||_this._tyty ){
current = toN + (fromN - toN) * (easeInt==1?progress:easeInt);
}
if(_this.element!=null){
_this._setAttr(item.name,current,item.unit,progress,item)
}
}else{
if(_this.element!=null){
_this._setAttr(item.name,0,item.unit,progress,item)
}
}
}
}
}
if(_this.pauseing){
if(_this.canFrameIds!=null){
cancelAnimationFrame(_this.canFrameIds)
}
_this.running = false
console.log(_this.pauseing,"动画暂停")
_this.progress = progress
return;
}
// console.log(_this.running,"动画结束")
if(progress>=1||_this._isStopping){
if(_this.isDescPlay&&_this.attrIndex<_this.tagetsAttr.length){
_this.attrIndex +=1;
_this.progress = 0
_this._run_web();
return;
}
if(_this.canFrameIds!=null) {
cancelAnimationFrame(_this.canFrameIds)
}
_this.attrIndex = 0;
_this.progress = 0
if(_this.tyty){
_this._tyty = !_this._tyty
}
if(_this.loop==-1){
_this._run_web();
return;
}else{
_this._loop+=1;
if(_this._loop<_this.loop){
_this._run_web();
return;
}
}
_this.running = false
_this.completeCallBack()
return;
}
if (progress < 1 && _this.running) {
_this.doCallBack(progress)
_this.canFrameIds = requestAnimationFrame(run)
}
}
_this.startCallBack()
run()
// #endif
}
private _run_weapp() {
// #ifdef MP
let _this = this;
_this.startTime = 0
clearTimeout(_this.dhid)
function run(){
if (_this.startTime<=0) {
_this.startTime = Date.now(); // 记录动画开始时间
}
const progress = Math.min((Date.now() - _this.startTime) / _this.duration + _this.progress, 1); // 计算当前进度
if(_this.element!=null){
if(!_this.isDescPlay){
for(let i=0;i<_this.tagetsAttr.length;i++){
let item = _this.tagetsAttr[i]
item.progress = progress
if(!_this.isColorStyle(item.name)){
let fromN = parseFloat(item.from)
let toN = parseFloat(item.to)
let easeInt = 1
if(_this.easing!=null){
let eas = _this.easing!
easeInt = eas(progress)
}
let current = fromN + (toN - fromN) * (easeInt==1?progress:easeInt); // 根据进度计算当前值
if(_this.reverse||_this._tyty ){
current = toN + (fromN - toN) * (easeInt==1?progress:easeInt);
}
if(_this.element!=null){
_this._setAttr(item.name,current,item.unit,progress,item)
}
}else{
if(_this.element!=null){
_this._setAttr(item.name,0,item.unit,progress,item)
}
}
}
}else{
if(_this.attrIndex<_this.tagetsAttr.length){
let item = _this.tagetsAttr[_this.attrIndex]
item.progress = progress
if(!_this.isColorStyle(item.name)){
let fromN = parseFloat(item.from)
let toN = parseFloat(item.to)
let easeInt = 1
if(_this.easing!=null){
let eas = _this.easing!
easeInt = eas(progress)
}
let current = fromN + (toN - fromN) * (easeInt==1?progress:easeInt); // 根据进度计算当前值
if(_this.reverse||_this._tyty ){
current = toN + (fromN - toN) * (easeInt==1?progress:easeInt);
}
if(_this.element!=null){
_this._setAttr(item.name,current,item.unit,progress,item)
}
}else{
if(_this.element!=null){
_this._setAttr(item.name,0,item.unit,progress,item)
}
}
}
}
}
if(_this.pauseing){
clearTimeout(_this.dhid)
_this.running = false
console.log(_this.pauseing,"动画暂停")
_this.progress = progress
return;
}
// console.log(_this.running,"动画结束")
if(progress>=1||_this._isStopping){
if(_this.isDescPlay&&_this.attrIndex<_this.tagetsAttr.length){
_this.attrIndex +=1;
_this.progress = 0
_this._run_weapp();
return;
}
clearTimeout(_this.dhid)
_this.attrIndex = 0;
_this.progress = 0
if(_this.tyty){
_this._tyty = !_this._tyty
}
if(_this.loop==-1){
_this._run_weapp();
return;
}else{
_this._loop+=1;
if(_this._loop<_this.loop){
_this._run_weapp();
return;
}
}
_this.running = false
_this.completeCallBack()
return;
}
if (progress < 1 && _this.running) {
_this.dhid = setTimeout(()=>{
_this.doCallBack(progress)
run()
}, 6); // 递归调用自身,实现动画效果
}
}
_this.startCallBack()
run()
// #endif
}
private _run_andriod() {
// #ifdef APP-ANDROID
let _this = this;
_this.startTime = 0
let dhid = 0
if(this.ChoreographerDemo==null){
this.ChoreographerDemo = Choreographer.getInstance();
}else{
if(this.FrameCallbackCallFun!=null){
_this.ChoreographerDemo!.removeFrameCallback(this.FrameCallbackCallFun!)
}
}
class frameCallback extends Choreographer.FrameCallback {
override doFrame(frameTimeNanos:Long) {
if (_this.startTime<=0) {
_this.startTime = Date.now(); // 记录动画开始时间
}
const progress = Math.min((Date.now() - _this.startTime) / _this.duration + _this.progress, 1); // 计算当前进度
if(_this.element!=null){
if(!_this.isDescPlay){
for(let i=0;i<_this.tagetsAttr.length;i++){
let item = _this.tagetsAttr[i]
if(!_this.isColorStyle(item.name)){
let fromN = parseFloat(item.from)
let toN = parseFloat(item.to)
let easeInt = 1
if(_this.easing!=null){
let eas = _this.easing!
easeInt = eas(progress)
}
let current = fromN + (toN - fromN) * (easeInt==1?progress:easeInt); // 根据进度计算当前值
if(_this.reverse||_this._tyty ){
current = toN + (fromN - toN) * (easeInt==1?progress:easeInt);
}
if(_this.element!=null){
_this._setAttr(item.name,current,item.unit,progress,item)
}
}else{
if(_this.element!=null){
_this._setAttr(item.name,0,item.unit,progress,item)
}
}
}
}else{
if(_this.attrIndex<_this.tagetsAttr.length){
let item = _this.tagetsAttr[_this.attrIndex]
item.progress = progress
if(!_this.isColorStyle(item.name)){
let fromN = parseFloat(item.from)
let toN = parseFloat(item.to)
let easeInt = 1
if(_this.easing!=null){
let eas = _this.easing!
easeInt = eas(progress)
}
let current = fromN + (toN - fromN) * (easeInt==1?progress:easeInt); // 根据进度计算当前值
if(_this.reverse||_this._tyty ){
current = toN + (fromN - toN) * (easeInt==1?progress:easeInt);
}
if(_this.element!=null){
_this._setAttr(item.name,current,item.unit,progress,item)
}
}else{
if(_this.element!=null){
_this._setAttr(item.name,0,item.unit,progress,item)
}
}
}
}
}
if(progress>=1||_this._isStopping){
if(_this.isDescPlay&&_this.attrIndex<_this.tagetsAttr.length){
_this.attrIndex +=1;
_this.progress = 0
_this._run_andriod();
return;
}
_this.progress = 0
if(_this.tyty){
_this._tyty = !_this._tyty
}
if(_this.loop==-1){
_this._run_andriod();
return;
}else{
_this._loop+=1;
if(_this._loop<_this.loop){
_this._run_andriod();
return;
}
}
_this.running = false
_this.completeCallBack()
return;
}
if(_this.pauseing){
_this.running = false
console.log(_this.pauseing,"动画暂停")
_this.progress = progress
return;
}
if (progress < 1 && _this.running) {
_this.doCallBack(progress)
_this.ChoreographerDemo!.postFrameCallback(this);
}
}
};
_this.startCallBack()
this.FrameCallbackCallFun = new frameCallback()
_this.ChoreographerDemo!.postFrameCallback(this.FrameCallbackCallFun!);
// #endif
}
private __run_web() {
// #ifdef H5
let _this = this;
if(_this.canFrameIds!=null) {
cancelAnimationFrame(_this.canFrameIds)
}
function run(){
_this.enterCallFun()
if(_this._isStopping){
_this.running = false
if(_this.canFrameIds!=null) {
cancelAnimationFrame(_this.canFrameIds)
}
return;
}
if (_this.running) {
_this.canFrameIds = requestAnimationFrame(run)
}
}
run()
// #endif
}
private __run_andriod() {
// #ifdef APP-ANDROID
let _this = this;
if(this.ChoreographerDemo==null){
this.ChoreographerDemo = Choreographer.getInstance();
}else{
if(this.FrameCallbackCallFun!=null){
_this.ChoreographerDemo!.removeFrameCallback(this.FrameCallbackCallFun!)
}
}
class frameCallback extends Choreographer.FrameCallback {
override doFrame(frameTimeNanos:Long) {
_this.enterCallFun()
if(_this._isStopping){
_this.running = false
return;
}
if (_this.running) {
_this.ChoreographerDemo!.postFrameCallback(this);
}
}
};
this.FrameCallbackCallFun = new frameCallback()
_this.ChoreographerDemo!.postFrameCallback(this.FrameCallbackCallFun!);
// #endif
}
private __run_weapp(){
// #ifdef MP
let _this = this;
clearTimeout(_this.dhid)
function run(){
_this.enterCallFun()
if(_this._isStopping){
_this.running = false
clearTimeout(_this.dhid)
return;
}
if (_this.running) {
_this.dhid = setTimeout(run, 6);
}
}
run()
// #endif
}
/**
* 启动动画
*/
play():xAnimate {
if(this.running) return this;
this.running = true;
// 开始动画
this._isStopping = false;
this.pauseing = false;
this._loop = 0
this.attrIndex =0;
// #ifdef H5 || APP-IOS || APP-HARMONY
this._run_web();
// #endif
// #ifdef APP-ANDROID
this._run_andriod();
// #endif
// #ifdef MP
this._run_weapp();
// #endif
return this;
}
/**
* 停止,会让动画直接结束,把目标值直接前进到结束的地方。
*/
stop():xAnimate {
let _this = this;
this._isStopping = true;
this.progress = 0
this.attrIndex = _this.tagetsAttr.length
// #ifdef WEB || APP-IOS
if(_this.canFrameIds!=null){
cancelAnimationFrame(_this.canFrameIds)
}
// #endif
// #ifdef APP-ANDROID
if(this.ChoreographerDemo==null){
this.ChoreographerDemo = Choreographer.getInstance();
}else{
if(this.FrameCallbackCallFun!=null){
this.ChoreographerDemo!.removeFrameCallback(this.FrameCallbackCallFun!)
}
}
// #endif
// #ifdef MP
clearTimeout(_this.dhid)
// setTimeout(function(){
// for(let i=0;i<_this.tagetsAttr.length;i++){
// let item = _this.tagetsAttr[i]
// item.now = item.to;
// if(_this.element!=null){
// _this.element!.style!.setProperty(item.name,(_this.reverse?item.from:item.to)+item.unit)
// }
// }
// _this.reverse = false;
// },10)
// _this.running = false;
// #endif
return this;
}
/**
* 暂停,会保留当前的动画效果,不会直接结束。
*/
pause():xAnimate {
this.pauseing = true;
return this;
}
/**
* 刷新回调函数
*/
enterFrame(evt:()=>void){
this.stop()
this.enterCallFun=evt;
this.running = true;
this._isStopping = false
// #ifdef WEB || APP-IOS
this.__run_web()
// #endif
// #ifdef APP-ANDROID
this.__run_andriod();
// #endif
// #ifdef MP
this.__run_weapp();
// #endif
}
}
@@ -0,0 +1,726 @@
import { toHex } from "./xCoreUtil.uts";
import { xConfig } from "../../config/xConfig.uts";
/**
* 颜色值列表
*/
export const colors = new Map<string, string>([
['primary', '#0088ff'],
['success', '#34c759'],
['danger', '#ff8d28'],
['warn', '#F3BF12'],
['error', '#ff383c'],
['info', '#f2f2f7'],
['kleinblue', '#002FA7'], // 克莱因蓝
['chinesered', '#FF0000'], // 中国红
['internationalorange', '#FF4F00'], // 国际橙
['egyptianvlue', '#1034A6'], // 埃及艳蓝
['parisviolet', '#6C3082'], // 巴黎紫
['moroccanblue', '#1256A7'], // 摩洛哥蓝
['brazilgreen', '#009B3A'], // 巴西绿
['britishracinggreen', '#004225'], // 英国赛车绿
['indianyellow', '#E3A857'], // 印度黄
['australiangold', '#FFDF00'], // 澳大利亚金
['venetianred', '#C80815'], // 威尼斯红
['majorelleblue', '#6050DC'], // 马若雷蓝
['tuscanred', '#7C3030'], // 托斯卡红
['naplesyellow', '#FADA5E'], // 那不勒斯黄
['capumortuum', '#592720'], // 死者之首
['mayablue', '#73C2FB'], // 玛雅蓝
['persianrose', '#FE28A2'], // 波斯玫瑰
['tyrianpurple', '#66023C'], // 泰尔紫
['saharasand', '#F1E788'], // 撒哈拉沙
['burmeseruby', '#B00A0A'], // 缅甸红宝石
['transparent', 'rgba(0,0,0,0)'],
['aliceblue', '#F0F8FF'],
['antiquewhite', '#FAEBD7'],
['aqua', '#00FFFF'],
['aquamarine', '#7FFFD4'],
['azure', '#F0FFFF'],
['beige', '#F5F5DC'],
['bisque', '#FFE4C4'],
['black', '#000000'],
['blanchedalmond', '#FFEBCD'],
['blue', '#0000FF'],
['blueviolet', '#8A2BE2'],
['brown', '#A52A2A'],
['burlywood', '#DEB887'],
['cadetblue', '#5F9EA0'],
['chartreuse', '#7FFF00'],
['chocolate', '#D2691E'],
['coral', '#FF7F50'],
['cornflowerblue', '#6495ED'],
['cornsilk', '#FFF8DC'],
['crimson', '#DC143C'],
['cyan', '#00FFFF'],
['darkblue', '#00008B'],
['darkcyan', '#008B8B'],
['darkgoldenrod', '#B8860B'],
['darkgray', '#A9A9A9'],
['darkgreen', '#006400'],
['darkkhaki', '#BDB76B'],
['darkmagenta', '#8B008B'],
['darkolivegreen', '#556B2F'],
['darkorange', '#FF8C00'],
['darkorchid', '#9932CC'],
['darkred', '#8B0000'],
['darksalmon', '#E9967A'],
['darkseagreen', '#8FBC8F'],
['darkslateblue', '#483D8B'],
['darkslategray', '#2F4F4F'],
['darkturquoise', '#00CED1'],
['darkviolet', '#9400D3'],
['deeppink', '#FF1493'],
['deepskyblue', '#00BFFF'],
['dimgray', '#696969'],
['dodgerblue', '#1E90FF'],
['firebrick', '#B22222'],
['floralwhite', '#FFFAF0'],
['forestgreen', '#228B22'],
['fuchsia', '#FF00FF'],
['gainsboro', '#DCDCDC'],
['ghostwhite', '#F8F8FF'],
['gold', '#FFD700'],
['goldenrod', '#DAA520'],
['gray', '#808080'],
['green', '#008000'],
['greenyellow', '#ADFF2F'],
['honeydew', '#F0FFF0'],
['hotpink', '#FF69B4'],
['indianred', '#CD5C5C'],
['indigo', '#4B0082'],
['ivory', '#FFFFF0'],
['khaki', '#F0E68C'],
['lavender', '#E6E6FA'],
['lavenderblush', '#FFF0F5'],
['lawngreen', '#7CFC00'],
['lemonchiffon', '#FFFACD'],
['lightblue', '#ADD8E6'],
['lightcoral', '#F08080'],
['lightcyan', '#E0FFFF'],
['lightgoldenrodyellow', '#FAFAD2'],
['lightgray', '#D3D3D3'],
['lightgreen', '#90EE90'],
['lightpink', '#FFB6C1'],
['lightsalmon', '#FFA07A'],
['lightseagreen', '#20B2AA'],
['lightskyblue', '#87CEFA'],
['lightslategray', '#778899'],
['lightsteelblue', '#B0C4DE'],
['lightyellow', '#FFFFE0'],
['lime', '#00FF00'],
['limegreen', '#32CD32'],
['linen', '#FAF0E6'],
['magenta', '#FF00FF'],
['maroon', '#800000'],
['mediumaquamarine', '#66CDAA'],
['mediumblue', '#0000CD'],
['mediumorchid', '#BA55D3'],
['mediumpurple', '#9370DB'],
['mediumseagreen', '#3CB371'],
['mediumslateblue', '#7B68EE'],
['mediumspringgreen', '#00FA9A'],
['mediumturquoise', '#48D1CC'],
['mediumvioletred', '#C71585'],
['midnightblue', '#191970'],
['mintcream', '#F5FFFA'],
['mistyrose', '#FFE4E1'],
['moccasin', '#FFE4B5'],
['navajowhite', '#FFDEAD'],
['navy', '#000080'],
['oldlace', '#FDF5E6'],
['olive', '#808000'],
['olivedrab', '#6B8E23'],
['orange', '#FFA500'],
['orangered', '#FF4500'],
['orchid', '#DA70D6'],
['palegoldenrod', '#EEE8AA'],
['palegreen', '#98FB98'],
['paleturquoise', '#AFEEEE'],
['palevioletred', '#DB7093'],
['papayawhip', '#FFEFD5'],
['peachpuff', '#FFDAB9'],
['peru', '#CD853F'],
['pink', '#FFC0CB'],
['plum', '#DDA0DD'],
['powderblue', '#B0E0E6'],
['purple', '#800080'],
['rebeccapurple', '#663399'],
['red', '#FF0000'],
['rosybrown', '#BC8F8F'],
['royalblue', '#4169E1'],
['saddlebrown', '#8B4513'],
['salmon', '#FA8072'],
['sandybrown', '#F4A460'],
['seagreen', '#2E8B57'],
['seashell', '#FFF5EE'],
['sienna', '#A0522D'],
['silver', '#C0C0C0'],
['skyblue', '#87CEEB'],
['slateblue', '#6A5ACD'],
['slategray', '#708090'],
['snow', '#FFFAFA'],
['springgreen', '#00FF7F'],
['steelblue', '#4682B4'],
['tan', '#D2B48C'],
['teal', '#008080'],
['thistle', '#D8BFD8'],
['tomato', '#FF6347'],
['turquoise', '#40E0D0'],
['violet', '#EE82EE'],
['wheat', '#F5DEB3'],
['white', '#FFFFFF'],
['whitesmoke', '#F5F5F5'],
['yellow', '#FFFF00'],
['yellowgreen', '#9ACD32']
])
export function isValidColor(color : string) : boolean {
// return chroma.valid(color);
const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{8})$/;
const rgbRegex = /^rgb\((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\s*,\s*(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\s*,\s*(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\)$/;
const rgbaRegex = /^rgba\((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\s*,\s*(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\s*,\s*(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\s*,\s*((1(\.0{1,2})?)|(0(\.\d{1,2})?))\)$/;
const hslRegex = /^hsl\((\d{1,3}), (\d{1,3})%, (\d{1,3})%\)$/;
const hslaRegex = /^hsla\(\s*((\d{1,2}|[1-2]\d{2}|3[0-5]\d)(\.\d+)?|\d+(\.\d+)?)\s*,\s*((\d{1,2}|\d{0,1}\d{1}\d{1}|[1-2]\d{2}|3[0-5]\d)(\.\d+)?|\d+(\.\d+)?)%\s*,\s*((\d{1,2}|\d{0,1}\d{1}\d{1}|[1-2]\d{2}|3[0-5]\d)(\.\d+)?|\d+(\.\d+)?)%\s*,\s*((1|0(\.\d{1,2})?|(\.\d{1,2})))\)$/i;
if (color == '') {
return false; // 颜色值为空
} else if (color === 'inherit' || color === 'transparent') {
return false; // 特殊颜色值
} else if (color === 'currentColor') {
return false; // currentColor 不是有效的颜色值
} else if (hexRegex.test(color) || rgbRegex.test(color) || rgbaRegex.test(color) || hslRegex.test(color) || hslaRegex.test(color)) {
return true; // 符合颜色值的格式
}
return false;
}
/**
* 获取默认的颜色值
* @param [string] 颜色名称或者16进制颜色值
*/
export function getDefaultColor(sColor : string) : string {
if (sColor == "") return ""
let sc = sColor.toLocaleLowerCase().trim().replace(" ", "");
if (isValidColor(sc)) {
// 如果符合所有颜色值,进行转换为16进制。css方便统一解析。
return sc;
}
let colorhtme = xConfig.theme.get(sc)
// 检测是否是颜色名称。
let sco = colorhtme==null?colors.get(sc):colorhtme;
if (typeof sco == 'string') return sco as string;
return colors.get("primary")!;
}
/**
* 16进制转rgb
*/
export function hexToRgb(sColors : string) : UTSJSONObject {
if (sColors == "") {
return { r: 0, g: 0, b: 0, a: 0 }
}
let reg = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{8})$/;
let sColor : string = sColors.toLowerCase();
if (sColor != '' && reg.test(sColor)) {
if (sColor.length == 4) {
let sColorNew = "#";
for (let i = 1; i < 4; i += 1) {
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1));
}
sColor = sColorNew;
}
//处理六位的颜色值
let sColorChange : number[] = [];
sColorChange.push(parseInt(sColor.substring(1, 3), 16));
sColorChange.push(parseInt(sColor.substring(3, 5), 16));
sColorChange.push(parseInt(sColor.substring(5, 7), 16));
if (sColor.length == 9) {
sColorChange.push(parseInt(sColor.substring(7, 9), 16) / 255);
}
return {
r: sColorChange[0],
g: sColorChange[1],
b: sColorChange[2],
a: sColorChange.length == 4 ? sColorChange[3] : 1
}
} else if (/^(rgb|RGB|rgba|RGBA)/.test(sColor)) {
let arr : string[] = sColor.replace(/(?:\(|\)|rgba|rgb|RGB|RGBA)*/g, "").split(",")
let p : number[] = arr.map((val : string) : number => parseInt(val));
if (p.length < 3) {
return {
r: 0,
g: 0,
b: 0,
a: 1
}
}
if (p.length == 3) {
arr.push('1')
}
return {
r: p[0],
g: p[1],
b: p[2],
a: parseFloat(arr[3])
}
} else {
return {
r: 0,
g: 0,
b: 0,
a: 1
}
}
}
/**
* rgb转hsl
*/
export function rgbToHsl(rgb : UTSJSONObject) : UTSJSONObject {
let r = rgb.getNumber("r")
r = r as number / 255
let g = rgb.getNumber("g")
g = g as number / 255
let b = rgb.getNumber("b")
b = b as number / 255
let a = rgb.getNumber("a")
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
let maxmindiff = max - min;
let maxmindiffAdd = max + min;
// #ifdef APP-ANDROID && uniVersion >=4.31
maxmindiff = maxmindiff.toDouble()
maxmindiffAdd = maxmindiffAdd.toDouble()
// #endif
let h = 60 * (4 + (r - g) / maxmindiff);
let sdy = 2 - max - min
// #ifdef APP-ANDROID && uniVersion >=4.31
sdy = sdy.toDouble()
// #endif
let s = (max - min) / sdy;
let l = (max + min) / 2;
if (max === r) {
h = (60 * (g - b)) / maxmindiff;
} else if (max === g) {
h = 60 * (2 + (b - r) / maxmindiff);
}
if (h < 0) {
h += 360;
}
if (max === min) {
s = 0;
} else if (l < 0.5) {
s = (max - min) / maxmindiffAdd;
}
return { h: h, s: s * 100, l: l * 100, a: a };
}
/**
* hsl转rgb
*/
export function hslToRgb(hsl : UTSJSONObject) : UTSJSONObject {
let h = hsl.getNumber("h")!
let s = hsl.getNumber("s")!
let l = hsl.getNumber("l")!
let a = hsl.getNumber("a")!
h = h / 360;
s = s / 100;
l = l / 100;
let r = l;
let g = l;
let b = l;
function hue2rgb(pxx : number, q : number, txs : number) : number {
let t = txs;
let p = pxx;
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
}
if (s > 0) {
let q = l * (1 + s);
if (l >= 0.5) {
q = l + s - l * s;
}
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
r = Math.round(r * 255);
g = Math.round(g * 255);
b = Math.round(b * 255);
return { r, g, b, a };
}
/**
* rgb转16进制
*/
export function rgbToHex(rgb : UTSJSONObject) : string {
let r = rgb.getNumber("r")!
let g = rgb.getNumber("g")!
let b = rgb.getNumber("b")!
let a = rgb.getNumber("a")!
return "#" + toHex(r) + toHex(g) + toHex(b) + toHex(a * 255)
}
/**
* rgb转16进制
*/
export function rgbToHexNoAlpha(rgb : UTSJSONObject) : string {
let r = rgb.getNumber("r")!
let g = rgb.getNumber("g")!
let b = rgb.getNumber("b")!
return "#" + toHex(r) + toHex(g) + toHex(b)
}
export function hslaToCss(hsl : UTSJSONObject) : string {
let rgb = hslToRgb(hsl)
return rgbToHex(rgb)
}
export function hslaToRgbCss(hsl : UTSJSONObject) : string {
let rgb = hslToRgb(hsl)
let r = rgb.getNumber("r")!
let g = rgb.getNumber("g")!
let b = rgb.getNumber("b")!
let a = rgb.getNumber("a")!
return `rgba(${r},${g},${b},${a})`
}
// 根据 HSLA 颜色计算亮度
export function getLuminance(color : UTSJSONObject, type = "rgba") : number {
let colordefault : UTSJSONObject = type == 'rgba' ? color : hslToRgb(color);
const r = colordefault.getNumber("r")!
const g = colordefault.getNumber("g")!
const b = colordefault.getNumber("b")!
const rs = r / 255
const gs = g / 255
const bs = b / 255
const rl = Math.pow(rs <= 0.03928 ? rs / 12.92 : ((rs + 0.055) / 1.055), 2.4);
const gl = Math.pow(gs <= 0.03928 ? gs / 12.92 : ((gs + 0.055) / 1.055), 2.4);
const bl = Math.pow(bs <= 0.03928 ? bs / 12.92 : ((bs + 0.055) / 1.055), 2.4);
return 0.2126 * rl + 0.7152 * gl + 0.0722 * bl;
}
/**
* 对颜色加深
*/
export function colorAddDeepen(sColor : string) : string {
let rgb = hexToRgb(getDefaultColor(sColor));
let hsl = rgbToHsl(rgb);
let l = hsl.getNumber("l")! - 5;
l = Math.max(0, Math.min(l, 100))
return hslaToRgbCss({ h: hsl.getNumber("h"), s: hsl.getNumber("s"), l, a: hsl.getNumber("a")! })
}
/**
* 对颜色提亮
*/
export function colorAddBrighten(sColor : string) : string {
let rgb = hexToRgb(getDefaultColor(sColor));
let hsl = rgbToHsl(rgb);
let l = hsl.getNumber("l")! + 10;
l = Math.max(0, Math.min(l, 100))
return rgbToHex(hslToRgb({ h: hsl.getNumber("h"), s: hsl.getNumber("s"), l, a: hsl.getNumber("a")! }))
}
/**
* 获取浅色系主题,根据颜色值计算。返回正常值和hover值。
*/
export function colorGetThint(sColor : string) {
let rgb = hexToRgb(getDefaultColor(sColor));
let hsl = rgbToHsl(rgb);
let s = hsl.getNumber("s")!;
// let l = hsl.getNumber("l");
if (Math.ceil(s) > 0) {
s = 45
}
return {
default: rgbToHex(hslToRgb({ h: hsl.getNumber("h"), s, l: 93, a: hsl.getNumber("a")! })),
hover: rgbToHex(hslToRgb({ h: hsl.getNumber("h"), s, l: 85, a: hsl.getNumber("a")! }))
}
}
/**
* 获取正常主题,根据颜色值计算。返回正常值和hover值。
*/
export function colorGetHover(sColor : string) {
return {
default: sColor,
hover: colorAddDeepen(sColor)
}
}
/**
* 处理相主题色。
* @param color 需要处理的颜色值
* @param hoverColor 对应按下去的颜色默认与color相等。
* @returns UTSJSONObject
*/
export function getDefaultColorObj(color : string, hoverColor : string) : UTSJSONObject {
let hsla : UTSJSONObject = rgbToHsl(hexToRgb(getDefaultColor(color)));
let hoverHsla : UTSJSONObject = rgbToHsl(hexToRgb(getDefaultColor(hoverColor)));
let shadow = hslaToRgbCss({ h: hsla.getNumber("h")!, s: (hsla.getNumber("s")!) == 0 ? 0 : 60, l: 40, a: 0.4 });
let shadowHOver = hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: (hoverHsla.getNumber("s")!) == 0 ? 0 : 60, l: 40, a: 0.4 });
if ((hsla.getNumber("h")!) == 0 && (hsla.getNumber("s")!) == 0) {
shadow = "transparent"
}
if ((hoverHsla.getNumber("h")!) == 0 && (hoverHsla.getNumber("s")!) == 0) {
shadowHOver = "transparent"
}
let lightnum = getLuminance(hoverHsla, 'hsla');
let h = hsla.getNumber('h')!;
let maxLightNum = 0.55
if(h>=31&&h<=90){
maxLightNum = 0.60
}
let o = {
default: {
background: hslaToRgbCss(hsla),
borderColor: hslaToRgbCss({ h: hsla.getNumber("h")!, s: hsla.getNumber("s")!, a: hsla.getNumber("a")!, l: Math.min(Math.max(0, hsla.getNumber("l")! - 3), hsla.getNumber("l")!) }),
fontColor: lightnum < maxLightNum && hsla.getNumber("a")! > 0.1 ? '#ffffff' : '#000000',
shadow: shadow
},
active: {
background: hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: Math.min(Math.max(0, hoverHsla.getNumber("l")! - 5), hoverHsla.getNumber("l")!) }),
borderColor: hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: Math.max(Math.max(0, hoverHsla.getNumber("l")! - 10), hoverHsla.getNumber("l")!) }),
fontColor: lightnum < maxLightNum && hoverHsla.getNumber("a")! > 0.1 ? '#ffffff' : '#000000',
shadow: shadowHOver
}
} as UTSJSONObject
return o;
}
const hslColorMap = new Map<string,number[]>([
['红色', [331, 360]], // 0-30度
['红色', [0, 30]], // 0-30度
['橙色', [31, 60]], // 30-60度
['黄色', [61, 90]], // 60-90度
['绿色', [91, 150]], // 90-150度
['青色', [151, 210]], // 150-210度
['蓝色', [211, 270]], // 210-270度
['紫色', [271, 330]] // 270-330度
]);
/**
* 处理相主题色。
* @param color 需要处理的颜色值
* @param hoverColor 对应按下去的颜色默认与color相等。
* @returns UTSJSONObject
*/
export function getTextColorObj(color : string, hoverColor : string, isCoverDark ?: boolean) : UTSJSONObject {
let dark = isCoverDark == null ? false : isCoverDark
// #ifdef WEB
dark = isCoverDark == undefined ? false : dark
// #endif
let hsla : UTSJSONObject = rgbToHsl(hexToRgb(getDefaultColor(color)));
let hsla2 : UTSJSONObject = rgbToHsl(hexToRgb(getDefaultColor(color)));
let hoverHsla : UTSJSONObject = rgbToHsl(hexToRgb(getDefaultColor(hoverColor)));
// let fontcolor = getLuminance(hsla, 'hsla') < 0.3 && hsla.getNumber("a")! > 0.1 ? hslaToRgbCss(hsla) : hslaToRgbCss({ h: hsla.getNumber("h")!, s: hsla.getNumber("s")!, a: hsla.getNumber("a")!, l:30 })
// let bordercolor = hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: dark==true?21:92 })
let bgcolor = hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: dark == true ? 20 : 95 })
if (dark) {
let p = hsla2;
// p.set('l',90)
// fontcolor = hslaToRgbCss(p)
p.set('l', 20)
p.set('s', (p.getNumber('s')) != 0 ? 0 : 20)
bgcolor = hslaToRgbCss(p)
// p.set('l',22)
// bordercolor = hslaToRgbCss(p)
}
let lightnum = getLuminance(hoverHsla, 'hsla');
let h = hsla.getNumber('h')!;
let maxLightNum = 0.3
// if(h>=31&&h<=90){
// maxLightNum = 1
// }
// tmxColor.hslaToCss({ ...hsla}) : tmxColor.hslaToCss({ ...hsla,l:30})
let o = {
default: {
background: "transparent",
borderColor: "transparent",
fontColor: lightnum < maxLightNum && hsla.getNumber("a")! > 0.1 ? hslaToRgbCss(hsla) : hslaToRgbCss({ h: hsla.getNumber("h")!, s: hsla.getNumber("s")!, a: hsla.getNumber("a")!, l: 30 }),
shadow: "transparent",
},
active: {
background: bgcolor,
borderColor: "transparent",
fontColor: lightnum < maxLightNum && hoverHsla.getNumber("a")! > 0.1 ? hslaToRgbCss(hoverHsla) : hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: 30 }),
shadow: "transparent",
}
} as UTSJSONObject
return o;
}
/**
* 处理相主题色。
* @param color 需要处理的颜色值
* @param hoverColor 对应按下去的颜色默认与color相等。
* @returns UTSJSONObject
*/
export function getThinColorObj(color : string, hoverColor : string, isCoverDark ?: boolean) : UTSJSONObject {
let dark = isCoverDark == null ? false : isCoverDark
// #ifdef WEB
dark = isCoverDark == undefined ? false : dark
// #endif
let hsla : UTSJSONObject = rgbToHsl(hexToRgb(getDefaultColor(color)));
let hoverHsla : UTSJSONObject = rgbToHsl(hexToRgb(getDefaultColor(hoverColor)));
// tmxColor.hslaToCss({ ...hsla}) : tmxColor.hslaToCss({ ...hsla,l:30})
let fontcolor = getLuminance(hsla, 'hsla') < 0.3 && hsla.getNumber("a")! > 0.1 ? hslaToRgbCss(hsla) : hslaToRgbCss({ h: hsla.getNumber("h")!, s: hsla.getNumber("s")!, a: hsla.getNumber("a")!, l: 30 })
let bordercolor = hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: dark == true ? 21 : 92 })
let bgcolor = hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: dark == true ? 20 : 95 })
let lightnum = getLuminance(hoverHsla, 'hsla');
if (dark) {
let p = hsla;
p.set('l', 98)
fontcolor = hslaToRgbCss(p)
p.set('l', 20)
p.set('s', (p.getNumber('s')) != 0 ? 5 : 20)
bgcolor = hslaToRgbCss(p)
p.set('l', 22)
bordercolor = hslaToRgbCss(p)
}
let o = {
default: {
background: bgcolor,
borderColor: bordercolor,
fontColor: fontcolor,
shadow: "transparent",
},
active: {
background: hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: dark ? 10 : 50, a: hoverHsla.getNumber("a")!, l: dark == true ? 15 : 88 }),
borderColor: hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: 0, l: dark == true ? 18 : 80 }),
fontColor: lightnum < 0.3 && hoverHsla.getNumber("a")! > 0.1 ? hslaToRgbCss(hoverHsla) : hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: 64 }),
shadow: "transparent",
}
} as UTSJSONObject
return o;
}
/**
* 处理相主题色。
* @param color 需要处理的颜色值
* @param hoverColor 对应按下去的颜色默认与color相等。
* @returns UTSJSONObject
*/
export function getOutlineColorObj(color : string, hoverColor : string, isCoverDark ?: boolean) : UTSJSONObject {
let dark = isCoverDark == null ? false : isCoverDark
// #ifdef WEB
dark = isCoverDark == undefined ? false : dark
// #endif
let hsla : UTSJSONObject = rgbToHsl(hexToRgb(getDefaultColor(color)));
let hoverHsla : UTSJSONObject = rgbToHsl(hexToRgb(getDefaultColor(hoverColor)));
// tmxColor.hslaToCss({ ...hsla}) : tmxColor.hslaToCss({ ...hsla,l:30})
let lightnum = getLuminance(hoverHsla, 'hsla');
let h = hsla.getNumber('h')!;
let borderLight = 70;
// let maxLightNum = 0.55
if(dark){
borderLight = 50
}
let o = {
default: {
background: 'transparent',
borderColor: hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: 1, l: borderLight+5 }),
fontColor: lightnum < 0.6 && hsla.getNumber("a")! > 0.1 ? hslaToRgbCss(hsla) : hslaToRgbCss({ h: hsla.getNumber("h")!, s: hsla.getNumber("s")!, a: hsla.getNumber("a")!, l: 30 }),
shadow: "transparent",
},
active: {
background: 'transparent',
borderColor: hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: 1, l: borderLight }),
fontColor: lightnum < 0.6 && hoverHsla.getNumber("a")! > 0.1 ? hslaToRgbCss(hoverHsla) : hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: 30 }),
shadow: "transparent",
}
} as UTSJSONObject
return o;
}
/**
* 设置颜色的亮度值。29
* isCoverDark:是根据亮度值,是否要设置n的值,如果n超过了
* 指定的30就不操作。
*/
export function setTextColorLightByDark(color : string) : string {
let realColor = getDefaultColor(color);
let hsla = rgbToHsl(hexToRgb(realColor))
let l = hsla.getNumber('l')!
let s = hsla.getNumber('s')!
if (l < 30) {
if (s > 0) {
hsla.set('l', 50)
} else {
hsla.set('l', 100)
}
}
return hslaToRgbCss(hsla)
}
/**
* 变动的颜色是否需要变深。如果不需要返回原始颜色
* 如果果需要自动加深。
*/
export function setBgColorLightByDark(color : string) : string {
let realColor = getDefaultColor(color);
let hsla = rgbToHsl(hexToRgb(realColor))
let l = hsla.getNumber('l')!
let s = hsla.getNumber('s')!
if (s == 0 && l > 50) {
hsla.set('l', 100 - l)
}
if (s > 0) {
if (l > 50) {
hsla.set('l', 100 - l)
}
}
return hslaToRgbCss(hsla)
}
export function isBlackAndWhite(color : string) : boolean {
let realColor = getDefaultColor(color);
let hsla = rgbToHsl(hexToRgb(realColor))
let s = hsla.getNumber('s')!
return s == 0
}
+333
View File
@@ -0,0 +1,333 @@
import { getDefaultColor } from "./xCoreColorUtil.uts";
import { xConfig } from "../../config/xConfig.uts"
/**
* 数字转16进制
*/
function toHex(numbers : number) : string {
// let i = 0 as number
// if (numbers === i) {
// return '00';
// }
// let hex = '';
// const hexChars = '0123456789abcdef';
// while (numbers > 0) {
// const remainder = numbers % 16;
// hex = hexChars[remainder] + hex;
// numbers = Math.floor(numbers / 16);
// }
let n = numbers.toString(16) as string;
if(n.length==1){
n = '0'+n
}
return n;
}
/**
* 随机一个uid
* @param rdix 随机因子
* @param length 取的长度
* @param isAddStr 是否限制随机结果中的长度,不允许输出长度
* @returns String
*/
function getUid(rdix = 1, length = 12) : string {
let ix = "";
// #ifndef APP
ix = Math.floor(Math.random() * rdix * Math.floor(Math.random() * Date.now())).toString().substring(0, length);
// #endif
// #ifdef APP
ix = Math.floor(Math.random() * rdix * Math.floor(Math.random() * Date.now())).toString().substring(0, length as Int);
// #endif
return ix;
}
/**
* 给定一个值,来填充边距所需要的数组值
* @param val any
* @returns [左,上,右,下]
*/
function toFillMarginAr(val : number[]) : number[] {
let ar : number[] = [];
if (val.length == 1) {
let firstEl = val[0];
ar = [firstEl, firstEl, firstEl, firstEl]
} else if (val.length == 2) {
ar = [val[0], val[1], val[0], val[1]]
} else if (val.length == 3) {
ar = [val[0], val[1], val[2], 0]
}
return ar;
}
function rpx2px(n : number, _w = 750) : number {
let r = n
// #ifdef APP
r = uni.rpx2px(n);
// #endif
// #ifdef H5
function getLayoutRatio() : number {
const devicePixelRatio = window.devicePixelRatio || 1;
const screenWidth = window.innerWidth
if (screenWidth <= 950) {
// 小屏幕设备
return screenWidth / 750;
} else {
return 0.5
}
}
if (uni?.rpx2px) {
r = uni.rpx2px(n)
} else {
r = getLayoutRatio() * n
}
// #endif
return r
}
function px2dp(n : number) : number {
let w = n;
// #ifdef APP
const mets = UTSAndroid.getAppContext()!.resources!.getDisplayMetrics()
// 屏幕逻辑像素的宽度
let width = mets.widthPixels;
// 屏幕宽度
let screenWidth = uni.getWindowInfo().screenWidth
w = n / (width / screenWidth);
// #endif
return w
}
function checkIsCssUnit(str : string|number, unit : string) : string {
if(typeof str != 'string'){
return (str as number).toString() + unit;
}
let s = str as string;
if (s.indexOf("px") > -1 || s.indexOf("%") > -1 || s.indexOf("auto") > -1 || s.indexOf("vw") > -1 || s.indexOf("vh") > -1) {
return s;
}
return s + (unit==''?'px':unit)
}
// function checkIsCssUnit(str : string|number, unit : string) : string {
// let screenWidth = uni.getWindowInfo().windowWidth;
// let base = 0;
// let baseUnit = unit == ''?xConfig.unit:unit;
// if(typeof str != 'string'){
// base = str as number;
// }else if(typeof str == 'string'){
// let s = str as string;
// if (
// s.indexOf("px") > -1 ||
// s.indexOf("rpx") > -1 ||
// s.indexOf("%") > -1 ||
// s.indexOf("auto") > -1 ||
// s.indexOf("vw") > -1 ||
// s.indexOf("rem") > -1 ||
// s.indexOf("em") > -1 ||
// s.indexOf("in") > -1 ||
// s.indexOf("vh") > -1)
// {
// return s;
// }
// base = parseFloat(s);
// base = isNaN(base)?0:base
// }
// if(baseUnit=='rpx'){
// let baseDesize = Math.max(xConfig.designSize,375)
// let baseMaxWidth = Math.max(xConfig.maximumCalculatedSize,375)
// let origSize = 375;
// let ratio = 1 - baseDesize / origSize;
// let maxRatio = screenWidth / baseMaxWidth;
// let baseSize = base - (ratio*base)
// // 说明超过了设定缩放的最大屏幕尺寸。
// if(maxRatio>=1){
// base = baseSize;
// }else{
// const calcRatio = Math.max(screenWidth/baseDesize,1)
// base = calcRatio*baseSize ;
// }
// let baseunitReal = 'px'
// // #ifdef H5
// if(base<12){
// base = base/16
// baseunitReal = "rem"
// }
// // #endif
// return base + baseunitReal
// }
// return base + baseUnit
// }
function fillArrayCssValue(val : Array<string>) : string[] {
let ar : string[] = val.map((el : string) : string => {
return checkIsCssUnit(el, xConfig.unit)
})
if (ar.length == 0) return [];
if (ar.length == 1) return [ar[0], ar[0], ar[0], ar[0]]
if (ar.length == 2) {
return [ar[1], ar[0], ar[1], ar[0]]
}
if (ar.length == 3) return [ar[1], ar[2], '0px', ar[0]]
return [ar[1], ar[2], ar[3], ar[0]];
}
function fillArrayCssValueByround(val : Array<string>) : string[] {
let ar : string[] = val.map((el : string) : string => {
return checkIsCssUnit(el, xConfig.unit)
})
if (ar.length == 0) return [];
if (ar.length == 1) return [ar[0], ar[0], ar[0], ar[0]]
if (ar.length == 2) {
return [ar[1], ar[0], ar[1], ar[0]]
}
if (ar.length == 3) return [ar[0], ar[1], ar[0], '0px']
return [ar[0], ar[1], ar[2], ar[3]];
}
function fillArrayCssValueBycolor(val : Array<string>) : string[] {
let ar : string[] = val.map((el : string) : string => {
return getDefaultColor(el)
})
if (ar.length == 0) return [];
if (ar.length == 1) return [ar[0], ar[0], ar[0], ar[0]]
if (ar.length == 2) {
return [ar[1], ar[0], ar[1], ar[0]]
}
if (ar.length == 3) return [ar[1], ar[2], '0px', ar[0]]
return [ar[1], ar[2], ar[3], ar[0]];
}
/**
* 对数组进行分组。按数量分组
*/
function splitArray<T>(target : Array<T>, value : number) : Array<Array<T>> {
var result = [] as Array<Array<T>>;
for (var i = 0; i < target.length; i += value) {
let ml = target.slice(i, i + value);
result.push(ml);
}
return result;
}
/**
* 对数组进行分组。按指定组数量
*/
function splitArrayByGroup<T>(target : Array<T>, group : number) : Array<Array<T>> {
var groupSize = Math.ceil(target.length / group);
var result = [] as Array<Array<T>>;
for (var i = 0; i < target.length; i += groupSize) {
result.push(target.slice(i, i + groupSize));
}
return result;
}
/**
* 获取字符的css单位。
*/
function getUnit(n ?: string) : string {
if (n == null || n == '') return xConfig.unit;
let unit = n.replace(/[\d|\-|\+|\.]/g, '');
if (unit == "") {
unit = xConfig.unit;
}
return unit;
}
/**
* 设置当前页面是否刷新。
*/
function setPagePullRefresh(enbledpull : boolean) {
// #ifdef APP
let pages = getCurrentPages()
let page = pages[pages.length - 1]
// let pageJson = page.$getPageStyle()
// pageJson.set("enablePullDownRefresh",enbledpull)
page.$setPageStyle({ "enablePullDownRefresh": enbledpull } as UTSJSONObject)
// #endif
}
/**
* 获取当前的下拉状态。
*/
function getPagePullRefresh() : boolean {
// #ifdef APP
let pages = getCurrentPages()
let page = pages[pages.length - 1]
let pageJson = page.$getPageStyle()
let enb = pageJson.get("enablePullDownRefresh") as boolean|null;
if(enb==null) return false;
return enb as boolean;
// #endif
return false;
}
/**
* 防抖
*/
function debounce(
func : (args : any) => void,
wait : number,
immediate ?: boolean
) : (args : any) => number | null {
let timeout : number | null = null;
let callNow = immediate == null ? true : (immediate! as boolean);
// 返回的函数是实际被调用的防抖函数
return (args : any) : number | null => {
if (timeout != null && callNow == true) {
return timeout;
}
if (timeout != null) {
clearTimeout(timeout! as number);
timeout = null;
}
// 决定是否立即执行函数
if (callNow == true) {
func(args)
timeout = setTimeout(() => {
timeout = null;
}, wait);
return timeout;
}
// 否则,设置定时器以稍后执行函数
timeout = setTimeout(() => {
func(args)
}, wait);
return timeout
}
}
export {
toHex,
toFillMarginAr,
getUid,
checkIsCssUnit,
px2dp,
rpx2px,
splitArray,
splitArrayByGroup,
fillArrayCssValue,
fillArrayCssValueByround,
fillArrayCssValueBycolor,
getUnit,
setPagePullRefresh,
getPagePullRefresh,
debounce
}
+545
View File
@@ -0,0 +1,545 @@
# xDate
### 开发文档
[TMUI4.0文档](https://xui.tmui.design/)
[TMUI4.0组件库](https://ext.dcloud.net.cn/plugin?id=16369)
日期处理库
### 说明
这是tmui4.0|XUI的日期处理库,提供了丰富的日期操作功能。简单好上手,单库只有14kb,能满足大部分日期处理需求。库采用类Jq式的链式调用,使用灵活方便。
### 兼容性
| IOS | Android | WEB | 小程序 |
| --- | --- | --- | --- |
| 支持 | 支持 | 支持 | 支持 |
### 导入使用
在页面中直接使用快捷导入即可使用,它是class类,需要new
```ts
import {xDate, createDate} from "@/uni_modules/tmx-ui/index.uts"
// 打印当前
console.log(new xDate().date)
// 辅助函数用来创建系统的Date对象时间
console.log(createDate('2023-3-1'))
```
### 创建实例
支持string, number, Date, null四种类型
```ts
import {xDate, createDate} from "@/uni_modules/tmx-ui/index.uts"
// 为空,默认是现在
const now = new xDate()
// 传递字符串的毫秒数
const date1 = new xDate('15888888')
// 传递字符串的时间
const date2 = new xDate('2023-2-3')
const date3 = new xDate('2023/2/3')
// 传递Date对象
const date4 = new xDate(new Date())
```
### 实例方法
| 名称 | 参数 | 介绍 |
| --- | --- | --- |
| format | s: string \| null = null | 格式化日期,默认格式为YYYY/MM/DD hh:mm:ss |
| getYear | 无 | 获取当前年份 |
| getMonth | 无 | 获取当前月份(0-11,0代表1月) |
| getMonthName | useShort: boolean = false | 获取当前月份的名称(支持国际化) |
| getDate | 无 | 获取当前日(1-31 |
| getHours | 无 | 获取当前小时(0-23 |
| getMinutes | 无 | 获取当前分钟(0-59 |
| getSeconds | 无 | 获取当前秒(0-59 |
| getDateOf | d: xDateTypeTime = 'd' | 获取指定类型的数据,d可为'y'年,'m'月,'d'日,'h'时,'M'分,'s'秒,'ms'毫秒,'w'周 |
| setDateOf | n: number, d: xDateTypeTime = 'd' | 设定日期,支持链式调用 |
| getTime | d: xDateTypeTime | 获取时间戳,可指定单位 |
| getClone | 无 | 获取当前时间的副本 |
| getWeek | 无 | 获取本年的第几周 |
| getDateStartOf | d: 'm' \| 'w' \| 'y' = 'm' | 获取时间的第一天(月/周/年) |
| getDateEndOf | d: 'm' \| 'w' \| 'y' = 'm' | 获取时间的最后一天(月/周/年) |
| getWeekDay | 无 | 获取星期几(0-6,0表示星期天) |
| getWeekDayCn | model: string[] \| null = null, useShort: boolean = true | 获取星期名称(支持国际化) |
| getMonthCountDay | 无 | 获取当前月份的最大天数 |
| getDateInfo | str: string \| null = null | 获取日期详细信息 |
| getDaysOf | d: 'm' \| 'w' = 'm' | 获取日期数组(月/周) |
| add | count: number, d: xDateTypeTime = 'd' | 日期加法操作,支持链式调用 |
| subtraction | count: number, d: xDateTypeTime = 'd' | 日期减法操作,支持链式调用 |
| isBetween | start: xDate, end: xDate, type: xDateTypeTime = 'ms', d: '()' \| '[]' \| '(]' \| '[)' = '[]' | 判断日期是否在区间内 |
| isBetweenOf | targetDate: xDate, d: '>' \| '>=' \| '<' \| '<=' \| '=' = '>', type: xDateTypeTime = 'ms' | 与目标日期比较 |
| fromBetweenLongTime | target: any, model: Map<number, string> \| null, format: string \| null | 计算与目标时间过了多久(支持国际化) |
| diffTime | target: any, type: xDateTypeTime = 's' | 计算与目标时间相差多少个单位 |
| getQuarter | type: string = '' | 获取季度信息 |
| setDateLocale | locale: xDateLanguage | 设置xDate的语言 |
| getDateLocale | 无 | 获取当前xDate使用的语言 |
### 类型定义
```ts
// 日期时间单位类型
export type xDateTypeTime = 'y' | 'm' | 'd' | 'h' | 'M' | 's' | 'ms' | 'w'
// 季度信息类型
export type xDateTypeQuarter = {
quarter : number,
start : string,
end : string
}
// 日期格式类型
export type DateFormat = 'RFC2822' | 'ISO8601' | 'CUSTOM';
// 日期信息类型
export type xDateDayInfoType = {
year: number,
month: number,
day: number,
hours: number,
minutes: number,
seconds: number,
week: number,
weeks: number,
weekCn: string,
date: string
}
// 支持的语言类型
export type xDateLanguage = 'zh-Hans' | 'en' | 'ja' | 'ko' | 'zh-Hant' | 'fr' | 'ru';
// 国际化相对时间配置类型
export type xDateI18nTypeRelativeTime = {
future: string,
past: string,
s: string,
m: string,
mm: string,
h: string,
hh: string,
d: string,
dd: string,
M: string,
MM: string,
y: string,
yy: string
}
// 国际化配置类型
export type xDateI18nType = {
weekdays: string[],
weekdaysShort: string[],
months: string[],
monthsShort: string[],
meridiem: (hour: number, minute: number, isLowercase: boolean) => string,
relativeTime: xDateI18nTypeRelativeTime
}
```
### 国际化相关API
```ts
// 设置xDate的语言
setDateLocale(locale: xDateLanguage): void
// 获取当前xDate使用的语言
getDateLocale(): xDateLanguage
// 获取当前月份的名称
getMonthName(useShort: boolean = false): string
// 获取星期名称(支持国际化)
getWeekDayCn(model: string[] | null = null, useShort: boolean = true): string
```
```
### API说明
#### 辅助函数
##### createDate
```ts
function createDate(dateStrs: string): Date
```
- 描述:用来解析非标准时间以及各种奇怪的时间格式
- 参数:
- dateStrs: 日期字符串,支持多种格式如YYYY、YYYY-MM、YYYY-MM-DD等
- 返回值:Date对象
##### dateCovertXdate
```ts
function dateCovertXdate(date: Date): xDate
```
- 描述:将一个日期转换为xDate对象
- 参数:
- date: Date对象
- 返回值:xDate对象
#### xDate类方法
##### 构造函数
```ts
constructor(dateStr: string | number | Date | null = null)
```
- 描述:创建xDate实例
- 参数:
- dateStr: 可以是字符串、数字、Date对象或null(默认为当前时间)
##### detectDateFormat
```ts
detectDateFormat(dateStr: string): DateFormat
```
- 描述:检测字符串的日期格式类型
- 参数:
- dateStr: 日期字符串
- 返回值:日期格式类型('RFC2822'、'ISO8601'或'CUSTOM'
##### format
```ts
format(s: string | null = null): string
```
- 描述:格式化日期
- 参数:
- s: 模板,比如YYYY/MM/DD hh:mm:ss,默认为YYYY/MM/DD hh:mm:ss
- 返回值:格式化后的日期字符串
##### getYear
```ts
getYear(): number
```
- 描述:获取当前年
- 返回值:根据当地时间,返回一个对应于给定日期的年份数字
##### getMonth
```ts
getMonth(): number
```
- 描述:获取当前月(0-11,0代表1月)
- 返回值:一个0到11的整数值
##### getMonthName
```ts
getMonthName(useShort: boolean = false): string
```
- 描述:获取当前月份的名称(支持国际化)
- 参数:
- useShort: 是否使用短名称,默认为false
- 返回值:返回当前语言环境下的月份名称
##### getDate
```ts
getDate(): number
```
- 描述:获取当前天
- 返回值:返回一个1到31的整数值
##### getHours
```ts
getHours(): number
```
- 描述:获取当前小时
- 返回值:返回一个0到23之间的整数值
##### getMinutes
```ts
getMinutes(): number
```
- 描述:获取当前分钟
- 返回值:返回一个0到59的整数值
##### getSeconds
```ts
getSeconds(): number
```
- 描述:获取当前秒
- 返回值:返回一个0到59的整数值
##### getDateOf
```ts
getDateOf(d: xDateTypeTime = 'd'): number
```
- 描述:获取指定类型的数据
- 参数:
- d: 'y'年 'm'月 'd'日 'h'时 'M'分 's'秒 'ms'毫秒 'w'周
- 返回值:指定类型的数值
##### setDateOf
```ts
setDateOf(n: number, d: xDateTypeTime = 'd'): xDate
```
- 描述:设定日期
- 参数:
- n: 设定的数据
- d: 比如d=y,那么就是设置本日期的年份数据
- 返回值:xDate对象实例(支持链式调用)
##### getTime
```ts
getTime(d: xDateTypeTime): number
```
- 描述:获取时间戳
- 参数:
- d: 'y'年 'm'月 'd'日 'h'时 'M'分 's'秒 'ms'毫秒
- 返回值:返回从UTC时间1970年1月1日午夜开始以毫秒为单位存储的时间值
- 注意:如果不提供d就默认返回毫秒,比如设置d=m那么后面的h,M,s,ms会被设置为0
##### getClone
```ts
getClone(): xDate
```
- 描述:当前时间的副本
- 返回值:新的xDate对象
##### getWeek
```ts
getWeek(): number
```
- 描述:本年的第几周
- 返回值:周次
##### getDateStartOf
```ts
getDateStartOf(d: 'm' | 'w' | 'y' = 'm'): xDate
```
- 描述:取时间的第一天
- 参数:
- d: 'm'表示取本月的第一天,'w'取本周的第一天,'y'表示本年的第一天
- 返回值:返回xDate对象
##### getDateEndOf
```ts
getDateEndOf(d: 'm' | 'w' | 'y' = 'm'): xDate
```
- 描述:取时间的最后一天
- 参数:
- d: 'm'表示取本月的最后一天,'w'取本周的最后一天,'y'表示本年的最后一天
- 返回值:返回xDate对象
##### getWeekDay
```ts
getWeekDay(): number
```
- 描述:根据本地时间,返回一个具体日期中一周的第几天,0表示星期天
- 返回值:根据本地时间,返回一个0到6之间的整数值
##### getWeekDayCn
```ts
getWeekDayCn(model: string[] | null = null, useShort: boolean = true): string
```
- 描述:获取星期名称(支持国际化)
- 参数:
- model: 具有星期的中文模板,请按照顺序放置如["周日","周一","周二","周三","周四","周五","周六"]
- useShort: 是否使用短名称,默认为true
- 返回值:返回当前语言环境下的星期名称
##### getMonthCountDay
```ts
getMonthCountDay(): number
```
- 描述:返回当前日期本月的最大天数
- 返回值:当前月份的最大天数
##### getDateInfo
```ts
getDateInfo(str: string | null = null): xDateDayInfoType
```
- 描述:返回当日的信息
- 参数:
- str: 日期字符串,为null时使用当前xDate实例
- 返回值:返回一个包含年月日周次,星期,农历等的对象
##### getDaysOf
```ts
getDaysOf(d: 'm' | 'w' = 'm'): xDateDayInfoType[]
```
- 描述:返回日期数组
- 参数:
- d: 'm'表示返回本月的日期数据,'w'表示返回本周的日期数据
- 返回值:返回一个日期数组
##### add
```ts
add(count: number, d: xDateTypeTime = 'd'): xDate
```
- 描述:为当前日期进行加操作
- 参数:
- count: 要增加的数量
- d: 'y'年 'm'月 'd'日 'h'时 'M'分 's'秒 'w'周
- 返回值:xDate对象(支持链式调用)
##### subtraction
```ts
subtraction(count: number, d: xDateTypeTime = 'd'): xDate
```
- 描述:为当前日期进行减操作
- 参数:
- count: 要减少的数量
- d: 'y'年 'm'月 'd'日 'h'时 'M'分 's'秒 'w'周
- 返回值:xDate对象(支持链式调用)
##### isBetween
```ts
isBetween(start: xDate, end: xDate, type: xDateTypeTime = 'ms', d: '()' | '[]' | '(]' | '[)' = '[]'): boolean
```
- 描述:日期是否在一个区间内
- 参数:
- start: 开始日期
- end: 结束日期
- type: 要比较的单位,默认为ms
- d: 区间类型,'()'不含起始'[]'包含起始'(]'不包含开始但包含结束'[)'包含开始但不包含结束
- 返回值:是否在区间内
##### isBetweenOf
```ts
isBetweenOf(targetDate: xDate, d: '>' | '>=' | '<' | '<=' | '=' = '>', type: xDateTypeTime = 'ms'): boolean
```
- 描述:与目标日期比较
- 参数:
- targetDate: 要比较的日期
- d: 比较类型,'>'大于目标日期,'>='大于等于目标'<'小于目标,'<='小于等于目标,'='全等
- type: 要比较的单位
- 返回值:比较结果
##### fromBetweenLongTime
```ts
fromBetweenLongTime(target: any, model: Map<number, string> | null, format: string | null): string
```
- 描述:与目标时间过了多久(支持国际化)
- 参数:
- target: 要对比的相对时间,如果填写null就表示当前时间以来的的多久
- model: 如果为null或者为空的map采用默认值,map格式key表示以秒为单位的时间,value为对应的文本
- format: 超过最大值最使用格式化日期,如果为null默认为YYYY-MM-DD
- 返回值:返回对应的时间文本
##### diffTime
```ts
diffTime(target: any, type: xDateTypeTime = 's'): number
```
- 描述:与目标时间相差多少个单位
- 参数:
- target: 目标时间
- type: 相差的单位,默认:s秒
- 返回值:返回以xDateTypeTime为单位相关的时间数
##### getQuarter
```ts
getQuarter(type: string = ''): xDateTypeQuarter[]
```
- 描述:获取季度
- 参数:
- type: 'y'表示获取本年的4个季度,空值表示获取当前时间所在的季度
- 返回值:返回一个季度数组{quarter:number,start:string,end:string}
##### setDateLocale
```ts
setDateLocale(locale: xDateLanguage): void
```
- 描述:设置xDate的语言
- 参数:
- locale: 语言代码,支持'zh-Hans'(中文简体)、'en'(英文)、'ja'(日文)、'ko'(韩文)、'zh-Hant'(繁体中文)、'fr'(法语)、'ru'(俄语)
- 返回值:无
##### getDateLocale
```ts
getDateLocale(): xDateLanguage
```
- 描述:获取当前xDate使用的语言
- 返回值:当前语言代码
### 使用示例
#### 基本使用
```ts
import {xDate, createDate} from "@/uni_modules/tmx-ui/index.uts"
// 创建日期实例
const date = new xDate('2023-05-15')
// 格式化日期
console.log(date.format('YYYY年MM月DD日')) // 输出: 2023年05月15日
// 获取年月日
console.log(date.getYear()) // 输出: 2023
console.log(date.getMonth()) // 输出: 4 (5月)
console.log(date.getDate()) // 输出: 15
// 链式调用
const nextWeek = date.getClone().add(1, 'w')
console.log(nextWeek.format()) // 输出下周同一天的日期
// 获取月份天数
console.log(date.getMonthCountDay()) // 输出: 31 (5月有31天)
// 获取日期信息
const info = date.getDateInfo()
console.log(info.weekCn) // 输出: 周一 (假设5月15日是周一)
// 日期比较
const date1 = new xDate('2023-01-01')
const date2 = new xDate('2023-12-31')
console.log(date.isBetween(date1, date2)) // 输出: true
```
#### 高级用法
```ts
import {xDate} from "@/uni_modules/tmx-ui/index.uts"
// 获取本月所有日期
const currentDate = new xDate()
const daysInMonth = currentDate.getDaysOf('m')
console.log(`本月共有 ${daysInMonth.length}`)
// 获取本周日期
const daysInWeek = currentDate.getDaysOf('w')
console.log(`本周的第一天是 ${daysInWeek[0].date}`)
// 获取季度信息
const quarters = currentDate.getQuarter('y')
console.log(`今年第一季度: ${quarters[0].start}${quarters[0].end}`)
// 计算两个日期之间的差距
const birthday = new xDate('1990-01-01')
const daysDiff = currentDate.diffTime(birthday, 'd')
console.log(`距离生日已经过去了 ${daysDiff}`)
// 友好的时间显示
const pastTime = new xDate().subtraction(3, 'h')
const timeText = currentDate.fromBetweenLongTime(pastTime, null, null)
console.log(timeText) // 输出: 3小时前
```
#### 国际化使用示例
```ts
import {xDate} from "@/uni_modules/tmx-ui/index.uts"
// 创建日期实例
const date = new xDate('2023-05-15')
// 默认使用中文简体
console.log(date.getWeekDayCn()) // 输出: 周一 (假设5月15日是周一)
console.log(date.getMonthName()) // 输出: 五月
// 切换到英文
date.setDateLocale('en')
console.log(date.getWeekDayCn()) // 输出: Mon
console.log(date.getMonthName()) // 输出: May
console.log(date.getMonthName(false)) // 输出: May (完整名称)
// 切换到日文
date.setDateLocale('ja')
console.log(date.getWeekDayCn()) // 输出: 月
console.log(date.getMonthName()) // 输出: 5月
// 相对时间显示也会根据语言环境变化
const currentDate = new xDate()
const pastTime = new xDate().subtraction(3, 'h')
// 中文环境
currentDate.setDateLocale('zh-Hans')
console.log(currentDate.fromBetweenLongTime(pastTime, null, null)) // 输出: 3小时前
// 英文环境
currentDate.setDateLocale('en')
console.log(currentDate.fromBetweenLongTime(pastTime, null, null)) // 输出: 3 hours ago
// 获取当前语言设置
console.log(currentDate.getDateLocale()) // 输出: en
```
+873
View File
@@ -0,0 +1,873 @@
import { xDateDayInfoType } from "../../interface.uts"
import { getCurrentLocale, getLocaleData, setLocale, xDateLanguage } from "./xDateI18n.uts"
export type xDateTypeTime = 'y' | 'm' | 'd' | 'h' | 'M' | 's' | 'ms' | 'w'
export type xDateTypeQuarter = {
quarter : number,
start : string,
end : string
}
export type DateFormat = 'RFC2822' | 'ISO8601' | 'CUSTOM';
/**
* 用来解析非标准时间以及各种奇怪的时间格式。
*/
export function createDate(dateStrs : string) : Date {
const dateStr = dateStrs.replace(/\//g, '-')
const result = new Date();
// YYYY,YYYY-MM,YYYY-MM-DD,YYYY-MM-DD HH,YYYY-MM-DD HH:mm,YYYY-MM-DD HH:mm:ss
let regxyy = /^(\d{4})$/
let regxyymm = /^(\d{4})[-/](\d{1,2})$/
let regxyymmdd = /^(\d{4})[-/](\d{1,2})[-/](\d{1,2})$/
let regxyymmddhh = /^(\d{4})[-/](\d{1,2})[-/](\d{1,2}) (\d{1,2})$/
let regxyymmddhhmm = /^(\d{4})[-/](\d{1,2})[-/](\d{1,2}) (\d{1,2}):(\d{1,2})$/
let regxyymmddhhmmss = /^(\d{4})[-/](\d{1,2})[-/](\d{1,2}) (\d{1,2}):(\d{1,2}):(\d{1,2})$/
let year = result.getFullYear();
let month = result.getMonth() - 1
let day = result.getDate()
let hour = result.getHours()
let minute = result.getMinutes()
let second = result.getSeconds()
result.setSeconds(59);
result.setMinutes(59);
result.setHours(59);
result.setDate(1);
if (regxyymmddhhmmss.test(dateStr)) {
const match = dateStr.match(regxyymmddhhmmss)!;
year = (parseInt(match[1] as string));
month = (parseInt(match[2] as string) - 1);
day = (parseInt(match[3] as string));
hour = (parseInt(match[4] as string));
minute = (parseInt(match[5] as string));
second = (parseInt(match[6] as string));
} else if (regxyymmddhhmm.test(dateStr)) {
const match = dateStr.match(regxyymmddhhmm)!;
year = (parseInt(match[1] as string));
month = (parseInt(match[2] as string) - 1);
day = (parseInt(match[3] as string));
hour = (parseInt(match[4] as string));
minute = (parseInt(match[5] as string));
} else if (regxyymmddhh.test(dateStr)) {
const match = dateStr.match(regxyymmddhh)!;
year = (parseInt(match[1] as string));
month = (parseInt(match[2] as string) - 1);
day = (parseInt(match[3] as string));
hour = (parseInt(match[4] as string));
} else if (regxyymmdd.test(dateStr)) {
const match = dateStr.match(regxyymmdd)!;
year = (parseInt(match[1] as string));
month = (parseInt(match[2] as string) - 1);
day = (parseInt(match[3] as string));
} else if (regxyymm.test(dateStr)) {
const match = dateStr.match(regxyymm)!;
year = (parseInt(match[1] as string));
month = (parseInt(match[2] as string) - 1);
} else if (regxyy.test(dateStr)) {
const match = dateStr.match(regxyy)!;
year = (parseInt(match[1] as string));
}
result.setSeconds(second);
result.setMinutes(minute);
result.setHours(hour);
result.setFullYear(year);
result.setMonth(month);
result.setDate(day);
return result;
}
/**
* 日期库 xDate
*/
export class xDate {
date : Date;
/**
* dateStr,可能是数字,字符串,Date对象
*/
constructor(dateStr : string | number | Date | null = null) {
this.date = this.checkDate(dateStr)
}
private checkDate(dateStr : string | number | Date | null = null) : Date {
let tempDate = new Date();
if (dateStr == null) {
return tempDate;
}
if (typeof dateStr == 'number') {
return new Date(dateStr! as number);
} else if (typeof dateStr == 'string') {
if (dateStr == '') {
return tempDate;
}
const dateformatStr = this.detectDateFormat(dateStr!)
if (dateformatStr == 'CUSTOM') {
let str = dateStr! as string;
str = str.replace(/-/g, '/')
let isNumberStr = /^\d+$/.test(str)
if (!isNumberStr) {
return createDate(dateStr!)
} else {
return new Date(parseInt(str!));
}
} else {
return new Date(dateStr! as string);
}
} else if (dateStr instanceof Date) {
return dateStr! as Date;
}
return tempDate;
}
/**
* 检测字符串的日期格式类型
* @param dateStr - 日期字符串
* @returns 日期格式类型
*/
detectDateFormat(dateStr : string) : DateFormat {
// RFC2822 格式检测
if (/^(?:\w{3},\s)?(?:\d{1,2}\s(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s\d{4}\s\d{2}:\d{2}(?::\d{2})?(?:\sGMT)?)|(?:\w{3}\s(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s\d{1,2}\s\d{2}:\d{2}(?::\d{2})?\s\d{4})/.test(dateStr)) {
return 'RFC2822';
}
// ISO8601 格式检测
if (/^\d{4}(-\d\d(-\d\d(T\d\d:\d\d(:\d\d)?(\.\d+)?(([+-]\d\d:\d\d)|Z)?)?)?)?$/.test(dateStr)) {
return 'ISO8601';
}
return 'CUSTOM';
}
/**
* 给数字添加前缀0
*/
translateFullDate(d : number, w : number) : string {
let dstr = d.toString();
if (dstr.length < w) {
let dar : number[] = new Array(w - dstr.length).fill(0);
dstr = dar.join('') + dstr
}
return dstr;
}
/**
* 格式化日期
* @param {string} s 模板,比如YYYY/MM/DD hh:mm:ss
* @returns {string} 格式化后的日期字符串
*/
format(s : string | null = null) : string {
let str = "YYYY/MM/DD hh:mm:ss"
if (s != null) {
str = s;
}
str = str.replace(/YYYY/g, this.translateFullDate(this.date.getFullYear(), 2))
str = str.replace(/MM/g, this.translateFullDate(this.date.getMonth() + 1, 2))
str = str.replace(/DD/g, this.translateFullDate(this.date.getDate(), 2))
str = str.replace(/hh/g, this.translateFullDate(this.date.getHours(), 2))
str = str.replace(/mm/g, this.translateFullDate(this.date.getMinutes(), 2))
str = str.replace(/ss/g, this.translateFullDate(this.date.getSeconds(), 2))
return str;
}
/**
* 获取当前年
* @return — 根据当地时间,返回一个对应于给定日期的年份数字。
*/
getYear() : number {
return this.date.getFullYear()
}
/**
* 获取当前月
* @description 是从0开始,1-11月
* @return — 一个 0 到 11 的整数值:0 代表一月份,1 代表二月份,2 代表三月份,依次类推
*/
getMonth() : number {
return this.date.getMonth()
}
/**
* 获取当前月份的名称
* @param {boolean} useShort - 是否使用短名称
* @return — 返回当前语言环境下的月份名称
*/
getMonthName(useShort : boolean = false) : string {
const monthIndex = this.date.getMonth();
return useShort ?
getLocaleData().monthsShort[monthIndex] :
getLocaleData().months[monthIndex];
}
/**
* 获取当前天
* @return — 返回一个 1 到 31 的整数值。
*/
getDate() : number {
return this.date.getDate()
}
/**
* 获取当前小时
* @return — 返回一个 0 到 23 之间的整数值。
*/
getHours() : number {
return this.date.getHours()
}
/**
* 获取当前分钟
* @return — 返回一个 0 到 59 的整数值。
*/
getMinutes() : number {
return this.date.getMinutes()
}
/**
* 获取当前秒
* @return — 返回一个 0 到 59 的整数值。
*/
getSeconds() : number {
return this.date.getSeconds()
}
/**
* 获取指定是数据
* @param {xDateTypeTime} d - 'y' 年 'm' 月 'd' 日 'h' 时 'M' 分 's' 秒 'ms' 毫秒
* @returns - 指定类型的数值
*/
getDateOf(d : xDateTypeTime = 'd') : number {
if (d == 'y') {
return this.date.getFullYear()
}
else if (d == 'm') {
return this.date.getMonth()
}
else if (d == 'd') {
return this.date.getDate()
}
else if (d == 'h') {
return this.date.getHours()
}
else if (d == 'M') {
return this.date.getMinutes()
}
else if (d == 's') {
return this.date.getSeconds()
}
return this.date.getMilliseconds()
}
/**
* 设定日期
* @param {number} n - 设定的数据,
* @param {string} d - 比如d=y,那么就是设置本日期的年份数据,'y' 年 'm' 月 'd' 日 'h' 时 'M' 分 's' 秒 'ms' 毫秒
* @returns - xDate对象实例
*/
setDateOf(n : number, d : xDateTypeTime = 'd') : xDate {
if (d == 'y') {
this.date.setFullYear(n)
}
else if (d == 'm') {
this.date.setMonth(n)
}
else if (d == 'd') {
this.date.setDate(n)
}
else if (d == 'h') {
this.date.setHours(n)
}
else if (d == 'M') {
this.date.setMinutes(n)
}
else if (d == 's') {
this.date.setSeconds(n)
}
else if (d == 'ms') {
this.date.setMilliseconds(n)
}
return this;
}
/**
* @param {string} - d:'y' 年 'm' 月 'd' 日 'h' 时 'M' 分 's' 秒 'ms' 毫秒
* @returns - 返回从UTC时间1970年1月1日午夜开始以毫秒为单位存储的时间值。
* @description 注意,如果不提供d就默认返回毫秒,比如设置d=m那么后面的h,M,s,ms会被设置为0
*/
getTime(d : xDateTypeTime) : number {
let date = new Date(this.date.getTime())
if (d == 'y') {
date.setMonth(0)
date.setDate(1)
date.setHours(0)
date.setMinutes(0)
date.setSeconds(0)
date.setMilliseconds(0)
return date.getTime()
}
else if (d == 'm') {
date.setDate(1)
date.setHours(0)
date.setMinutes(0)
date.setSeconds(0)
date.setMilliseconds(0)
return date.getTime()
}
else if (d == 'd') {
date.setHours(0)
date.setMinutes(0)
date.setSeconds(0)
date.setMilliseconds(0)
return date.getTime()
}
else if (d == 'h') {
date.setMinutes(0)
date.setSeconds(0)
date.setMilliseconds(0)
return date.getTime()
}
else if (d == 'M') {
date.setSeconds(0)
date.setMilliseconds(0)
return date.getTime()
}
else if (d == 's') {
date.setMilliseconds(0)
return date.getTime()
}
return date.getTime()
}
/**
* 当前时间的副本
*/
getClone() : xDate {
return new xDate(this.format());
}
/**
* 本年的第几周
* @return 周次
*/
getWeek() : number {
let target = new Date(this.format())
target.setDate(target.getDate() - (target.getDay() == 0 ? 7 : target.getDay()));
let firstDayOfYear = new Date(target.getFullYear(), 0, 1);
firstDayOfYear.setDate(firstDayOfYear.getDate() - (firstDayOfYear.getDay() == 0 ? 7 : firstDayOfYear.getDay()));
return Math.ceil((((target.getTime() - firstDayOfYear.getTime()) / 86400000) + 1) / 7);
}
/**
* 取时间的第一天
* @param {m,w,y} - m表示取本月的第一天,w取本周的第一天,y表示本年的第一天
* @returns {xDate} - 返回xDate对象
*/
getDateStartOf(d : 'm' | 'w' | 'y' = 'm') : xDate {
// 获取当前日期对象
let now = new Date(this.format())
if (d == 'w') {
// 将日期设置为所在周的周一(遵循ISO 8601标准,周一开始于周一)
let dayOfWeek = now.getDay();
if (dayOfWeek === 0) { // 如果是星期日,则向前推一天至上周的周一
now.setDate(now.getDate() - 7);
}
now.setDate(now.getDate() - dayOfWeek + 1);
}
if (d == 'm' || d == 'y') {
now.setDate(1);
}
if (d == 'y') {
now.setMonth(0);
}
now.setHours(0);
now.setMinutes(0);
now.setSeconds(0);
return dateCovertXdate(now);
}
/**
* 取时间的最后一天
* @param {string} - m,w,y m表示取本月的最后一天,w取本周的最后一天,y表示本年的最后一天
* @returns {xDate} - 返回xDate对象
*/
getDateEndOf(d : 'm' | 'w' | 'y' = 'm') : xDate {
// 获取当前日期对象
let now = new Date(this.format())
if (d == 'w') {
// 将日期设置为所在周的周一(遵循ISO 8601标准,周一开始于周一)
let dayOfWeek = now.getDay();
if (dayOfWeek === 0) { // 如果是星期日,则向前推一天至上周的周一
return dateCovertXdate(now);
}
now.setDate(now.getDate() + (7 - dayOfWeek));
}
if (d == 'm') {
now.setDate(this.getMonthCountDay());
}
if (d == 'y') {
// 先设置月份为12月,再设置为该月的最后一天
now.setMonth(11);
// 设置为12月的最后一天
now.setDate(31);
}
now.setHours(23);
now.setMinutes(59);
now.setSeconds(59);
return dateCovertXdate(now);
}
/**
* 根据本地时间,返回一个具体日期中一周的第几天,0 表示星期天。
* @return — 根据本地时间,返回一个 0 到 6 之间的整数值,代表星期几:0 代表星期日,1 代表星期一,2 代表星期二,依次类推。
*/
getWeekDay() : number {
return this.date.getDay()
}
/**
* 根据本地时间,返回一个具体日期中一周的第几天,0 表示星期天。
* @param {string[]} model 具有星期的中文模板,请按照顺序放置如["周日","周一","周二","周三","周四","周五","周六"]
* @param {boolean} useShort 是否使用短名称
* @return — 返回一个当前语言环境下的星期名称
*/
getWeekDayCn(model : string[] | null = null, useShort : boolean = true) : string {
let ml = useShort ? getLocaleData().weekdaysShort : getLocaleData().weekdays;
if (model != null && model?.length == 7) {
ml = model!;
}
return ml[this.getWeekDay()]!
}
/**
* 返回当前日期本月的最大天数
* @returns - 当前月份的最大天数
*/
getMonthCountDay() : number {
let nextDate = new Date(this.format())
// 先设置为1号,避免后面设置月份时超过月的天数。
nextDate.setDate(1)
nextDate.setMonth(this.getMonth() + 1)
// 设置为上月的最后一天。
nextDate.setDate(0)
return nextDate.getDate()
}
/**
* 返回当日的信息
* @returns - 返回一个包含年月日周次,星期,农历等的对象
*/
getDateInfo(str : string | null = null) : xDateDayInfoType {
let date = this as xDate
if (str != null) {
date = new xDate(str)
}
let info = {
year: date.getYear(),
month: date.getMonth(),
day: date.getDate(),
hours: date.getHours(),
minutes: date.getMinutes(),
seconds: date.getSeconds(),
week: date.getWeekDay(),
weeks: date.getWeek(),
weekCn: date.getWeekDayCn(),
date: ""
} as xDateDayInfoType
info.date = info.year + '/' + (info.month + 1) + '/' + info.day
return info
}
/**
* 返回日期数组
* @param {string} - m,w,m表示返回本月的日期数据,w表示返回本周的日期数据
* @returns - 返回一个日期数组
*/
getDaysOf(d : 'm' | 'w' = 'm') : xDateDayInfoType[] {
let dates = [] as xDateDayInfoType[]
if (d == 'w') {
let first = this.getDateStartOf('w')
for (let i = 0; i < 7; i++) {
let date = first.getClone()
date.date.setDate(date.date.getDate() + i)
dates.push(date.getDateInfo())
}
}
if (d == 'm') {
let first = this.getDateStartOf('m')
let maxDay = this.getMonthCountDay()
for (let i = 1; i <= maxDay; i++) {
let date = first.getClone()
date.date.setDate(i)
dates.push(date.getDateInfo())
}
}
return dates;
}
/**
* 按指定天数返回日期数组
* @param {string} d - 返回多少天的日期数组
* @param {string} type - after表示返回当前日期之后的日期,before表示返回当前日期之前的日期
*/
getDaysOfNum(d : number = 0, type : 'after' | 'before') : xDateDayInfoType[] {
let ar = [] as xDateDayInfoType[];
let nowdate = this.getClone()
if (type == 'after') {
for (let i = 0; i < d; i++) {
nowdate.add(1, 'd')
ar.push(nowdate.getDateInfo())
}
} else if (type == 'before') {
for (let i = 0; i < d; i++) {
nowdate.subtraction(1, 'd')
ar.push(nowdate.getDateInfo())
}
ar.reverse()
}
return ar;
}
/**
* 为当前日期进行加操作
* @param {string} - y,m,d,h,M,s,w,代表的是年份,月份,日期,小时,分钟,秒数,周
*/
add(count : number, d : xDateTypeTime = 'd') : xDate {
if (d == 'y') {
this.date.setFullYear(this.getYear() + count)
}
else if (d == 'm') {
this.date.setMonth(this.getMonth() + count)
}
else if (d == 'd') {
this.date.setDate(this.getDate() + count)
}
else if (d == 'h') {
this.date.setHours(this.getHours() + count)
}
else if (d == 'M') {
this.date.setMinutes(this.getMinutes() + count)
}
else if (d == 's') {
this.date.setSeconds(this.getSeconds() + count)
}
// 添加一周
else if (d == 'w') {
this.date.setDate(this.getDate() + count * 7)
}
return this;
}
/**
* 为当前日期进行减操作
* @param {string} - y,m,d,h,M,s,w,代表的是年份,月份,日期,小时,分钟,秒数,周
*/
subtraction(count : number, d : xDateTypeTime = 'd') : xDate {
if (d == 'y') {
this.date.setFullYear(this.getYear() - count)
}
else if (d == 'm') {
this.date.setMonth(this.getMonth() - count)
}
else if (d == 'd') {
this.date.setDate(this.getDate() - count)
}
else if (d == 'h') {
this.date.setHours(this.getHours() - count)
}
else if (d == 'M') {
this.date.setMinutes(this.getMinutes() - count)
}
else if (d == 's') {
this.date.setSeconds(this.getSeconds() - count)
}
// 添加一周
else if (d == 'w') {
this.date.setDate(this.getDate() - count * 7)
}
return this;
}
/**
* 日期是否在一个区间内
* @param {xDate} start 开始日期
* @param {xDate} end 结束日期
* @param {string} - type要比较的单位,默认为ms,'y' 年 'm' 月 'd' 日 'h' 时 'M' 分 's' 秒 'ms' 毫秒
* @param {string} - d,默认为[] 区间类型,'()'不含起始'[]'包含起始'(]'不包含开始,但包含结束'[)'包含开始,但不包含结束
* @returns {boolean} 是否在区间内
*/
isBetween(start : xDate, end : xDate, type : xDateTypeTime = 'ms', d : '()' | '[]' | '(]' | '[)' = '[]') : boolean {
let startTime = start.getTime(type)
// new Date('1970-1-1 8:0:0').getTime()
let endTime = end.getTime(type)
let nowTime = this.getTime(type)
if (d == '()') {
return nowTime > startTime && nowTime < endTime
}
else if (d == '[]') {
return nowTime >= startTime && nowTime <= endTime
}
else if (d == '(]') {
return nowTime > startTime && nowTime <= endTime
}
else if (d == '[)') {
return nowTime >= startTime && nowTime < endTime
}
return nowTime >= startTime && nowTime <= endTime
}
/**
* 与目标日期比较
* @param {xDate} targetDate 要比较的日期
* @param {string} - d,默认为> 比较类型,'>'大于目标日期,'>='大于等于目标'<'小于目标,'<='小于等于目标,'='全等
* @param {string} - type要比较的单位,默认为ms,'y' 年 'm' 月 'd' 日 'h' 时 'M' 分 's' 秒 'ms' 毫秒
* @returns {boolean} 比较结果
*/
isBetweenOf(targetDate : xDate, d : '>' | '>=' | '<' | '<=' | '=' = '>', type : xDateTypeTime = 'ms') : boolean {
let startTime = targetDate.getTime(type)
let nowTime = this.getTime(type)
if (d == '>') {
return nowTime > startTime
}
else if (d == '>=') {
return nowTime >= startTime
}
else if (d == '<') {
return nowTime < startTime
}
else if (d == '<=') {
return nowTime <= startTime
}
else if (d == '=') {
return nowTime == startTime
}
return nowTime > startTime
}
/**
* 与目标时间过了多久
* @param {string|xDate|null} target 要对比的相对时间,如果填写null就表示当前时间以来的的多久
* @param {Map<number,string>} model 如果为null或者为空的map采用默认值,map格式key表示以秒为单位的时间,value为对应的文本
* 比如60,1分钟前
* @param {string} format 超过最大值最使用格式化日期,如果为null默认为YYYY-MM-DD
* @returns {string} 返回对应的时间文本
*/
fromBetweenLongTime(target : any, model : Map<number, string> | null, format : string | null) : string {
let bijiaodate : xDate = new xDate()
if (target instanceof xDate) {
bijiaodate = (target as xDate)
} else if (typeof target == 'string') {
bijiaodate = new xDate(target as string)
} else if (target == null) {
bijiaodate = new xDate()
}
// 使用国际化相对时间配置
let relativeTime = getLocaleData().relativeTime;
let mapmodel = new Map<number, string>([
[30, relativeTime.s],
[60, relativeTime.m],
[60 * 5, '5' + relativeTime.mm.replace('%d', '')],
[60 * 10, '10' + relativeTime.mm.replace('%d', '')],
[60 * 30, '30' + relativeTime.mm.replace('%d', '')],
[60 * 60, relativeTime.h],
[60 * 60 * 2, '2' + relativeTime.hh.replace('%d', '')],
[60 * 60 * 3, '3' + relativeTime.hh.replace('%d', '')],
[60 * 60 * 5, '5' + relativeTime.hh.replace('%d', '')],
[60 * 60 * 23, '23' + relativeTime.hh.replace('%d', '')],
[60 * 60 * 24 * 1, relativeTime.d],
[60 * 60 * 24 * 2, '2' + relativeTime.dd.replace('%d', '')],
[60 * 60 * 24 * 7, '7' + relativeTime.dd.replace('%d', '')],
[60 * 60 * 24 * 30, relativeTime.M],
[60 * 60 * 24 * 30 * 2, '2' + relativeTime.MM.replace('%d', '')],
[60 * 60 * 24 * 30 * 3, '3' + relativeTime.MM.replace('%d', '')],
[60 * 60 * 24 * 30 * 4, '4' + relativeTime.MM.replace('%d', '')],
[60 * 60 * 24 * 30 * 5, '5' + relativeTime.MM.replace('%d', '')],
[60 * 60 * 24 * 30 * 6, '6' + relativeTime.MM.replace('%d', '')],
[60 * 60 * 24 * 30 * 12, relativeTime.y],
[60 * 60 * 24 * 30 * 12 + 60 * 60 * 24 * 30, '']
])
let fmt = format == null ? 'YYYY-MM-DD' : (format!)
if (model != null) {
if (model!.size > 0) {
mapmodel = model!
}
}
let str = ""
let fanzhumap = [] as number[];
mapmodel.forEach((value : string, key : number) => {
fanzhumap.push(key)
})
fanzhumap.reverse()
try {
let startTime = bijiaodate.getTime('s')
let nowTime = this.getTime('s')
let diff = (nowTime - startTime) / 1000
if (diff > 0) {
for (let i = 0; i < fanzhumap.length; i++) {
let key = fanzhumap[i];
if (diff >= key) {
console.log(diff, key, 5 * 60 * 60, '---', fanzhumap)
str = mapmodel.get(key)!
break;
}
}
} else {
str = mapmodel.get(fanzhumap[fanzhumap.length - 1])!
}
if (str == '') {
str = bijiaodate.format(fmt)
}
} catch (e) {
//TODO handle the exception
}
return str
}
/**
* 与目标时间相差多少个单位
* @param {string|xDate|null} target 目标时间
* @param {xDateTypeTime} type 相差的单位,默认:s秒
* @returns {string} 反返回以xDateTypeTime为单位相关的时间数
*/
diffTime(target : any, type : xDateTypeTime = 's') : number {
let startTime = 0
let typed : xDateTypeTime = (type == 'w' ? 'd' : type)
if (target instanceof xDate) {
startTime = (target as xDate).getTime(typed)
} else if (typeof target == 'string') {
startTime = new xDate(target as string).getTime(typed)
} else if (target == null) {
startTime = new xDate().getTime(typed)
}
let nowTime = this.getTime(typed)
let diff = Math.abs(nowTime - startTime);
let d = 0
if (type == 's') {
d = diff
} else if (type == 'M') {
d = diff / 60 / 1000
} else if (type == 'h') {
d = diff / 60 / 60 / 1000
} else if (type == 'd') {
d = diff / 60 / 60 / 24 / 1000
} else if (type == 'm') {
d = diff / 60 / 60 / 24 / 30 / 1000
} else if (type == 'y') {
d = diff / 60 / 60 / 24 / 30 / 12 / 1000
} else if (type == 'w') {
let ondate = (1000 * 60 * 60 * 24 * 7);
d = diff / ondate
}
return Math.floor(d)
}
/**
* 获取季度
* @param {string} type y表示获取本年的4个季度,空值表示获取当前时间所在的季度
* @returns {xDateTypeQuarter[]} 返回一个季度数组{quarter:number,start:string,end:string}
*/
getQuarter(type : string = '') : xDateTypeQuarter[] {
let q1 = [1, 2, 3]
let q2 = [4, 5, 6]
let q3 = [7, 8, 9]
let q4 = [10, 11, 12]
let nowMonth = this.getMonth() + 1
let qall = [q1, q2, q3, q4] as number[][]
let qmap = [] as xDateTypeQuarter[]
if (type == 'y') {
for (let i = 0; i < qall.length; i++) {
let item = qall[i]
let sdate = new xDate(this.getYear() + '/' + item[0] + '/1')
let edate = new xDate(this.getYear() + '/' + item[item.length - 1] + '/1')
let start = sdate.format('YYYY/MM/DD')
let end = edate.getDateEndOf('m').format('YYYY/MM/DD')
qmap.push({
quarter: i,
start,
end
} as xDateTypeQuarter)
}
} else {
for (let i = 0; i < qall.length; i++) {
let item = qall[i]
if (item.includes(nowMonth)) {
let sdate = new xDate(this.getYear() + '/' + item[0] + '/1')
let edate = new xDate(this.getYear() + '/' + item[item.length - 1] + '/1')
let start = sdate.format('YYYY/MM/DD')
let end = edate.getDateEndOf('m').format('YYYY/MM/DD')
qmap.push({
quarter: i,
start,
end
} as xDateTypeQuarter)
break;
}
}
}
return qmap;
}
/**
* 设置xDate的语言
* @param {xDateLanguage} locale - 语言代码
*/
setDateLocale(locale : xDateLanguage) : void {
setLocale(locale);
}
/**
* 获取当前xDate使用的语言
* @returns {xDateLanguage} - 当前语言代码
*/
getDateLocale() : xDateLanguage {
return getCurrentLocale();
}
/**
* 根据开始和结束时间取之间的时间(含起始)
* @param {string|xDate|null} start 目标时间
* @param {string|xDate|null} end 目标时间
*/
getBetweenDate(start : string | number | Date, end : string | number | Date, minx : 'min' | 'max' | 'auto' = 'auto') : Date {
let _start = this.checkDate(start);
let _end = this.checkDate(end);
let startDiff = _start.getTime()
let endDiff = _end.getTime()
let nowDiff = this.getTime('ms')
if(nowDiff>=startDiff&&nowDiff<=endDiff){
return this.date;
}
if (minx == 'min') {
return _start;
}
if (minx == 'max') {
return _end;
}
if (startDiff < endDiff) {
return _end;
}
return _start;
}
}
/**
* 将一个日期转换为xDate对象
*/
export const dateCovertXdate = function (date : Date) : xDate {
return new xDate(date)
}
+258
View File
@@ -0,0 +1,258 @@
/**
* xDate国际化支持
* 支持中文、英文、日文、韩文、繁体中文、法语和俄语
*/
export type xDateLanguage = 'zh-Hans' | 'en' | 'ja' | 'ko' | 'zh-Hant' | 'fr' | 'ru';
export type xDateI18nTypeRelativeTime = {
future: string,
past: string,
s: string,
m: string,
mm: string,
h: string,
hh: string,
d: string,
dd: string,
M: string,
MM: string,
y: string,
yy: string
}
export type xDateI18nType = {
weekdays: string[],
weekdaysShort: string[],
months: string[],
monthsShort: string[],
meridiem: (hour: number, minute: number, isLowercase: boolean) => string,
relativeTime: xDateI18nTypeRelativeTime
}
// 中文简体
const zhHans: xDateI18nType = {
weekdays: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
weekdaysShort: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
monthsShort: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
meridiem: (hour: number, minute: number, isLowercase: boolean): string => {
return hour < 12 ? '上午' : '下午';
},
relativeTime: {
future: '%s内',
past: '%s前',
s: '几秒',
m: '1分钟',
mm: '%d分钟',
h: '1小时',
hh: '%d小时',
d: '1天',
dd: '%d天',
M: '1个月',
MM: '%d个月',
y: '1年',
yy: '%d年'
}
};
// 英文
const en: xDateI18nType = {
weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
weekdaysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
meridiem: (hour: number, minute: number, isLowercase: boolean): string => {
return hour < 12 ? (isLowercase ? 'am' : 'AM') : (isLowercase ? 'pm' : 'PM');
},
relativeTime: {
future: 'in %s',
past: '%s ago',
s: 'a few seconds',
m: 'a minute',
mm: '%d minutes',
h: 'an hour',
hh: '%d hours',
d: 'a day',
dd: '%d days',
M: 'a month',
MM: '%d months',
y: 'a year',
yy: '%d years'
}
};
// 日文
const ja: xDateI18nType = {
weekdays: ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日'],
weekdaysShort: ['日', '月', '火', '水', '木', '金', '土'],
months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
monthsShort: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
meridiem: (hour: number, minute: number, isLowercase: boolean): string => {
return hour < 12 ? '午前' : '午後';
},
relativeTime: {
future: '%s後',
past: '%s前',
s: '数秒',
m: '1分',
mm: '%d分',
h: '1時間',
hh: '%d時間',
d: '1日',
dd: '%d日',
M: '1ヶ月',
MM: '%dヶ月',
y: '1年',
yy: '%d年'
}
};
// 韩文
const ko: xDateI18nType = {
weekdays: ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'],
weekdaysShort: ['일', '월', '화', '수', '목', '금', '토'],
months: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
monthsShort: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
meridiem: (hour: number, minute: number, isLowercase: boolean): string => {
return hour < 12 ? '오전' : '오후';
},
relativeTime: {
future: '%s 후',
past: '%s 전',
s: '몇 초',
m: '1분',
mm: '%d분',
h: '1시간',
hh: '%d시간',
d: '1일',
dd: '%d일',
M: '1개월',
MM: '%d개월',
y: '1년',
yy: '%d년'
}
};
// 繁体中文
const zhHant: xDateI18nType = {
weekdays: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
weekdaysShort: ['週日', '週一', '週二', '週三', '週四', '週五', '週六'],
months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
monthsShort: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
meridiem: (hour: number, minute: number, isLowercase: boolean): string => {
return hour < 12 ? '上午' : '下午';
},
relativeTime: {
future: '%s內',
past: '%s前',
s: '幾秒',
m: '1分鐘',
mm: '%d分鐘',
h: '1小時',
hh: '%d小時',
d: '1天',
dd: '%d天',
M: '1個月',
MM: '%d個月',
y: '1年',
yy: '%d年'
}
};
// 法语
const fr: xDateI18nType = {
weekdays: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
weekdaysShort: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
months: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'],
monthsShort: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'],
meridiem: (hour: number, minute: number, isLowercase: boolean): string => {
return '';
},
relativeTime: {
future: 'dans %s',
past: 'il y a %s',
s: 'quelques secondes',
m: 'une minute',
mm: '%d minutes',
h: 'une heure',
hh: '%d heures',
d: 'un jour',
dd: '%d jours',
M: 'un mois',
MM: '%d mois',
y: 'un an',
yy: '%d ans'
}
};
// 俄语
const ru: xDateI18nType = {
weekdays: ['воскресенье', 'понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота'],
weekdaysShort: ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'],
months: ['январь', 'февраль', 'март', 'апрель', 'май', 'июнь', 'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь'],
monthsShort: ['янв.', 'февр.', 'март', 'апр.', 'май', 'июнь', 'июль', 'авг.', 'сент.', 'окт.', 'нояб.', 'дек.'],
meridiem: (hour: number, minute: number, isLowercase: boolean): string => {
return '';
},
relativeTime: {
future: 'через %s',
past: '%s назад',
s: 'несколько секунд',
m: 'минута',
mm: '%d минут',
h: 'час',
hh: '%d часов',
d: 'день',
dd: '%d дней',
M: 'месяц',
MM: '%d месяцев',
y: 'год',
yy: '%d лет'
}
};
// 语言映射表
export const locales: Map<xDateLanguage, xDateI18nType> = new Map([
['zh-Hans', zhHans],
['en', en],
['ja', ja],
['ko', ko],
['zh-Hant', zhHant],
['fr', fr],
['ru', ru]
]);
// 默认语言
let currentLocale: xDateLanguage = 'zh-Hans';
/**
* 获取当前语言设置
*/
export function getCurrentLocale(): xDateLanguage {
return currentLocale;
}
/**
* 设置当前语言
* @param locale 语言代码
*/
export function setLocale(locale: xDateLanguage): void {
if (locales.has(locale)) {
currentLocale = locale;
}
}
/**
* 获取当前语言的本地化数据
*/
export function getLocaleData(): xDateI18nType {
return locales.get(currentLocale)!;
}
/**
* 获取指定语言的本地化数据
* @param locale 语言代码
*/
export function getLocaleDataByCode(locale: xDateLanguage): xDateI18nType | null {
return locales.has(locale) ? locales.get(locale)! : null;
}
+676
View File
@@ -0,0 +1,676 @@
import { xRequestCall } from "./config.uts"
import { xRequestOptions, xRequestOptionsCallBack, xRequestResult, xRequestHistoryType, xRequestMethond } from "../../interface.uts"
import { xConfig } from "../../config/xConfig.uts"
const i18n = xConfig.i18n;
/**
* 请求库xRequest
* @version 1.0.0
* @author tmzdy
* @host https://xui.tmui.design
* @description 异常简便,没有繁杂的配置选项,只有快速的开发部署。
*/
/**
* auth 表示全局设置了全限auth为false,后面的所有请求都被中止请求,从而触发此事件。
* 如果此时返回true,将继续请求。
* before 如果返回true,将继续请求。返回false被中断请求。
* after 可以返回请求的后台数据格式化并返回到最终的请求结果中。
*
* auth,before监听函数请务必返回Promise.resolve(boolean),其中函数内得到的arg为xRequestOptionsCallBack类型(请求参数配置数据)
* 除了authbefore 函数体内arg 得到的参数全是xRequestResult(请求结果数据), 返回随意,因为内部不采用。
* after,需要返回 Promise.resolve(xRequestResult),会覆盖success请求后的数据,相当于你在此hook事件中修改和格式化服务器,并返回到最终结果中。
*/
type xRequestEventType = "before" | "after" | "abort" | "timeout" | "error" | "auth" | "success" | "complete"
type funType = (arg : any) => Promise<any>;
type funCall = {
fun : funType,
type : xRequestEventType
}
function getUid(rdix = 1, length = 12) : string {
let ix = "";
// #ifndef APP
ix = Math.floor(Math.random() * rdix * Math.floor(Math.random() * Date.now())).toString().substring(0, length);
// #endif
// #ifdef APP
ix = Math.floor(Math.random() * rdix * Math.floor(Math.random() * Date.now())).toString().substring(0, length as Int);
// #endif
return ix;
}
// 缓存管理器
class xCacheManager {
private cache : UTSJSONObject = {};
constructor() {
let cs = uni.getStorageSync('xCacheManager');
if (cs instanceof UTSJSONObject) {
this.cache = cs! as UTSJSONObject
}
}
// 生成缓存键
generateCacheKey(config : xRequestOptionsCallBack) : string {
const url = config.url;
const method = config.method;
let keyQ = (url?.split('?')?.[0] ?? url) as string;
// 简化缓存键生成,避免复杂对象序列化可能导致的不一致
let key = `${method}_${keyQ}`;
// 对data进行类似处理
if (config.data != null) {
// 如果是简单类型,直接使用
if (!(config.data instanceof UTSJSONObject)) {
key += `_d:${config.data}`;
} else if (config.data instanceof UTSJSONObject) {
let _data = config.data as UTSJSONObject;
let _keys = [] as string[]
for (const key in _data) {
_keys.push(key)
}
// 对于对象,只使用键名作为缓存键的一部分
const dataKeys = _keys.sort()
key += `_d:${dataKeys.join(',')}`;
}
}
return key;
}
// 设置缓存
set(config : xRequestOptionsCallBack, response : any) : void {
const key = this.generateCacheKey(config);
this.cache.set(key, {
data: response,
timestamp: Date.now()
} as UTSJSONObject)
try {
uni.setStorageSync('xCacheManager', this.cache);
} catch (error) {
// 如果存储失败,可能是缓存太大,清理一部分
this.pruneCache();
try {
uni.setStorageSync('xCacheManager', this.cache);
} catch (e) {
console.error('二次缓存设置失败');
}
}
}
// 获取缓存
get(config : xRequestOptionsCallBack) : any | null {
const key = this.generateCacheKey(config);
const cacheItem = this.cache.getJSON(key)
if (cacheItem == null) return null;
const cacheTime = config.cacheTime == 0 ? 60000 : config.cacheTime
const now = Date.now();
const timestamp = cacheItem.getNumber("timestamp")!
// 检查缓存是否过期
if (now - timestamp > cacheTime) {
let allkeys = {} as UTSJSONObject;
for (const keySelf in this.cache) {
if (keySelf != key) {
allkeys.set(keySelf, this.cache[keySelf])
}
}
this.cache = allkeys
uni.setStorageSync('xCacheManager', allkeys);
return null;
}
// console.log('命中缓存:', key);
return cacheItem.getAny('data');
}
// 清除缓存
clear() : void {
this.cache = {};
uni.removeStorageSync('xCacheManager');
}
// 清理部分缓存
private pruneCache() : void {
const _keys = [] as string[]
for (const key in this.cache) {
_keys.push(key)
}
if (_keys.length > 20) { // 如果缓存项超过20个
// 按时间戳排序,删除最旧的一半
const sortedKeys = _keys.sort((a : string, b : string) : number => {
let ad = this.cache[a]! as UTSJSONObject
let bd = this.cache[b]! as UTSJSONObject
return ad.getNumber('timestamp')! - bd.getNumber('timestamp')!
});
const toRemove = sortedKeys.slice(0, Math.floor(_keys.length / 2));
let allkeys = {} as UTSJSONObject;
for (let i = 0; i < sortedKeys.length; i++) {
let key = sortedKeys[i]
allkeys.set(key, this.cache[key])
}
this.cache = allkeys
uni.setStorageSync('xCacheManager', allkeys);
console.log(`已清理${toRemove.length}个旧缓存项`);
}
}
}
export class xRequest {
private auth = true;
private cacheManager : xCacheManager;
private selfOpts = {
useCache: false,
cacheTime: 60 * 1000,
hostUrl: xRequestCall.hostUrl,
successStatusCode: 200,
url: "",
data: {} as UTSJSONObject,
header: {
'content-type': 'application/json'
} as UTSJSONObject,
method: "GET",
timeout: 6000,
firstIpv4: false,
/** 请求前是否显示loading遮罩 */
showLoadToast: xRequestCall.showLoadToast,
/** 请求成功后是否提示,否则不提示 */
showSuccessToast: xRequestCall.showSuccessToast,
/** 出错时,是否显示提示,否则不提示 */
showErrorToast: xRequestCall.showErrorToast,
loadToastText: i18n.t("tmui4x.xRequest.loading"),
successToastText: i18n.t("tmui4x.xRequest.success"),
errorToastText: ""
} as xRequestOptionsCallBack
private _lisentEventList = [] as funCall[]
private status = null as xRequestEventType | null
private result = {
data: null,
statusCode: 0,
header: {} as any,
cookies: [] as string[],
} as xRequestResult
private reqtask = null as null | RequestTask
constructor(opts : xRequestOptions | null = null) {
this.setOptions(opts)
this.cacheManager = new xCacheManager()
}
/**
* 设置当前请求的参数
* 只影响本次的实例请求,不影响其它请求。
* 但配置中的header,data会与全局的合并。
*/
setOptions(opts : xRequestOptions | null = null) : xRequestOptionsCallBack {
if (opts == null) return this.selfOpts;
let headerTmep = opts.header
headerTmep = opts.header == null ? this.selfOpts.header : opts.header!
// #ifdef WEB
headerTmep = opts.header == undefined ? this.selfOpts.header : opts.header
// #endif
let dataTmep = opts?.data ?? ({} as UTSJSONObject)
if (xRequestCall.header != null) {
let obj = xRequestCall.header as UTSJSONObject;
let newObj = headerTmep! as UTSJSONObject;
for (let key in newObj) {
obj.set(key, newObj.get(key))
}
headerTmep = obj;
}
let firstIpv4 = (opts?.firstIpv4 ?? false) as boolean;
let method = (opts?.method ?? this.selfOpts.method) as xRequestMethond
let timeout = (opts?.timeout ?? this.selfOpts.timeout) as number
let successStatusCode = (opts?.successStatusCode ?? this.selfOpts.successStatusCode) as number
let url = (opts?.url ?? this.selfOpts.url) as string
let showLoadToast = (opts?.showLoadToast ?? this.selfOpts.showSuccessToast) as boolean
let showSuccessToast = (opts?.showSuccessToast ?? this.selfOpts.showSuccessToast) as boolean
let showErrorToast = (opts?.showErrorToast ?? this.selfOpts.showErrorToast) as boolean
let hostUrl = (opts?.hostUrl ?? xRequestCall.hostUrl) as string
let cacheTime = (opts?.cacheTime ?? this.selfOpts.cacheTime) as number
let useCache = (opts?.useCache ?? this.selfOpts.useCache) as boolean
let loadToastText = (opts?.loadToastText ?? this.selfOpts!.loadToastText) as string
let successToastText = (opts?.successToastText ?? this.selfOpts!.successToastText) as string
let errorToastText = (opts?.errorToastText ?? this.selfOpts!.errorToastText) as string
this.selfOpts.firstIpv4 = firstIpv4!
this.selfOpts.method = method! as xRequestMethond;
this.selfOpts.timeout = timeout!
this.selfOpts.cacheTime = cacheTime!
this.selfOpts.useCache = useCache!
this.selfOpts.successStatusCode = successStatusCode!
this.selfOpts.data = dataTmep!
this.selfOpts.header = headerTmep!
this.selfOpts.url = url! as string
this.selfOpts.showLoadToast = showLoadToast! as boolean;
this.selfOpts.showSuccessToast = showSuccessToast! as boolean
this.selfOpts.showErrorToast = showErrorToast! as boolean
this.selfOpts.responseType = opts.responseType
this.selfOpts.dataType = opts.dataType
this.selfOpts.hostUrl = hostUrl
this.selfOpts!.loadToastText = loadToastText
this.selfOpts!.successToastText = successToastText
this.selfOpts!.errorToastText = errorToastText
return this.selfOpts;
}
/**
* 添加事件监听
* 可以重复添加,被添加的函数会被按顺序执行。
* 相当于Hooks
*/
addEventListener(event : xRequestEventType = "success", fun : (arg : any) => Promise<any>) : xRequest {
this._lisentEventList.push({
type: event,
fun
} as funCall)
return this;
}
/**
* 中断当前的事件
* @returns {boolean} true表示中断成功,false表示失败
*/
abort() : boolean {
if (this.reqtask != null) {
this.reqtask!.abort()
return true;
}
return false
}
/**
* 设置请求授权失败还是通过
* 如果设置了false
* 接下来的所有请求全部会被中断掉。哪怕你异步或者跨页面发起请求。
*/
static setAuth(isPass : boolean) {
xRequestCall.authPass = isPass
}
/**
* 设置的头数据是会被所有请求共享
* 包括跨页面。
* 同时新增的数据会与旧的数据合并,如果之前不存在则新增。
*/
static setHeader(header : UTSJSONObject | null = null) : UTSJSONObject | null {
if (xRequestCall.header == null) {
xRequestCall.header = header;
} else {
let obj = xRequestCall.header as UTSJSONObject;
let newObj = header! as UTSJSONObject;
for (let key in newObj) {
obj.set(key, newObj.get(key))
}
xRequestCall.header = obj;
}
return xRequestCall.header
}
/**
* 设置请求的主域名地址
* 所有的请求共享,包括跨页面。
*/
static setHostUrl(url : string | null) {
xRequestCall.hostUrl = url == null ? "" : url!;
}
static setDev(dev : boolean = false) {
xRequestCall.dev = dev;
if (dev) {
console.warn('tmui4.0提醒:' + `你开启了请求调试模式`)
} else {
console.warn('tmui4.0提醒:' + `你关闭了请求调试模式`)
}
}
/**
* 获取整个应用运行期间的所有请求日志列表
*/
static getHistory() : xRequestHistoryType[] {
return xRequestCall.history.slice(0)
}
static setShowToast(showLoadToast : boolean = true, showSuccessToast : boolean = true, showErrorToast : boolean = true) {
xRequestCall.showLoadToast = showLoadToast
xRequestCall.showSuccessToast = showSuccessToast
xRequestCall.showErrorToast = showErrorToast
}
/**
* 执行Hooks函数。
*/
private _callFun_build() : Promise<any[]> {
if (this.status == null) return Promise.resolve([true] as any[]);
// "before"|"after"|"abort"|"timeout"|"error"|"auth"|"success"|"complete"
let fun = this._lisentEventList.filter((el) : boolean => el.type == this.status!)
let funCalls = fun.map((el) : funType => el.fun)
if (funCalls.length == 0) {
if (this.status == 'auth' || this.status == 'before') {
return Promise.resolve([true] as any[]);
}
return Promise.resolve([this.result] as any[]);
}
return this._buildPromise(funCalls)
}
/**
* 递归执行异步函数
* 由于官方的Promise.all,不知道是bug还是什么,始终无法执行成功
* 因此自己写了个。
*/
private _buildPromise(lst : funType[]) : Promise<any[]> {
let _this = this;
let len = lst.length;
let i = 0;
let p = [] as any[]
async function customPromiseAll(evt : funType) : Promise<any> {
if (i >= len) return Promise.resolve(p);
let arg : any = _this.selfOpts
// 非这两事件,全部传递结果集给监听事件中。
if (_this.status != 'auth' && _this.status != 'before') {
arg = _this.result as xRequestResult
}
let v = await evt(arg);
// 如果这两事件中返回的不是boolean,则强制转换为true通过。
if ((_this.status == 'auth') && typeof v != 'boolean') {
v = true;
}
if ((_this.status == 'before') && typeof v != 'boolean') {
let beforeOpts = JSON.stringify(v)
_this.setOptions(JSON.parse<xRequestOptions>(beforeOpts!)!)
v = true;
}
// before中按顺序中断了前面的,后面的不要再执行,直接结束。
if ((_this.status == 'before') && typeof v == 'boolean') {
let iabort = v as boolean;
if (!iabort) {
p.push(v);
return Promise.resolve(p);
}
}
// 请后,与事件中返回的修改后的数据覆盖原本的。
if ((_this.status == 'after') && typeof v == 'object') {
_this.result = v as xRequestResult
}
// 其它事件则全部结果集。
if (_this.status == 'success' || _this.status == 'timeout' || _this.status == 'error' || _this.status == 'abort' || _this.status == 'complete') {
v = _this.result
}
i += 1;
p.push(v);
if (i >= len) return Promise.resolve(p);
return await customPromiseAll(lst[i]);
}
return new Promise((res, rej) => {
customPromiseAll(lst[i]).then(() => {
res(p)
})
})
}
/**
* 请置指定请求的状态值。
*/
private _setDevReqStatus(id : string) {
if (!xRequestCall.dev) return;
let eventType = this.status;
let index = -1;
for (let i = 0; i < xRequestCall.history.length; i++) {
let item = xRequestCall.history[i];
if (id == item.id) {
index = i;
break;
}
}
if (index > -1 && eventType != null) {
xRequestCall.history[index].status = eventType;
if (eventType == 'complete' || eventType == 'abort' || eventType == 'auth') {
xRequestCall.history[index].loaded = true;
xRequestCall.history[index].loading = false;
}
if (eventType == 'before') {
xRequestCall.history[index].loading = true;
}
if (eventType == 'success') {
xRequestCall.history[index].time = Date.now() - xRequestCall.history[index].time;
}
if (eventType == 'complete' || eventType == 'success') {
xRequestCall.history[index].result = this.result;
}
}
}
async request(opts : xRequestOptions | null = null) : Promise<xRequestResult> {
let _this = this;
let _thisOpts = this.setOptions(opts)
let requestId = getUid()
if (xRequestCall.dev) {
xRequestCall.history.push({
id: requestId,
loading: true,
loaded: false,
status: "",
time: Date.now(),
api: _thisOpts.url,
result: {} as any
} as xRequestHistoryType)
}
if (_thisOpts.showLoadToast) {
uni.showLoading({ title: _thisOpts!.loadToastText!, mask: true })
}
const url = _thisOpts.hostUrl + _thisOpts.url;
if (url == "" || _thisOpts.hostUrl == "") {
this.status = 'error';
this._setDevReqStatus(requestId);
await this._callFun_build()
console.warn("未填写请求接口")
if (_thisOpts.showLoadToast) {
uni.hideLoading()
}
if (_thisOpts.showErrorToast) {
return new Promise<xRequestResult>((tres, trej) => {
uni.showToast({ title: i18n.t("tmui4x.xRequest.hostFailEmpty"), icon: 'error', mask: true, complete() { trej(_this.result) } })
})
}
return Promise.reject(_this.result)
}
if (xRequestCall.authPass == false) {
this.status = 'auth'
console.warn("授权失败,中断请求")
this._setDevReqStatus(requestId);
await this._callFun_build()
if (_thisOpts.showLoadToast) {
uni.hideLoading()
}
return Promise.reject(_thisOpts)
}
// 检查是否使用缓存,只针对post及get方式进行缓存.
if (_thisOpts.useCache && (_thisOpts.method == 'GET' || _thisOpts.method == 'POST')) {
// 尝试从缓存获取响应
const cachedResponse = this.cacheManager.get(_thisOpts);
if (cachedResponse != null) {
if (process.env.NODE_ENV == 'development') {
console.log('缓存接口:', _thisOpts.url)
}
const cachedata = cachedResponse as UTSJSONObject
const _cookies = cachedata.getArray<string>('cookies');
const cacheResult = {
data: cachedata.getAny('data'),
statusCode: cachedata.getNumber('statusCode')!,
header: cachedata.getAny('header')!,
cookies: _cookies == null ? ([] as string[]) : _cookies!,
} as xRequestResult
if (_thisOpts.showLoadToast) {
uni.hideLoading()
}
return Promise.resolve(cacheResult);
}
}
this.status = 'before';
this._setDevReqStatus(requestId);
let jg2 = await this._callFun_build()
let isAbor = (jg2 as boolean[]).some((el) : boolean => !el)
// 是否中断请求
if (isAbor) {
console.warn("事件before中断了请求")
this.status = 'abort';
this._setDevReqStatus(requestId);
await this._callFun_build()
if (_thisOpts.showLoadToast) {
uni.hideLoading()
}
return Promise.reject(this.result)
}
// "before" | "after" | "abort" | "timeout" | "error" | "auth" | "success" | "complete"
return new Promise((res, rej) => {
_this.reqtask = uni.request({
url: url,
data: _thisOpts.data,
header: _thisOpts.header,
method: _thisOpts.method,
timeout: _thisOpts.timeout,
firstIpv4: _thisOpts.firstIpv4,
sslVerify: false,
withCredentials: false,
dataType: _thisOpts.dataType,
responseType: _thisOpts.responseType,
success(rst) {
if (process.env.NODE_ENV == 'development') {
console.log('请求接口成功:', _thisOpts.url, rst)
}
_this.result = {
data: rst.data,
statusCode: rst.statusCode,
header: rst.header,
cookies: rst.cookies as string[],
} as xRequestResult
_this.status = 'after';
_this._setDevReqStatus(requestId);
_this._callFun_build()
.then((v) => {
if (_thisOpts.successStatusCode != rst.statusCode) {
_this.status = 'error';
_this.result.statusCode = rst.statusCode
_this._setDevReqStatus(requestId);
_this._callFun_build()
.then(() => {
if (_thisOpts.showLoadToast) {
uni.hideLoading()
}
if (_thisOpts.showErrorToast) {
let msg = _thisOpts!.errorToastText
msg = msg==""?(i18n.t("tmui4x.xRequest.error")+rst.statusCode.toString()):msg
uni.showToast({ title: msg!, mask: true, icon: 'error', complete() { rej(_this.result) } })
} else {
rej(_this.result)
}
})
return;
}
let jgtss = v as xRequestResult[]
let formartv = _this.result as xRequestResult
if (jgtss.length > 0) {
formartv = jgtss[jgtss.length - 1]
}
_this.result = formartv;
_this.status = 'success';
_this._setDevReqStatus(requestId);
_this._callFun_build()
.then(() => {
if (_thisOpts.showLoadToast) {
uni.hideLoading()
}
_this.cacheManager.set(_thisOpts, _this.result);
if (_thisOpts.showSuccessToast) {
//默认读取data下的msg字段。
let d = formartv.data;
let msg = _thisOpts!.successToastText
if (typeof d == 'object' && d != null && !Array.isArray(d)&&msg=="") {
try {
let s = d as UTSJSONObject;
msg = s.getString('msg') != null ? s.getString('msg')! : msg
} catch (e) {
//TODO handle the exception
console.error("服务没有返回msg字段")
}
}
msg = msg==""?i18n.t("tmui4x.xRequest.success"):msg
uni.showToast({ title: msg!, icon: 'none', mask: true, complete() { res(_this.result) } })
} else {
res(_this.result)
}
})
})
},
fail(er) {
if (process.env.NODE_ENV == 'development') {
console.error(er)
}
if (er.errCode == 5) {
_this.status = 'timeout';
} else {
_this.status = 'error';
}
_this.result.statusCode = er.errCode
_this._setDevReqStatus(requestId);
_this._callFun_build()
.then(() => {
if (_thisOpts.showLoadToast) {
uni.hideLoading()
}
if (_thisOpts.showErrorToast) {
uni.showToast({ title: i18n.t("tmui4x.xRequest.error") + er.errCode.toString(), mask: true, icon: 'error', complete() { rej(_this.result) } })
} else {
rej(_this.result)
}
})
},
complete() {
_this.status = 'complete';
_this._setDevReqStatus(requestId);
_this._callFun_build()
}
})
})
}
}
+435
View File
@@ -0,0 +1,435 @@
/**
* 动画过渡函数
* @version 1.0.0
* @author tmzdy
* @host https://xui.tmui.design
* @description 异常简便,没有繁杂的配置选项,只有快速的开发部署。
*/
import bezier from "./bezier.uts"
import { xTweenStatus, xTweenCallbackFunType, xTweenAnimate, xTweenEventCallFunType, xTweenEventCall } from "../../interface.uts"
import { getUid } from "./xCoreUtil.uts";
type FrameRequestCallback = (time:number)=>void;
export class xTween {
private frameId : null | number = null;
//当前全局动画是否在执行中,只要有一个动画在执行就会是true
isRuning : boolean = false;
//所有动画是否结束
isStoping : boolean = true;
//刷新率,不会立即得出结题,需要有至少执行16ms才可能计算出帖率
frmae = 0
// 舞台是否在执行中
isRendering = false;
private _callListFun : xTweenEventCallFunType[] = []
private _frema_starttimes = 0
private enters : (listAni : xTweenEventCallFunType[], tims : number) => void = (listAni : xTweenEventCallFunType[], tims : number) => { }
private complete : () => void = () => { }
private lastTime = 0
private nextHandle = 0
// 存储回调函数的映射
private callbacks : Map<number, FrameRequestCallback> = new Map();
private easingList = new Map<string, number[]>([
["linear", [0.250, 0.250, 0.750, 0.750]],
["ease", [0.250, 0.100, 0.250, 1.000]],
["easeIn", [0.420, 0.000, 1.000, 1.000]],
["easeOut", [0.000, 0.000, 0.580, 1.000]],
["easeInOut", [0.420, 0.000, 0.580, 1.000]],
["easeInQuad", [0.550, 0.085, 0.680, 0.530]],
["easeOutQuad", [0.250, 0.460, 0.450, 0.940]],
["easeInOutQuad", [0.455, 0.030, 0.515, 0.955]],
["easeInCubic", [0.550, 0.055, 0.675, 0.190]],
["easeOutCubic", [0.215, 0.610, 0.355, 1.000]],
["easeInOutCubic", [0.645, 0.045, 0.355, 1.000]],
["easeInQuart", [0.895, 0.030, 0.685, 0.220]],
["easeOutQuart", [0.165, 0.840, 0.440, 1.000]],
["easeInOutQuart", [0.770, 0.000, 0.175, 1.000]],
["easeInQuint", [0.755, 0.050, 0.855, 0.060]],
["easeOutQuint", [0.230, 1.000, 0.320, 1.000]],
["easeInOutQuint", [0.860, 0.000, 0.070, 1.000]],
["easeInSine", [0.470, 0.000, 0.745, 0.715]],
["easeOutSine", [0.390, 0.575, 0.565, 1.000]],
["easeInOutSine", [0.445, 0.050, 0.550, 0.950]],
["easeInExpo", [0.950, 0.050, 0.795, 0.035]],
["easeOutExpo", [0.190, 1.000, 0.220, 1.000]],
["easeInOutExpo", [1.000, 0.000, 0.000, 1.000]],
["easeInCirc", [0.600, 0.040, 0.980, 0.335]],
["easeOutCirc", [0.075, 0.820, 0.165, 1.000]],
["easeInOutBack", [0.680, -0.550, 0.265, 1.550]],
["tmxEase", [0.42, 0.38, 0.15, 0.93]]
]);
constructor() {
}
private customRequestAnimationFrame(callback : FrameRequestCallback) : number {
const currTime : number = Date.now();
const timeToCall : number = Math.max(0, 16 - (currTime - this.lastTime));
const handle : number = this.nextHandle++;
this.callbacks.set(handle, callback);
const id = setTimeout(() => {
callback(currTime + timeToCall);
}, timeToCall);
this.lastTime = currTime + timeToCall;
return handle;
}
private customCancelAnimationFrame(handle : number) {
if (this.callbacks.has(handle)) {
this.callbacks.delete(handle);
clearTimeout(handle);
}
}
/**
* 启动渲染,并非执行动画,执行动画需要使用play
* 但如果不启动渲染,play中的动画都无法执行.
*/
startRender() : xTween {
this.isRendering = true;
if (this.frameId == null) {
this._run(this)
}
return this;
}
/**
* 销毁渲染.
*/
destroy() {
if (this.frameId != null) {
// #ifdef APP||WEB
cancelAnimationFrame(this.frameId!)
// #endif
// #ifdef MP
this.customCancelAnimationFrame(this.frameId!)
// #endif
}
this.frameId = null;
this.isRendering = false;
}
getFrame() : number {
return this.frmae
}
/**
* 所有事件动画执行完毕
*/
setComplete(call : () => void) : xTween {
this.complete = call
return this;
}
/**
* 设置帖动画,舞台渲染执行帖函数
* 它不是animate动画,没有进度,只会不停的执行.直接舞台渲染被注销.
*/
setEnter(call : (listAni : xTweenEventCallFunType[], tims : number) => void) : xTween {
this.enters = call
return this;
}
/**
* 添加一个动画,可以重复加达到多个动画联合执行的效果
* 并且 每一个动画都是独立存在,这样可以细微的控制每个动画
* 来达到联合处理的效果.
*/
addAnimate(opts : xTweenAnimate) : string {
let uid = getUid();
let call : xTweenEventCall = (item : xTweenEventCallFunType) => { };
this._callListFun.push({
id: uid,
ease: this._getEasing(opts?.ease ?? 'linear'),
duration: opts.duration,
status: 1,
progress: 0,
oldProgeress: 0,
startTime: 0,
autoRemove: opts?.autoRemove != null ? (opts!.autoRemove as boolean) : true,
complete: opts?.complete != null ? (opts!.complete!) : call,
enter: opts?.enter != null ? (opts!.enter!) : call,
start: opts?.start != null ? (opts!.start!) : call,
pause: opts?.pause != null ? (opts!.pause!) : call,
loop: opts?.loop != null ? (opts!.loop!) : 1,
tyty: opts?.tyty != null ? (opts!.tyty!) : false,
step: opts?.step != null ? (opts!.step!) : 1,
_finishLoop: 0,
reverse: false
} as xTweenEventCallFunType)
return uid;
}
/**
* 删除一个事件.
* @param {string} uid 如果为null表示删除所有.
*/
removeAnimate(uid : string | null = null) : xTween {
if (uid == null) {
this._callListFun = [] as xTweenEventCallFunType[]
} else {
let index = this._getCallIndex(uid as string);
if (index > -1) {
this._callListFun.splice(index, 1)
}
}
return this;
}
/**
* 播放动画。
* @param {string|null} uid 动画id,如果不填写或者为null表示播放所有动画
*/
play(uid : string | null = null) {
if (uid != null && uid != '') {
let index = this._getCallIndex(uid! as string)
if (index > -1) {
let item = this._callListFun[index]
if (item.status == 1) {
this._by_run_set_status(item, 1)
this._by_run_item_callFun(item, 0)
}
// 马上要执行运行
this._by_run_set_status(item, 4)
}
} else {
for (let i = 0; i < this._callListFun.length; i++) {
let item = this._callListFun[i]
if (item.status == 1) {
this._by_run_set_status(item, 1)
this._by_run_item_callFun(item, 0)
}
this._by_run_set_status(item, 4)
}
}
this._setGlobaleStatus(1)
}
getAnimationListLen() : number {
return this._callListFun.length;
}
/**
* 暂停动画。
* @param {string|null} uid 动画id,如果不填写或者为null表示暂停所有动画
*/
pause(uid : string | null = null) : xTween {
if (uid != null && uid != '') {
let index = this._getCallIndex(uid! as string)
if (index > -1) {
let item = this._callListFun[index]
this._by_run_set_status(item, this._isLoopPauseing(item) ? 6 : 3)
this._by_run_item_callFun(item, item.progress)
}
} else {
for (let i = 0; i < this._callListFun.length; i++) {
let item = this._callListFun[i]
this._by_run_set_status(item, this._isLoopPauseing(item) ? 6 : 3)
this._by_run_item_callFun(item, item.progress)
}
this._setGlobaleStatus(2)
}
return this;
}
/**
* 结束动画。
* @param {string|null} uid 动画id,如果不填写或者为null表示结束所有动画
*/
stop(uid : string | null = null) : xTween {
if (uid != null && uid != '') {
let index = this._getCallIndex(uid! as string)
if (index > -1) {
let item = this._callListFun[index]
this._by_run_set_status(item, 2)
this._by_run_item_callFun(item, 1)
}
} else {
for (let i = 0; i < this._callListFun.length; i++) {
let item = this._callListFun[i]
this._by_run_set_status(item, 2)
this._by_run_item_callFun(item, 1)
}
this._callListFun = this._callListFun.filter((el : xTweenEventCallFunType) : boolean => !el.autoRemove)
this.enters(this._callListFun, 0)
}
return this;
}
private _getEasing(args : any) : xTweenCallbackFunType | null {
let fun : xTweenCallbackFunType | null = (x : number) : number => x;
if (typeof args == 'string') {
let animateNumber = this.easingList.get(args as string)
if (animateNumber != null) {
let ease = animateNumber! as number[]
fun = bezier(ease[0], ease[1], ease[2], ease[3])
}
} else if (Array.isArray(args)) {
let argsar = args as number[]
if (argsar.length == 4) {
fun = bezier(argsar[0], argsar[1], argsar[2], argsar[3])
}
} else {
let animateNumber = this.easingList.get('linear')
if (animateNumber != null) {
let ease = animateNumber! as number[]
fun = bezier(ease[0], ease[1], ease[2], ease[3])
}
}
return fun;
}
private _getCallIndex(uid : string) : number {
let index = -1;
for (let i = 0; i < this._callListFun.length; i++) {
let item = this._callListFun[i]
if (item.id == uid) {
index = i;
break;
}
}
return index;
}
private _isLoopPauseing(item : xTweenEventCallFunType) : boolean {
if (item.loop == -1) return true;
if (item._finishLoop < item.loop) return true;
return false;
}
private _by_run_set_status(item : xTweenEventCallFunType, status : xTweenStatus) {
if (status == 1) {
item.progress = 0
item.startTime = 0
item._finishLoop = 0
} else if (status == 2) {
item.progress = 1;
item.oldProgeress = 0;
item.startTime = 0
item._finishLoop = item.loop
} else if (status == 3) {
item.startTime = 0
item.oldProgeress = item.progress
} else if (status == 5) {
item.progress = 0
item.oldProgeress = 0
item.startTime = 0
} else if (status == 6) {
item.oldProgeress = item.progress
item.startTime = 0
}
item.status = status;
}
private _by_run_item_callFun(item : xTweenEventCallFunType, progress : number) {
if (item.status == 1) {
item.start(item)
}
if (item.status == 2) {
item.enter(item)
item.complete(item)
if (item.autoRemove) {
let index = this._getCallIndex(item.id)
if (index > -1) {
this._callListFun.splice(index, 1)
}
}
}
if (item.status == 3) {
item.pause(item)
}
if (item.status == 4 || item.status == 5) {
item.enter(item)
}
}
/**
* 1:执行中
* 2:未在执行动画。
*/
private _setGlobaleStatus(type : number) {
if (type == 1) {
this.isRuning = true;
this.isStoping = false
} else if (type == 2) {
this.isRuning = false;
this.isStoping = true
}
}
private _run(_this : xTween) {
if (!_this.isRendering) {
return;
}
function actions(times:number){
_this.enters(_this._callListFun, times)
if (_this._frema_starttimes == 0) {
_this._frema_starttimes = times
}
let isAllCompelted = true;
for (let i = 0; i < _this._callListFun.length; i++) {
let item = _this._callListFun[i]
let isFinishStatus = 1
if (item.status == 4 || item.status == 5) {
if (item.startTime == 0) {
item.startTime = times
}
// 计算当前进度
if (_this.frmae > 0) {
let progress = Math.min((times - (item.startTime)) / item.duration + item.oldProgeress, 1);
let eas = item.ease!
item.progress = eas(progress)
if (progress == isFinishStatus) {
item._finishLoop += 1
if (item.loop > 0) {
if (item.loop == item._finishLoop) {
_this._by_run_set_status(item, 2)
_this._by_run_item_callFun(item, isFinishStatus)
} else {
_this._by_run_set_status(item, 5)
}
} else if (item.loop == -1) {
_this._by_run_set_status(item, 5)
}
if (item.tyty) {
item.reverse = !item.reverse
}
}
_this._by_run_item_callFun(item, item.progress)
}
}
if (item.progress != 2) {
isAllCompelted = false;
}
}
if (isAllCompelted && _this.isRuning) {
_this.complete()
_this._setGlobaleStatus(2)
}
_this.frmae = Math.ceil(1000 / (times - _this._frema_starttimes))
_this._frema_starttimes = times
_this._run(_this)
}
// #ifdef APP||WEB
_this.frameId = requestAnimationFrame((times : number) => {
actions(times)
})
// #endif
// #ifdef MP
_this.frameId = this.customRequestAnimationFrame((times : number) => {
actions(times)
})
// #endif
}
}
@@ -0,0 +1,528 @@
import { XUPLOADFILE_INFO, XUPLOADFILE_FILE_VALUE, XUPLOADFILE_FILE_INFO, XUPLOADFILE_EVENT_NAME } from "../../interface.uts"
import { getUid } from "./xCoreUtil.uts"
import { xConfig } from "../../config/xConfig.uts"
type CONFIG = {
count : number,
sourceType : Array<string>,
sizeType : Array<string>,
hostUrl : string,
name : string,
header : UTSJSONObject,
formData : UTSJSONObject,
multipart : boolean,
autoUpload : boolean,
statusCode : number,
compress:boolean,
quality:number|null,
compressedHeight:number|null,
compressedWidth:number|null
}
/**
* 媒体文件上传
*/
export class xUploadMedia {
model = 'photo'
videoOps={
pageOrientation:'auto',
albumMode:'system',
sourceType:['album', 'camera'] as string[],
compressed:true,
maxDuration:60,
camera:'back'
} as UTSJSONObject
config = {
count: 9,
sourceType: ['album', 'camera'],
sizeType: ['original', 'compressed'],
hostUrl: "",
name: "file",
header: {} as UTSJSONObject,
multipart: false,
formData:{} as UTSJSONObject,
autoUpload: true,
statusCode: 200,
compress:true,
quality:80,
compressedHeight:null,
compressedWidth:null
} as CONFIG
fileList : Array<XUPLOADFILE_FILE_INFO> = []
currentIndex = 0;
uploading = false;
uploadObj = null as null | UploadTask
systemError = xConfig.i18n.t("tmui4x.uploadMedia.systemError")
limitMaxCount = xConfig.i18n.t("tmui4x.uploadMedia.limitMaxCount")
constructor(opts : XUPLOADFILE_INFO = {} as XUPLOADFILE_INFO) {
this.chuliConfigArgs(opts)
}
chooseBefore = (res : string[]) : Promise<string[]> => Promise.resolve(res)
complete = (res : XUPLOADFILE_FILE_INFO[]) : Promise<XUPLOADFILE_FILE_INFO[]> => Promise.resolve(res)
change = (res : XUPLOADFILE_FILE_INFO[]) : Promise<XUPLOADFILE_FILE_INFO[]> => Promise.resolve(res)
beforeComplete = (res : XUPLOADFILE_FILE_INFO) :XUPLOADFILE_FILE_INFO => res
myChangeSync = function (res : XUPLOADFILE_FILE_INFO[]) { }
beforeUpload = (res : XUPLOADFILE_FILE_INFO) : Promise<XUPLOADFILE_FILE_INFO> => Promise.resolve(res)
events:Map<XUPLOADFILE_EVENT_NAME,(res : any) => Promise<any>> = new Map();
/**
* 处理配置参数
* @param opts 配置参数
*/
private chuliConfigArgs(opts : XUPLOADFILE_INFO) : CONFIG {
this.config = {
count: opts.count == null ? this.config.count : opts.count as number,
statusCode: opts.statusCode == null ? this.config.statusCode : opts.statusCode as number,
sourceType: opts.sourceType == null ? this.config.sourceType : opts.sourceType as string[],
sizeType: opts.sizeType == null ? this.config.sizeType : opts.sizeType as string[],
hostUrl: opts.hostUrl == null ? this.config.hostUrl : opts.hostUrl as string,
name: opts.name == null ? this.config.name : opts.name as string,
header: opts.header == null ? this.config.header : opts.header as UTSJSONObject,
formData: opts.formData == null ? this.config.formData : opts.formData as UTSJSONObject,
multipart: opts.multipart == null ? this.config.multipart : opts.multipart as boolean,
autoUpload: opts.autoUpload == null ? this.config.autoUpload : opts.autoUpload as boolean,
compress: opts.compress == null ? this.config.compress : opts.compress as boolean,
quality: opts.quality == null ? this.config.quality : opts.quality as number,
compressedHeight: opts.compressedHeight,
compressedWidth: opts.compressedWidth,
} as CONFIG
return this.config
}
stop() {
if (this.uploadObj == null) return
this.currentIndex = 0;
this.uploading = false;
this.uploadObj!.abort()
this.uploadObj = null;
console.info("xUploadMedia:中断上传")
}
chooseMedia() {
if (this.fileList.length == this.config.count) {
// "已超最大上传数量"
uni.showToast({ title: this.limitMaxCount, mask: true, icon: 'none' })
console.warn("xUploadMedia:已经超过最大上传数量")
return;
}
if(this.model == 'photo'){
uni.chooseImage({
count: Math.max(this.config.count - this.fileList.length, 0),
sourceType: this.config.sourceType,
sizeType: this.config.sizeType,
success: (res) => {
let temps = [] as UTSJSONObject[]
// #ifdef uniVersion >= 4.31
let items = res.tempFiles;
for(let i=0;i<res.tempFiles.length;i++){
temps.push({path:items[i].path,size:items[i].size} as UTSJSONObject)
}
// #endif
// #ifdef uniVersion < 4.31
temps = res.tempFiles as UTSJSONObject[]
// #endif
let chooseBefore = this.events.get('chooseBefore')
if(chooseBefore!=null){
// 处理新文件。
if (Array.isArray(temps)) {
chooseBefore(res.tempFilePaths).then(() => {
this.addNewFile(temps)
if (this.config.autoUpload && !this.uploading){
this.start()
}
}).catch(er => {
console.error(er)
uni.showModal({
title:this.systemError,
content:er as string,
showCancel:false
})
})
} else {
// @ts-ignore
let tps = res.tempFilePaths as UTSJSONObject
chooseBefore([tps] as UTSJSONObject[]).then(() => {
this.addNewFile(temps)
if (this.config.autoUpload && !this.uploading){
this.start()
}
}).catch(er => {
console.error(er)
uni.showModal({
title:this.systemError,
content:er as string,
showCancel:false
})
})
}
}else{
if (Array.isArray(temps)) {
this.addNewFile(temps)
if (this.config.autoUpload && !this.uploading){
this.start()
}
}else{
// @ts-ignore
let tps = res.tempFilePaths as UTSJSONObject
this.addNewFile([tps] as UTSJSONObject[])
if (this.config.autoUpload && !this.uploading){
this.start()
}
}
}
},
fail: (err) => {
this.chooseBefore([] as string[])
console.warn("xUploadMedia:", err)
}
})
}else if(this.model == 'video'){
let pageOrientation = this.videoOps.getString('pageOrientation')
pageOrientation = pageOrientation==null?'auto':pageOrientation
let albumMode = this.videoOps.getString('albumMode')
albumMode = albumMode==null?'system':albumMode
let sourceType = this.videoOps.getArray<string>('sourceType')
sourceType = sourceType==null?(['album', 'camera'] as string[]):sourceType
let compressed = this.videoOps.getBoolean('compressed')
compressed = compressed==null?true:compressed
let maxDuration = this.videoOps.getNumber('maxDuration')
maxDuration = maxDuration==null?60:maxDuration
let camera = this.videoOps.getString('camera')
camera = camera==null?'back':camera
uni.chooseVideo({
pageOrientation,
albumMode,
sourceType,
compressed,
maxDuration,
camera,
success: (res) => {
let temps = [
{path:res.tempFilePath,size:res.size} as UTSJSONObject
] as UTSJSONObject[]
let chooseBefore = this.events.get('chooseBefore')
if(chooseBefore!=null){
chooseBefore([res.tempFilePath] as string[]).then(() => {
this.addNewFile(temps)
if (this.config.autoUpload && !this.uploading){
this.start()
}
}).catch(er => {
console.error(er)
uni.showModal({
title:this.systemError,
content:er as string,
showCancel:false
})
})
}else{
this.addNewFile(temps)
if (this.config.autoUpload && !this.uploading){
this.start()
}
}
},
fail: (err) => {
this.chooseBefore([] as string[])
console.warn("xUploadMedia:", err)
}
} as ChooseVideoOptions)
}
}
/**
* 增加事件监听
* @param eventName 事件名称
* @param callback(res : T) => Promise<T> 回调,注意如果是chooseBefore,外部回调时res是string[],其它为 XUPLOADFILE_FILE_INFO[]
*/
addListenEvent(eventName : XUPLOADFILE_EVENT_NAME, callback : (res : any) => Promise<any>) {
this.events.set(eventName,callback)
// if (eventName == 'chooseBefore') {
// this.chooseBefore = (res2 : string[]) : Promise<string[]> => callback(res2 as unknown[] as T) as Promise<string[]>
// } else if (eventName == 'complete') {
// this.complete = (res2 : XUPLOADFILE_FILE_INFO[]) : Promise<XUPLOADFILE_FILE_INFO[]> => callback(res2 as unknown[] as T) as Promise<XUPLOADFILE_FILE_INFO[]>
// } else if (eventName == 'change') {
// this.change = (res2 : XUPLOADFILE_FILE_INFO[]) : Promise<XUPLOADFILE_FILE_INFO[]> => callback(res2 as unknown[] as T) as Promise<XUPLOADFILE_FILE_INFO[]>
// }
}
/**
* 增加事件监听
* @param eventName 事件名称
* @param callback(res : T) => Promise<T> 回调,注意如果是chooseBefore,外部回调时res是string[],其它为 XUPLOADFILE_FILE_INFO[]
*/
addListenEventsss<T extends unknown[]>(eventName : XUPLOADFILE_EVENT_NAME, callback : (res : T) => Promise<T>) {
if (eventName == 'chooseBefore') {
this.chooseBefore = (res2 : string[]) : Promise<string[]> => callback(res2 as unknown[] as T) as Promise<string[]>
} else if (eventName == 'complete') {
this.complete = (res2 : XUPLOADFILE_FILE_INFO[]) : Promise<XUPLOADFILE_FILE_INFO[]> => callback(res2 as unknown[] as T) as Promise<XUPLOADFILE_FILE_INFO[]>
} else if (eventName == 'change') {
this.change = (res2 : XUPLOADFILE_FILE_INFO[]) : Promise<XUPLOADFILE_FILE_INFO[]> => callback(res2 as unknown[] as T) as Promise<XUPLOADFILE_FILE_INFO[]>
}
}
/**
* 设置上传的配置参数
* @param opts 配置参数-XUPLOADFILE_INFO
*/
setConfig(opts : XUPLOADFILE_INFO) {
this.chuliConfigArgs(opts)
}
setVideoOps(config:UTSJSONObject,model:string){
this.videoOps = config
this.model = model;
}
setChangeSync(callBack : (res : XUPLOADFILE_FILE_INFO[]) => void) {
this.myChangeSync = function (res2 : XUPLOADFILE_FILE_INFO[]) {
callBack(res2)
}
}
delFile(id : string) : boolean {
let index = this.fileList.findIndex((item : XUPLOADFILE_FILE_INFO) : boolean => item.id == id)
if (index >= 0) {
let item = this.fileList[index];
if (item.status == 1) {
item.status = 4
item.statusText = "取消上传"
this.stop()
}
this.fileList.splice(index, 1)
this.myChangeSync(this.fileList)
return true;
}
return false
}
clear(){
this.fileList = [] as XUPLOADFILE_FILE_INFO[]
this.myChangeSync(this.fileList)
}
/**
* 添加新选择的未上传文件
*/
private addNewFile(files : UTSJSONObject[]) {
if (files.length == 0) return;
let t = this;
// #ifdef APP-ANDROID||APP-IOS
if(this.model == 'photo'){
let i=0;
function compress(){
if(i>=files.length) return;
let item = files[i]! as UTSJSONObject;
uni.compressImage({
src: item.getString('path')!,
quality: t.config.quality,
compressedHeight: t.config.compressedHeight,
compressedWidth: t.config.compressedWidth,
success: (res:CompressImageSuccess) => {
item.set("path",res.tempFilePath)
i+=1;
compress()
},
fail: (err) => {
i+=1;
compress()
}
})
}
if(this.config.compress){
compress()
}
}
// #endif
files.forEach((item : UTSJSONObject) => {
let id = getUid();
let name = "";
let size = 0
// #ifdef APP
let items = item as UTSJSONObject
name = items.getString('path')!
size = items.getNumber('size')!
// #endif
// #ifndef APP
name = item.path!
size = item.size!
// #endif
this.fileList.push({
id: id,
type: "",
size: size,
extension: name.substring(name.lastIndexOf(".") + 1),
statusText: '待上传',
status: 0,
path: name,
progress: 0,
response: "",
name: name.substring(name.lastIndexOf("/") + 1),
model:this.model
} as XUPLOADFILE_FILE_INFO)
})
this.myChangeSync(this.fileList)
}
addFile(files:XUPLOADFILE_FILE_VALUE[]){
let ids = this.fileList.map((el:XUPLOADFILE_FILE_INFO):string=>el.id)
let i=0;
files.forEach((el:XUPLOADFILE_FILE_VALUE)=>{
let oldId = el.id == null?"":el.id! as string;
if(!ids.includes(oldId)){
let id = getUid();
let name = el.url
let status = el?.status??2;
this.fileList.push({
id: id,
type: "",
size: 0,
extension: name.substring(name.lastIndexOf(".") + 1),
statusText: status==2?'上传成功':'待上传',
status: el?.status??2,
path: name,
progress: status==2?100:0,
response: el.response == null ?"":el.response! as string,
name: name.substring(name.lastIndexOf("/") + 1)
} as XUPLOADFILE_FILE_INFO)
++i;
}
})
if(i>0){
this.myChangeSync(this.fileList)
}
}
_addFilesByself(files:XUPLOADFILE_FILE_INFO[]){
this.fileList = files.slice(0)
}
/**
* 获取待上传的文件数量。
*/
private getWaitUploadFilesNumber() : number {
let num = 0;
this.fileList.forEach((item : XUPLOADFILE_FILE_INFO) => {
if (item.status == 0 || item.status == 3 || item.status == 4) {
num += 1;
}
})
return num;
}
start() {
if (this.uploading) return;
this.uploading = true;
this.currentIndex = 0
this.uploadFile();
}
private uploadFile() {
// if (!this.config.autoUpload) return;
// if (this.fileList.length == 0 || this.getWaitUploadFilesNumber() == 0) {
// this.uploadObj = null
// this.uploading = false;
// console.warn("xUploadMedia:上传结束了")
// return;
// }
if (this.currentIndex >= this.fileList.length) {
this.uploadObj = null;
this.uploading = false;
this.complete(this.fileList)
this.myChangeSync(this.fileList)
let complete = this.events.get('complete')
if(complete!=null){
complete(this.fileList.slice(0))
}
return;
}
this.beforeUpload(this.fileList[this.currentIndex]).then((beforeUploadFileRes:XUPLOADFILE_FILE_INFO)=>{
this.fileList[this.currentIndex] = beforeUploadFileRes;
let nowitemStatus = this.fileList[this.currentIndex].status
if (nowitemStatus == 1 || nowitemStatus == 2 || nowitemStatus == 5) {
this.uploadObj = null
this.uploading = false;
this.currentIndex += 1;
this.uploadFile();
return;
}
this.fileList[this.currentIndex].status = 1;
this.fileList[this.currentIndex].statusText = "上传中";
this.myChangeSync(this.fileList)
this.uploadObj = uni.uploadFile({
url: this.config.hostUrl,
filePath: this.fileList[this.currentIndex].path,
name: this.config.name,
formData: this.config.formData,
header:this.config.header,
success: (uploadFileRes) => {
if (uploadFileRes.statusCode != 200) {
this.fileList[this.currentIndex].status = 3;
this.fileList[this.currentIndex].statusText = "上传失败";
this.currentIndex += 1;
this.uploadFile();
this.myChangeSync(this.fileList)
return;
}
this.fileList[this.currentIndex].status = 2;
this.fileList[this.currentIndex].response = uploadFileRes.data;
this.fileList[this.currentIndex].statusText = "上传成功";
let itemtemp = this.fileList.slice(0)[this.currentIndex]
let calllItem = this.beforeComplete(itemtemp)
this.fileList[this.currentIndex] = calllItem
this.currentIndex += 1;
this.uploadFile();
this.myChangeSync(this.fileList)
},
fail: (err) => {
console.error("上传失败了,请检查配置:",err)
this.fileList[this.currentIndex].status = 3;
this.fileList[this.currentIndex].statusText = "上传失败";
this.currentIndex += 1;
this.uploadFile();
},
complete: () => {
this.myChangeSync(this.fileList)
}
})
this.uploadObj?.onProgressUpdate((res) => {
this.fileList[this.currentIndex].progress = res.progress;
// if(res.progress == 100){
// this.fileList[this.currentIndex].status = 1;
// this.fileList[this.currentIndex].statusText = "上传中";
// }else{
// this.fileList[this.currentIndex].status = 1;
// this.fileList[this.currentIndex].statusText = "上传中";
// }
this.myChangeSync(this.fileList)
})
})
}
}