1
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
import Context from 'android.content.Context'
|
||||
import LinearLayout from 'android.widget.LinearLayout';
|
||||
import ViewGroup from 'android.view.ViewGroup';
|
||||
import View from 'android.view.View';
|
||||
import TextView from 'android.widget.TextView';
|
||||
import ImageView from 'android.widget.ImageView';
|
||||
import Gravity from 'android.view.Gravity';
|
||||
import Color from 'android.graphics.Color';
|
||||
import Activity from 'android.app.Activity';
|
||||
import { ref} from "vue"
|
||||
import TextUtils from 'android.text.TextUtils';
|
||||
import GradientDrawable from 'android.graphics.drawable.GradientDrawable'
|
||||
import MotionEvent from 'android.view.MotionEvent';
|
||||
import Typeface from 'android.graphics.Typeface';
|
||||
import {hexToRgb,getDefaultColor,toFillMarginAr,colorGetHover,colorGetThint} from "../util/xCoreColorUtil.uts"
|
||||
import {getUid,dp2px} from "../util/xCoreUtil.uts"
|
||||
|
||||
import xLinearView from "./linearView.uts";
|
||||
import xCardView from "./cardView.uts";
|
||||
import xIcon from "./icon.uts";
|
||||
import xText from "./text.uts";
|
||||
|
||||
class xButton {
|
||||
view:xLinearView;
|
||||
textLayer:xText;
|
||||
iconLayer:xIcon;
|
||||
bgLayer:xCardView;
|
||||
wrapLinear:xLinearView;
|
||||
width:number = 180;
|
||||
heigth:number = 64;
|
||||
private _backgroundColor = "primary";
|
||||
status = 'default';//default,success,warn,danger
|
||||
type = 'primary';//normal,primary,secondary,dashed,outlin,text
|
||||
|
||||
constructor(context : Context){
|
||||
let boxLinear = new xLinearView(context)
|
||||
let wrapLinear = new xLinearView(context)
|
||||
let bgView = new xCardView(context)
|
||||
let iconView = new xIcon(context)
|
||||
let laberView = new xText(context)
|
||||
|
||||
boxLinear.setLayoutParams(this.width.toInt(),this.heigth.toInt(),0)
|
||||
boxLinear.setAlign(Gravity.CENTER)
|
||||
|
||||
|
||||
|
||||
wrapLinear.setLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT,0)
|
||||
wrapLinear.setAlign(Gravity.CENTER_VERTICAL|Gravity.CENTER)
|
||||
|
||||
bgView.setRadius(6);
|
||||
|
||||
iconView.setFontColor("white").setFontSize(18)
|
||||
laberView.setBackgroundColor('transparent').setFontColor("white").setFontSize(16)
|
||||
laberView.setLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT,0)
|
||||
|
||||
|
||||
wrapLinear.append(iconView.getView(),laberView.getView())
|
||||
bgView.append(wrapLinear.getView() as View)
|
||||
boxLinear.append(bgView.getView() as View)
|
||||
|
||||
this.textLayer = laberView;
|
||||
this.wrapLinear = wrapLinear;
|
||||
this.bgLayer = bgView;
|
||||
this.iconLayer = iconView;
|
||||
this.view = boxLinear;
|
||||
this._setBackgroundColor('','up')
|
||||
bgView.setTouchStart((event : MotionEvent):void=>{
|
||||
this._setBackgroundColor('','down')
|
||||
}).setTouchEnd((event : MotionEvent):void=>{
|
||||
this._setBackgroundColor('','up')
|
||||
})
|
||||
|
||||
}
|
||||
/**
|
||||
* type:
|
||||
* auto:按钮宽度自动为内容宽,w宽度失效。
|
||||
* block:按钮宽度自动为父级宽,w宽度失效。
|
||||
* '':空值,取w,h为按钮宽和高。
|
||||
* 'mini':44,24,10
|
||||
* 'small':64,32,12
|
||||
* 'medium':180,64,15
|
||||
* 'large':220,76,16
|
||||
*/
|
||||
setSize(w:number,h:number,type= ''):xButton{
|
||||
if(type =='auto'){
|
||||
this.width = w;
|
||||
this.heigth = h;
|
||||
this.view.setLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,this.heigth.toInt(),0)
|
||||
}else if(type =='mini'){
|
||||
this.heigth = 28;
|
||||
this.view.setLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,this.heigth.toInt(),0)
|
||||
// this.bgLayer.setLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,this.heigth.toInt(),0)
|
||||
|
||||
|
||||
|
||||
this.setFontSize(10)
|
||||
}else if(type =='small'){
|
||||
this.width = 64;
|
||||
this.heigth = 32;
|
||||
this.view.setLayoutParams(this.width.toInt(),this.heigth.toInt(),0)
|
||||
}else if(type =='medium'||type==''){
|
||||
this.width = 180;
|
||||
this.heigth = 64;
|
||||
this.view.setLayoutParams(this.width.toInt(),this.heigth.toInt(),0)
|
||||
}else if(type =='large'){
|
||||
this.width = 220;
|
||||
this.heigth = 76;
|
||||
this.view.setLayoutParams(this.width.toInt(),this.heigth.toInt(),0)
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
//primary,secondary,dashed,outlin,text
|
||||
setType(str:string):xButton{
|
||||
this.type = str;
|
||||
this._setBackgroundColor('','up')
|
||||
return this;
|
||||
}
|
||||
//primary,secondary,dashed,outlin,text
|
||||
setStatus(str:string):xButton{
|
||||
this.status = str;
|
||||
this._setBackgroundColor('','up')
|
||||
return this;
|
||||
}
|
||||
setDisabled(dis:boolean) : xButton{
|
||||
this.bgLayer.setDisabled(dis);
|
||||
this._setBackgroundColor("#A5A5A5",'down')
|
||||
return this;
|
||||
}
|
||||
setLabel(str:string):xButton{
|
||||
let label = this.textLayer as xText;
|
||||
label.setText(str);
|
||||
return this;
|
||||
}
|
||||
setRadius(radius:number):xButton{
|
||||
let bgView = this.bgLayer as xCardView;
|
||||
bgView.setRadius(radius);
|
||||
return this;
|
||||
}
|
||||
|
||||
setClick(fun?:(event: MotionEvent)=>void) : xButton{
|
||||
if(typeof fun !== 'undefined'){
|
||||
this.bgLayer.setClick(fun)
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
setIcon(name:string):xButton{
|
||||
let iconLayer = this.iconLayer as xIcon;
|
||||
let label = this.textLayer as xText;
|
||||
iconLayer.setIcon(name);
|
||||
label.setPadding([10,0,0,0])
|
||||
return this;
|
||||
}
|
||||
setFontSize(size:number):xButton{
|
||||
let label = this.textLayer as xText;
|
||||
let iconLayer = this.iconLayer as xIcon;
|
||||
label.setFontSize(size.toInt())
|
||||
iconLayer.setFontSize(size.toInt())
|
||||
return this;
|
||||
}
|
||||
|
||||
setFontColor(str:string):xButton{
|
||||
let label = this.textLayer as xText;
|
||||
let iconLayer = this.iconLayer as xIcon;
|
||||
label.setFontColor(str)
|
||||
iconLayer.setFontColor(str)
|
||||
return this;
|
||||
}
|
||||
setBorder(w?:number,colorStr?:string,dashed?:boolean):xButton{
|
||||
this.bgLayer.setBorder(w,colorStr,dashed)
|
||||
return this;
|
||||
}
|
||||
private _setBackgroundColor(str:string,clickStatus:string):xButton{
|
||||
let cr = this._backgroundColor;
|
||||
|
||||
let fontColor = ""
|
||||
if(str !=''){
|
||||
cr = str;
|
||||
}else{
|
||||
if(this.status==""||this.status=="default"){
|
||||
cr = 'primary'
|
||||
}else if(this.status == 'success'){
|
||||
cr = 'green'
|
||||
}else if(this.status == 'warn'){
|
||||
cr = 'orange'
|
||||
}else if(this.status == 'danger'){
|
||||
cr = 'red'
|
||||
}else if(this.status == 'normal'){
|
||||
cr = '#D8D8D8'
|
||||
fontColor = '#333333'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let lightColor = colorGetThint(cr);
|
||||
let defaultColor = colorGetHover(cr);
|
||||
|
||||
if(clickStatus=='down'){
|
||||
if(this.type == 'primary' || this.type==""){
|
||||
this.bgLayer.setBackgroundColor(defaultColor.getString("hover")!);
|
||||
|
||||
}else if(this.type == 'secondary'){
|
||||
this.setFontColor(cr)
|
||||
this.bgLayer.setBackgroundColor(lightColor.getString("hover")!);
|
||||
}else if(this.type == 'outline'){
|
||||
this.setFontColor(cr)
|
||||
this.bgLayer.setBackgroundColor('transparent');
|
||||
this.setBorder(1,cr,false)
|
||||
}else if(this.type == 'dashed'){
|
||||
this.setFontColor(cr)
|
||||
this.bgLayer.setBackgroundColor(lightColor.getString("hover")!);
|
||||
this.setBorder(1,cr,true)
|
||||
}else if(this.type == 'text'){
|
||||
this.setFontColor(defaultColor.getString("hover")!)
|
||||
this.bgLayer.setBackgroundColor('transparent');
|
||||
}
|
||||
}else if(clickStatus=='up'){
|
||||
if(this.type == 'primary' || this.type==""){
|
||||
this.bgLayer.setBackgroundColor(defaultColor.getString("default")!);
|
||||
}else if(this.type == 'secondary'){
|
||||
this.setFontColor(cr)
|
||||
this.bgLayer.setBackgroundColor(lightColor.getString("default")!);
|
||||
}else if(this.type == 'outline'){
|
||||
this.setFontColor(cr)
|
||||
this.bgLayer.setBackgroundColor('transparent');
|
||||
this.setBorder(1,cr,false)
|
||||
}else if(this.type == 'dashed'){
|
||||
this.setFontColor(cr)
|
||||
this.bgLayer.setBackgroundColor(lightColor.getString("default")!);
|
||||
this.setBorder(1,cr,true)
|
||||
}else if(this.type == 'text'){
|
||||
this.setFontColor(cr)
|
||||
this.bgLayer.setBackgroundColor('transparent');
|
||||
}
|
||||
}
|
||||
|
||||
if(fontColor!=''){
|
||||
this.setFontColor(fontColor)
|
||||
}
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setBackgroundColor(str:string):xButton{
|
||||
this.bgLayer.setBackgroundColor(str);
|
||||
this._backgroundColor = str;
|
||||
return this;
|
||||
}
|
||||
|
||||
getView():View{
|
||||
return this.view.getView() as View;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default xButton;
|
||||
@@ -0,0 +1,242 @@
|
||||
import Context from 'android.content.Context'
|
||||
import LinearLayout from 'android.widget.LinearLayout';
|
||||
import RelativeLayout from 'android.widget.RelativeLayout';
|
||||
import ViewGroup from 'android.view.ViewGroup';
|
||||
import View from 'android.view.View';
|
||||
import TextView from 'android.widget.TextView';
|
||||
import ImageView from 'android.widget.ImageView';
|
||||
import Gravity from 'android.view.Gravity';
|
||||
import Color from 'android.graphics.Color';
|
||||
import Activity from 'android.app.Activity';
|
||||
// import CardView from 'androidx.cardview.widget.CardView';
|
||||
import { ref, } from "vue"
|
||||
import TextUtils from 'android.text.TextUtils';
|
||||
import GradientDrawable from 'android.graphics.drawable.GradientDrawable'
|
||||
import MotionEvent from 'android.view.MotionEvent';
|
||||
|
||||
import {hexToRgb,getDefaultColor} from "../util/xCoreColorUtil.uts"
|
||||
import {getUid, dp2px} from "../util/xCoreUtil.uts"
|
||||
|
||||
import xView from "./view.uts";
|
||||
|
||||
|
||||
class xCardView extends xView {
|
||||
override view:any;
|
||||
bgLayer:xView;
|
||||
contentView:any
|
||||
wrap:RelativeLayout
|
||||
contentParams:LinearLayout.LayoutParams
|
||||
constructor(context : Context){
|
||||
super(context);
|
||||
let box = new LinearLayout(context);
|
||||
box.setOrientation(LinearLayout.VERTICAL)
|
||||
box.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
(0).toInt(),
|
||||
(0).toFloat()
|
||||
))
|
||||
let wrap = new RelativeLayout(context);
|
||||
let content = new LinearLayout(context);
|
||||
|
||||
let RLay = new RelativeLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
wrap.setLayoutParams(RLay)
|
||||
|
||||
|
||||
|
||||
this.bgLayer = new xView(context);
|
||||
|
||||
|
||||
content.setBackground(this.bgLayer.bgView);
|
||||
content.setClipToOutline(true);
|
||||
this.contentParams = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
content.setLayoutParams(this.contentParams)
|
||||
content.setOrientation(LinearLayout.VERTICAL)
|
||||
this.contentView = content;
|
||||
this.wrap =wrap;
|
||||
wrap.addView(this.bgLayer.getView())
|
||||
wrap.addView(content as View)
|
||||
box.addView(wrap as View)
|
||||
|
||||
this.view = box;
|
||||
}
|
||||
override getView():LinearLayout{
|
||||
return this.view as LinearLayout;
|
||||
}
|
||||
|
||||
override setBackgroundColor(colorStr : string) : xCardView {
|
||||
|
||||
|
||||
this.bgLayer.setBackgroundColor(colorStr)
|
||||
return this;
|
||||
}
|
||||
|
||||
override setRadius(radius:any) : xCardView{
|
||||
this.bgLayer.setRadius(radius)
|
||||
return this;
|
||||
}
|
||||
|
||||
override setBorder(w?:number,colorStr?:string,dashed?:boolean):xCardView{
|
||||
this.bgLayer.setBorder(w,colorStr,dashed)
|
||||
return this;
|
||||
}
|
||||
|
||||
override setHeight(height : number) : xCardView {
|
||||
console.error("不支持,请通过linerView设置,并将本CardView加入其中来设置高度。")
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
override setPadding(n:any):xCardView{
|
||||
let view = this.contentView as ViewGroup;
|
||||
if(Array.isArray(n)){
|
||||
let rd = n as number[];
|
||||
if(rd.length==0){
|
||||
let rds = 0;
|
||||
let rdf = dp2px(rds).toInt();
|
||||
view.setPadding(rdf,rdf,rdf,rdf)
|
||||
}else if(rd.length==1){
|
||||
let rdf = dp2px(rd[0]).toInt();
|
||||
view.setPadding(rdf,rdf,rdf,rdf)
|
||||
}else if(rd.length==2){
|
||||
view.setPadding(dp2px(rd[0]).toInt(),dp2px(rd[1]).toInt(),dp2px(rd[0]).toInt(),dp2px(rd[1]).toInt())
|
||||
}else if(rd.length==3){
|
||||
let rds = 0;
|
||||
let rdf = rds.toInt();
|
||||
view.setPadding(dp2px(rd[0]).toInt(),dp2px(rd[1]).toInt(),dp2px(rd[2]).toInt(),rdf)
|
||||
}else if(rd.length==4){
|
||||
view.setPadding(dp2px(rd[0]).toInt(),dp2px(rd[1]).toInt(),dp2px(rd[2]).toInt(),dp2px(rd[3]).toInt())
|
||||
}
|
||||
}else{
|
||||
|
||||
let rd = n as number;
|
||||
let rdf = dp2px(rd).toInt();
|
||||
view.setPadding(rdf,rdf,rdf,rdf)
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
override setDisabled(dis:boolean) : xCardView{
|
||||
this.bgLayer.setDisabled(dis)
|
||||
return this;
|
||||
}
|
||||
|
||||
override setSwiper(fun?:(event: MotionEvent,detail?:UTSJSONObject)=>void) : xCardView{
|
||||
if(typeof fun !== 'undefined'){
|
||||
this.bgLayer.setSwiper(fun)
|
||||
}
|
||||
return this;
|
||||
}
|
||||
override setDoubleClick(fun?:(event: MotionEvent)=>void) : xCardView{
|
||||
if(typeof fun !== 'undefined'){
|
||||
this.bgLayer.setDoubleClick(fun)
|
||||
}
|
||||
return this;
|
||||
}
|
||||
override setClick(fun?:(event: MotionEvent)=>void) : xCardView{
|
||||
if(typeof fun !== 'undefined'){
|
||||
this.bgLayer.setClick(fun)
|
||||
}
|
||||
return this;
|
||||
}
|
||||
override setTouchStart(fun?:(event: MotionEvent)=>void) : xCardView{
|
||||
if(typeof fun !== 'undefined'){
|
||||
this.bgLayer.setTouchStart(fun)
|
||||
}
|
||||
return this;
|
||||
}
|
||||
override setTouchMove(fun?:(event: MotionEvent)=>void) : xCardView{
|
||||
if(typeof fun !== 'undefined'){
|
||||
this.bgLayer.setTouchMove(fun)
|
||||
}
|
||||
return this;
|
||||
}
|
||||
override setTouchEnd(fun?:(event: MotionEvent)=>void) : xCardView{
|
||||
if(typeof fun !== 'undefined'){
|
||||
this.bgLayer.setTouchEnd(fun)
|
||||
}
|
||||
return this;
|
||||
}
|
||||
override setTouchCancel(fun?:(event: MotionEvent)=>void) : xCardView{
|
||||
if(typeof fun !== 'undefined'){
|
||||
this.bgLayer.setTouchCancel(fun)
|
||||
}
|
||||
return this;
|
||||
}
|
||||
override setTouchLongPress(fun?:(event: MotionEvent)=>void) : xCardView{
|
||||
if(typeof fun !== 'undefined'){
|
||||
this.bgLayer.setTouchCancel(fun)
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
setAlign(cr:Int) : xCardView{
|
||||
let view = this.contentView as LinearLayout;
|
||||
view.setGravity(cr)
|
||||
return this;
|
||||
}
|
||||
setLayoutParams(width?:number,height?:number,layouWidth?:number):xCardView{
|
||||
let view = this.contentView as LinearLayout;
|
||||
let w = (0).toInt();
|
||||
let h = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
let flex = (1).toFloat();
|
||||
|
||||
if(typeof width =='number'){
|
||||
let tw = width!;
|
||||
w = tw.toInt();
|
||||
}
|
||||
if(typeof height =='number'){
|
||||
let th = height!;
|
||||
h = th.toInt();
|
||||
}
|
||||
if(typeof layouWidth =='number'&&layouWidth!=-1){
|
||||
let tf = layouWidth!;
|
||||
flex = tf.toFloat();
|
||||
console.log('null',layouWidth)
|
||||
let layoutParams_crd = new LinearLayout.LayoutParams(
|
||||
w,
|
||||
h,
|
||||
flex
|
||||
)
|
||||
this.contentParams = layoutParams_crd
|
||||
}else{
|
||||
console.log('null')
|
||||
let layoutParams_crd = new LinearLayout.LayoutParams(
|
||||
w,
|
||||
h
|
||||
)
|
||||
this.contentParams = layoutParams_crd
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 未知原因设置这个会闪退。
|
||||
// view.setLayoutParams(layoutParams_crd);
|
||||
return this;
|
||||
}
|
||||
|
||||
append(children : View,...args: View[]):xCardView{
|
||||
let view = this.contentView as ViewGroup;
|
||||
view.addView(children)
|
||||
args.forEach((el)=>view.addView(el))
|
||||
|
||||
return this;
|
||||
}
|
||||
appendChild(children : View):xCardView {
|
||||
let view = this.contentView as ViewGroup;
|
||||
view.addView(children)
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export default xCardView;
|
||||
@@ -0,0 +1,68 @@
|
||||
import Context from 'android.content.Context'
|
||||
import LinearLayout from 'android.widget.LinearLayout';
|
||||
import ViewGroup from 'android.view.ViewGroup';
|
||||
import View from 'android.view.View';
|
||||
import TextView from 'android.widget.TextView';
|
||||
import ImageView from 'android.widget.ImageView';
|
||||
import Gravity from 'android.view.Gravity';
|
||||
import Color from 'android.graphics.Color';
|
||||
import Activity from 'android.app.Activity';
|
||||
import { ref} from "vue"
|
||||
import TextUtils from 'android.text.TextUtils';
|
||||
import GradientDrawable from 'android.graphics.drawable.GradientDrawable'
|
||||
import MotionEvent from 'android.view.MotionEvent';
|
||||
import Typeface from 'android.graphics.Typeface';
|
||||
import {hexToRgb,getDefaultColor,toFillMarginAr} from "../util/xCoreColorUtil.uts"
|
||||
import {getUid,dp2px} from "../util/xCoreUtil.uts"
|
||||
|
||||
|
||||
class xIcon {
|
||||
view:any
|
||||
constructor(context : Context,name:string=""){
|
||||
let view = new TextView(context);
|
||||
let layoutParams_crd = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
|
||||
view.setGravity(Gravity.CENTER)
|
||||
view.setLayoutParams(layoutParams_crd);
|
||||
this.view = view;
|
||||
|
||||
this._setCodeStr(name)
|
||||
}
|
||||
getView():View{
|
||||
return this.view as View;
|
||||
}
|
||||
setIcon(name:string=""):xIcon{
|
||||
this._setCodeStr(name)
|
||||
return this;
|
||||
}
|
||||
|
||||
setFontSize(n:number):xIcon{
|
||||
let view = this.view as TextView;
|
||||
view.setTextSize(dp2px(n).toFloat())
|
||||
return this;
|
||||
}
|
||||
setFontColor(str:string):xIcon{
|
||||
let view = this.view as TextView;
|
||||
view.setTextColor(Color.parseColor(getDefaultColor(str)))
|
||||
return this;
|
||||
}
|
||||
|
||||
private _setCodeStr(code:string){
|
||||
let view = this.view as TextView;
|
||||
let assetManager = view.getContext()!.getAssets();
|
||||
let typeface = Typeface.createFromAsset(assetManager, "remixicon.ttf")
|
||||
view.setTypeface(typeface)
|
||||
if(!TextUtils.isEmpty(code)){
|
||||
let codePoint = Integer.parseInt(code, 16);
|
||||
let charArray = Character.toChars(codePoint);
|
||||
let text = new String(charArray);
|
||||
view.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default xIcon;
|
||||
@@ -0,0 +1,130 @@
|
||||
import Context from 'android.content.Context'
|
||||
import LinearLayout from 'android.widget.LinearLayout';
|
||||
import RelativeLayout from 'android.widget.RelativeLayout';
|
||||
import ViewGroup from 'android.view.ViewGroup';
|
||||
import View from 'android.view.View';
|
||||
import TextView from 'android.widget.TextView';
|
||||
import ImageView from 'android.widget.ImageView';
|
||||
import Gravity from 'android.view.Gravity';
|
||||
import Color from 'android.graphics.Color';
|
||||
import Activity from 'android.app.Activity';
|
||||
// import CardView from 'androidx.cardview.widget.CardView';
|
||||
import { ref, } from "vue"
|
||||
import TextUtils from 'android.text.TextUtils';
|
||||
import GradientDrawable from 'android.graphics.drawable.GradientDrawable'
|
||||
import MotionEvent from 'android.view.MotionEvent';
|
||||
|
||||
import {hexToRgb,getDefaultColor} from "../util/xCoreColorUtil.uts"
|
||||
import {getUid, dp2px} from "../util/xCoreUtil.uts"
|
||||
|
||||
import xView from "./view.uts";
|
||||
|
||||
class xLinearView extends xView {
|
||||
override view:any;
|
||||
contentParams:LinearLayout.LayoutParams
|
||||
constructor(context : Context){
|
||||
super(context);
|
||||
let box = new LinearLayout(context);
|
||||
this.contentParams = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
)
|
||||
box.setLayoutParams(this.contentParams)
|
||||
|
||||
this.view = box;
|
||||
}
|
||||
override getView():LinearLayout{
|
||||
return this.view as LinearLayout;
|
||||
}
|
||||
|
||||
override setBackgroundColor(colorStr : string) : xLinearView {
|
||||
console.error("不支持,请通过cardView设置")
|
||||
return this;
|
||||
}
|
||||
|
||||
override setRadius(radius:any) : xLinearView{
|
||||
console.error("不支持,请通过cardView设置")
|
||||
return this;
|
||||
}
|
||||
|
||||
override setBorder(w?:number,colorStr?:string,dashed?:boolean):xLinearView{
|
||||
console.error("不支持,请通过cardView设置")
|
||||
return this;
|
||||
}
|
||||
|
||||
override setHeight(height : number) : xLinearView {
|
||||
console.error("不支持,请通过linerView设置,并将本CardView加入其中来设置高度。")
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
//让内容的对齐方向,
|
||||
//https://developer.android.google.cn/reference/kotlin/android/view/Gravity
|
||||
//只有tmxTextView,有这个属性。
|
||||
setAlign(cr:Int) : xLinearView{
|
||||
let view = this.view as LinearLayout;
|
||||
view.setGravity(cr)
|
||||
return this;
|
||||
}
|
||||
|
||||
setLayoutParams(width?:number,height?:number,layouWidth?:number) : xLinearView{
|
||||
let view = this.view as LinearLayout;
|
||||
let w = (0).toInt();
|
||||
let h = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
let flex = (1).toFloat();
|
||||
|
||||
if(typeof width =='number'){
|
||||
w = width!.toInt();
|
||||
}
|
||||
if(typeof height =='number'){
|
||||
h = height!.toInt();
|
||||
}
|
||||
if(typeof layouWidth =='number'&&layouWidth!=-1){
|
||||
let tf = layouWidth!;
|
||||
flex = tf.toFloat();
|
||||
console.log('null',layouWidth)
|
||||
let layoutParams_crd = new LinearLayout.LayoutParams(
|
||||
w,
|
||||
h,
|
||||
flex
|
||||
)
|
||||
this.contentParams = layoutParams_crd
|
||||
}else{
|
||||
console.log('null')
|
||||
let layoutParams_crd = new LinearLayout.LayoutParams(
|
||||
w,
|
||||
h
|
||||
)
|
||||
this.contentParams = layoutParams_crd
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
//内容的排版方向:vertical,horizontal
|
||||
setLayoutDirection(dir:string) : xLinearView{
|
||||
let view = this.view as LinearLayout;
|
||||
if(dir=='VERTICAL'){
|
||||
view.setOrientation(LinearLayout.VERTICAL)
|
||||
}else if(dir=='HORIZONTAL'){
|
||||
view.setOrientation(LinearLayout.HORIZONTAL)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
append(children : View,...args: View[]) : xLinearView {
|
||||
let view = this.view as ViewGroup;
|
||||
view.addView(children)
|
||||
args.forEach((el)=>view.addView(el))
|
||||
|
||||
return this;
|
||||
}
|
||||
appendChild(children : View) : xLinearView {
|
||||
let view = this.view as ViewGroup;
|
||||
view.addView(children)
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export default xLinearView;
|
||||
@@ -0,0 +1,301 @@
|
||||
import Context from 'android.content.Context'
|
||||
import LinearLayout from 'android.widget.LinearLayout';
|
||||
import ViewGroup from 'android.view.ViewGroup';
|
||||
import View from 'android.view.View';
|
||||
import TextView from 'android.widget.TextView';
|
||||
import ImageView from 'android.widget.ImageView';
|
||||
import Gravity from 'android.view.Gravity';
|
||||
import Color from 'android.graphics.Color';
|
||||
import Activity from 'android.app.Activity';
|
||||
// import CardView from 'androidx.cardview.widget.CardView';
|
||||
import { ref, } from "vue"
|
||||
import TextUtils from 'android.text.TextUtils';
|
||||
import GradientDrawable from 'android.graphics.drawable.GradientDrawable'
|
||||
import MotionEvent from 'android.view.MotionEvent';
|
||||
import Typeface from 'android.graphics.Typeface';
|
||||
import {hexToRgb,getDefaultColor,toFillMarginAr} from "../util/xCoreColorUtil.uts"
|
||||
import {getUid,dp2px} from "../util/xCoreUtil.uts"
|
||||
|
||||
import xView from "./view.uts";
|
||||
|
||||
class xText extends xView {
|
||||
override view:any
|
||||
constructor(context : Context,str:string=""){
|
||||
super(context);
|
||||
let view = new TextView(context);
|
||||
let layoutParams_crd = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
(1).toFloat()
|
||||
)
|
||||
view.setText(str);
|
||||
view.setLayoutParams(layoutParams_crd);
|
||||
|
||||
this.bgView = new GradientDrawable();
|
||||
this.bgView.setShape(0x00000000)
|
||||
this.bgView.setColor(Color.WHITE)
|
||||
this.bgView.mutate()
|
||||
view.setBackground(this.bgView)
|
||||
|
||||
this.view = view;
|
||||
}
|
||||
override getView():View{
|
||||
|
||||
return this.view as View;
|
||||
}
|
||||
override setBackgroundColor(colorStr : string) : xText {
|
||||
this.bgView.mutate()
|
||||
if(colorStr == 'transparent'){
|
||||
this.bgView.setColor(Color.TRANSPARENT)
|
||||
}else{
|
||||
this.bgView.setColor(Color.parseColor(getDefaultColor(colorStr)))
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
override setBorder(w?:number,colorStr?:string,dashed?:boolean) : xText {
|
||||
this.bgView.mutate()
|
||||
|
||||
let tw:number = 1;
|
||||
let tcolor = 'transparent';
|
||||
let tdashed = false;
|
||||
if(typeof tw !== 'undefined'){
|
||||
tw = w as number;
|
||||
}
|
||||
if(typeof tw !== 'undefined'){
|
||||
tcolor = colorStr as string;
|
||||
}
|
||||
|
||||
if(typeof dashed !== 'undefined'){
|
||||
tdashed = dashed as boolean;
|
||||
}
|
||||
|
||||
let colorNum = Color.TRANSPARENT;
|
||||
if(tcolor != 'transparent'){
|
||||
colorNum = Color.parseColor(getDefaultColor(tcolor));
|
||||
}
|
||||
if(dashed == true){
|
||||
let dashedWidth = dp2px(6)
|
||||
let dashedGap = dp2px(4)
|
||||
this.bgView.setStroke(dp2px(tw).toInt(),colorNum,dashedWidth.toFloat(),dashedGap.toFloat())
|
||||
}else{
|
||||
this.bgView.setStroke(dp2px(tw).toInt(),colorNum)
|
||||
}
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
override setRadius(radius:any) : xText{
|
||||
let x =0
|
||||
let y =0
|
||||
this.bgView.mutate()
|
||||
if(Array.isArray(radius)){
|
||||
let rd = radius as number[];
|
||||
if(rd.length==0){
|
||||
let rdfs = 0;
|
||||
this.bgView.setCornerRadius(dp2px(rdfs).toFloat());
|
||||
// view.setBackground(this.bgView)
|
||||
}else if(rd.length==1){
|
||||
let rdfs = rd[0];
|
||||
this.bgView.setCornerRadius(dp2px(rdfs).toFloat());
|
||||
// view.setBackground(this.bgView)
|
||||
}else if(rd.length==2){
|
||||
let tl = [
|
||||
dp2px(rd[0]).toFloat(),dp2px(rd[0]).toFloat(),
|
||||
dp2px(rd[1]).toFloat(),dp2px(rd[1]).toFloat(),
|
||||
x.toFloat(),x.toFloat(),
|
||||
x.toFloat(),x.toFloat()
|
||||
]
|
||||
let tlint = tl.toKotlinList().toFloatArray()
|
||||
this.bgView.setCornerRadii(tlint);
|
||||
}else if(rd.length==3){
|
||||
let tl = [
|
||||
dp2px(rd[0]).toFloat(),dp2px(rd[0]).toFloat(),
|
||||
dp2px(rd[1]).toFloat(),dp2px(rd[1]).toFloat(),
|
||||
dp2px(rd[2]).toFloat(),dp2px(rd[2]).toFloat(),
|
||||
x.toFloat(),x.toFloat()
|
||||
]
|
||||
let tlint = tl.toKotlinList().toFloatArray()
|
||||
this.bgView.setCornerRadii(tlint);
|
||||
}else if(rd.length==4){
|
||||
let tl = [
|
||||
dp2px(rd[0]).toFloat(),dp2px(rd[0]).toFloat(),
|
||||
dp2px(rd[1]).toFloat(),dp2px(rd[1]).toFloat(),
|
||||
dp2px(rd[2]).toFloat(),dp2px(rd[2]).toFloat(),
|
||||
dp2px(rd[3]).toFloat(),dp2px(rd[3]).toFloat(),
|
||||
]
|
||||
let tlint = tl.toKotlinList().toFloatArray()
|
||||
this.bgView.setCornerRadii(tlint);
|
||||
}
|
||||
}else{
|
||||
|
||||
let rd = radius as number;
|
||||
|
||||
this.bgView.setCornerRadius(dp2px(rd).toFloat());
|
||||
}
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setFontSize(n:number):xText{
|
||||
let view = this.view as TextView;
|
||||
view.setTextSize(dp2px(n).toFloat())
|
||||
return this;
|
||||
}
|
||||
setFontColor(str:string):xText{
|
||||
let view = this.view as TextView;
|
||||
view.setTextColor(Color.parseColor(getDefaultColor(str)))
|
||||
|
||||
return this;
|
||||
}
|
||||
//是否允许复制。
|
||||
setTextIsSelectable(isSelectet:boolean):xText{
|
||||
let view = this.view as TextView;
|
||||
view.setTextIsSelectable(isSelectet)
|
||||
return this;
|
||||
}
|
||||
//bold,normal,ligth
|
||||
setFontWeight(str:string):xText{
|
||||
let view = this.view as TextView;
|
||||
if(str=='bold'){
|
||||
view.setTypeface(null,Typeface.BOLD)
|
||||
}else{
|
||||
view.setTypeface(null,Typeface.NORMAL)
|
||||
}
|
||||
return this;
|
||||
}
|
||||
setFontStyle(str:string):xText{
|
||||
let view = this.view as TextView;
|
||||
if(str=='italtc'){
|
||||
view.setTypeface(null,Typeface.ITALIC)
|
||||
}else if(str=='italtc-bold'){
|
||||
view.setTypeface(null,Typeface.BOLD_ITALIC)
|
||||
}
|
||||
return this;
|
||||
}
|
||||
setText(str:string):xText{
|
||||
let view = this.view as TextView;
|
||||
view.setText(str)
|
||||
|
||||
return this;
|
||||
}
|
||||
override setPadding(n:any):xText{
|
||||
let view = this.view as TextView;
|
||||
if(Array.isArray(n)){
|
||||
let rd = n as number[];
|
||||
if(rd.length==0){
|
||||
let rds = 0;
|
||||
let rdf = dp2px(rds).toInt();
|
||||
view.setPadding(rdf,rdf,rdf,rdf)
|
||||
}else if(rd.length==1){
|
||||
let rdf = dp2px(rd[0]).toInt();
|
||||
view.setPadding(rdf,rdf,rdf,rdf)
|
||||
}else if(rd.length==2){
|
||||
view.setPadding(dp2px(rd[0]).toInt(),dp2px(rd[1]).toInt(),dp2px(rd[0]).toInt(),dp2px(rd[1]).toInt())
|
||||
}else if(rd.length==3){
|
||||
let rds = 0;
|
||||
let rdf = rds.toInt();
|
||||
view.setPadding(dp2px(rd[0]).toInt(),dp2px(rd[1]).toInt(),dp2px(rd[2]).toInt(),rdf)
|
||||
}else if(rd.length==4){
|
||||
view.setPadding(dp2px(rd[0]).toInt(),dp2px(rd[1]).toInt(),dp2px(rd[2]).toInt(),dp2px(rd[3]).toInt())
|
||||
}
|
||||
}else{
|
||||
|
||||
let rd = n as number;
|
||||
let rdf = dp2px(rd).toInt();
|
||||
view.setPadding(rdf,rdf,rdf,rdf)
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
//让内容的对齐方向,
|
||||
//https://developer.android.google.cn/reference/kotlin/android/view/Gravity
|
||||
//只有xTextView,有这个属性。
|
||||
setAlign(cr:Int) : xText{
|
||||
let view = this.view as TextView;
|
||||
view.setGravity(cr)
|
||||
return this;
|
||||
}
|
||||
//追加文本
|
||||
//start,end不填写就追加到末尾。
|
||||
setIcon(code:string,start:number=0,end:number=0) : xText{
|
||||
let view = this.view as TextView;
|
||||
let nowText = view.getText().toString();
|
||||
|
||||
let assetManager = view.getContext()!.getAssets();
|
||||
let typeface = Typeface.createFromAsset(assetManager, "remixicon.ttf")
|
||||
view.setTypeface(typeface)
|
||||
|
||||
if(!TextUtils.isEmpty(code)){
|
||||
let codePoint = Integer.parseInt(code, 16);
|
||||
let charArray = Character.toChars(codePoint);
|
||||
let text = new String(charArray);
|
||||
view.setText(text+" "+nowText);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
setLayoutParams(width?:number,height?:number,layouWidth?:number) : xText{
|
||||
let view = this.view as TextView;
|
||||
let w = (0).toInt();
|
||||
let h = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
let flex = (1).toFloat();
|
||||
|
||||
if(typeof width =='number'){
|
||||
w = width!.toInt();
|
||||
}
|
||||
if(typeof height =='number'){
|
||||
h = height!.toInt();
|
||||
}
|
||||
if(typeof layouWidth =='number'){
|
||||
flex = layouWidth!.toFloat();
|
||||
}
|
||||
|
||||
let layoutParams_crd = new LinearLayout.LayoutParams(
|
||||
w,
|
||||
h,
|
||||
flex
|
||||
)
|
||||
view.setLayoutParams(layoutParams_crd);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setLineHeight(line:number) : xText{
|
||||
let view = this.view as TextView;
|
||||
view.setLineHeight(line.toInt())
|
||||
return this;
|
||||
}
|
||||
setLetterSpacing(space:number) : xText{
|
||||
let view = this.view as TextView;
|
||||
view.setLineHeight(space.toInt())
|
||||
return this;
|
||||
}
|
||||
setHighlightColor(color:string) : xText{
|
||||
let view = this.view as TextView;
|
||||
if(!TextUtils.isEmpty(color)){
|
||||
view.setHighlightColor(Color.parseColor(getDefaultColor(color)))
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
// 设置文字的省略情况,0表示不显示省略号,数字表示几行显示省略号。
|
||||
setEllipsis(lines:number=0) : xText{
|
||||
let view = this.view as TextView;
|
||||
if(lines>0){
|
||||
view.setMaxLines(lines.toInt())
|
||||
view.setEllipsize(TextUtils.TruncateAt.END)
|
||||
}else{
|
||||
view.setMaxLines((1).toInt())
|
||||
view.setEllipsize(null)
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default xText;
|
||||
@@ -0,0 +1,486 @@
|
||||
import Context from 'android.content.Context'
|
||||
import LinearLayout from 'android.widget.LinearLayout';
|
||||
import ViewGroup from 'android.view.ViewGroup';
|
||||
import View from 'android.view.View';
|
||||
import TextView from 'android.widget.TextView';
|
||||
import ImageView from 'android.widget.ImageView';
|
||||
import Gravity from 'android.view.Gravity';
|
||||
import Color from 'android.graphics.Color';
|
||||
import Activity from 'android.app.Activity';
|
||||
// import CardView from 'androidx.cardview.widget.CardView';
|
||||
import { ref, } from "vue"
|
||||
import TextUtils from 'android.text.TextUtils';
|
||||
import GradientDrawable from 'android.graphics.drawable.GradientDrawable'
|
||||
import MotionEvent from 'android.view.MotionEvent';
|
||||
import ColorStateList from 'android.content.res.ColorStateList';
|
||||
import attr from 'android.R.attr'
|
||||
import {hexToRgb,getDefaultColor} from "../util/xCoreColorUtil.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);
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机一个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;
|
||||
}
|
||||
|
||||
class MyClickListener implements View.OnClickListener {
|
||||
callbackFun:()=>void;
|
||||
constructor(cal?:()=>void){
|
||||
this.callbackFun = cal as ()=>void;
|
||||
}
|
||||
override onClick(v?:View):void {
|
||||
if(typeof this.callbackFun !='underfinde'){
|
||||
this.callbackFun();
|
||||
}
|
||||
}
|
||||
}
|
||||
class MyTouchListener implements View.OnTouchListener {
|
||||
//down,up,move,cancel,longClick
|
||||
callbackFun:(types:string,event : MotionEvent,detail:UTSJSONObject)=>void;
|
||||
dubleTime = 0;
|
||||
tid = 56;
|
||||
_x = 0;
|
||||
_y = 0;
|
||||
// 判断方向的差值
|
||||
swipe_mindiff = 40
|
||||
// 滑动时,一定时间内只能触发一次事件。不能连续触发。
|
||||
tid_siper = 100;
|
||||
swipeDirection = ""
|
||||
constructor(call:(types:string,event : MotionEvent,detail?:UTSJSONObject)=>void) {
|
||||
this.callbackFun = call;
|
||||
}
|
||||
override onTouch(view : View, event : MotionEvent) : Boolean {
|
||||
|
||||
if(event.action == MotionEvent.ACTION_DOWN ){
|
||||
this._x = event.getX();
|
||||
this._y = event.getY();
|
||||
this.swipeDirection = ""
|
||||
this.callbackFun('down',event,{});
|
||||
let difftime = new Date().getTime() - this.dubleTime;
|
||||
if(difftime >0 && difftime<=300){
|
||||
this.callbackFun('doubleClick',event ,{});
|
||||
}
|
||||
this.dubleTime = new Date().getTime();
|
||||
clearTimeout(this.tid)
|
||||
tid = setTimeout(function() {
|
||||
this.callbackFun('longPress',event,{});
|
||||
}, 800);
|
||||
}
|
||||
if(event.action == MotionEvent.ACTION_UP ){
|
||||
clearTimeout(this.tid)
|
||||
this.callbackFun('up',event,{});
|
||||
if(new Date().getTime() - this.dubleTime > 50){
|
||||
this.callbackFun('click',event,{});
|
||||
}
|
||||
}
|
||||
if(event.action == MotionEvent.ACTION_CANCEL || event.action== MotionEvent.ACTION_OUTSIDE ){
|
||||
this.callbackFun('cancel',event,{});
|
||||
clearTimeout(this.tid)
|
||||
|
||||
}
|
||||
|
||||
if(event.action == MotionEvent.ACTION_MOVE ){
|
||||
let x = event.getX();
|
||||
let y = event.getY();
|
||||
let deltaX = Math.abs(x - this._x);
|
||||
let deltaY = Math.abs(y - this._y);
|
||||
if(deltaX > deltaY && deltaX > this.swipe_mindiff){
|
||||
this.swipeDirection = (this._x > x) ? "left" : "right";
|
||||
}else if(deltaY > deltaX && deltaY > this.swipe_mindiff){
|
||||
this.swipeDirection = (this._y < y) ? "down" : "up";
|
||||
}
|
||||
if(this.swipeDirection !=""){
|
||||
this.callbackFun('swiper',event,{x,y,diffX:deltaX,diffY:deltaY,direction:this.swipeDirection});
|
||||
}
|
||||
// this._x = x;
|
||||
// this._y = y;
|
||||
this.callbackFun('move',event,{});
|
||||
clearTimeout(this.tid)
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class xView {
|
||||
public view : any;
|
||||
public tagId:string = 'x_view_id_'+getUid();
|
||||
public bgView:GradientDrawable
|
||||
private _onclickFun= (_event : MotionEvent):void=>{};
|
||||
private _onTouchStart= (_event : MotionEvent):void=>{};
|
||||
private _onTouchEnd= (_event : MotionEvent):void=>{};
|
||||
private _onTouchCancel= (_event : MotionEvent):void=>{};
|
||||
private _onTouchMove= (_event : MotionEvent):void=>{};
|
||||
private _onTouchLongPress= (_event : MotionEvent):void=>{};
|
||||
private _onTouchDubleClick= (_event : MotionEvent):void=>{};
|
||||
private _onTouchSwiper= (_event : MotionEvent,detail?:UTSJSONObject):void=>{};
|
||||
// 设置禁用,不会触发上面的事件。
|
||||
private _disabled = false;
|
||||
constructor(context : Context) {
|
||||
let view = new View(context);
|
||||
|
||||
view.setTag(this.tagId)
|
||||
let layoutParams_crd = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
view.setLayoutParams(layoutParams_crd);
|
||||
this.bgView = new GradientDrawable();
|
||||
|
||||
// let statuesInts= [[attr.state_enabled].toKotlinList().toIntArray(),[attr.state_pressed].toKotlinList().toIntArray(),[-attr.state_enabled].toKotlinList().toIntArray()]
|
||||
// let statuesColors= [Color.WHITE,Color.RED,Color.RED]
|
||||
// let states = new ColorStateList(statuesInts.toKotlinList().toTypedArray(),statuesColors.toKotlinList().toIntArray())
|
||||
|
||||
this.bgView.setShape(GradientDrawable.RECTANGLE)
|
||||
this.bgView.setColor(Color.WHITE)
|
||||
this.bgView.mutate()
|
||||
view.setBackground(this.bgView)
|
||||
|
||||
this.view = view;
|
||||
|
||||
// view.setOnClickListener(new MyClickListener(():void=>{
|
||||
// this._onclickFun()
|
||||
// }));
|
||||
view.setOnTouchListener(new MyTouchListener((types:string,event : MotionEvent,detail?:UTSJSONObject):void=>{
|
||||
|
||||
if(this._disabled == false){
|
||||
if(types=='down'){
|
||||
this._onTouchStart(event)
|
||||
}
|
||||
if(types=='up'){
|
||||
this._onTouchEnd(event);
|
||||
}
|
||||
if(types=='click'){
|
||||
this._onclickFun(event);
|
||||
}
|
||||
if(types=='move'){
|
||||
this._onTouchMove(event)
|
||||
}
|
||||
if(types=='cancel'){
|
||||
this._onTouchCancel(event)
|
||||
}
|
||||
if(types=='longPress'){
|
||||
this._onTouchLongPress(event)
|
||||
}
|
||||
if(types=='doubleClick'){
|
||||
this._onTouchDubleClick(event)
|
||||
}
|
||||
if(types=='swiper'){
|
||||
this._onTouchSwiper(event,detail)
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
}
|
||||
getView():View{
|
||||
return this.view as View;
|
||||
}
|
||||
setDisabled(dis:boolean) : xView{
|
||||
this._disabled = dis;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发距离是大于50时触发并判断方向
|
||||
* x,y当前坐标,diffX滑动时距离开始时按下的横向x距离,diffY表示竖向。Direction为方向:left,right,up,dowon
|
||||
*detail:
|
||||
*{x,y,diffX,diffY,direction}
|
||||
*/
|
||||
setSwiper(fun?:(event: MotionEvent,detail?:UTSJSONObject)=>void) : xView{
|
||||
if(typeof fun !== 'undefined'){
|
||||
this._onTouchSwiper = fun;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
setDoubleClick(fun?:(event: MotionEvent)=>void) : xView{
|
||||
if(typeof fun !== 'undefined'){
|
||||
this._onTouchDubleClick = fun;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
setClick(fun?:(event: MotionEvent)=>void) : xView{
|
||||
if(typeof fun !== 'undefined'){
|
||||
this._onclickFun = fun;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
setTouchStart(fun?:(event: MotionEvent)=>void) : xView{
|
||||
if(typeof fun !== 'undefined'){
|
||||
this._onTouchStart = fun;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
setTouchMove(fun?:(event: MotionEvent)=>void) : xView{
|
||||
if(typeof fun !== 'undefined'){
|
||||
this._onTouchMove = fun;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
setTouchEnd(fun?:(event: MotionEvent)=>void) : xView{
|
||||
|
||||
if(typeof fun !== 'undefined'){
|
||||
this._onTouchEnd = fun;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
setTouchCancel(fun?:(event: MotionEvent)=>void) : xView{
|
||||
if(typeof fun !== 'undefined'){
|
||||
this._onTouchCancel = fun;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
setTouchLongPress(fun?:(event: MotionEvent)=>void) : xView{
|
||||
if(typeof fun !== 'undefined'){
|
||||
this._onTouchLongPress = fun;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
setBackgroundColor(colorStr : string) : xView {
|
||||
this.bgView.mutate()
|
||||
if(colorStr == 'transparent'){
|
||||
this.bgView.setColor(Color.TRANSPARENT)
|
||||
}else{
|
||||
this.bgView.setColor(Color.parseColor(getDefaultColor(colorStr)))
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setBorder(w?:number,colorStr?:string,dashed?:boolean) : xView {
|
||||
this.bgView.mutate()
|
||||
|
||||
let tw:number = 1;
|
||||
let tcolor = 'transparent';
|
||||
let tdashed = false;
|
||||
if(typeof tw !== 'undefined'){
|
||||
tw = w as number;
|
||||
}
|
||||
if(typeof tw !== 'undefined'){
|
||||
tcolor = colorStr as string;
|
||||
}
|
||||
|
||||
if(typeof dashed !== 'undefined'){
|
||||
tdashed = dashed as boolean;
|
||||
}
|
||||
|
||||
let colorNum = Color.TRANSPARENT;
|
||||
if(tcolor != 'transparent'){
|
||||
colorNum = Color.parseColor(getDefaultColor(tcolor));
|
||||
}
|
||||
if(dashed == true){
|
||||
let dashedWidth = 8
|
||||
let dashedGap = 5
|
||||
this.bgView.setStroke(tw.toInt(),colorNum,dashedWidth.toFloat(),dashedGap.toFloat())
|
||||
}else{
|
||||
this.bgView.setStroke(tw.toInt(),colorNum)
|
||||
}
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
setMargin(n:any):xView{
|
||||
let view = this.view as View;
|
||||
let selfPrams = view.getLayoutParams()
|
||||
let params = new LinearLayout.LayoutParams(
|
||||
selfPrams.width,
|
||||
selfPrams.height
|
||||
)
|
||||
|
||||
if(Array.isArray(n)){
|
||||
let rd = n as number[];
|
||||
if(rd.length==0){
|
||||
let rds = 0;
|
||||
let rdf = (rds).toInt();
|
||||
params.setMargins(rdf,rdf,rdf,rdf)
|
||||
}else if(rd.length==1){
|
||||
let rdf = (rd[0]).toInt();
|
||||
params.setMargins(rdf,rdf,rdf,rdf)
|
||||
}else if(rd.length==2){
|
||||
let rds = 0;
|
||||
params.setMargins((rd[0]).toInt(),(rd[1]).toInt(),(rd[0]).toInt(),(rd[1]).toInt())
|
||||
}else if(rd.length==3){
|
||||
let rds = 0;
|
||||
let rdf = rds.toInt();
|
||||
params.setMargins((rd[0]).toInt(),(rd[1]).toInt(),(rd[2]).toInt(),rdf)
|
||||
}else if(rd.length==4){
|
||||
params.setMargins((rd[0]).toInt(),(rd[1]).toInt(),(rd[2]).toInt(),(rd[3]).toInt())
|
||||
}
|
||||
}else{
|
||||
|
||||
let rd = n as number;
|
||||
let rdf = (rd).toInt();
|
||||
params.setMargins(rdf,rdf,rdf,rdf)
|
||||
}
|
||||
|
||||
view.setLayoutParams(params);
|
||||
return this;
|
||||
}
|
||||
|
||||
setPadding(n:any):xView{
|
||||
let view = this.view as View;
|
||||
if(Array.isArray(n)){
|
||||
let rd = n as number[];
|
||||
if(rd.length==0){
|
||||
let rds = 0;
|
||||
let rdf = (rds).toInt();
|
||||
view.setPadding(rdf,rdf,rdf,rdf)
|
||||
}else if(rd.length==1){
|
||||
let rdf = (rd[0]).toInt();
|
||||
view.setPadding(rdf,rdf,rdf,rdf)
|
||||
}else if(rd.length==2){
|
||||
view.setPadding((rd[0]).toInt(),(rd[1]).toInt(),(rd[0]).toInt(),(rd[1]).toInt())
|
||||
}else if(rd.length==3){
|
||||
let rds = 0;
|
||||
let rdf = rds.toInt();
|
||||
view.setPadding((rd[0]).toInt(),(rd[1]).toInt(),(rd[2]).toInt(),rdf)
|
||||
}else if(rd.length==4){
|
||||
view.setPadding((rd[0]).toInt(),(rd[1]).toInt(),(rd[2]).toInt(),(rd[3]).toInt())
|
||||
}
|
||||
}else{
|
||||
|
||||
let rd = n as number;
|
||||
let rdf = (rd).toInt();
|
||||
view.setPadding(rdf,rdf,rdf,rdf)
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
setRadius(radius:any) : xView{
|
||||
let x =0
|
||||
let y =0
|
||||
this.bgView.mutate()
|
||||
if(Array.isArray(radius)){
|
||||
let rd = radius as number[];
|
||||
if(rd.length==0){
|
||||
let rdfs = 0;
|
||||
this.bgView.setCornerRadius((rdfs).toFloat());
|
||||
// view.setBackground(this.bgView)
|
||||
}else if(rd.length==1){
|
||||
let rdfs = rd[0];
|
||||
this.bgView.setCornerRadius((rdfs).toFloat());
|
||||
// view.setBackground(this.bgView)
|
||||
}else if(rd.length==2){
|
||||
let tl = [
|
||||
(rd[0]).toFloat(),(rd[0]).toFloat(),
|
||||
(rd[1]).toFloat(),(rd[1]).toFloat(),
|
||||
x.toFloat(),x.toFloat(),
|
||||
x.toFloat(),x.toFloat()
|
||||
]
|
||||
let tlint = tl.toKotlinList().toFloatArray()
|
||||
this.bgView.setCornerRadii(tlint);
|
||||
}else if(rd.length==3){
|
||||
let tl = [
|
||||
(rd[0]).toFloat(),(rd[0]).toFloat(),
|
||||
(rd[1]).toFloat(),(rd[1]).toFloat(),
|
||||
(rd[2]).toFloat(),(rd[2]).toFloat(),
|
||||
x.toFloat(),x.toFloat()
|
||||
]
|
||||
let tlint = tl.toKotlinList().toFloatArray()
|
||||
this.bgView.setCornerRadii(tlint);
|
||||
}else if(rd.length==4){
|
||||
let tl = [
|
||||
(rd[0]).toFloat(),(rd[0]).toFloat(),
|
||||
(rd[1]).toFloat(),(rd[1]).toFloat(),
|
||||
(rd[2]).toFloat(),(rd[2]).toFloat(),
|
||||
(rd[3]).toFloat(),(rd[3]).toFloat(),
|
||||
]
|
||||
let tlint = tl.toKotlinList().toFloatArray()
|
||||
this.bgView.setCornerRadii(tlint);
|
||||
}
|
||||
}else{
|
||||
|
||||
let rd = radius as number;
|
||||
|
||||
this.bgView.setCornerRadius((rd).toFloat());
|
||||
}
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
/** 设置当前宽和高为父级的相对宽和高
|
||||
*w,h有效值为:auto|100%
|
||||
*/
|
||||
setSizeBy(w:string="100%",h:string = "auto"){
|
||||
let view = this.view as View;
|
||||
let ww = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
let hh = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
if(w=="auto"){
|
||||
ww = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
}
|
||||
if(h=="auto"){
|
||||
hh = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
}
|
||||
|
||||
let layoutParams_crd = new LinearLayout.LayoutParams(ww,hh)
|
||||
view.setLayoutParams(layoutParams_crd);
|
||||
}
|
||||
setSize(width : number, height : number) : xView {
|
||||
let view = this.view as View;
|
||||
let layoutParams_crd = new LinearLayout.LayoutParams(
|
||||
(width).toInt(),
|
||||
(height).toInt()
|
||||
)
|
||||
view.setLayoutParams(layoutParams_crd);
|
||||
return this;
|
||||
}
|
||||
setWidth(width : number) : xView {
|
||||
let view = this.view as View;
|
||||
let lpar = view.getLayoutParams();
|
||||
let layoutParams_crd = new LinearLayout.LayoutParams(
|
||||
(width).toInt(),
|
||||
(lpar.height).toInt(),
|
||||
)
|
||||
view.setLayoutParams(layoutParams_crd);
|
||||
return this;
|
||||
}
|
||||
setHeight(height : number) : xView {
|
||||
let view = this.view as View;
|
||||
let lpar = view.getLayoutParams();
|
||||
let layoutParams_crd = new LinearLayout.LayoutParams(
|
||||
(lpar.width).toInt(),
|
||||
(height).toInt(),
|
||||
)
|
||||
view.setLayoutParams(layoutParams_crd);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default xView;
|
||||
@@ -0,0 +1,17 @@
|
||||
import Dialog from "android.app.Dialog"
|
||||
import Context from 'android.content.Context'
|
||||
import { UTSAndroid } from "io.dcloud.uts";
|
||||
import FrameLayout from "android.widget.FrameLayout";
|
||||
class xOverflay {
|
||||
|
||||
static show(){
|
||||
let d = new Dialog(UTSAndroid.getUniActivity() as Context);
|
||||
|
||||
let decorView = UTSAndroid.getUniActivity()!.window.decorView;
|
||||
let frameContent = decorView.findViewById<FrameLayout>(android.R.id.content) as FrameLayout
|
||||
// test
|
||||
d.show();
|
||||
}
|
||||
}
|
||||
|
||||
export default xOverflay;
|
||||
@@ -0,0 +1,839 @@
|
||||
import { ShapeType, ICanvasEvent, ICanvasDomEvent, IEventListener, CanvasEventType, ICanvasOptional, IcanvasDomEventDetail } from './interface.uts';
|
||||
// import { Shape } from './lib/shape.uts';
|
||||
import { Shape } from '@/uni_modules/tmx-ui/core/canvas/lib/shape.uts';
|
||||
export class ICanvas {
|
||||
canvas : CanvasContext | null = null;
|
||||
ctx : CanvasRenderingContext2D | null = null;
|
||||
canvasNode : NodeInfo | null = null
|
||||
|
||||
width : number = 0;
|
||||
height : number = 0;
|
||||
boxWidth : number = 0;
|
||||
boxHeight : number = 0;
|
||||
dpr : number = 1;
|
||||
/**
|
||||
* 命中检测事件范围,默认只对clik点击进行命中测试,如果全放开会有性能风险
|
||||
* 相当于是事件过滤。比如['click']那么元素只会触发click事件,其它事件不会响应
|
||||
* 空数组响应所有事件
|
||||
*/
|
||||
checkPointInHitEvents = ['click'] as CanvasEventType[]
|
||||
|
||||
//点击时的位置
|
||||
private touch_x = 0
|
||||
private touch_y = 0
|
||||
//移动时的位置
|
||||
private touch_move_x = 0
|
||||
private touch_move_y = 0
|
||||
//结束时的位置
|
||||
private touch_end_x = 0
|
||||
private touch_end_y = 0
|
||||
//结束时相对起始时之间的位置距离
|
||||
private touch_end_len_x = 0
|
||||
private touch_end_len_y = 0
|
||||
//移动时相对上一次移动的距离
|
||||
private touch_move_len_x = 0
|
||||
private touch_move_len_y = 0
|
||||
//点击时的时间
|
||||
private touchStartTime = 0
|
||||
//点击抬起时的结束时间.
|
||||
private touchEndTime = 0
|
||||
|
||||
private touchTimeTid = 0
|
||||
|
||||
|
||||
id = Math.random().toString(16).substring(4)
|
||||
|
||||
gaptimeId = 23
|
||||
|
||||
private shapes : ShapeType[] = [];
|
||||
private eventListeners : Map<CanvasEventType, IEventListener[]> = new Map();
|
||||
|
||||
component : any | null = null;
|
||||
canvasId : string;
|
||||
|
||||
|
||||
constructor(config : ICanvasOptional) {
|
||||
this.canvasId = config.canvasId;
|
||||
this.component = config.component
|
||||
}
|
||||
init() : Promise<any | null> {
|
||||
const _this = this;
|
||||
return new Promise((res) => {
|
||||
uni.createSelectorQuery()
|
||||
.in(_this.component)
|
||||
.select("#" + _this.canvasId)
|
||||
.boundingClientRect()
|
||||
.exec(result => {
|
||||
if (result.length == 0) return;
|
||||
let node = result[0]! as NodeInfo
|
||||
uni.createCanvasContextAsync({
|
||||
id: _this.canvasId,
|
||||
component: _this.component as ComponentPublicInstance | null,
|
||||
success: (context : CanvasContext) => {
|
||||
_this.canvasNode = node
|
||||
_this.canvas = context;
|
||||
|
||||
const dpr = uni.getWindowInfo().pixelRatio ?? 1;
|
||||
const canvasContext = context.getContext('2d')!;
|
||||
// #ifdef APP
|
||||
canvasContext.resetTransform()
|
||||
// #endif
|
||||
const canvas = canvasContext.canvas;
|
||||
canvas.width = canvas.offsetWidth * dpr;
|
||||
canvas.height = canvas.offsetHeight * dpr;
|
||||
canvasContext.scale(dpr, dpr);
|
||||
_this.ctx = canvasContext as CanvasRenderingContext2D;
|
||||
|
||||
_this.dpr = dpr;
|
||||
|
||||
_this.ctx!.globalAlpha = 1;
|
||||
_this.width = node.width!;
|
||||
_this.height = node.height!;
|
||||
_this.boxWidth = canvas.offsetWidth;
|
||||
_this.boxHeight = canvas.offsetHeight;
|
||||
res(_this)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private _resizePos() {
|
||||
const _this = this;
|
||||
uni.createSelectorQuery()
|
||||
.in(_this.component)
|
||||
.select("#" + _this.canvasId)
|
||||
.boundingClientRect()
|
||||
.exec(result => {
|
||||
if (result.length == 0) return;
|
||||
let node = result[0]! as NodeInfo
|
||||
_this.canvasNode = node
|
||||
})
|
||||
}
|
||||
|
||||
clear() : ICanvas {
|
||||
this.ctx!.clearRect(0, 0, this.width, this.height)
|
||||
return this;
|
||||
}
|
||||
|
||||
addShape(shape : ShapeType|ShapeType[]) : ICanvas {
|
||||
if(Array.isArray(shape)){
|
||||
let _this = this;
|
||||
shape.forEach((el)=>{
|
||||
_this.shapes.push(el);
|
||||
})
|
||||
}else{
|
||||
this.shapes.push(shape);
|
||||
}
|
||||
this.shapes.sort((a, b ) : number => {
|
||||
let ela = a as Shape
|
||||
let elb = b as Shape
|
||||
return ela.zIndex - elb.zIndex
|
||||
})
|
||||
return this;
|
||||
}
|
||||
|
||||
removeShape(shape : ShapeType) : ICanvas {
|
||||
for (let i = 0; i < this.shapes.length; i++) {
|
||||
let itemshape = this.shapes[i]
|
||||
if (itemshape instanceof Shape && shape instanceof Shape) {
|
||||
if (itemshape.id == shape.id) {
|
||||
this.shapes.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
render() : ICanvas {
|
||||
this.clear();
|
||||
for (let i = 0; i < this.shapes.length; i++) {
|
||||
const shape = this.shapes[i];
|
||||
if (shape instanceof Shape) {
|
||||
shape.draw(this.ctx!);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
update() : ICanvas {
|
||||
|
||||
let needsRender = false;
|
||||
for (let i = 0; i < this.shapes.length; i++) {
|
||||
const shape = this.shapes[i];
|
||||
if (shape instanceof Shape) {
|
||||
if (shape.needsUpdate) {
|
||||
needsRender = true;
|
||||
shape.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (needsRender) {
|
||||
this.render();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
getImage():Promise<string>{
|
||||
return new Promise((res,_rej)=>{
|
||||
// #ifdef WEB||H5
|
||||
try {
|
||||
this.ctx.canvas.toBlob((blob)=>{
|
||||
res(URL.createObjectURL(blob))
|
||||
},'images/png',1)
|
||||
} catch (error) {
|
||||
console.error(error,'注意画布中的图片要同域')
|
||||
res('')
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP
|
||||
let ele = uni.getElementById(this.canvasId) as UniElement
|
||||
ele!.takeSnapshot({
|
||||
success(result){
|
||||
res(result.tempFilePath)
|
||||
},
|
||||
fail(){
|
||||
res('')
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef MP
|
||||
uni.canvasToTempFilePath({
|
||||
canvasId:this.canvasId,
|
||||
success(result){
|
||||
res(result.tempFilePath)
|
||||
},
|
||||
fail(){
|
||||
res('')
|
||||
}
|
||||
},this.component)
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
/** 将外部事件绑定到对象上,实现事件分发. **/
|
||||
bindEvent(eventType : any | null) {
|
||||
|
||||
let _this = this;
|
||||
clearTimeout(this.touchTimeTid)
|
||||
if (eventType == null || this.canvasNode == null) return;
|
||||
const winTop = uni.getWindowInfo().windowTop
|
||||
const offsetTop = this.canvasNode!.top! + 0
|
||||
const offsetLeft = this.canvasNode!.left!
|
||||
//防止触摸和鼠标事件同时触发。
|
||||
let isTouch = false;
|
||||
|
||||
// 移动端
|
||||
// #ifdef APP-ANDROID||H5
|
||||
if (eventType instanceof TouchEvent) {
|
||||
// #endif
|
||||
// #ifdef APP-IOS||APP-HARMONY
|
||||
if (eventType instanceof UniTouchEvent) {
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
if (true) {
|
||||
// #endif
|
||||
|
||||
eventType.stopPropagation()
|
||||
eventType.preventDefault()
|
||||
|
||||
isTouch = true;
|
||||
let changedTouches = eventType.changedTouches
|
||||
// #ifndef APP
|
||||
changedTouches = Array.from(changedTouches)
|
||||
// #endif
|
||||
const clientX = changedTouches[0].clientX - offsetLeft
|
||||
const clientY = changedTouches[0].clientY - offsetTop
|
||||
|
||||
if (eventType.type == 'touchstart') {
|
||||
this.touch_x = clientX
|
||||
this.touch_y = clientY
|
||||
this.touch_move_x = clientX
|
||||
this.touch_move_y = clientY
|
||||
this.touch_end_x = clientX
|
||||
this.touch_end_y = clientY
|
||||
|
||||
|
||||
this.touch_end_len_x = 0
|
||||
this.touch_end_len_y = 0
|
||||
this.touch_move_len_x = 0
|
||||
this.touch_move_len_y = 0
|
||||
|
||||
|
||||
this.touchStartTime = Date.now()
|
||||
this.buildEvents('down', {
|
||||
type: 'down',
|
||||
x: changedTouches[0].clientX - offsetLeft,
|
||||
y: changedTouches[0].clientY - offsetTop,
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
}],
|
||||
touches: changedTouches.map((el) : ICanvasDomEvent => {
|
||||
return {
|
||||
type: 'down',
|
||||
x: el.clientX - offsetLeft,
|
||||
y: el.clientY - offsetTop,
|
||||
touches: [],
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
}],
|
||||
} as ICanvasDomEvent
|
||||
})
|
||||
} as ICanvasDomEvent)
|
||||
this.touchTimeTid = setTimeout(() => {
|
||||
_this.buildEvents('longpress', {
|
||||
type: 'longpress',
|
||||
x: changedTouches[0].clientX - offsetLeft,
|
||||
y: changedTouches[0].clientY - offsetTop,
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
}],
|
||||
touches: changedTouches.map((el) : ICanvasDomEvent => {
|
||||
return {
|
||||
type: 'longpress',
|
||||
x: el.clientX - offsetLeft,
|
||||
y: el.clientY - offsetTop,
|
||||
touches: [],
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
}],
|
||||
} as ICanvasDomEvent
|
||||
})
|
||||
} as ICanvasDomEvent)
|
||||
}, 500)
|
||||
} else if (eventType.type == 'touchmove') {
|
||||
|
||||
this.touch_move_len_x = this.touch_move_x - clientX
|
||||
this.touch_move_len_y = this.touch_move_y - clientY
|
||||
|
||||
this.touch_end_len_x = clientX - this.touch_x
|
||||
this.touch_end_len_y = clientY - this.touch_y
|
||||
|
||||
this.touch_move_x = clientX
|
||||
this.touch_move_y = clientY
|
||||
|
||||
this.buildEvents('move', {
|
||||
type: 'move',
|
||||
x: changedTouches[0].clientX - offsetLeft,
|
||||
y: changedTouches[0].clientY - offsetTop,
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
}],
|
||||
touches: changedTouches.map((el) : ICanvasDomEvent => {
|
||||
return {
|
||||
type: 'move',
|
||||
x: el.clientX - offsetLeft,
|
||||
y: el.clientY - offsetTop,
|
||||
touches: [],
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
}],
|
||||
} as ICanvasDomEvent
|
||||
})
|
||||
} as ICanvasDomEvent)
|
||||
|
||||
} else if (eventType.type == 'touchend') {
|
||||
this.touch_end_x = clientX
|
||||
this.touch_end_y = clientY
|
||||
this.touch_end_len_x = this.touch_end_x - this.touch_x
|
||||
this.touch_end_len_y = this.touch_end_y - this.touch_y
|
||||
|
||||
let nowTIme = Date.now()
|
||||
this.buildEvents('up', {
|
||||
type: 'up',
|
||||
x: changedTouches[0].clientX - offsetLeft,
|
||||
y: changedTouches[0].clientY - offsetTop,
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
}],
|
||||
touches: changedTouches.map((el) : ICanvasDomEvent => {
|
||||
return {
|
||||
type: 'up',
|
||||
x: el.clientX - offsetLeft,
|
||||
y: el.clientY - offsetTop,
|
||||
touches: [],
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
}],
|
||||
} as ICanvasDomEvent
|
||||
})
|
||||
} as ICanvasDomEvent)
|
||||
// 判断是不是单击
|
||||
const difftime = nowTIme - this.touchStartTime
|
||||
if (difftime > 50 && difftime < 250) {
|
||||
this.buildEvents('click', {
|
||||
type: 'click',
|
||||
x: changedTouches[0].clientX - offsetLeft,
|
||||
y: changedTouches[0].clientY - offsetTop,
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
}],
|
||||
touches: changedTouches.map((el) : ICanvasDomEvent => {
|
||||
return {
|
||||
type: 'click',
|
||||
x: el.clientX - offsetLeft,
|
||||
y: el.clientY - offsetTop,
|
||||
touches: [],
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
}],
|
||||
} as ICanvasDomEvent
|
||||
})
|
||||
} as ICanvasDomEvent)
|
||||
}
|
||||
// 判断是不是双击
|
||||
const diffEndtime = nowTIme - this.touchEndTime
|
||||
if (diffEndtime > 100 && diffEndtime < 280) {
|
||||
this.buildEvents('dbclick', {
|
||||
type: 'dbclick',
|
||||
x: changedTouches[0].clientX - offsetLeft,
|
||||
y: changedTouches[0].clientY - offsetTop,
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
}],
|
||||
touches: [] as ICanvasDomEvent[]
|
||||
})
|
||||
}
|
||||
this.touchEndTime = nowTIme
|
||||
} else if (eventType.type == 'touchcancel') {
|
||||
this.touch_end_x = clientX
|
||||
this.touch_end_y = clientY
|
||||
this.touch_end_len_x = this.touch_end_x - this.touch_x
|
||||
this.touch_end_len_y = this.touch_end_y - this.touch_y
|
||||
const touchesList = changedTouches.map((el) : ICanvasDomEvent => {
|
||||
return {
|
||||
type: 'cancel',
|
||||
x: el.clientX - offsetLeft,
|
||||
y: el.clientY - offsetTop,
|
||||
touches: [],
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
} as IcanvasDomEventDetail ] as IcanvasDomEventDetail[],
|
||||
} as ICanvasDomEvent
|
||||
})
|
||||
this.buildEvents('cancel', {
|
||||
type: 'cancel',
|
||||
x: changedTouches[0].clientX - offsetLeft,
|
||||
y: changedTouches[0].clientY - offsetTop,
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
} as IcanvasDomEventDetail ] as IcanvasDomEventDetail[],
|
||||
touches: touchesList as ICanvasDomEvent[]
|
||||
} as ICanvasDomEvent)
|
||||
}
|
||||
}
|
||||
// 兼容PC
|
||||
// #ifdef WEB
|
||||
|
||||
if (eventType instanceof MouseEvent && !(eventType instanceof PointerEvent) && !isTouch) {
|
||||
eventType.stopPropagation()
|
||||
const clientX = eventType.layerX
|
||||
const clientY = eventType.layerY
|
||||
|
||||
if (eventType.type == 'mousedown') {
|
||||
this.touch_x = clientX
|
||||
this.touch_y = clientY
|
||||
this.touch_move_x = clientX
|
||||
this.touch_move_y = clientY
|
||||
this.touch_end_x = clientX
|
||||
this.touch_end_y = clientY
|
||||
|
||||
|
||||
this.touch_end_len_x = 0
|
||||
this.touch_end_len_y = 0
|
||||
this.touch_move_len_x = 0
|
||||
this.touch_move_len_y = 0
|
||||
|
||||
this.touchStartTime = Date.now()
|
||||
window.ICnavansId = this.id
|
||||
this.buildEvents('down', {
|
||||
type: 'down',
|
||||
x: clientX,
|
||||
y: clientY,
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
}],
|
||||
touches: [] as ICanvasDomEvent[]
|
||||
})
|
||||
|
||||
this.touchTimeTid = setTimeout(() => {
|
||||
_this.buildEvents('longpress', {
|
||||
type: 'longpress',
|
||||
x: clientX,
|
||||
y: clientY,
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
}],
|
||||
touches: [] as ICanvasDomEvent[]
|
||||
} as ICanvasDomEvent)
|
||||
}, 500)
|
||||
}
|
||||
if (eventType.type == 'mousemove' && window.ICnavansId === this.id) {
|
||||
this.touch_move_len_x = this.touch_move_x - clientX
|
||||
this.touch_move_len_y = this.touch_move_y - clientY
|
||||
|
||||
this.touch_end_len_x = clientX - this.touch_x
|
||||
this.touch_end_len_y = clientY - this.touch_y
|
||||
|
||||
this.touch_move_x = clientX
|
||||
this.touch_move_y = clientY
|
||||
|
||||
this.buildEvents('move', {
|
||||
type: 'move',
|
||||
x: clientX,
|
||||
y: clientY,
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
}],
|
||||
touches: [] as ICanvasDomEvent[]
|
||||
})
|
||||
}
|
||||
if ((eventType.type == 'mouseup') && window.ICnavansId === this.id) {
|
||||
this.touch_end_x = clientX
|
||||
this.touch_end_y = clientY
|
||||
this.touch_end_len_x = this.touch_end_x - this.touch_x
|
||||
this.touch_end_len_y = this.touch_end_y - this.touch_y
|
||||
|
||||
let nowTIme = Date.now()
|
||||
window.ICnavansId = ""
|
||||
|
||||
this.buildEvents('up', {
|
||||
type: 'up',
|
||||
x: clientX,
|
||||
y: clientY,
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
}],
|
||||
touches: [] as ICanvasDomEvent[]
|
||||
})
|
||||
|
||||
// 判断是不是单击
|
||||
const difftime = nowTIme - this.touchStartTime
|
||||
if (difftime > 50 && difftime < 250) {
|
||||
this.buildEvents('click', {
|
||||
type: 'click',
|
||||
x: clientX,
|
||||
y: clientY,
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
}],
|
||||
touches: [] as ICanvasDomEvent[]
|
||||
} as ICanvasDomEvent)
|
||||
}
|
||||
|
||||
// 判断是不是双击
|
||||
const diffEndtime = nowTIme - this.touchEndTime
|
||||
if (diffEndtime > 100 && diffEndtime < 280) {
|
||||
this.buildEvents('dbclick', {
|
||||
type: 'dbclick',
|
||||
x: clientX,
|
||||
y: clientY,
|
||||
detail: [],
|
||||
touches: [] as ICanvasDomEvent[]
|
||||
})
|
||||
}
|
||||
this.touchEndTime = nowTIme
|
||||
}
|
||||
if ((eventType.type == 'mouseleave') && window.ICnavansId === this.id) {
|
||||
this.touch_end_x = clientX
|
||||
this.touch_end_y = clientY
|
||||
this.touch_end_len_x = this.touch_end_x - this.touch_x
|
||||
this.touch_end_len_y = this.touch_end_y - this.touch_y
|
||||
|
||||
window.ICnavansId = ""
|
||||
this.buildEvents('cancel', {
|
||||
type: 'cancel',
|
||||
x: clientX,
|
||||
y: clientY,
|
||||
detail: [{
|
||||
startX: this.touch_x,
|
||||
startY: this.touch_y,
|
||||
endX: this.touch_end_x,
|
||||
endY: this.touch_end_y,
|
||||
moveX: this.touch_move_x,
|
||||
moveY: this.touch_move_y,
|
||||
moveLenX: this.touch_move_len_x,
|
||||
moveLenY: this.touch_move_len_y,
|
||||
endLenX: this.touch_end_len_x,
|
||||
endLenY: this.touch_end_len_y
|
||||
}],
|
||||
touches: [] as ICanvasDomEvent[]
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
// #endif
|
||||
|
||||
|
||||
}
|
||||
|
||||
addEventListener(eventName : CanvasEventType, listener : IEventListener) : ICanvas {
|
||||
if (!this.eventListeners.has(eventName)) {
|
||||
this.eventListeners.set(eventName, []);
|
||||
}
|
||||
this.eventListeners.get(eventName)!.push(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
removeEventListener(eventName : CanvasEventType, listener : IEventListener) : ICanvas {
|
||||
const listeners = this.eventListeners.get(eventName);
|
||||
if (listeners == null) return this;
|
||||
const index = listeners.indexOf(listener);
|
||||
if (index != -1) {
|
||||
listeners.splice(index, 1);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private getTargetShape(eventName : CanvasEventType, x : number, y : number) : ShapeType[] {
|
||||
let shapes = [] as ShapeType[]
|
||||
// 只针对开命中事件进行检测
|
||||
if (!this.checkPointInHitEvents.includes(eventName) && this.checkPointInHitEvents.length > 0) return shapes;
|
||||
for (let i = this.shapes.length - 1; i >= 0; i--) {
|
||||
const shape = this.shapes[i];
|
||||
if (shape instanceof Shape) {
|
||||
if (shape.isPointInPath(x, y, shape.id)) {
|
||||
shapes.push(shape)
|
||||
if (!shape.bubbleEvent) {
|
||||
return shapes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return shapes;
|
||||
}
|
||||
|
||||
private buildEvents(eventName : CanvasEventType, eventDetail : ICanvasDomEvent) {
|
||||
this._resizePos()
|
||||
const listeners = this.eventListeners.get(eventName);
|
||||
const target = this.getTargetShape(eventName, eventDetail.x, eventDetail.y);
|
||||
|
||||
|
||||
const event : ICanvasEvent = {
|
||||
type: eventName,
|
||||
x: eventDetail.x,
|
||||
y: eventDetail.y,
|
||||
target: target,
|
||||
detail: eventDetail.detail,
|
||||
touches: eventDetail.touches.map((el : ICanvasDomEvent) : ICanvasEvent => {
|
||||
return {
|
||||
type: eventName,
|
||||
x: el.x,
|
||||
y: el.y,
|
||||
target: target,
|
||||
detail: el.detail,
|
||||
touches: [] as ICanvasEvent[]
|
||||
} as ICanvasEvent
|
||||
})
|
||||
};
|
||||
if (listeners != null) {
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
let children = listeners[i]
|
||||
//执行画布的监听事件
|
||||
children(event);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.shapes.length; i++) {
|
||||
const shape = this.shapes[i];
|
||||
if (shape instanceof Shape) {
|
||||
if (event.detail.length == 0) continue;
|
||||
let startX = event.detail[0].startX
|
||||
let startY = event.detail[0].startY
|
||||
if (shape.isPointInPath(startX, startY, shape.id) && shape.draggable && eventName == 'down') {
|
||||
shape.draggableing = true;
|
||||
}
|
||||
if (shape.draggable && (eventName == 'up' || eventName == 'cancel') && shape.draggableing) {
|
||||
shape.draggableing = false;
|
||||
}
|
||||
if (shape.draggableing && eventName == 'move') {
|
||||
shape.drag(eventName, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (target.length > 0) {
|
||||
let isUpdate = false;
|
||||
for (let i = 0; i < target.length; i++) {
|
||||
const shape = target[i];
|
||||
if (shape instanceof Shape) {
|
||||
shape.buildEvents(eventName, event)
|
||||
if(shape.draggable&&!isUpdate){
|
||||
isUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(isUpdate){
|
||||
this.render()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
import { ICanvas } from '@/uni_modules/tmx-ui/core/canvas/ICanvas.uts';
|
||||
export type ShapeType = any;
|
||||
/** 旋转元素时的中心点,默认是topLeft左顶,center表示元素的中间 */
|
||||
export type ITextAlignType = 'left' | 'center' | 'right';
|
||||
export type ITextBaselineType = 'top' | 'middle' | 'bottom';
|
||||
export type ILineJoinType = "round" | "bevel" | "miter";
|
||||
export type ILineCapType = "butt" | "round" | "square";
|
||||
export type CanvasRotateCenter = "topLeft"|"center"|"topRight"|"bottomLeft"|"bottomRight"
|
||||
export type DirectionEventType = 'down' | 'move' | 'up';
|
||||
|
||||
export type InteractionEventType = 'click' | 'longpress' | 'dbclick' | 'cancel';
|
||||
export type CanvasEventType = DirectionEventType | InteractionEventType;
|
||||
export type ShapeSetAttrType = string | number | boolean | Array<string>
|
||||
export type IShapeVector2d = {
|
||||
x:number,
|
||||
y:number
|
||||
}
|
||||
export type ICanvasEvent = {
|
||||
type: CanvasEventType;
|
||||
/** 在画布上的坐标X */
|
||||
x: number;
|
||||
/** 在画布上的坐标Y */
|
||||
y: number;
|
||||
detail:IcanvasDomEventDetail[],
|
||||
touches:ICanvasEvent[];
|
||||
target: ShapeType[];
|
||||
}
|
||||
export type IcanvasDomEventDetail = {
|
||||
startX:number,
|
||||
startY:number,
|
||||
endX:number,
|
||||
endY:number,
|
||||
moveX:number,
|
||||
moveY:number,
|
||||
/** 相对上一次移动的距离*/
|
||||
moveLenX:number,
|
||||
/** 相对上一次移动的距离*/
|
||||
moveLenY:number,
|
||||
/** 相对起始时到当前位置移动的距离*/
|
||||
endLenX:number,
|
||||
/** 相对起始时到当前位置移动的距离*/
|
||||
endLenY:number
|
||||
// /** 多点触摸时,两点之间的距离 */
|
||||
// touchesLenX:number,
|
||||
// /** 多点触摸时,两点之间的距离 */
|
||||
// touchesLenY:number
|
||||
}
|
||||
export type ICanvasDomEvent = {
|
||||
type: CanvasEventType;
|
||||
x: number;
|
||||
y: number;
|
||||
detail:IcanvasDomEventDetail[],
|
||||
touches:ICanvasDomEvent[];
|
||||
}
|
||||
|
||||
export type IEventListener = (event: ICanvasEvent) => void;
|
||||
|
||||
export type IEventShape = {
|
||||
type: CanvasEventType;
|
||||
x: number;
|
||||
y: number;
|
||||
/** 元素本身内的坐标X */
|
||||
layerX:number;
|
||||
/** 元素本身内的坐标Y */
|
||||
layerY:number;
|
||||
touches:IEventShape[];
|
||||
target:ShapeType
|
||||
}
|
||||
export type IEventShapeListener = (event: IEventShape) => void;
|
||||
|
||||
export type IEventMap = Map<string, IEventListener[]>;
|
||||
export type ICanvasOptional = {
|
||||
/** 选式时传递当前组件this,组合式传递 getCurrentInstance()?.proxy ?? null */
|
||||
component:any|null,
|
||||
/** 画布id */
|
||||
canvasId:string,
|
||||
}
|
||||
export type IShapeOptional = {
|
||||
x?: number;
|
||||
y?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
fill?: string;
|
||||
stroke?: string;
|
||||
strokeWidth?: number;
|
||||
opacity?: number;
|
||||
visible?: boolean;
|
||||
rotation?: number;
|
||||
scaleX?: number;
|
||||
scaleY?: number;
|
||||
/** 元素相对舞台x坐标系的坐标,就是x,y为起始点,如果x,y为0就相当于是舞台的顶左 **/
|
||||
offsetX?: number;
|
||||
/** 元素相对舞台y坐标系的坐标,就是x,y为起始点,如果x,y为0就相当于是舞台的顶左 **/
|
||||
offsetY?: number;
|
||||
draggable?: boolean;
|
||||
// 元素之间上下重叠时,是否允许穿透冒泡逐层触发,默认不允许
|
||||
bubbleEvent?: boolean;
|
||||
rotateCenter?:CanvasRotateCenter;
|
||||
|
||||
// 线条样式
|
||||
lineJoin?: ILineJoinType;
|
||||
lineDashOffset?: number;
|
||||
lineDash ?:number[] ;
|
||||
lineCap?: ILineCapType;
|
||||
|
||||
src?:string;
|
||||
// 二维码的前景块的颜色。
|
||||
foreground?:string;
|
||||
// 二维码内容。
|
||||
qrcodeText?:string;
|
||||
|
||||
text?: string;
|
||||
fontSize?: number;
|
||||
fontFamily?: string;
|
||||
textAlign?: ITextAlignType;
|
||||
textBaseline?: ITextBaselineType;
|
||||
padding?: number;
|
||||
lineHeight?: number;
|
||||
|
||||
innerRadius ?: number;
|
||||
outerRadius ?: number;
|
||||
startAngle ?: number;
|
||||
endAngle ?: number;
|
||||
|
||||
radius ?:number;
|
||||
|
||||
sides ?:number;
|
||||
//多边形线的点
|
||||
points ?:number[];
|
||||
//多边形线是否自动闭合
|
||||
closed ?:boolean;
|
||||
// 多边线将连接线曲线化
|
||||
tension ?:number
|
||||
//将曲线平滑化
|
||||
bezier ?:boolean;
|
||||
// 椭圆的小径
|
||||
radiusX ?:number
|
||||
//椭圆的大径
|
||||
radiusY ?:number
|
||||
//星的角数量,默认5角星
|
||||
numPoints ?: number;
|
||||
|
||||
pointStart?:IShapeVector2d;
|
||||
pointEnd?:IShapeVector2d;
|
||||
|
||||
strokeGradient?:string[];
|
||||
fillGradient?:string[];
|
||||
clip?:boolean;
|
||||
zIndex?:number;
|
||||
textBgColor?:string;
|
||||
}
|
||||
export type IShapeBoundRect = {
|
||||
x : number,
|
||||
y : number,
|
||||
height : number,
|
||||
width : number
|
||||
}
|
||||
|
||||
export type QrGenerateFrameResult = {
|
||||
frameBuffer: number[],
|
||||
width: number
|
||||
}
|
||||
export interface IShape {
|
||||
canvas : ICanvas,
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
fill: string;
|
||||
stroke: string;
|
||||
strokeWidth: number;
|
||||
opacity: number;
|
||||
visible: boolean;
|
||||
rotation: number;
|
||||
scaleX: number;
|
||||
scaleY: number;
|
||||
/** 元素相对舞台x坐标系的坐标,就是x,y为起始点,如果x,y为0就相当于是舞台的顶左 **/
|
||||
offsetX: number;
|
||||
/** 元素相对舞台y坐标系的坐标,就是x,y为起始点,如果x,y为0就相当于是舞台的顶左 **/
|
||||
offsetY: number;
|
||||
draggable: boolean;
|
||||
// 元素之间上下重叠时,是否允许穿透冒泡逐层触发,默认不允许
|
||||
bubbleEvent: boolean;
|
||||
rotateCenter:CanvasRotateCenter;
|
||||
/**toggle方法中的选中状态 */
|
||||
toggleStatus:boolean;
|
||||
|
||||
|
||||
|
||||
// 线条样式
|
||||
lineJoin: ILineJoinType;
|
||||
lineDashOffset: number;
|
||||
lineDash:number[]
|
||||
lineCap: ILineCapType;
|
||||
|
||||
// text数据
|
||||
|
||||
text : string;
|
||||
fontSize : number;
|
||||
fontFamily : string ;
|
||||
textAlign : ITextAlignType;
|
||||
textBaseline : ITextBaselineType;
|
||||
padding : number;
|
||||
lineHeight : number;
|
||||
|
||||
//ring数据
|
||||
|
||||
innerRadius : number;
|
||||
outerRadius : number;
|
||||
startAngle : number;
|
||||
endAngle : number;
|
||||
|
||||
radius :number;
|
||||
sides: number;
|
||||
|
||||
src:string;
|
||||
|
||||
// 二维码的前景块的颜色。
|
||||
foreground:string;
|
||||
// 二维码内容。
|
||||
qrcodeText:string;
|
||||
|
||||
points :number[];
|
||||
//多边形线是否自动闭合
|
||||
closed :boolean;
|
||||
// 多边线将连接线曲线化
|
||||
tension:number
|
||||
bezier :boolean;
|
||||
|
||||
// 椭圆的小径
|
||||
radiusX :number;
|
||||
//椭圆的大径
|
||||
radiusY :number;
|
||||
|
||||
//星的角数量,默认5角星
|
||||
numPoints: number;
|
||||
|
||||
pointStart:IShapeVector2d;
|
||||
pointEnd:IShapeVector2d;
|
||||
|
||||
strokeGradient:string[];
|
||||
fillGradient:string[];
|
||||
|
||||
clip:boolean;
|
||||
zIndex:number;
|
||||
textBgColor:string;
|
||||
drag : (eventName : CanvasEventType, parentEventDetail : ICanvasEvent) => void
|
||||
|
||||
draw(ctx: CanvasRenderingContext2D): void;
|
||||
isPointInPath(x: number, y: number,shapeId:string): boolean;
|
||||
getBoundRect() : IShapeBoundRect;
|
||||
setAttr(key:string,value:any):void;
|
||||
getAttr(key: string): any | null;
|
||||
toggle(call:(status:boolean,target:any)=>void):void;
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
import { Shape } from '../shape.uts';
|
||||
import { ICanvas } from '@/uni_modules/tmx-ui/core/canvas/ICanvas.uts';
|
||||
|
||||
export type EasingFunction = (t: number) => number;
|
||||
type UpdateCall = (t: number) => void;
|
||||
type AnimationConfig = {
|
||||
props: UTSJSONObject;
|
||||
duration: number;
|
||||
easing?: EasingFunction;
|
||||
startValues: Map<string, number>;
|
||||
}
|
||||
|
||||
export class Tween {
|
||||
private target: Shape;
|
||||
private startValues: Map<string, number> = new Map();
|
||||
private endValues: Map<string, number> = new Map();
|
||||
private duration: number;
|
||||
private startTime: number = 0;
|
||||
private isPlaying: boolean = false;
|
||||
private easing: EasingFunction;
|
||||
private onUpdateFun: ((progress:number) => void) | null = null;
|
||||
private onCompleteFun: (() => void) | null = null;
|
||||
private canvas: CanvasContext;
|
||||
private parentICanvas: ICanvas;
|
||||
private requestAnimationFrameId = 0;
|
||||
private loop: number = 0; // 循环次数,0表示不循环,-1表示无限循环
|
||||
private currentLoop: number = 0; // 当前已完成的循环次数
|
||||
private yoyo: boolean = false; // 是否开启往返播放
|
||||
private isReverse: boolean = false; // 当前是否为反向播放
|
||||
private animationQueue: AnimationConfig[] = []; // 动画队列
|
||||
private currentAnimationIndex: number = -1; // 当前执行的动画索引
|
||||
private delayDur = 0
|
||||
private delayDurTid = 12
|
||||
constructor(target: Shape, canvas: ICanvas) {
|
||||
this.target = target;
|
||||
this.canvas = canvas.canvas!;
|
||||
this.parentICanvas = canvas;
|
||||
this.duration = 1000;
|
||||
this.easing = Easing.linear as EasingFunction;
|
||||
}
|
||||
|
||||
to(props: UTSJSONObject, duration: number = 1000): Tween {
|
||||
return this.addTo(props, duration);
|
||||
}
|
||||
|
||||
addTo(props: UTSJSONObject, duration: number = 1000, easing: EasingFunction = this.easing): Tween {
|
||||
const startValues = new Map<string, number>();
|
||||
for (const key in props) {
|
||||
let keyValue = props[key];
|
||||
if (typeof keyValue == 'number') {
|
||||
let startValue = this.target.getAttr(key);
|
||||
if(typeof startValue == 'number'){
|
||||
startValues.set(key, startValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.animationQueue.push({
|
||||
props: props,
|
||||
duration: duration,
|
||||
easing: easing,
|
||||
startValues: startValues
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
// 在队列中插入一个仅用于延时的动画段
|
||||
delay(ms: number): Tween {
|
||||
this.delayDur = ms;
|
||||
return this;
|
||||
}
|
||||
|
||||
clearAnimations(): Tween {
|
||||
this.stop();
|
||||
this.canvas.cancelAnimationFrame(this.requestAnimationFrameId);
|
||||
this.requestAnimationFrameId = 0;
|
||||
this.animationQueue = [];
|
||||
this.currentAnimationIndex = -1;
|
||||
this.startValues.clear();
|
||||
this.endValues.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
start(): Tween {
|
||||
let _this = this;
|
||||
if (this.isPlaying) {
|
||||
return this;
|
||||
}
|
||||
if(this.delayDur>0){
|
||||
_this.delayDurTid = setTimeout(function() {
|
||||
if (_this.requestAnimationFrameId != 0) {
|
||||
_this.canvas.cancelAnimationFrame(_this.requestAnimationFrameId);
|
||||
_this.requestAnimationFrameId = 0;
|
||||
}
|
||||
|
||||
_this.update();
|
||||
}, _this.delayDur);
|
||||
}else{
|
||||
if (this.requestAnimationFrameId != 0) {
|
||||
this.canvas.cancelAnimationFrame(this.requestAnimationFrameId);
|
||||
this.requestAnimationFrameId = 0;
|
||||
}
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
|
||||
this.isPlaying = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
stop(): Tween {
|
||||
clearTimeout(this.delayDurTid)
|
||||
this.isPlaying = false;
|
||||
if (this.requestAnimationFrameId != 0) {
|
||||
this.canvas.cancelAnimationFrame(this.requestAnimationFrameId);
|
||||
this.requestAnimationFrameId = 0;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
const selftThis = this;
|
||||
let updateSelf = null as null|UpdateCall;
|
||||
selftThis.currentLoop = 1;
|
||||
selftThis.isReverse = false;
|
||||
// 开始执行队列中的第一个动画
|
||||
if (selftThis.currentAnimationIndex == -1 && selftThis.animationQueue.length > 0) {
|
||||
selftThis.currentAnimationIndex = 0;
|
||||
const currentAnimation = selftThis.animationQueue[selftThis.currentAnimationIndex];
|
||||
selftThis.duration = currentAnimation.duration;
|
||||
selftThis.easing = currentAnimation.easing ?? selftThis.easing;
|
||||
selftThis.startValues.clear();
|
||||
selftThis.endValues.clear();
|
||||
for (const key in currentAnimation.props) {
|
||||
let keyValue = currentAnimation.props[key];
|
||||
if (typeof keyValue == 'number') {
|
||||
let startValue = currentAnimation.startValues?.get(key);
|
||||
if(typeof startValue == 'number'){
|
||||
selftThis.startValues.set(key, startValue!);
|
||||
selftThis.endValues.set(key, keyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateSelf = (time:number)=>{
|
||||
|
||||
if (!selftThis.isPlaying) return;
|
||||
const currentTime = time;
|
||||
let elapsed = currentTime - selftThis.startTime;
|
||||
|
||||
if (elapsed > selftThis.duration) {
|
||||
|
||||
if (selftThis.yoyo && !selftThis.isReverse && (selftThis.currentLoop < selftThis.loop||selftThis.loop==-1)) {
|
||||
// 开启yoyo且当前为正向播放,切换为反向播放
|
||||
selftThis.isReverse = true;
|
||||
|
||||
if (selftThis.isPlaying) {
|
||||
selftThis.requestAnimationFrameId = selftThis.canvas.requestAnimationFrame((t) => {
|
||||
selftThis.startTime = t;
|
||||
updateSelf!(t)
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if(this.yoyo){
|
||||
selftThis.isReverse = false;
|
||||
}
|
||||
// 检查是否还有下一个动画
|
||||
if (selftThis.currentAnimationIndex < selftThis.animationQueue.length - 1) {
|
||||
selftThis.currentAnimationIndex++;
|
||||
|
||||
const nextAnimation = selftThis.animationQueue[selftThis.currentAnimationIndex];
|
||||
selftThis.duration = nextAnimation.duration;
|
||||
selftThis.easing = nextAnimation.easing ?? selftThis.easing;
|
||||
selftThis.startValues.clear();
|
||||
selftThis.endValues.clear();
|
||||
for (const key in nextAnimation.props) {
|
||||
let keyValue = nextAnimation.props[key];
|
||||
if (typeof keyValue == 'number') {
|
||||
let startValue = selftThis.target.getAttr(key);
|
||||
if(typeof startValue == 'number'){
|
||||
selftThis.startValues.set(key, nextAnimation.startValues.get(key)!);
|
||||
selftThis.endValues.set(key, keyValue);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
selftThis.requestAnimationFrameId = selftThis.canvas.requestAnimationFrame((t) => {
|
||||
selftThis.startTime = t;
|
||||
updateSelf!(t);
|
||||
});
|
||||
return;
|
||||
} else if (selftThis.loop == -1 || selftThis.currentLoop < selftThis.loop) {
|
||||
// 重置动画索引,开始新一轮循环
|
||||
selftThis.currentAnimationIndex = 0;
|
||||
selftThis.currentLoop++;
|
||||
if(selftThis.animationQueue.length>0){
|
||||
const nextAnimation = selftThis.animationQueue[selftThis.currentAnimationIndex];
|
||||
selftThis.duration = nextAnimation.duration;
|
||||
selftThis.easing = nextAnimation.easing ?? selftThis.easing;
|
||||
selftThis.startValues.clear();
|
||||
selftThis.endValues.clear();
|
||||
|
||||
for (const key in nextAnimation.props) {
|
||||
let keyValue = nextAnimation.props[key];
|
||||
if (typeof keyValue == 'number') {
|
||||
let startValue = selftThis.target.getAttr(key);
|
||||
if(typeof startValue == 'number'){
|
||||
selftThis.startValues.set(key, nextAnimation.startValues.get(key)!);
|
||||
selftThis.endValues.set(key, keyValue);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
selftThis.requestAnimationFrameId = selftThis.canvas.requestAnimationFrame((t) => {
|
||||
selftThis.startTime = t;
|
||||
updateSelf!(t);
|
||||
});
|
||||
}else{
|
||||
selftThis.duration = currentTime;
|
||||
selftThis.easing = selftThis.easing;
|
||||
selftThis.startValues.clear();
|
||||
selftThis.endValues.clear();
|
||||
selftThis.requestAnimationFrameId = selftThis.canvas.requestAnimationFrame((t) => {
|
||||
selftThis.startTime = t;
|
||||
updateSelf!(t);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
selftThis.isPlaying = false;
|
||||
selftThis.currentAnimationIndex = -1; // 重置动画索引
|
||||
selftThis.canvas.cancelAnimationFrame(selftThis.requestAnimationFrameId);
|
||||
selftThis.requestAnimationFrameId = 0;
|
||||
selftThis.animationQueue = [];
|
||||
selftThis.startValues.clear();
|
||||
selftThis.endValues.clear();
|
||||
}
|
||||
}
|
||||
|
||||
let progress = selftThis.easing(elapsed / selftThis.duration);
|
||||
if (selftThis.isReverse) {
|
||||
progress = 1 - progress;
|
||||
}
|
||||
|
||||
selftThis.startValues.forEach((startValue, key) => {
|
||||
const endValue = selftThis.endValues.get(key)!;
|
||||
const value = startValue + (endValue - startValue) * progress;
|
||||
selftThis.target.setAttr(key,value);
|
||||
});
|
||||
|
||||
selftThis.target.needsUpdate = true;
|
||||
|
||||
if (selftThis.onUpdateFun!=null) {
|
||||
selftThis.onUpdateFun!(progress);
|
||||
}
|
||||
selftThis.parentICanvas.update();
|
||||
if (!selftThis.isPlaying && selftThis.onCompleteFun!=null) {
|
||||
selftThis.onCompleteFun!();
|
||||
return;
|
||||
}
|
||||
if (selftThis.isPlaying) {
|
||||
selftThis.requestAnimationFrameId = selftThis.canvas.requestAnimationFrame((t) => updateSelf!(t));
|
||||
}
|
||||
}
|
||||
selftThis.requestAnimationFrameId = selftThis.canvas.requestAnimationFrame((t) => {
|
||||
selftThis.startTime = t;
|
||||
updateSelf!(t);
|
||||
});
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.stop();
|
||||
this.animationQueue = [];
|
||||
this.currentAnimationIndex = -1;
|
||||
this.startValues.clear();
|
||||
this.endValues.clear();
|
||||
this.onUpdateFun = null;
|
||||
this.onCompleteFun = null;
|
||||
this.loop = 0;
|
||||
this.yoyo = false;
|
||||
|
||||
}
|
||||
|
||||
setEasing(easingFunction: EasingFunction): Tween {
|
||||
this.easing = easingFunction;
|
||||
return this;
|
||||
}
|
||||
|
||||
onUpdate(callback: (progress:number) => void): Tween {
|
||||
this.onUpdateFun = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
onComplete(callback: () => void): Tween {
|
||||
this.onCompleteFun = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
setLoop(count: number): Tween {
|
||||
this.loop = count;
|
||||
return this;
|
||||
}
|
||||
|
||||
setYoyo(value: boolean): Tween {
|
||||
this.yoyo = value;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export const Easing = {
|
||||
linear: (t: number): number => t,
|
||||
|
||||
easeInQuad: (t: number): number => t * t,
|
||||
|
||||
easeOutQuad: (t: number): number => t * (2 - t),
|
||||
|
||||
easeInOutQuad: (t: number): number => {
|
||||
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
||||
},
|
||||
|
||||
easeInCubic: (t: number): number => t * t * t,
|
||||
|
||||
easeOutCubic: (t: number): number => (t-1) * t * t + 1,
|
||||
|
||||
easeInOutCubic: (t: number): number => {
|
||||
return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
|
||||
},
|
||||
|
||||
easeInElastic: (t: number): number => {
|
||||
const c4 = (2 * Math.PI) / 3;
|
||||
return t === 0 ? 0 : t === 1 ? 1 : -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * c4);
|
||||
},
|
||||
|
||||
easeOutElastic: (t: number): number => {
|
||||
const c4 = (2 * Math.PI) / 3;
|
||||
return t === 0 ? 0 : t === 1 ? 1 : Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
|
||||
},
|
||||
|
||||
easeInOutElastic: (t: number): number => {
|
||||
const c5 = (2 * Math.PI) / 4.5;
|
||||
return t === 0 ? 0 : t === 1 ? 1 : t < 0.5
|
||||
? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2
|
||||
: (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c5)) / 2 + 1;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,200 @@
|
||||
import { Shape } from './shape.uts';
|
||||
import { CanvasRotateCenter, ShapeSetAttrType, IShapeBoundRect, IShapeOptional } from '../interface.uts';
|
||||
import { ICanvas } from '@/uni_modules/tmx-ui/core/canvas/ICanvas.uts';
|
||||
export class IArc extends Shape {
|
||||
constructor(config : IShapeOptional,canvas:ICanvas) {
|
||||
super(config,canvas);
|
||||
this.radius = config?.radius ?? 30;
|
||||
this.startAngle = config?.startAngle ?? 0;
|
||||
this.endAngle = config?.endAngle ?? 90;
|
||||
this.type = "IArc"
|
||||
}
|
||||
|
||||
setRadius(value : number) : IArc {
|
||||
this.radius = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override getBoundRect() : IShapeBoundRect {
|
||||
// 计算精确的弧段/扇形包围盒
|
||||
const centerX = this.x;
|
||||
const centerY = this.y;
|
||||
const r = Math.max(0, this.radius);
|
||||
const d2r = Math.PI / 180;
|
||||
|
||||
// 角度标准化到 [0, 360)
|
||||
const normalize = (deg:number):number => {
|
||||
let a = deg % 360;
|
||||
if (a < 0) a += 360;
|
||||
return a;
|
||||
};
|
||||
|
||||
let start = normalize(this.startAngle);
|
||||
let end = normalize(this.endAngle);
|
||||
if (end < start) end += 360; // 保证 end >= start(顺时针绘制)
|
||||
|
||||
const withinSweep = (deg:number):boolean => {
|
||||
let a = normalize(deg);
|
||||
if (a < start) a += 360;
|
||||
return a >= start && a <= end;
|
||||
};
|
||||
|
||||
const candidatesX:number[] = [];
|
||||
const candidatesY:number[] = [];
|
||||
|
||||
// 起止点
|
||||
const sx = centerX + r * Math.cos(start * d2r);
|
||||
const sy = centerY + r * Math.sin(start * d2r);
|
||||
const ex = centerX + r * Math.cos(end * d2r);
|
||||
const ey = centerY + r * Math.sin(end * d2r);
|
||||
candidatesX.push(sx, ex);
|
||||
candidatesY.push(sy, ey);
|
||||
|
||||
// 若为扇形(有填充),中心点也参与包围盒
|
||||
const isFilled = (this.fill != "" || this.fillGradient.length > 0);
|
||||
if (isFilled) {
|
||||
candidatesX.push(centerX);
|
||||
candidatesY.push(centerY);
|
||||
}
|
||||
|
||||
// 检查跨越极值角(0, 90, 180, 270)
|
||||
const extrema = [0, 90, 180, 270];
|
||||
for (const deg of extrema) {
|
||||
if (withinSweep(deg)) {
|
||||
const rad = deg * d2r;
|
||||
candidatesX.push(centerX + r * Math.cos(rad));
|
||||
candidatesY.push(centerY + r * Math.sin(rad));
|
||||
}
|
||||
}
|
||||
|
||||
let minX = candidatesX.length>0?candidatesX[0]:centerX;
|
||||
let maxX = candidatesX.length>0?candidatesX[0]:centerX;
|
||||
let minY = candidatesY.length>0?candidatesY[0]:centerY;
|
||||
let maxY = candidatesY.length>0?candidatesY[0]:centerY;
|
||||
for (let i=1;i<candidatesX.length;i++) {
|
||||
const vx = candidatesX[i];
|
||||
if (!isNaN(vx)) {
|
||||
if (vx < minX) minX = vx;
|
||||
if (vx > maxX) maxX = vx;
|
||||
}
|
||||
}
|
||||
for (let i=1;i<candidatesY.length;i++) {
|
||||
const vy = candidatesY[i];
|
||||
if (!isNaN(vy)) {
|
||||
if (vy < minY) minY = vy;
|
||||
if (vy > maxY) maxY = vy;
|
||||
}
|
||||
}
|
||||
|
||||
// 描边扩张
|
||||
const hasStroke = (this.stroke != "" || this.strokeGradient.length > 0);
|
||||
const pad = hasStroke ? this.strokeWidth : 0;
|
||||
minX -= pad;
|
||||
minY -= pad;
|
||||
maxX += pad;
|
||||
maxY += pad;
|
||||
|
||||
return {
|
||||
x: minX,
|
||||
y: minY,
|
||||
width: Math.max(0, maxX - minX),
|
||||
height: Math.max(0, maxY - minY)
|
||||
} as IShapeBoundRect;
|
||||
}
|
||||
|
||||
override setWidth(value : number) : IArc {
|
||||
this.height = value;
|
||||
this.width = value;
|
||||
this.radius = value / 2
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override setHeight(value : number) : IArc {
|
||||
this.height = value;
|
||||
this.width = value;
|
||||
this.radius = value / 2
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setStartAngle(angle:number):IArc{
|
||||
this.startAngle = angle;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
setEndAngle(angle:number):IArc{
|
||||
this.endAngle = angle;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
override draw(ctx : CanvasRenderingContext2D) {
|
||||
if (this.visible == false) return;
|
||||
super.draw(ctx);
|
||||
ctx.beginPath();
|
||||
// 将角度转换为弧度
|
||||
const startRad = this.startAngle * Math.PI / 180;
|
||||
const endRad = this.endAngle * Math.PI / 180;
|
||||
// 如果有填充色,先移动到圆心,绘制扇形
|
||||
if (this.fill != ""||this.fillGradient.length>0) {
|
||||
ctx.moveTo(this.x, this.y);
|
||||
}
|
||||
// 绘制圆弧
|
||||
ctx.arc(this.x, this.y, this.radius, startRad, endRad, false);
|
||||
|
||||
if (this.fill != ""||this.fillGradient.length>0) {
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
if (this.stroke != ""||this.strokeGradient.length>0) {
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
override isPointInPath(x: number, y: number, shapeId: string): boolean {
|
||||
if (!this.visible || (shapeId != "" && shapeId != this.id)) return false;
|
||||
const realX = x - (this.offsetX) - this.x;
|
||||
const realY = y - (this.offsetY) - this.y;
|
||||
|
||||
// 计算点到圆心的距离
|
||||
const dx = realX;
|
||||
const dy = realY;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// 计算点相对于圆心的角度(弧度)
|
||||
const angle = Math.atan2(dy, dx);
|
||||
// 将角度转换为0-360度范围
|
||||
let degrees = angle * 180 / Math.PI;
|
||||
if (degrees < 0) degrees += 360;
|
||||
|
||||
// 将起始角度和结束角度标准化到0-360度范围
|
||||
let start = this.startAngle % 360;
|
||||
if (start < 0) start += 360;
|
||||
let end = this.endAngle % 360;
|
||||
if (end < 0) end += 360;
|
||||
// 确保end大于start
|
||||
if (end < start) end += 360;
|
||||
|
||||
// 检查点的角度是否在弧的范围内
|
||||
const inAngle = degrees >= start && degrees <= end;
|
||||
|
||||
if (this.fill !== "") {
|
||||
// 填充模式:检查是否在扇形内
|
||||
return inAngle && distance <= this.radius;
|
||||
} else if (this.stroke !== "") {
|
||||
// 非填充模式:检查是否在圆弧线附近
|
||||
// 考虑整个线宽作为检测范围
|
||||
const tolerance = this.strokeWidth;
|
||||
const distanceFromArc = Math.abs(distance - this.radius);
|
||||
return inAngle && distanceFromArc <= tolerance;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import { Shape } from './shape.uts';
|
||||
import { CanvasRotateCenter, IShapeBoundRect, IShapeOptional } from '../interface.uts';
|
||||
import { ICanvas } from '@/uni_modules/tmx-ui/core/canvas/ICanvas.uts';
|
||||
|
||||
export class ICircle extends Shape {
|
||||
override type = 'ICircle'
|
||||
constructor(config : IShapeOptional,canvas:ICanvas) {
|
||||
super(config,canvas);
|
||||
this.radius = config?.radius ?? 30;
|
||||
}
|
||||
|
||||
setRadius(value : number) : ICircle {
|
||||
this.radius = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override getBoundRect() : IShapeBoundRect {
|
||||
const r = Math.max(0, this.radius);
|
||||
const pad = (this.stroke != "" ? this.strokeWidth / 2 : 0);
|
||||
const d = r * 2 + pad * 2;
|
||||
return {
|
||||
x: this.x - r - pad,
|
||||
y: this.y - r - pad,
|
||||
width: d,
|
||||
height: d
|
||||
} as IShapeBoundRect;
|
||||
}
|
||||
|
||||
override setWidth(value : number) : ICircle {
|
||||
this.height = value;
|
||||
this.width = value;
|
||||
this.radius = value / 2
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override setHeight(value : number) : ICircle {
|
||||
this.height = value;
|
||||
this.width = value;
|
||||
this.radius = value / 2
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override draw(ctx : CanvasRenderingContext2D) {
|
||||
if (this.visible == false) return;
|
||||
super.draw(ctx);
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
|
||||
ctx.closePath();
|
||||
if (this.fill != ""||this.fillGradient.length>0) {
|
||||
ctx.fill();
|
||||
}
|
||||
if (this.stroke != "") {
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
override isPointInPath(x: number, y: number, shapeId: string): boolean {
|
||||
if (!this.visible || (shapeId != "" && shapeId != this.id)) return false;
|
||||
|
||||
// 计算点击位置相对于圆心的实际坐标
|
||||
let realX = x - this.offsetX - this.x;
|
||||
let realY = y - this.offsetY - this.y;
|
||||
|
||||
// 如果有旋转,需要将坐标转换回未旋转状态
|
||||
if (this.rotation != 0) {
|
||||
const angle = -this.rotation * Math.PI / 180;
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
let centerX = 0;
|
||||
let centerY = 0;
|
||||
|
||||
// 根据不同的旋转中心点设置centerX和centerY
|
||||
switch(this.rotateCenter) {
|
||||
case 'topLeft':
|
||||
centerX = 0;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'topRight':
|
||||
centerX = this.width;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'bottomLeft':
|
||||
centerX = 0;
|
||||
centerY = this.height;
|
||||
break;
|
||||
case 'bottomRight':
|
||||
centerX = this.width;
|
||||
centerY = this.height;
|
||||
break;
|
||||
case 'center':
|
||||
default:
|
||||
centerX = this.width/2;
|
||||
centerY = this.height/2;
|
||||
break;
|
||||
}
|
||||
|
||||
const dx = realX - centerX;
|
||||
const dy = realY - centerY;
|
||||
realX = centerX + dx * cos - dy * sin;
|
||||
realY = centerY + dx * sin + dy * cos;
|
||||
}
|
||||
|
||||
// 计算点到圆心的距离
|
||||
const distance = Math.sqrt(realX * realX + realY * realY);
|
||||
|
||||
// 考虑缩放因素
|
||||
const scaledRadius = this.radius * Math.min(this.scaleX, this.scaleY);
|
||||
|
||||
// 如果只有描边模式,检查点是否在圆形线条附近
|
||||
if (this.stroke != "" && this.fill == ""&&this.fillGradient.length==0) {
|
||||
const strokeWidth = this.strokeWidth / 2;
|
||||
return Math.abs(distance - scaledRadius) <= strokeWidth;
|
||||
}
|
||||
|
||||
// 如果同时有描边和填充,或者只有填充
|
||||
if (this.fill != ""||this.fillGradient.length>0) {
|
||||
// 如果有描边,扩大检测范围到描边外缘
|
||||
if (this.stroke != "") {
|
||||
return distance <= (scaledRadius + this.strokeWidth / 2);
|
||||
}
|
||||
|
||||
// 只有填充时,检查点是否在圆内
|
||||
return distance <= scaledRadius;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import { Shape } from './shape.uts';
|
||||
import { CanvasRotateCenter, IShapeBoundRect, IShapeOptional } from '../interface.uts';
|
||||
import { ICanvas } from '@/uni_modules/tmx-ui/core/canvas/ICanvas.uts';
|
||||
|
||||
export class IEllipse extends Shape {
|
||||
override type = 'IEllipse'
|
||||
constructor(config : IShapeOptional,canvas:ICanvas) {
|
||||
super(config,canvas);
|
||||
this.radiusX = config?.radiusX ?? 80;
|
||||
this.radiusY = config?.radiusY ?? 20;
|
||||
}
|
||||
|
||||
setRadiusX(value : number) : IEllipse {
|
||||
this.radiusX = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setRadiusY(value : number) : IEllipse {
|
||||
this.radiusY = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override getBoundRect() : IShapeBoundRect {
|
||||
const rx = Math.max(0, this.radiusX);
|
||||
const ry = Math.max(0, this.radiusY);
|
||||
const pad = (this.stroke != "" ? this.strokeWidth / 2 : 0);
|
||||
return {
|
||||
x: this.x - rx - pad,
|
||||
y: this.y - ry - pad,
|
||||
width: rx * 2 + pad * 2,
|
||||
height: ry * 2 + pad * 2
|
||||
} as IShapeBoundRect;
|
||||
}
|
||||
|
||||
override setWidth(value : number) : IEllipse {
|
||||
this.width = value;
|
||||
this.radiusX = value / 2;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override setHeight(value : number) : IEllipse {
|
||||
this.height = value;
|
||||
this.radiusY = value / 2;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override draw(ctx : CanvasRenderingContext2D) {
|
||||
if (this.visible == false) return;
|
||||
super.draw(ctx);
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(this.x, this.y, this.radiusX, this.radiusY, 0, 0, Math.PI * 2, false);
|
||||
ctx.closePath();
|
||||
if (this.fill != "") {
|
||||
ctx.fill();
|
||||
}
|
||||
if (this.stroke != "") {
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
override isPointInPath(x: number, y: number, shapeId: string): boolean {
|
||||
if (!this.visible || (shapeId != "" && shapeId != this.id)) return false;
|
||||
|
||||
// 计算点击位置相对于椭圆中心的实际坐标
|
||||
let realX = x - this.offsetX - this.x;
|
||||
let realY = y - this.offsetY - this.y;
|
||||
|
||||
// 如果有旋转,需要将坐标转换回未旋转状态
|
||||
if (this.rotation != 0) {
|
||||
const angle = -this.rotation * Math.PI / 180;
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
let centerX = 0;
|
||||
let centerY = 0;
|
||||
|
||||
// 根据不同的旋转中心点设置centerX和centerY
|
||||
switch(this.rotateCenter) {
|
||||
case 'topLeft':
|
||||
centerX = 0;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'topRight':
|
||||
centerX = this.width;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'bottomLeft':
|
||||
centerX = 0;
|
||||
centerY = this.height;
|
||||
break;
|
||||
case 'bottomRight':
|
||||
centerX = this.width;
|
||||
centerY = this.height;
|
||||
break;
|
||||
case 'center':
|
||||
default:
|
||||
centerX = this.width/2;
|
||||
centerY = this.height/2;
|
||||
break;
|
||||
}
|
||||
|
||||
const dx = realX - centerX;
|
||||
const dy = realY - centerY;
|
||||
realX = centerX + dx * cos - dy * sin;
|
||||
realY = centerY + dx * sin + dy * cos;
|
||||
}
|
||||
|
||||
// 考虑缩放因素
|
||||
const scaledRadiusX = this.radiusX * Math.abs(this.scaleX);
|
||||
const scaledRadiusY = this.radiusY * Math.abs(this.scaleY);
|
||||
|
||||
// 计算点是否在椭圆内(标准椭圆方程)
|
||||
const normalizedX = realX / scaledRadiusX;
|
||||
const normalizedY = realY / scaledRadiusY;
|
||||
const distance = normalizedX * normalizedX + normalizedY * normalizedY;
|
||||
|
||||
// 如果只有描边模式,检查点是否在椭圆线条附近
|
||||
if (this.stroke != "" && this.fill == "") {
|
||||
const strokeWidth = this.strokeWidth / 2;
|
||||
const outerDistance = Math.sqrt(distance);
|
||||
return Math.abs(outerDistance - 1) * Math.min(scaledRadiusX, scaledRadiusY) <= strokeWidth;
|
||||
}
|
||||
|
||||
// 如果同时有描边和填充,或者只有填充
|
||||
if (this.fill != "") {
|
||||
// 如果有描边,扩大检测范围到描边外缘
|
||||
if (this.stroke != "") {
|
||||
const strokeOffset = this.strokeWidth / (2 * Math.min(scaledRadiusX, scaledRadiusY));
|
||||
return distance <= (1 + strokeOffset) * (1 + strokeOffset);
|
||||
}
|
||||
// 只有填充时,检查点是否在椭圆内
|
||||
return distance <= 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
import { Shape } from './shape.uts';
|
||||
import { IShapeBoundRect, IShapeOptional, ShapeSetAttrType } from '../interface.uts';
|
||||
import { ICanvas } from '@/uni_modules/tmx-ui/core/canvas/ICanvas.uts';
|
||||
|
||||
/**
|
||||
* 图片
|
||||
* @description 注意创建添加完图片后。要通过方法setSrc(xx)来设置加载图片
|
||||
* @version 1.0.0
|
||||
* @date 2025/2/6
|
||||
* @copright 不允许外发给别人,只可在tmui4x是使用
|
||||
*/
|
||||
export class ImageShape extends Shape {
|
||||
override type = 'ImageShape'
|
||||
private image : any | null = null;
|
||||
loaded : boolean = false;
|
||||
private srcWidth : number = 0;
|
||||
private srcHeight : number = 0;
|
||||
private cropX : number = 0;
|
||||
private cropY : number = 0;
|
||||
private cropWidth : number = 0;
|
||||
private cropHeight : number = 0;
|
||||
private useCrop : boolean = false;
|
||||
private canvasContext : CanvasContext;
|
||||
constructor(config : IShapeOptional,canvas:ICanvas) {
|
||||
super(config,canvas);
|
||||
this.canvasContext = canvas.canvas!
|
||||
this.type = 'ImageShape';
|
||||
if(this.src!=''){
|
||||
this.setSrc(this.src)
|
||||
}
|
||||
}
|
||||
|
||||
setSrc(src : string) : ImageShape {
|
||||
if (!this.visible) return this;
|
||||
this.src = src;
|
||||
this.loaded = false;
|
||||
if (this.src != '') {
|
||||
this.loadImage();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private loadImage() : void {
|
||||
if (this.loaded) return;
|
||||
const image = this.canvasContext.createImage();
|
||||
image.src = this.src;
|
||||
let _this = this;
|
||||
image.onload = () => {
|
||||
_this.image = image;
|
||||
_this.loaded = true;
|
||||
if (_this.width == 0) _this.width = image.width;
|
||||
if (_this.height == 0) _this.height = image.height;
|
||||
_this.srcWidth = image.width;
|
||||
_this.srcHeight = image.height;
|
||||
_this.needsUpdate = true;
|
||||
_this.canvas.update()
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
crop(x : number, y : number, width : number, height : number) : ImageShape {
|
||||
this.cropX = x;
|
||||
this.cropY = y;
|
||||
this.cropWidth = width;
|
||||
this.cropHeight = height;
|
||||
this.useCrop = true;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
resetCrop() : ImageShape {
|
||||
this.useCrop = false;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override draw(ctx : CanvasRenderingContext2D) : void {
|
||||
|
||||
if (!this.loaded || this.image == null) return;
|
||||
super.draw(ctx);
|
||||
|
||||
// #ifdef APP||WEB
|
||||
if (this.useCrop) {
|
||||
ctx.drawImage(
|
||||
this.image! as Image,
|
||||
this.cropX,
|
||||
this.cropY,
|
||||
this.cropWidth,
|
||||
this.cropHeight,
|
||||
this.x,
|
||||
this.y,
|
||||
this.width,
|
||||
this.height
|
||||
);
|
||||
} else {
|
||||
ctx.drawImage(
|
||||
this.image! as Image,
|
||||
this.x,
|
||||
this.y,
|
||||
this.width,
|
||||
this.height
|
||||
);
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
if (this.useCrop) {
|
||||
ctx.drawImage(
|
||||
this.image!,
|
||||
this.cropX,
|
||||
this.cropY,
|
||||
this.cropWidth,
|
||||
this.cropHeight,
|
||||
this.x,
|
||||
this.y,
|
||||
this.width,
|
||||
this.height
|
||||
);
|
||||
} else {
|
||||
ctx.drawImage(
|
||||
this.image!,
|
||||
this.x,
|
||||
this.y,
|
||||
this.width,
|
||||
this.height
|
||||
);
|
||||
}
|
||||
// #endif
|
||||
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
override isPointInPath(x : number, y : number, shapeId : string) : boolean {
|
||||
if (!this.visible || !this.loaded || (shapeId != "" && shapeId != this.id)) return false;
|
||||
|
||||
const realX = x - this.x - this.offsetX;
|
||||
const realY = y - this.y - this.offsetY;
|
||||
|
||||
if (this.rotation != 0) {
|
||||
const angle = -this.rotation * Math.PI / 180;
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
let centerX = 0;
|
||||
let centerY = 0;
|
||||
|
||||
// 根据不同的旋转中心点设置centerX和centerY
|
||||
switch(this.rotateCenter) {
|
||||
case 'topLeft':
|
||||
centerX = 0;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'topRight':
|
||||
centerX = this.width;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'bottomLeft':
|
||||
centerX = 0;
|
||||
centerY = this.height;
|
||||
break;
|
||||
case 'bottomRight':
|
||||
centerX = this.width;
|
||||
centerY = this.height;
|
||||
break;
|
||||
case 'center':
|
||||
default:
|
||||
centerX = this.width/2;
|
||||
centerY = this.height/2;
|
||||
break;
|
||||
}
|
||||
|
||||
const dx = realX - centerX;
|
||||
const dy = realY - centerY;
|
||||
const rotatedX = centerX + dx * cos - dy * sin;
|
||||
const rotatedY = centerY + dx * sin + dy * cos;
|
||||
return rotatedX >= 0 && rotatedX <= this.width * this.scaleX && rotatedY >= 0 && rotatedY <= this.height * this.scaleY;
|
||||
}
|
||||
|
||||
return realX >= 0 && realX <= this.width * this.scaleX && realY >= 0 && realY <= this.height * this.scaleY;
|
||||
}
|
||||
|
||||
override getBoundRect() : IShapeBoundRect {
|
||||
return {
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
width: this.width,
|
||||
height: this.height
|
||||
} as IShapeBoundRect;
|
||||
}
|
||||
|
||||
override getAttr(key: string): any | null {
|
||||
const superValue = super.getAttr(key);
|
||||
if(superValue!=null) return superValue;
|
||||
switch (key) {
|
||||
case 'src': return this.src;
|
||||
case 'cropX': return this.cropX;
|
||||
case 'cropY': return this.cropY;
|
||||
case 'cropWidth': return this.cropWidth;
|
||||
case 'cropHeight': return this.cropHeight;
|
||||
case 'useCrop': return this.useCrop;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
import { Shape } from '@/uni_modules/tmx-ui/core/canvas/lib/shape.uts';
|
||||
import { ICanvas } from '@/uni_modules/tmx-ui/core/canvas/ICanvas.uts';
|
||||
type LayoutDirection = 'horizontal'|'vertical'
|
||||
type LayoutAnchor = 'top'|'bottom'
|
||||
type LayoutMode = 'left'|'right'|'between'|'center';//左对齐,右对齐,两边对齐均分,中间对齐
|
||||
type CurSizeType = {width:number,height:number}
|
||||
type RoysType = {items:Shape[], sizes:CurSizeType[], rowWidth:number, rowHeight:number}
|
||||
type CoysType = {items:Shape[], sizes:CurSizeType[], colWidth:number, colHeight:number}
|
||||
export class ILayout {
|
||||
x:number = 0
|
||||
y:number = 0
|
||||
width:number = 0;
|
||||
height:number = 0;
|
||||
shapeList:Shape[] = [];
|
||||
colSpace:number = 10
|
||||
rowSpace:number = 10
|
||||
direction:LayoutDirection = 'horizontal'
|
||||
mode:LayoutMode = 'left'
|
||||
anchor:LayoutAnchor = 'top';//是以顶开始往排,还是以容器底往上排
|
||||
// 自动断行
|
||||
wrap:boolean = false
|
||||
icanvas:ICanvas;
|
||||
/**
|
||||
* 创建布局容器
|
||||
* @param ic 画布实例(默认用于初始化容器宽高)
|
||||
*/
|
||||
constructor(ic:ICanvas){
|
||||
this.icanvas = ic;
|
||||
this.width = ic.width
|
||||
this.height = ic.height
|
||||
}
|
||||
/**
|
||||
* 添加需要参与布局的元素(按 id 去重)
|
||||
* @param shapes 形状列表
|
||||
*/
|
||||
addShape(shapes:Shape[]){
|
||||
let ids = this.shapeList.map((el:Shape):string => el.id)
|
||||
let realShapes = shapes.filter((el:Shape):boolean => !ids.includes(el.id))
|
||||
this.shapeList.push(...realShapes)
|
||||
}
|
||||
/**
|
||||
* 设置容器 X 偏移
|
||||
* @param val X 坐标
|
||||
*/
|
||||
setsX(val:number){
|
||||
this.x = val
|
||||
}
|
||||
/**
|
||||
* 设置容器 Y 偏移
|
||||
* @param val Y 坐标
|
||||
*/
|
||||
setsY(val:number){
|
||||
this.y = val
|
||||
}
|
||||
/**
|
||||
* 设置容器位置
|
||||
* @param x X 坐标
|
||||
* @param y Y 坐标
|
||||
*/
|
||||
setsPosition(x:number,y:number){
|
||||
this.x = x
|
||||
this.y = y
|
||||
}
|
||||
/**
|
||||
* 设置容器尺寸
|
||||
* @param w 宽度
|
||||
* @param h 高度
|
||||
*/
|
||||
setsSize(w:number,h:number){
|
||||
this.width = w;
|
||||
this.height = h;
|
||||
}
|
||||
/**
|
||||
* 设置容器宽度
|
||||
* @param w 宽度
|
||||
*/
|
||||
setsWidth(w:number){
|
||||
this.width = w;
|
||||
}
|
||||
/**
|
||||
* 设置容器高度
|
||||
* @param h 高度
|
||||
*/
|
||||
setsHeight(h:number){
|
||||
this.height = h;
|
||||
}
|
||||
/**
|
||||
* 设置列间距(横向相邻元素间距)
|
||||
* @param val 间距像素
|
||||
*/
|
||||
setsSpaceCol(val:number){
|
||||
this.colSpace = val;
|
||||
}
|
||||
/**
|
||||
* 设置行间距(纵向相邻元素间距)
|
||||
* @param val 间距像素
|
||||
*/
|
||||
setsSpaceRow(val:number){
|
||||
this.rowSpace = val
|
||||
}
|
||||
/**
|
||||
* 同时设置列/行间距
|
||||
* @param col 列间距
|
||||
* @param row 行间距
|
||||
*/
|
||||
setsSpace(col:number,row:number){
|
||||
this.colSpace = col;
|
||||
this.rowSpace = row
|
||||
}
|
||||
/**
|
||||
* 设置主轴方向
|
||||
* @param val 'horizontal' | 'vertical'
|
||||
*/
|
||||
setsLayoutDirection(val:LayoutDirection){
|
||||
this.direction = val;
|
||||
}
|
||||
/**
|
||||
* 设置主轴对齐模式
|
||||
* @param val 'left' | 'right' | 'between' | 'center'
|
||||
*/
|
||||
setsMode(val:LayoutMode){
|
||||
this.mode = val;
|
||||
}
|
||||
/**
|
||||
* 设置交叉轴锚点(顶/底对齐)
|
||||
* @param val 'top' | 'bottom'
|
||||
*/
|
||||
setsAnchor(val:LayoutAnchor){
|
||||
this.anchor = val;
|
||||
}
|
||||
/**
|
||||
* 设置是否自动换行/换列
|
||||
* @param val true 开启
|
||||
*/
|
||||
setsWrap(val:boolean){
|
||||
this.wrap = val;
|
||||
}
|
||||
/**
|
||||
* 执行布局:根据 direction / mode / anchor / wrap / spacing 将 shapeList 定位
|
||||
*/
|
||||
render(){
|
||||
const items = this.shapeList.filter((s:Shape)=> s.visible);
|
||||
if(items.length==0) return;
|
||||
if(this.direction=='horizontal'){
|
||||
this.layoutHorizontal(items);
|
||||
}else{
|
||||
this.layoutVertical(items);
|
||||
}
|
||||
items.forEach((el) => {
|
||||
el.needsUpdate = true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元素参与布局的尺寸(来自元素 getBoundRect)
|
||||
* @param shape 形状
|
||||
* @returns width/height
|
||||
*/
|
||||
private getItemSize(shape:Shape):CurSizeType{
|
||||
const rect = shape.getBoundRect();
|
||||
const w = Math.max(0, rect.width);
|
||||
const h = Math.max(0, rect.height);
|
||||
return {width:w,height:h};
|
||||
}
|
||||
|
||||
/**
|
||||
* 横向布局:按主轴水平排列,支持均分与换行
|
||||
* @param items 待布局元素
|
||||
*/
|
||||
private layoutHorizontal(items:Shape[]){
|
||||
if(!this.wrap){
|
||||
// single row
|
||||
const sizes = items.map(s=> this.getItemSize(s));
|
||||
const totalWidth = sizes.reduce((acc,it)=> acc + it.width, 0);
|
||||
const gaps = Math.max(0, items.length-1);
|
||||
let spacing = this.colSpace;
|
||||
let startX = this.x;
|
||||
if(this.mode=='between' && gaps>0){
|
||||
spacing = Math.max(0, (this.width - totalWidth) / gaps);
|
||||
startX = this.x;
|
||||
}else if(this.mode=='center'){
|
||||
startX = this.x + Math.max(0, (this.width - (totalWidth + spacing*gaps)) / 2);
|
||||
}else if(this.mode=='right'){
|
||||
startX = this.x + Math.max(0, this.width - (totalWidth + spacing*gaps));
|
||||
}else{
|
||||
startX = this.x;
|
||||
}
|
||||
let x = startX;
|
||||
for(let i=0;i<items.length;i++){
|
||||
const s = items[i];
|
||||
const sz = sizes[i];
|
||||
const y = this.anchor=='top' ? this.y : this.y + Math.max(0, this.height - sz.height);
|
||||
s.setX(x).setY(y);
|
||||
x += sz.width + spacing;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// wrapping: multiple rows
|
||||
let rows: RoysType[] = [];
|
||||
let curItems:Shape[] = [];
|
||||
let curSizes:CurSizeType[] = [];
|
||||
let curWidth = 0;
|
||||
let curHeight = 0;
|
||||
for(let i=0;i<items.length;i++){
|
||||
const s = items[i];
|
||||
const sz = this.getItemSize(s);
|
||||
const addWidth = (curItems.length>0? this.colSpace:0) + sz.width;
|
||||
if(curItems.length>0 && curWidth + addWidth > this.width){
|
||||
rows.push({items:curItems, sizes:curSizes, rowWidth:curWidth, rowHeight:curHeight});
|
||||
curItems = [];
|
||||
curSizes = [];
|
||||
curWidth = 0;
|
||||
curHeight = 0;
|
||||
}
|
||||
if(curItems.length>0){
|
||||
curWidth += this.colSpace;
|
||||
}
|
||||
curItems.push(s);
|
||||
curSizes.push(sz);
|
||||
curWidth += sz.width;
|
||||
curHeight = Math.max(curHeight, sz.height);
|
||||
}
|
||||
if(curItems.length>0){
|
||||
rows.push({items:curItems, sizes:curSizes, rowWidth:curWidth, rowHeight:curHeight});
|
||||
}
|
||||
// total height with row spaces
|
||||
const totalRowsHeight = rows.reduce((acc,r)=> acc + r.rowHeight, 0);
|
||||
const totalRowSpaces = this.rowSpace * Math.max(0, rows.length-1);
|
||||
let startY = this.anchor=='top' ? this.y : this.y + Math.max(0, this.height - (totalRowsHeight + totalRowSpaces));
|
||||
let y = startY;
|
||||
for(let r=0;r<rows.length;r++){
|
||||
const row = rows[r];
|
||||
// horizontal positioning per row by mode
|
||||
const gaps = Math.max(0, row.items.length-1);
|
||||
let spacing = this.colSpace;
|
||||
let startX = this.x;
|
||||
if(this.mode=='between' && gaps>0){
|
||||
spacing = Math.max(0, (this.width - row.rowWidth + this.colSpace*gaps) / gaps);
|
||||
startX = this.x;
|
||||
}else if(this.mode=='center'){
|
||||
startX = this.x + Math.max(0, (this.width - (row.rowWidth + this.colSpace*gaps)) / 2);
|
||||
}else if(this.mode=='right'){
|
||||
startX = this.x + Math.max(0, this.width - (row.rowWidth + this.colSpace*gaps));
|
||||
}else{
|
||||
startX = this.x;
|
||||
}
|
||||
let x = startX;
|
||||
for(let i=0;i<row.items.length;i++){
|
||||
const s = row.items[i];
|
||||
const sz = row.sizes[i];
|
||||
const offsetY = this.anchor=='top' ? 0 : (row.rowHeight - sz.height);
|
||||
s.setX(x).setY(y + offsetY);
|
||||
x += sz.width + spacing;
|
||||
}
|
||||
y += row.rowHeight + this.rowSpace;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 纵向布局:按主轴垂直排列,支持均分与换列
|
||||
* @param items 待布局元素
|
||||
*/
|
||||
private layoutVertical(items:Shape[]){
|
||||
if(!this.wrap){
|
||||
// single column
|
||||
const sizes = items.map(s=> this.getItemSize(s));
|
||||
const totalHeight = sizes.reduce((acc,it)=> acc + it.height, 0);
|
||||
const gaps = Math.max(0, items.length-1);
|
||||
let spacing = this.rowSpace;
|
||||
let startY = this.y;
|
||||
if(this.mode=='between' && gaps>0){
|
||||
spacing = Math.max(0, (this.height - totalHeight) / gaps);
|
||||
startY = this.y;
|
||||
}else if(this.anchor=='top'){
|
||||
startY = this.y;
|
||||
}else{
|
||||
startY = this.y + Math.max(0, this.height - (totalHeight + spacing*gaps));
|
||||
}
|
||||
let y = startY;
|
||||
for(let i=0;i<items.length;i++){
|
||||
const s = items[i];
|
||||
const sz = sizes[i];
|
||||
let x = this.x;
|
||||
if(this.mode=='center') x = this.x + Math.max(0, (this.width - sz.width)/2);
|
||||
else if(this.mode=='right') x = this.x + Math.max(0, this.width - sz.width);
|
||||
else x = this.x; // left or between on single column
|
||||
s.setX(x).setY(y);
|
||||
y += sz.height + spacing;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// wrapping into columns
|
||||
let cols: CoysType[] = [];
|
||||
let curItems:Shape[] = [];
|
||||
let curSizes:CurSizeType[] = [];
|
||||
let curHeight = 0;
|
||||
let curWidth = 0;
|
||||
for(let i=0;i<items.length;i++){
|
||||
const s = items[i];
|
||||
const sz = this.getItemSize(s);
|
||||
const addHeight = (curItems.length>0? this.rowSpace:0) + sz.height;
|
||||
if(curItems.length>0 && curHeight + addHeight > this.height){
|
||||
cols.push({items:curItems, sizes:curSizes, colWidth:curWidth, colHeight:curHeight});
|
||||
curItems = [];
|
||||
curSizes = [];
|
||||
curHeight = 0;
|
||||
curWidth = 0;
|
||||
}
|
||||
if(curItems.length>0){
|
||||
curHeight += this.rowSpace;
|
||||
}
|
||||
curItems.push(s);
|
||||
curSizes.push(sz);
|
||||
curHeight += sz.height;
|
||||
curWidth = Math.max(curWidth, sz.width);
|
||||
}
|
||||
if(curItems.length>0){
|
||||
cols.push({items:curItems, sizes:curSizes, colWidth:curWidth, colHeight:curHeight});
|
||||
}
|
||||
const totalColsWidth = cols.reduce((acc,c)=> acc + c.colWidth, 0);
|
||||
const totalColSpaces = this.colSpace * Math.max(0, cols.length-1);
|
||||
let startX = this.x;
|
||||
if(this.mode=='between' && cols.length>1){
|
||||
// distribute columns
|
||||
startX = this.x;
|
||||
}else if(this.mode=='center'){
|
||||
startX = this.x + Math.max(0, (this.width - (totalColsWidth + totalColSpaces))/2);
|
||||
}else if(this.mode=='right'){
|
||||
startX = this.x + Math.max(0, this.width - (totalColsWidth + totalColSpaces));
|
||||
}else{
|
||||
startX = this.x;
|
||||
}
|
||||
let x = startX;
|
||||
let betweenSpacing = this.colSpace;
|
||||
if(this.mode=='between' && cols.length>1){
|
||||
betweenSpacing = Math.max(0, (this.width - totalColsWidth) / (cols.length-1));
|
||||
}
|
||||
for(let c=0;c<cols.length;c++){
|
||||
const col = cols[c];
|
||||
let y = this.anchor=='top' ? this.y : this.y + Math.max(0, this.height - col.colHeight - this.rowSpace*(col.items.length-1));
|
||||
for(let i=0;i<col.items.length;i++){
|
||||
const s = col.items[i];
|
||||
const sz = col.sizes[i];
|
||||
let offsetX = 0;
|
||||
if(this.mode=='center') offsetX = Math.max(0, (col.colWidth - sz.width)/2);
|
||||
else if(this.mode=='right') offsetX = Math.max(0, col.colWidth - sz.width);
|
||||
else offsetX = 0;
|
||||
s.setX(x + offsetX).setY(y);
|
||||
y += sz.height + this.rowSpace;
|
||||
}
|
||||
x += col.colWidth + betweenSpacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
import { Shape } from './shape.uts';
|
||||
import { CanvasRotateCenter, IShapeBoundRect, IShapeOptional, IShapeVector2d } from '../interface.uts';
|
||||
import { ICanvas } from '@/uni_modules/tmx-ui/core/canvas/ICanvas.uts';
|
||||
|
||||
export class ILine extends Shape {
|
||||
override type = 'ILine'
|
||||
// 线条的起点和终点
|
||||
|
||||
|
||||
constructor(config: IShapeOptional, canvas: ICanvas) {
|
||||
super(config, canvas);
|
||||
// 更新宽高
|
||||
this.updateDimensions();
|
||||
}
|
||||
|
||||
// 设置起点
|
||||
setStart(x: number, y: number): ILine {
|
||||
this.pointStart.x = x;
|
||||
this.pointStart.y = y;
|
||||
this.updateDimensions();
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
// 设置终点
|
||||
setEnd(x: number, y: number): ILine {
|
||||
this.pointEnd.x = x;
|
||||
this.pointEnd.y = y;
|
||||
this.updateDimensions();
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
// 根据参考点旋转线条
|
||||
rotateByPoint(isStartPoint: boolean, angle: number): ILine {
|
||||
// 计算线条当前长度
|
||||
const length = Math.sqrt(
|
||||
Math.pow(this.pointEnd.x - this.pointStart.x, 2) +
|
||||
Math.pow(this.pointEnd.y - this.pointStart.y, 2)
|
||||
);
|
||||
|
||||
// 将角度转换为弧度,顺时针为正,逆时针为负
|
||||
const radians = angle * Math.PI / 180;
|
||||
|
||||
if (isStartPoint) {
|
||||
// 以起点为基准旋转
|
||||
this.pointEnd.x = this.pointStart.x + length * Math.cos(radians);
|
||||
this.pointEnd.y = this.pointStart.y + length * Math.sin(radians);
|
||||
} else {
|
||||
// 以终点为基准旋转
|
||||
this.pointStart.x = this.pointEnd.x - length * Math.cos(radians);
|
||||
this.pointStart.y = this.pointEnd.y - length * Math.sin(radians);
|
||||
}
|
||||
|
||||
// 更新线条的包围盒尺寸
|
||||
this.updateDimensions();
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
// 更新线条的包围盒尺寸
|
||||
private updateDimensions(): void {
|
||||
this.x = Math.min(this.pointStart.x, this.pointEnd.x);
|
||||
this.y = Math.min(this.pointStart.y, this.pointEnd.y);
|
||||
this.width = Math.abs(this.pointEnd.x - this.pointStart.x);
|
||||
this.height = Math.abs(this.pointEnd.y - this.pointStart.y);
|
||||
}
|
||||
|
||||
override getBoundRect(): IShapeBoundRect {
|
||||
// include stroke width padding around the line segment
|
||||
const pad = this.stroke != "" || this.strokeGradient.length>0 ? this.strokeWidth / 2 : 0;
|
||||
return {
|
||||
x: this.x - pad,
|
||||
y: this.y - pad,
|
||||
width: this.width + pad * 2,
|
||||
height: this.height + pad * 2
|
||||
} as IShapeBoundRect;
|
||||
}
|
||||
|
||||
override draw(ctx: CanvasRenderingContext2D) {
|
||||
if (this.visible == false) return;
|
||||
super.draw(ctx);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(this.pointStart.x, this.pointStart.y);
|
||||
ctx.lineTo(this.pointEnd.x, this.pointEnd.y);
|
||||
|
||||
if (this.stroke != ""||this.strokeGradient.length>0) {
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
override setHeight(value : number) : Shape {
|
||||
// 计算线条的中心点
|
||||
const centerX = (this.pointStart.x + this.pointEnd.x) / 2;
|
||||
const centerY = (this.pointStart.y + this.pointEnd.y) / 2;
|
||||
|
||||
// 计算当前线条的宽度(保持不变)
|
||||
const currentWidth = Math.abs(this.pointEnd.x - this.pointStart.x);
|
||||
|
||||
// 根据新的高度重新计算起点和终点的Y坐标
|
||||
// 保持线条的水平方向不变,只改变垂直方向
|
||||
if (this.pointStart.x <= this.pointEnd.x) {
|
||||
// 从左到右的线条
|
||||
this.pointStart.x = centerX - currentWidth / 2;
|
||||
this.pointEnd.x = centerX + currentWidth / 2;
|
||||
} else {
|
||||
// 从右到左的线条
|
||||
this.pointStart.x = centerX + currentWidth / 2;
|
||||
this.pointEnd.x = centerX - currentWidth / 2;
|
||||
}
|
||||
|
||||
// 根据新高度设置Y坐标
|
||||
if (this.pointStart.y <= this.pointEnd.y) {
|
||||
// 从上到下的线条
|
||||
this.pointStart.y = centerY - value / 2;
|
||||
this.pointEnd.y = centerY + value / 2;
|
||||
} else {
|
||||
// 从下到上的线条
|
||||
this.pointStart.y = centerY + value / 2;
|
||||
this.pointEnd.y = centerY - value / 2;
|
||||
}
|
||||
|
||||
this.updateDimensions();
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override setWidth(value : number) : Shape {
|
||||
// 计算线条的中心点
|
||||
const centerX = (this.pointStart.x + this.pointEnd.x) / 2;
|
||||
const centerY = (this.pointStart.y + this.pointEnd.y) / 2;
|
||||
|
||||
// 计算当前线条的高度(保持不变)
|
||||
const currentHeight = Math.abs(this.pointEnd.y - this.pointStart.y);
|
||||
|
||||
// 根据新的宽度重新计算起点和终点的X坐标
|
||||
// 保持线条的垂直方向不变,只改变水平方向
|
||||
if (this.pointStart.y <= this.pointEnd.y) {
|
||||
// 从上到下的线条
|
||||
this.pointStart.y = centerY - currentHeight / 2;
|
||||
this.pointEnd.y = centerY + currentHeight / 2;
|
||||
} else {
|
||||
// 从下到上的线条
|
||||
this.pointStart.y = centerY + currentHeight / 2;
|
||||
this.pointEnd.y = centerY - currentHeight / 2;
|
||||
}
|
||||
|
||||
// 根据新宽度设置X坐标
|
||||
if (this.pointStart.x <= this.pointEnd.x) {
|
||||
// 从左到右的线条
|
||||
this.pointStart.x = centerX - value / 2;
|
||||
this.pointEnd.x = centerX + value / 2;
|
||||
} else {
|
||||
// 从右到左的线条
|
||||
this.pointStart.x = centerX + value / 2;
|
||||
this.pointEnd.x = centerX - value / 2;
|
||||
}
|
||||
|
||||
this.updateDimensions();
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
// 获取两点之间的角度,以起点为基准,水平向右为0度,顺时针为正,逆时针为负
|
||||
getAngle(): number {
|
||||
// 直接使用起点和终点计算角度
|
||||
const angle = Math.atan2(this.pointEnd.y - this.pointStart.y, this.pointEnd.x - this.pointStart.x);
|
||||
|
||||
// 将弧度转换为角度,顺时针为正
|
||||
return angle * (180 / Math.PI);
|
||||
}
|
||||
getAngleByPoints(start:IShapeVector2d,end:IShapeVector2d): number {
|
||||
// 直接使用起点和终点计算角度
|
||||
const angle = Math.atan2(start.y - end.y, end.x - start.x);
|
||||
|
||||
// 将弧度转换为角度,顺时针为正,逆时针为负
|
||||
return angle * (180 / Math.PI);
|
||||
}
|
||||
|
||||
// 计算线条绕指定锚点的旋转角度
|
||||
getAngleByAnchor(anchorX: number, anchorY: number, startX: number, startY: number, endX: number, endY: number): number {
|
||||
// 计算向量1:从锚点到起点的向量
|
||||
const vector1X = startX - anchorX;
|
||||
const vector1Y = startY - anchorY;
|
||||
|
||||
// 计算向量2:从锚点到终点的向量
|
||||
const vector2X = endX - anchorX;
|
||||
const vector2Y = endY - anchorY;
|
||||
|
||||
// 使用向量的点积和叉积计算角度
|
||||
const dotProduct = vector1X * vector2X + vector1Y * vector2Y;
|
||||
const crossProduct = vector1X * vector2Y - vector1Y * vector2X;
|
||||
|
||||
// 计算角度(弧度)
|
||||
const angle = Math.atan2(crossProduct, dotProduct);
|
||||
|
||||
// 将弧度转换为角度,并确保角度范围在-180到180度之间
|
||||
return angle * (180 / Math.PI);
|
||||
}
|
||||
|
||||
override isPointInPath(x: number, y: number, shapeId: string): boolean {
|
||||
if (!this.visible || (shapeId != "" && shapeId != this.id)) return false;
|
||||
|
||||
// 计算点到线段的距离
|
||||
const lineLength = Math.sqrt(
|
||||
Math.pow(this.pointEnd.x - this.pointStart.x, 2) +
|
||||
Math.pow(this.pointEnd.y - this.pointStart.y, 2)
|
||||
);
|
||||
|
||||
if (lineLength === 0) return false;
|
||||
|
||||
const distance = Math.abs(
|
||||
(this.pointEnd.y - this.pointStart.y) * x -
|
||||
(this.pointEnd.x - this.pointStart.x) * y +
|
||||
this.pointEnd.x * this.pointStart.y -
|
||||
this.pointEnd.y * this.pointStart.x
|
||||
) / lineLength;
|
||||
|
||||
// 判断点是否在线段的范围内
|
||||
const dotProduct =
|
||||
((x - this.pointStart.x) * (this.pointEnd.x - this.pointStart.x) +
|
||||
(y - this.pointStart.y) * (this.pointEnd.y - this.pointStart.y)) / lineLength;
|
||||
|
||||
return distance <= this.strokeWidth && dotProduct >= 0 && dotProduct <= lineLength;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
import { Shape } from './shape.uts';
|
||||
import { ICanvas } from '@/uni_modules/tmx-ui/core/canvas/ICanvas.uts';
|
||||
import { IShapeBoundRect, IShapeOptional } from '../interface.uts';
|
||||
|
||||
function getControlPoints(x0 : number, y0 : number, x1 : number, y1 : number, x2 : number, y2 : number, t : number) : number[] {
|
||||
const d01 = Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2)),
|
||||
d12 = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)),
|
||||
fa = (t * d01) / (d01 + d12),
|
||||
fb = (t * d12) / (d01 + d12),
|
||||
p1x = x1 - fa * (x2 - x0),
|
||||
p1y = y1 - fa * (y2 - y0),
|
||||
p2x = x1 + fb * (x2 - x0),
|
||||
p2y = y1 + fb * (y2 - y0);
|
||||
|
||||
return [p1x, p1y, p2x, p2y];
|
||||
}
|
||||
|
||||
function expandPoints(p : number[], tension : number) : number[] {
|
||||
const len = p.length
|
||||
const allPoints : Array<number> = [];
|
||||
|
||||
for (let n = 2; n < len - 2; n += 2) {
|
||||
const cp = getControlPoints(
|
||||
p[n - 2],
|
||||
p[n - 1],
|
||||
p[n],
|
||||
p[n + 1],
|
||||
p[n + 2],
|
||||
p[n + 3],
|
||||
tension
|
||||
);
|
||||
if (isNaN(cp[0])) {
|
||||
continue;
|
||||
}
|
||||
allPoints.push(cp[0]);
|
||||
allPoints.push(cp[1]);
|
||||
allPoints.push(p[n]);
|
||||
allPoints.push(p[n + 1]);
|
||||
allPoints.push(cp[2]);
|
||||
allPoints.push(cp[3]);
|
||||
}
|
||||
|
||||
return allPoints;
|
||||
}
|
||||
|
||||
export class ILinePolygon extends Shape {
|
||||
override type = 'ILinePolygon'
|
||||
constructor(config : IShapeOptional, canvas : ICanvas) {
|
||||
super(config, canvas);
|
||||
}
|
||||
|
||||
override getBoundRect() : IShapeBoundRect {
|
||||
let points = this.points;
|
||||
if (points.length < 4) {
|
||||
let cp = [] as number[]
|
||||
for(let i=0;i<(4-points.length);i++){
|
||||
cp.push(0)
|
||||
}
|
||||
points = points.concat(cp)
|
||||
return {
|
||||
x: points[0],
|
||||
y: points[1],
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
}
|
||||
if (this.tension !== 0) {
|
||||
points = [
|
||||
points[0],
|
||||
points[1],
|
||||
...this._getTensionPoints(false),
|
||||
points[points.length - 2],
|
||||
points[points.length - 1],
|
||||
];
|
||||
} else {
|
||||
points = this.points;
|
||||
}
|
||||
let minX = this.points[0] + this.x;
|
||||
let maxX = this.points[0] + this.x;
|
||||
let minY = this.points[1] + this.y;
|
||||
let maxY = this.points[1] + this.y;
|
||||
let x=0
|
||||
let y =0;
|
||||
for (let i = 0; i < points.length / 2; i++) {
|
||||
x = points[i * 2] + this.x;
|
||||
y = points[i * 2 + 1] + this.y;
|
||||
minX = Math.min(minX, x);
|
||||
maxX = Math.max(maxX, x);
|
||||
minY = Math.min(minY, y);
|
||||
maxY = Math.max(maxY, y);
|
||||
}
|
||||
let borderWidth = (this.stroke!='' ? this.strokeWidth / 2 : 0);
|
||||
return {
|
||||
x: minX - borderWidth,
|
||||
y: minY - borderWidth,
|
||||
width: maxX - minX + borderWidth*2,
|
||||
height: maxY - minY + borderWidth*2,
|
||||
} as IShapeBoundRect;
|
||||
|
||||
|
||||
}
|
||||
|
||||
private getTensionPoints() {
|
||||
return this._getTensionPoints();
|
||||
}
|
||||
private _getTensionPoints(isXYOffset:boolean = true) {
|
||||
|
||||
if (this.closed) {
|
||||
return this._getTensionPointsClosed(isXYOffset);
|
||||
} else {
|
||||
return expandPoints(isXYOffset?this._getPoints():this.points, this.tension);
|
||||
}
|
||||
}
|
||||
private _getTensionPointsClosed(isXYOffset:boolean = true) {
|
||||
const p = isXYOffset?this._getPoints():this.points
|
||||
const len = p.length;
|
||||
const tension = this.tension;
|
||||
const firstControlPoints = getControlPoints(
|
||||
p[len - 2],
|
||||
p[len - 1],
|
||||
p[0],
|
||||
p[1],
|
||||
p[2],
|
||||
p[3],
|
||||
tension
|
||||
);
|
||||
const lastControlPoints = getControlPoints(
|
||||
p[len - 4],
|
||||
p[len - 3],
|
||||
p[len - 2],
|
||||
p[len - 1],
|
||||
p[0],
|
||||
p[1],
|
||||
tension
|
||||
);
|
||||
const middle = expandPoints(p, tension);
|
||||
const tp = [firstControlPoints[2], firstControlPoints[3]]
|
||||
.concat(middle)
|
||||
.concat([
|
||||
lastControlPoints[0],
|
||||
lastControlPoints[1],
|
||||
p[len - 2],
|
||||
p[len - 1],
|
||||
lastControlPoints[2],
|
||||
lastControlPoints[3],
|
||||
firstControlPoints[0],
|
||||
firstControlPoints[1],
|
||||
p[0],
|
||||
p[1],
|
||||
]);
|
||||
|
||||
return tp;
|
||||
}
|
||||
|
||||
private _getPoints():number[]{
|
||||
return this.points.map((el:number,index:number)=>{
|
||||
return (index+1)%2 == 0 ?el+this.y:el+this.x;
|
||||
})
|
||||
}
|
||||
override draw(ctx : CanvasRenderingContext2D) {
|
||||
if (this.visible == false || this.points.length < 4) return;
|
||||
super.draw(ctx);
|
||||
ctx.beginPath();
|
||||
|
||||
let points = this._getPoints()
|
||||
let length = points.length
|
||||
let tension = this.tension
|
||||
let closed = this.closed
|
||||
let bezier = this.bezier
|
||||
let tp = [] as number[]
|
||||
let len = 0
|
||||
let n = 0;
|
||||
|
||||
if (length==0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(points[0], points[1]);
|
||||
|
||||
|
||||
if (tension !== 0 && length > 4) {
|
||||
tp = this.getTensionPoints();
|
||||
len = tp.length;
|
||||
n = closed ? 0 : 4;
|
||||
|
||||
if (!closed) {
|
||||
ctx.quadraticCurveTo(tp[0], tp[1], tp[2], tp[3]);
|
||||
}
|
||||
|
||||
while (n < len - 2) {
|
||||
|
||||
// #ifdef APP-ANDROID
|
||||
ctx.bezierCurveTo(
|
||||
tp[(n++).toInt()],
|
||||
tp[(n++).toInt()],
|
||||
tp[(n++).toInt()],
|
||||
tp[(n++).toInt()],
|
||||
tp[(n++).toInt()],
|
||||
tp[(n++).toInt()]
|
||||
);
|
||||
// #endif
|
||||
// #ifndef APP-ANDROID
|
||||
ctx.bezierCurveTo(
|
||||
tp[n++],
|
||||
tp[n++],
|
||||
tp[n++],
|
||||
tp[n++],
|
||||
tp[n++],
|
||||
tp[n++]
|
||||
);
|
||||
// #endif
|
||||
}
|
||||
|
||||
if (!closed) {
|
||||
ctx.quadraticCurveTo(
|
||||
tp[len - 2],
|
||||
tp[len - 1],
|
||||
points[length - 2],
|
||||
points[length - 1]
|
||||
);
|
||||
}
|
||||
} else if (bezier) {
|
||||
|
||||
n = 2;
|
||||
while (n < length) {
|
||||
|
||||
// #ifdef APP-ANDROID
|
||||
ctx.bezierCurveTo(
|
||||
points[(n++).toInt()],
|
||||
points[(n++).toInt()],
|
||||
points[(n++).toInt()],
|
||||
points[(n++).toInt()],
|
||||
points[(n++).toInt()],
|
||||
points[(n++).toInt()]
|
||||
);
|
||||
// #endif
|
||||
// #ifndef APP-ANDROID
|
||||
ctx.bezierCurveTo(
|
||||
points[n++],
|
||||
points[n++],
|
||||
points[n++],
|
||||
points[n++],
|
||||
points[n++],
|
||||
points[n++]
|
||||
);
|
||||
// #endif
|
||||
}
|
||||
} else {
|
||||
for (n = 2; n < length; n += 2) {
|
||||
ctx.lineTo(points[n], points[n + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (closed&&this.fill!='') {
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
} else if(this.stroke!='') {
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
override isPointInPath(x : number, y : number, shapeId : string) : boolean {
|
||||
if (!this.visible || (shapeId != "" && shapeId != this.id)) return false;
|
||||
const rect = this.getBoundRect()
|
||||
const realX = x - (this.offsetX) - this.x - this.points[0];
|
||||
const realY = y - (this.offsetY) - this.y - this.points[1];
|
||||
if (this.rotation != 0) {
|
||||
const angle = -this.rotation * Math.PI / 180;
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
let centerX = 0;
|
||||
let centerY = 0;
|
||||
|
||||
// 根据不同的旋转中心点设置centerX和centerY
|
||||
switch(this.rotateCenter) {
|
||||
case 'topLeft':
|
||||
centerX = 0;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'topRight':
|
||||
centerX = this.width;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'bottomLeft':
|
||||
centerX = 0;
|
||||
centerY = this.height;
|
||||
break;
|
||||
case 'bottomRight':
|
||||
centerX = this.width;
|
||||
centerY = this.height;
|
||||
break;
|
||||
case 'center':
|
||||
default:
|
||||
centerX = this.width/2;
|
||||
centerY = this.height/2;
|
||||
break;
|
||||
}
|
||||
|
||||
const dx = realX - centerX;
|
||||
const dy = realY - centerY;
|
||||
const rotatedX = centerX + dx * cos - dy * sin;
|
||||
const rotatedY = centerY + dx * sin + dy * cos;
|
||||
return rotatedX >= 0 && rotatedX <= rect.width* this.scaleX && rotatedY >= 0 && rotatedY <= rect.height* this.scaleY;
|
||||
}
|
||||
let isInRect = realX >= 0 && realX <= rect.width* this.scaleX && realY >= 0 && realY <= rect.height* this.scaleY;
|
||||
|
||||
return isInRect
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,105 @@
|
||||
import { Shape } from './shape.uts';
|
||||
import { IShapeBoundRect, IShapeOptional } from '../interface.uts';
|
||||
import { ICanvas } from '@/uni_modules/tmx-ui/core/canvas/ICanvas.uts';
|
||||
import { generateFrame } from "./qrcode/qrcode.uts"
|
||||
export class IQrcode extends Shape {
|
||||
override type = 'IQrcode'
|
||||
constructor(config : IShapeOptional,canvas:ICanvas) {
|
||||
super(config,canvas);
|
||||
}
|
||||
|
||||
override getBoundRect() : IShapeBoundRect {
|
||||
const pad = (this.stroke!='' ? this.strokeWidth/2 : 0);
|
||||
return {
|
||||
x: this.x - pad,
|
||||
y: this.y - pad,
|
||||
width: this.width + pad*2,
|
||||
height: this.height + pad*2
|
||||
} as IShapeBoundRect;
|
||||
}
|
||||
|
||||
setRadius(value : number) : IQrcode {
|
||||
this.radius = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
// 根据新高度调整矩形,以左上角 (x,y) 为起点
|
||||
override setHeight(value : number) : Shape {
|
||||
const originalY = this.y;
|
||||
if (value >= 0) {
|
||||
this.y = originalY;
|
||||
this.height = value;
|
||||
} else {
|
||||
this.y = originalY + value; // value 为负,向上扩展
|
||||
this.height = Math.abs(value);
|
||||
}
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
// 根据新宽度调整矩形,以左上角 (x,y) 为起点
|
||||
override setWidth(value : number) : Shape {
|
||||
const originalX = this.x;
|
||||
if (value >= 0) {
|
||||
this.x = originalX;
|
||||
this.width = value;
|
||||
} else {
|
||||
this.x = originalX + value; // value 为负,向左扩展
|
||||
this.width = Math.abs(value);
|
||||
}
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
// 设置二维码图片图片。
|
||||
setQrcodeLabel(text:string){
|
||||
this.qrcodeText = text;
|
||||
this.needsUpdate = true;
|
||||
}
|
||||
override draw(ctx : CanvasRenderingContext2D) {
|
||||
if (this.visible == false) return;
|
||||
super.draw(ctx);
|
||||
|
||||
ctx.beginPath();
|
||||
if (this.radius > 0) {
|
||||
let radiuss = Math.min(this.radius, this.width / 2, this.height / 2);
|
||||
const radius = Math.max(radiuss, 0)
|
||||
ctx.moveTo(this.x + radius, this.y);
|
||||
ctx.lineTo(this.x + this.width - radius, this.y);
|
||||
ctx.arcTo(this.x + this.width, this.y, this.x + this.width, this.y + radius, radius);
|
||||
ctx.lineTo(this.x + this.width, this.y + this.height - radius);
|
||||
ctx.arcTo(this.x + this.width, this.y + this.height, this.x + this.width - radius, this.y + this.height, radius);
|
||||
ctx.lineTo(this.x + radius, this.y + this.height);
|
||||
ctx.arcTo(this.x, this.y + this.height, this.x, this.y + this.height - radius, radius);
|
||||
ctx.lineTo(this.x, this.y + radius);
|
||||
ctx.arcTo(this.x, this.y, this.x + radius, this.y, radius);
|
||||
} else {
|
||||
ctx.beginPath();
|
||||
ctx.rect(this.x, this.y, this.width, this.height);
|
||||
}
|
||||
ctx.closePath();
|
||||
if (this.fill != "") {
|
||||
ctx.fill();
|
||||
}
|
||||
if (this.stroke != "") {
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
let fo = generateFrame(this.qrcodeText, "H")
|
||||
let points = fo.frameBuffer
|
||||
let width = fo.width
|
||||
let px = this.width / width
|
||||
let borderWidth = 0
|
||||
for (let i = 0; i < width; i++) {
|
||||
for (let j = 0; j < width; j++) {
|
||||
if (points[j * width + i] > 0) {
|
||||
ctx.fillStyle = this.foreground;
|
||||
ctx.fillRect( borderWidth + px * i + this.x, borderWidth + px * j + this.y, px, px)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore()
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,86 @@
|
||||
import { Shape } from './shape.uts';
|
||||
import { CanvasRotateCenter, IShapeBoundRect, IShapeOptional, ShapeSetAttrType } from '../interface.uts';
|
||||
import { ICanvas } from '@/uni_modules/tmx-ui/core/canvas/ICanvas.uts';
|
||||
export class IRect extends Shape {
|
||||
override type = 'IRect'
|
||||
constructor(config : IShapeOptional,canvas:ICanvas) {
|
||||
super(config,canvas);
|
||||
}
|
||||
|
||||
override getBoundRect() : IShapeBoundRect {
|
||||
const pad = (this.stroke!='' ? this.strokeWidth/2 : 0);
|
||||
return {
|
||||
x: this.x - pad,
|
||||
y: this.y - pad,
|
||||
width: this.width + pad*2,
|
||||
height: this.height + pad*2
|
||||
} as IShapeBoundRect;
|
||||
}
|
||||
|
||||
setRadius(value : number) : IRect {
|
||||
this.radius = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
// 根据新高度调整矩形,以左上角 (x,y) 为起点
|
||||
override setHeight(value : number) : Shape {
|
||||
const originalY = this.y;
|
||||
if (value >= 0) {
|
||||
this.y = originalY;
|
||||
this.height = value;
|
||||
} else {
|
||||
this.y = originalY + value; // value 为负,向上扩展
|
||||
this.height = Math.abs(value);
|
||||
}
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
// 根据新宽度调整矩形,以左上角 (x,y) 为起点
|
||||
override setWidth(value : number) : Shape {
|
||||
const originalX = this.x;
|
||||
if (value >= 0) {
|
||||
this.x = originalX;
|
||||
this.width = value;
|
||||
} else {
|
||||
this.x = originalX + value; // value 为负,向左扩展
|
||||
this.width = Math.abs(value);
|
||||
}
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
override draw(ctx : CanvasRenderingContext2D) {
|
||||
if (this.visible == false) return;
|
||||
super.draw(ctx);
|
||||
|
||||
ctx.beginPath();
|
||||
if (this.radius > 0) {
|
||||
let radiuss = Math.min(this.radius, this.width / 2, this.height / 2);
|
||||
const radius = Math.max(radiuss, 0)
|
||||
ctx.moveTo(this.x + radius, this.y);
|
||||
ctx.lineTo(this.x + this.width - radius, this.y);
|
||||
ctx.arcTo(this.x + this.width, this.y, this.x + this.width, this.y + radius, radius);
|
||||
ctx.lineTo(this.x + this.width, this.y + this.height - radius);
|
||||
ctx.arcTo(this.x + this.width, this.y + this.height, this.x + this.width - radius, this.y + this.height, radius);
|
||||
ctx.lineTo(this.x + radius, this.y + this.height);
|
||||
ctx.arcTo(this.x, this.y + this.height, this.x, this.y + this.height - radius, radius);
|
||||
ctx.lineTo(this.x, this.y + radius);
|
||||
ctx.arcTo(this.x, this.y, this.x + radius, this.y, radius);
|
||||
} else {
|
||||
ctx.beginPath();
|
||||
ctx.rect(this.x, this.y, this.width, this.height);
|
||||
}
|
||||
ctx.closePath();
|
||||
if (this.fill != "") {
|
||||
ctx.fill();
|
||||
}
|
||||
if (this.stroke != "") {
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
import { Shape } from './shape.uts';
|
||||
import { ICanvas } from '@/uni_modules/tmx-ui/core/canvas/ICanvas.uts';
|
||||
import { IShapeBoundRect, IShapeOptional, IShapeVector2d } from '../interface.uts';
|
||||
|
||||
export class IRegularPolygon extends Shape {
|
||||
override type = 'IRegularPolygon'
|
||||
constructor(config : IShapeOptional, canvas : ICanvas) {
|
||||
super(config, canvas);
|
||||
this.radius = config?.radius ?? 30;
|
||||
this.sides = config?.sides ?? 3;
|
||||
}
|
||||
|
||||
setRadius(value : number) : IRegularPolygon {
|
||||
this.radius = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setSides(value : number) : IRegularPolygon {
|
||||
this.sides = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override getBoundRect() : IShapeBoundRect {
|
||||
const points = this.getPointsVec();
|
||||
let minX = points[0].x;
|
||||
let maxX = points[0].x;
|
||||
let minY = points[0].y;
|
||||
let maxY = points[0].y;
|
||||
points.forEach((point) => {
|
||||
minX = Math.min(minX, point.x);
|
||||
maxX = Math.max(maxX, point.x);
|
||||
minY = Math.min(minY, point.y);
|
||||
maxY = Math.max(maxY, point.y);
|
||||
});
|
||||
let borderWidth = (this.stroke!='' ? this.strokeWidth/2 : 0)
|
||||
return {
|
||||
x: minX-borderWidth,
|
||||
y: minY-borderWidth,
|
||||
width: maxX - minX+borderWidth*2,
|
||||
height: maxY - minY+borderWidth*2,
|
||||
} as IShapeBoundRect;
|
||||
|
||||
}
|
||||
|
||||
override setWidth(value : number) : IRegularPolygon {
|
||||
this.height = value;
|
||||
this.width = value;
|
||||
this.radius = value / 2
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override setHeight(value : number) : IRegularPolygon {
|
||||
this.height = value;
|
||||
this.width = value;
|
||||
this.radius = value / 2
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
private getPointsVec() : IShapeVector2d[] {
|
||||
const sides = this.sides as number;
|
||||
const radius = this.radius;
|
||||
const points : IShapeVector2d[] = [];
|
||||
for (let n = 0; n < sides; n++) {
|
||||
points.push({
|
||||
x: this.x + radius * Math.sin((n * 2 * Math.PI) / sides),
|
||||
y: this.y + (-1 * radius * Math.cos((n * 2 * Math.PI) / sides)),
|
||||
});
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
override draw(ctx : CanvasRenderingContext2D) {
|
||||
if (this.visible == false) return;
|
||||
super.draw(ctx);
|
||||
ctx.beginPath();
|
||||
const points = this.getPointsVec();
|
||||
ctx.moveTo(points[0].x, points[0].y);
|
||||
for (let n = 1; n < points.length; n++) {
|
||||
ctx.lineTo(points[n].x, points[n].y);
|
||||
}
|
||||
ctx.closePath();
|
||||
if (this.fill != "") {
|
||||
ctx.fill();
|
||||
}
|
||||
if (this.stroke != "") {
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
override isPointInPath(x : number, y : number, shapeId : string) : boolean {
|
||||
if (!this.visible || (shapeId != "" && shapeId != this.id)) return false;
|
||||
|
||||
// 计算相对于图形原点的坐标
|
||||
let realX = x - this.offsetX - this.x;
|
||||
let realY = y - this.offsetY - this.y;
|
||||
|
||||
// 处理旋转
|
||||
if (this.rotation != 0) {
|
||||
const angle = -this.rotation * Math.PI / 180;
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
let centerX = 0;
|
||||
let centerY = 0;
|
||||
|
||||
// 根据不同的旋转中心点设置centerX和centerY
|
||||
switch(this.rotateCenter) {
|
||||
case 'topLeft':
|
||||
centerX = 0;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'topRight':
|
||||
centerX = this.width;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'bottomLeft':
|
||||
centerX = 0;
|
||||
centerY = this.height;
|
||||
break;
|
||||
case 'bottomRight':
|
||||
centerX = this.width;
|
||||
centerY = this.height;
|
||||
break;
|
||||
case 'center':
|
||||
default:
|
||||
centerX = this.width/2;
|
||||
centerY = this.height/2;
|
||||
break;
|
||||
}
|
||||
|
||||
const dx = realX - centerX;
|
||||
const dy = realY - centerY;
|
||||
realX = centerX + dx * cos - dy * sin;
|
||||
realY = centerY + dx * sin + dy * cos;
|
||||
}
|
||||
|
||||
// 获取多边形的顶点
|
||||
const points = this.getPointsVec();
|
||||
const n = points.length;
|
||||
let inside = false;
|
||||
|
||||
// 使用射线法判断点是否在多边形内部
|
||||
for (let i = 0, j = n - 1; i < n; j = i++) {
|
||||
const xi = points[i].x - this.x;
|
||||
const yi = points[i].y - this.y;
|
||||
const xj = points[j].x - this.x;
|
||||
const yj = points[j].y - this.y;
|
||||
|
||||
if (((yi > realY) != (yj > realY)) &&
|
||||
(realX < (xj - xi) * (realY - yi) / (yj - yi) + xi)) {
|
||||
inside = !inside;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有描边,检查点是否在描边范围内
|
||||
if (!inside && this.stroke != "") {
|
||||
for (let i = 0, j = n - 1; i < n; j = i++) {
|
||||
const xi = points[i].x - this.x;
|
||||
const yi = points[i].y - this.y;
|
||||
const xj = points[j].x - this.x;
|
||||
const yj = points[j].y - this.y;
|
||||
|
||||
// 计算点到线段的距离
|
||||
const dx = xj - xi;
|
||||
const dy = yj - yi;
|
||||
const len = Math.sqrt(dx * dx + dy * dy);
|
||||
if (len > 0) {
|
||||
const t = ((realX - xi) * dx + (realY - yi) * dy) / (len * len);
|
||||
if (t >= 0 && t <= 1) {
|
||||
const px = xi + t * dx;
|
||||
const py = yi + t * dy;
|
||||
const distance = Math.sqrt((realX - px) * (realX - px) + (realY - py) * (realY - py));
|
||||
if (distance <= this.strokeWidth / 2) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inside;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import { Shape } from './shape.uts';
|
||||
import { CanvasRotateCenter, ShapeSetAttrType, IShapeBoundRect, IShapeOptional } from '../interface.uts';
|
||||
import { ICanvas } from '@/uni_modules/tmx-ui/core/canvas/ICanvas.uts';
|
||||
|
||||
|
||||
export class IRing extends Shape {
|
||||
override type = 'IRing'
|
||||
constructor(config : IShapeOptional,canvas:ICanvas) {
|
||||
super(config,canvas);
|
||||
this.width = this.outerRadius * 2
|
||||
this.height = this.outerRadius * 2
|
||||
}
|
||||
|
||||
setInnerRadius(value : number) : IRing {
|
||||
this.innerRadius = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
setOuterRadius(value : number) : IRing {
|
||||
this.outerRadius = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
override getBoundRect() : IShapeBoundRect {
|
||||
const r = Math.max(0, this.outerRadius);
|
||||
const pad = (this.stroke != "" ? this.strokeWidth / 2 : 0);
|
||||
const d = r * 2 + pad * 2;
|
||||
return {
|
||||
x: this.x - r - pad,
|
||||
y: this.y - r - pad,
|
||||
width: d,
|
||||
height: d
|
||||
} as IShapeBoundRect;
|
||||
}
|
||||
|
||||
override setWidth(value : number) : IRing {
|
||||
this.height = value;
|
||||
this.width = value;
|
||||
this.outerRadius = value / 2
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override setHeight(value : number) : IRing {
|
||||
this.height = value;
|
||||
this.width = value;
|
||||
this.outerRadius = value / 2
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
override draw(ctx : CanvasRenderingContext2D) {
|
||||
if (this.visible == false) return;
|
||||
super.draw(ctx);
|
||||
ctx.beginPath();
|
||||
// 将角度转换为弧度
|
||||
const startRad = this.startAngle * Math.PI / 180;
|
||||
const endRad = this.endAngle * Math.PI / 180;
|
||||
// 绘制外圆弧
|
||||
ctx.arc(this.x, this.y, this.outerRadius, startRad, endRad, false);
|
||||
// 绘制内圆弧(反方向)
|
||||
ctx.arc(this.x, this.y, this.innerRadius, endRad, startRad, true);
|
||||
ctx.closePath();
|
||||
if (this.fill != "") {
|
||||
ctx.fill();
|
||||
|
||||
}
|
||||
if (this.stroke != "") {
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
override isPointInPath(x : number, y : number, shapeId : string) : boolean {
|
||||
if (!this.visible || (shapeId != "" && shapeId != this.id)) return false;
|
||||
|
||||
// 计算点击位置相对于圆心的实际坐标
|
||||
let realX = x - this.offsetX - this.x;
|
||||
let realY = y - this.offsetY - this.y;
|
||||
|
||||
// 如果有旋转,需要将坐标转换回未旋转状态
|
||||
if (this.rotation != 0) {
|
||||
const angle = -this.rotation * Math.PI / 180;
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
let centerX = 0;
|
||||
let centerY = 0;
|
||||
|
||||
// 根据不同的旋转中心点设置centerX和centerY
|
||||
switch(this.rotateCenter) {
|
||||
case 'topLeft':
|
||||
centerX = 0;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'topRight':
|
||||
centerX = this.width;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'bottomLeft':
|
||||
centerX = 0;
|
||||
centerY = this.height;
|
||||
break;
|
||||
case 'bottomRight':
|
||||
centerX = this.width;
|
||||
centerY = this.height;
|
||||
break;
|
||||
case 'center':
|
||||
default:
|
||||
centerX = this.width/2;
|
||||
centerY = this.height/2;
|
||||
break;
|
||||
}
|
||||
|
||||
const dx = realX - centerX;
|
||||
const dy = realY - centerY;
|
||||
realX = centerX + dx * cos - dy * sin;
|
||||
realY = centerY + dx * sin + dy * cos;
|
||||
}
|
||||
|
||||
// 计算点到圆心的距离
|
||||
const distance = Math.sqrt(realX * realX + realY * realY);
|
||||
|
||||
// 考虑缩放因素
|
||||
const scaledInnerRadius = this.innerRadius * Math.min(this.scaleX, this.scaleY);
|
||||
const scaledOuterRadius = this.outerRadius * Math.min(this.scaleX, this.scaleY);
|
||||
|
||||
// 检查距离是否在内径和外径之间
|
||||
if (distance < scaledInnerRadius || distance > scaledOuterRadius) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 计算点击位置的角度(弧度)
|
||||
let angle = Math.atan2(realY, realX);
|
||||
// 将角度转换为0-2π范围
|
||||
if (angle < 0) angle += 2 * Math.PI;
|
||||
// 将角度转换为度数
|
||||
angle = angle * 180 / Math.PI;
|
||||
|
||||
// 将起始角度和结束角度标准化到0-360度范围
|
||||
let start = this.startAngle % 360;
|
||||
if (start < 0) start += 360;
|
||||
let end = this.endAngle % 360;
|
||||
if (end < 0) end += 360;
|
||||
|
||||
// 处理跨越0度线的情况
|
||||
if (start <= end) {
|
||||
return angle >= start && angle <= end;
|
||||
} else {
|
||||
return angle >= start || angle <= end;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import { Shape } from './shape.uts';
|
||||
import { ICanvas } from '@/uni_modules/tmx-ui/core/canvas/ICanvas.uts';
|
||||
import { IShapeBoundRect, IShapeOptional } from '../interface.uts';
|
||||
|
||||
export class ISector extends Shape {
|
||||
constructor(config : IShapeOptional,canvas:ICanvas) {
|
||||
super(config,canvas);
|
||||
this.radius = config?.radius ?? 30;
|
||||
this.startAngle = config?.startAngle ?? 0;
|
||||
this.endAngle = config?.endAngle ?? 90;
|
||||
this.type = "ISector"
|
||||
}
|
||||
|
||||
setRadius(value : number) : ISector {
|
||||
this.radius = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override getBoundRect() : IShapeBoundRect {
|
||||
// 与 IArc 相同的严谨包围盒逻辑:扇形需包含圆心
|
||||
const centerX = this.x;
|
||||
const centerY = this.y;
|
||||
const r = Math.max(0, this.radius);
|
||||
const d2r = Math.PI / 180;
|
||||
const normalize = (deg:number):number => { let a = deg % 360; if (a < 0) a += 360; return a; };
|
||||
let start = normalize(this.startAngle);
|
||||
let end = normalize(this.endAngle);
|
||||
if (end < start) end += 360;
|
||||
const withinSweep = (deg:number):boolean => { let a = normalize(deg); if (a < start) a += 360; return a >= start && a <= end; };
|
||||
const candidatesX:number[] = [centerX];
|
||||
const candidatesY:number[] = [centerY];
|
||||
const sx = centerX + r * Math.cos(start * d2r);
|
||||
const sy = centerY + r * Math.sin(start * d2r);
|
||||
const ex = centerX + r * Math.cos(end * d2r);
|
||||
const ey = centerY + r * Math.sin(end * d2r);
|
||||
candidatesX.push(sx, ex);
|
||||
candidatesY.push(sy, ey);
|
||||
const extrema = [0, 90, 180, 270];
|
||||
for (const deg of extrema) {
|
||||
if (withinSweep(deg)) {
|
||||
const rad = deg * d2r;
|
||||
candidatesX.push(centerX + r * Math.cos(rad));
|
||||
candidatesY.push(centerY + r * Math.sin(rad));
|
||||
}
|
||||
}
|
||||
let minX = candidatesX.length>0?candidatesX[0]:centerX;
|
||||
let maxX = candidatesX.length>0?candidatesX[0]:centerX;
|
||||
let minY = candidatesY.length>0?candidatesY[0]:centerY;
|
||||
let maxY = candidatesY.length>0?candidatesY[0]:centerY;
|
||||
for (let i=1;i<candidatesX.length;i++) {
|
||||
const vx = candidatesX[i];
|
||||
if (!isNaN(vx)) {
|
||||
if (vx < minX) minX = vx;
|
||||
if (vx > maxX) maxX = vx;
|
||||
}
|
||||
}
|
||||
for (let i=1;i<candidatesY.length;i++) {
|
||||
const vy = candidatesY[i];
|
||||
if (!isNaN(vy)) {
|
||||
if (vy < minY) minY = vy;
|
||||
if (vy > maxY) maxY = vy;
|
||||
}
|
||||
}
|
||||
const pad = (this.stroke != "" ? this.strokeWidth / 2 : 0);
|
||||
minX -= pad; minY -= pad; maxX += pad; maxY += pad;
|
||||
return { x: minX, y: minY, width: Math.max(0, maxX - minX), height: Math.max(0, maxY - minY) } as IShapeBoundRect;
|
||||
}
|
||||
|
||||
override setWidth(value : number) : ISector {
|
||||
this.height = value;
|
||||
this.width = value;
|
||||
this.radius = value / 2
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override setHeight(value : number) : ISector {
|
||||
this.height = value;
|
||||
this.width = value;
|
||||
this.radius = value / 2
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override draw(ctx : CanvasRenderingContext2D) {
|
||||
if (this.visible == false) return;
|
||||
super.draw(ctx);
|
||||
ctx.beginPath();
|
||||
// 将角度转换为弧度
|
||||
const startRad = this.startAngle * Math.PI / 180;
|
||||
const endRad = this.endAngle * Math.PI / 180;
|
||||
// 如果有填充色,先移动到圆心,绘制扇形
|
||||
if (this.fill !== "") {
|
||||
ctx.moveTo(this.x, this.y);
|
||||
}
|
||||
// 绘制圆弧
|
||||
ctx.arc(this.x, this.y, this.radius, startRad, endRad, false);
|
||||
// 如果有填充色,连接回圆心形成扇形
|
||||
if (this.fill != "") {
|
||||
ctx.lineTo(this.x, this.y);
|
||||
}
|
||||
ctx.closePath();
|
||||
if (this.fill != "") {
|
||||
ctx.fill();
|
||||
}
|
||||
if (this.stroke != "") {
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
override isPointInPath(x: number, y: number, shapeId: string): boolean {
|
||||
if (!this.visible || (shapeId != "" && shapeId != this.id)) return false;
|
||||
|
||||
// 计算实际点击位置(考虑偏移量)
|
||||
const realX = x - (this.offsetX) - this.x;
|
||||
const realY = y - (this.offsetY) - this.y;
|
||||
|
||||
// 如果有旋转,先进行反向旋转变换
|
||||
let finalX = realX;
|
||||
let finalY = realY;
|
||||
if (this.rotation != 0) {
|
||||
const angle = -this.rotation * Math.PI / 180;
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
let centerX = 0;
|
||||
let centerY = 0;
|
||||
|
||||
// 根据不同的旋转中心点设置centerX和centerY
|
||||
switch(this.rotateCenter) {
|
||||
case 'topLeft':
|
||||
centerX = 0;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'topRight':
|
||||
centerX = this.width;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'bottomLeft':
|
||||
centerX = 0;
|
||||
centerY = this.height;
|
||||
break;
|
||||
case 'bottomRight':
|
||||
centerX = this.width;
|
||||
centerY = this.height;
|
||||
break;
|
||||
case 'center':
|
||||
default:
|
||||
centerX = this.width/2;
|
||||
centerY = this.height/2;
|
||||
break;
|
||||
}
|
||||
|
||||
const dx = realX - centerX;
|
||||
const dy = realY - centerY;
|
||||
finalX = centerX + dx * cos - dy * sin;
|
||||
finalY = centerY + dx * sin + dy * cos;
|
||||
}
|
||||
|
||||
// 计算点到圆心的距离
|
||||
const distance = Math.sqrt(finalX * finalX + finalY * finalY);
|
||||
|
||||
// 如果点击位置超出半径范围,则不在扇形内
|
||||
if (distance > this.radius * this.scaleX) return false;
|
||||
|
||||
// 计算点击位置的角度(弧度)
|
||||
let angle = Math.atan2(finalY, finalX) * 180 / Math.PI;
|
||||
// 将角度转换为0-360度范围
|
||||
angle = (angle + 360) % 360;
|
||||
|
||||
// 将起始角度和结束角度标准化到0-360度范围
|
||||
let start = (this.startAngle + 360) % 360;
|
||||
let end = (this.endAngle + 360) % 360;
|
||||
|
||||
// 处理跨越0度线的情况
|
||||
if (end < start) {
|
||||
return angle >= start || angle <= end;
|
||||
}
|
||||
|
||||
// 判断点击角度是否在扇形角度范围内
|
||||
return angle >= start && angle <= end;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,857 @@
|
||||
import { ICanvas } from '@/uni_modules/tmx-ui/core/canvas/ICanvas.uts';
|
||||
// import { Path2DShape } from '@/uni_modules/tmx-ui/core/canvas/lib/path2d.uts';
|
||||
|
||||
import {
|
||||
IShape, IShapeVector2d,
|
||||
ILineJoinType, ILineCapType,
|
||||
IShapeOptional, IShapeBoundRect, ICanvasEvent, CanvasEventType, IEventShapeListener, IEventShape, CanvasRotateCenter, ShapeSetAttrType, ITextAlignType, ITextBaselineType
|
||||
} from '../interface.uts';
|
||||
// #ifdef APP-ANDROID
|
||||
import { IArc } from './arc.uts';
|
||||
import { ICircle } from './circle.uts';
|
||||
import { IEllipse } from './ellipse.uts';
|
||||
import { ImageShape } from './image.uts';
|
||||
import { ILine } from './line.uts';
|
||||
import { ILinePolygon } from './linePolygon.uts';
|
||||
import { Path2DShape } from './path2d.uts';
|
||||
import { IRect } from './rect.uts';
|
||||
import { IRegularPolygon } from './regularPolygon.uts';
|
||||
import { IRing } from './ring.uts';
|
||||
import { ISector } from './sector.uts';
|
||||
import { IStar } from './star.uts';
|
||||
import { IText } from './text.uts';
|
||||
// #endif
|
||||
|
||||
type CloneCallActions = (el:Shape,index:number) => void
|
||||
export class Shape implements IShape {
|
||||
canvas : ICanvas
|
||||
x : number = 0;
|
||||
y : number = 0;
|
||||
width : number = 0;
|
||||
height : number = 0;
|
||||
fill : string = "";
|
||||
stroke : string = "";
|
||||
strokeWidth : number = 0;
|
||||
opacity : number = 1;
|
||||
visible : boolean = true;
|
||||
rotation : number = 0;
|
||||
scaleX : number = 1;
|
||||
scaleY : number = 1;
|
||||
offsetX : number = 0;
|
||||
offsetY : number = 0;
|
||||
draggable : boolean = false;
|
||||
draggableing : boolean = false;
|
||||
// 元素之间上下重叠时,是否允许穿透冒泡逐层触发,默认不允许
|
||||
bubbleEvent = false;
|
||||
toggleStatus = false;
|
||||
|
||||
// 线条样式
|
||||
lineJoin : ILineJoinType = "round";
|
||||
lineDashOffset : number = 0;
|
||||
lineDash : number[] = []
|
||||
lineCap : ILineCapType = "butt";
|
||||
|
||||
text : string = "";
|
||||
fontSize : number = 12;
|
||||
fontFamily : string = "Arial";
|
||||
textAlign : 'left' | 'center' | 'right' = 'left';
|
||||
textBaseline : 'top' | 'middle' | 'bottom' = 'top';
|
||||
padding : number = 0;
|
||||
lineHeight : number = 1.2;
|
||||
|
||||
innerRadius : number = 10;
|
||||
outerRadius : number = 20;
|
||||
startAngle : number = 0;
|
||||
endAngle : number = 360;
|
||||
|
||||
src:string = "";
|
||||
|
||||
foreground:string = 'rgba(0,0,0,1)'
|
||||
qrcodeText:string = 'TMUI4x Great'
|
||||
|
||||
radius : number = 0;
|
||||
|
||||
sides : number = 3;
|
||||
|
||||
points : number[] = [];
|
||||
//多边形线是否自动闭合
|
||||
closed : boolean = true;
|
||||
tension : number = 0
|
||||
bezier : boolean = false;
|
||||
|
||||
radiusX : number = 20;
|
||||
radiusY : number = 80;
|
||||
//星的角数量,默认5角星
|
||||
numPoints : number = 5
|
||||
|
||||
|
||||
pointStart : IShapeVector2d = { x: 0, y: 0 };
|
||||
pointEnd : IShapeVector2d = { x: 0, y: 0 };
|
||||
|
||||
clip : boolean = false;
|
||||
zIndex : number = 0;
|
||||
textBgColor : string = '';
|
||||
|
||||
|
||||
id : string = "shape-" + (Math.random()).toString(8).substring(4, 20)
|
||||
type : string = ''
|
||||
/** 旋转元素时的中心点,默认是topLeft左顶,center表示元素的中间 */
|
||||
rotateCenter : CanvasRotateCenter = "topLeft"
|
||||
|
||||
|
||||
strokeGradient = [] as string[]
|
||||
fillGradient = [] as string[]
|
||||
|
||||
needsUpdate = false
|
||||
private toggleListener = (status : boolean, target : any) => { }
|
||||
private eventListeners : Map<CanvasEventType, IEventShapeListener[]> = new Map();
|
||||
|
||||
|
||||
drag : (eventName : CanvasEventType, parentEventDetail : ICanvasEvent) => void = (eventName : CanvasEventType, parentEventDetail : ICanvasEvent) => {
|
||||
if (!this.draggable) return
|
||||
this.x -= parentEventDetail.detail[0].moveLenX
|
||||
this.y -= parentEventDetail.detail[0].moveLenY
|
||||
|
||||
}
|
||||
constructor(config : IShapeOptional, canvas : ICanvas) {
|
||||
this.canvas = canvas
|
||||
this.x = config?.x ?? this.x;
|
||||
this.y = config?.y ?? this.y;
|
||||
this.width = config?.width ?? this.width;
|
||||
this.height = config?.height ?? this.height;
|
||||
this.fill = config?.fill ?? this.fill;
|
||||
this.stroke = config?.stroke ?? this.stroke;
|
||||
this.strokeWidth = config?.strokeWidth ?? this.strokeWidth;
|
||||
this.opacity = config?.opacity ?? this.opacity;
|
||||
this.visible = config?.visible ?? this.visible;
|
||||
this.rotation = config?.rotation ?? this.rotation;
|
||||
this.scaleX = config?.scaleX ?? this.scaleX;
|
||||
this.scaleY = config?.scaleY ?? this.scaleY;
|
||||
this.offsetX = config?.offsetX ?? this.offsetX;
|
||||
this.offsetY = config?.offsetY ?? this.offsetY;
|
||||
this.draggable = config?.draggable ?? this.draggable;
|
||||
this.bubbleEvent = config?.bubbleEvent ?? this.bubbleEvent;
|
||||
this.rotateCenter = config?.rotateCenter ?? this.rotateCenter;
|
||||
|
||||
this.lineJoin = config?.lineJoin ?? this.lineJoin;
|
||||
this.lineDashOffset = config?.lineDashOffset ?? this.lineDashOffset;
|
||||
this.lineDash = config?.lineDash ?? this.lineDash;
|
||||
this.lineCap = config?.lineCap ?? this.lineCap;
|
||||
|
||||
this.text = config?.text ?? this.text;
|
||||
this.fontSize = config?.fontSize ?? this.fontSize;
|
||||
this.fontFamily = config?.fontFamily ?? this.fontFamily;
|
||||
this.textAlign = config?.textAlign ?? this.textAlign;
|
||||
this.textBaseline = config?.textBaseline ?? this.textBaseline;
|
||||
this.padding = config?.padding ?? this.padding;
|
||||
this.lineHeight = config?.lineHeight ?? this.lineHeight;
|
||||
|
||||
this.innerRadius = config?.innerRadius ?? this.innerRadius;
|
||||
this.outerRadius = config?.outerRadius ?? this.outerRadius;
|
||||
this.startAngle = config?.startAngle ?? this.startAngle;
|
||||
this.endAngle = config?.endAngle ?? this.endAngle;
|
||||
|
||||
this.radius = config?.radius ?? this.radius;
|
||||
this.sides = config?.sides ?? this.sides;
|
||||
|
||||
this.points = config?.points ?? this.points;
|
||||
this.closed = config?.closed ?? this.closed;
|
||||
|
||||
this.tension = config?.tension ?? this.tension;
|
||||
this.bezier = config?.bezier ?? this.bezier;
|
||||
|
||||
this.src = config?.src ?? this.src;
|
||||
|
||||
this.foreground = config?.foreground ?? this.foreground;
|
||||
this.qrcodeText = config?.qrcodeText ?? this.qrcodeText;
|
||||
|
||||
|
||||
this.radiusX = config?.radiusX ?? this.radiusX;
|
||||
this.radiusY = config?.radiusY ?? this.radiusY;
|
||||
this.numPoints = config?.numPoints ?? this.numPoints;
|
||||
this.pointStart = config?.pointStart ?? this.pointStart;
|
||||
this.pointEnd = config?.pointEnd ?? this.pointEnd;
|
||||
|
||||
this.strokeGradient = config?.strokeGradient ?? this.strokeGradient;
|
||||
this.fillGradient = config?.fillGradient ?? this.fillGradient;
|
||||
this.clip = config?.clip ?? this.clip;
|
||||
this.zIndex = config?.zIndex ?? this.zIndex;
|
||||
this.textBgColor = config?.textBgColor ?? this.textBgColor;
|
||||
|
||||
|
||||
}
|
||||
|
||||
draw(ctx : CanvasRenderingContext2D) : void {
|
||||
if (this.visible == false) return;
|
||||
ctx.save();
|
||||
ctx.globalAlpha = this.opacity;
|
||||
ctx.translate(this.x + this.offsetX, this.y + this.offsetY);
|
||||
if (this.rotation != 0) {
|
||||
if (this.rotateCenter == 'topLeft') {
|
||||
ctx.rotate(this.rotation * Math.PI / 180);
|
||||
} else if (this.rotateCenter == 'center') {
|
||||
ctx.translate(this.width / 2, this.height / 2);
|
||||
ctx.rotate(this.rotation * Math.PI / 180);
|
||||
ctx.translate(-this.width / 2, -this.height / 2);
|
||||
} else if (this.rotateCenter == 'topRight') {
|
||||
ctx.translate(this.width, 0);
|
||||
ctx.rotate(this.rotation * Math.PI / 180);
|
||||
ctx.translate(-this.width, 0);
|
||||
} else if (this.rotateCenter == 'bottomLeft') {
|
||||
ctx.translate(0, this.height);
|
||||
ctx.rotate(this.rotation * Math.PI / 180);
|
||||
ctx.translate(0, -this.height);
|
||||
} else if (this.rotateCenter == 'bottomRight') {
|
||||
ctx.translate(this.width, this.height);
|
||||
ctx.rotate(this.rotation * Math.PI / 180);
|
||||
ctx.translate(-this.width, -this.height);
|
||||
}
|
||||
}
|
||||
if (this.scaleX != 1 || this.scaleY != 1) {
|
||||
|
||||
if (this.rotateCenter == 'topLeft') {
|
||||
ctx.scale(this.scaleX, this.scaleY);
|
||||
} else if (this.rotateCenter == 'center') {
|
||||
ctx.translate(this.width / 2, this.height / 2);
|
||||
ctx.scale(this.scaleX, this.scaleY);
|
||||
ctx.translate(-this.width / 2, -this.height / 2);
|
||||
} else if (this.rotateCenter == 'topRight') {
|
||||
ctx.translate(this.width, 0);
|
||||
ctx.scale(this.scaleX, this.scaleY);
|
||||
ctx.translate(-this.width, 0);
|
||||
} else if (this.rotateCenter == 'bottomLeft') {
|
||||
ctx.translate(0, this.height);
|
||||
ctx.scale(this.scaleX, this.scaleY);
|
||||
ctx.translate(0, -this.height);
|
||||
} else if (this.rotateCenter == 'bottomRight') {
|
||||
ctx.translate(this.width, this.height);
|
||||
ctx.scale(this.scaleX, this.scaleY);
|
||||
ctx.translate(-this.width, -this.height);
|
||||
}
|
||||
|
||||
}
|
||||
ctx.translate(-this.x, -this.y);
|
||||
if (this.clip) {
|
||||
ctx.clip()
|
||||
}
|
||||
if (this.fill != "" || this.fillGradient.length > 0) {
|
||||
if (this.fillGradient.length > 0) {
|
||||
const gradient = this.createLinearGradient(ctx, this.fillGradient);
|
||||
ctx.fillStyle = gradient;
|
||||
} else if (this.fill != "") {
|
||||
ctx.fillStyle = this.fill;
|
||||
}
|
||||
}
|
||||
if (this.stroke != "" || this.strokeGradient.length > 0) {
|
||||
if (this.strokeGradient.length > 0) {
|
||||
const gradient = this.createLinearGradient(ctx, this.strokeGradient);
|
||||
ctx.strokeStyle = gradient;
|
||||
} else {
|
||||
ctx.strokeStyle = this.stroke
|
||||
}
|
||||
ctx.lineWidth = this.strokeWidth;
|
||||
ctx.lineJoin = this.lineJoin;
|
||||
ctx.lineCap = this.lineCap;
|
||||
ctx.lineDashOffset = this.lineDashOffset;
|
||||
if (this.lineDash.length > 0) {
|
||||
ctx.setLineDash(this.lineDash)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
isPointInPath(x : number, y : number, shapeId : string) : boolean {
|
||||
if (!this.visible || (shapeId != "" && shapeId != this.id)) return false;
|
||||
const realX = x - (this.offsetX) - this.x;
|
||||
const realY = y - (this.offsetY) - this.y;
|
||||
|
||||
if (this.rotation != 0) {
|
||||
const angle = -this.rotation * Math.PI / 180;
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
let centerX = 0;
|
||||
let centerY = 0;
|
||||
|
||||
if (this.rotateCenter == 'topLeft') {
|
||||
centerX = 0;
|
||||
centerY = 0;
|
||||
} else if (this.rotateCenter == 'center') {
|
||||
centerX = this.width / 2;
|
||||
centerY = this.height / 2;
|
||||
} else if (this.rotateCenter == 'topRight') {
|
||||
centerX = this.width;
|
||||
centerY = 0;
|
||||
} else if (this.rotateCenter == 'bottomLeft') {
|
||||
centerX = 0;
|
||||
centerY = this.height;
|
||||
} else if (this.rotateCenter == 'bottomRight') {
|
||||
centerX = this.width;
|
||||
centerY = this.height;
|
||||
}
|
||||
|
||||
const dx = realX - centerX;
|
||||
const dy = realY - centerY;
|
||||
const rotatedX = centerX + dx * cos - dy * sin;
|
||||
const rotatedY = centerY + dx * sin + dy * cos;
|
||||
return rotatedX >= 0 && rotatedX <= this.width * this.scaleX && rotatedY >= 0 && rotatedY <= this.height * this.scaleY;
|
||||
}
|
||||
|
||||
return realX >= 0 && realX <= this.width * this.scaleX &&
|
||||
realY >= 0 && realY <= this.height * this.scaleY;
|
||||
}
|
||||
|
||||
addEventListener(eventName : CanvasEventType, listener : IEventShapeListener) : Shape {
|
||||
if (!this.eventListeners.has(eventName)) {
|
||||
this.eventListeners.set(eventName, []);
|
||||
}
|
||||
|
||||
this.eventListeners.get(eventName)!.push(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
removeEventListener(eventName : CanvasEventType, listener : IEventShapeListener) : Shape {
|
||||
const listeners = this.eventListeners.get(eventName);
|
||||
if (listeners == null) return this;
|
||||
const index = listeners.indexOf(listener);
|
||||
if (index != -1) {
|
||||
listeners.splice(index, 1);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
buildEvents(eventName : CanvasEventType, parentEventDetail : ICanvasEvent) {
|
||||
const listeners = this.eventListeners.get(eventName);
|
||||
|
||||
if (eventName == 'click') {
|
||||
this.toggleStatus = !this.toggleStatus
|
||||
this.toggleListener(this.toggleStatus, this)
|
||||
}
|
||||
|
||||
if (listeners == null) return;
|
||||
let layerX = parentEventDetail.x - ((this.offsetX * this.scaleX) + this.x);
|
||||
let layerY = parentEventDetail.y - ((this.offsetY * this.scaleY) + this.y);
|
||||
const events = {
|
||||
type: eventName,
|
||||
x: parentEventDetail.x,
|
||||
y: parentEventDetail.y,
|
||||
/** 元素本身内的坐标X */
|
||||
layerX: layerX,
|
||||
/** 元素本身内的坐标Y */
|
||||
layerY: layerY,
|
||||
target: this,
|
||||
touches: parentEventDetail.touches.map((el : ICanvasEvent) : IEventShape => {
|
||||
let layerX_self = el.x - ((this.offsetX * this.scaleX) + this.x);
|
||||
let layerY_self = el.y - ((this.offsetY * this.scaleY) + this.y);
|
||||
return {
|
||||
touches: [] as IEventShape[],
|
||||
type: eventName,
|
||||
x: el.x,
|
||||
y: el.y,
|
||||
layerX: layerX_self,
|
||||
layerY: layerY_self,
|
||||
target: this
|
||||
} as IEventShape
|
||||
})
|
||||
} as IEventShape
|
||||
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
let children = listeners[i]
|
||||
//执行画布的监听事件
|
||||
children(events);
|
||||
}
|
||||
}
|
||||
|
||||
setRotateCenter(center : CanvasRotateCenter) : Shape {
|
||||
this.rotateCenter = center;
|
||||
return this;
|
||||
}
|
||||
setX(value : number) : Shape {
|
||||
this.x = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setY(value : number) : Shape {
|
||||
this.y = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setWidth(value : number) : Shape {
|
||||
if(
|
||||
this.type=='ILinePolygon'||
|
||||
this.type=='ILayout'||
|
||||
this.type=='Path2DShape'||
|
||||
this.type=='IRegularPolygon'
|
||||
){
|
||||
console.warn(`所在图形:${this.type}不支持本方法`)
|
||||
return this;
|
||||
}
|
||||
this.width = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setHeight(value : number) : Shape {
|
||||
if(
|
||||
this.type=='ILinePolygon'||
|
||||
this.type=='ILayout'||
|
||||
this.type=='Path2DShape'||
|
||||
this.type=='IRegularPolygon'
|
||||
){
|
||||
console.warn(`所在图形:${this.type}不支持本方法`)
|
||||
return this;
|
||||
}
|
||||
this.height = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setFill(value : string) : Shape {
|
||||
this.fill = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setStroke(value : string) : Shape {
|
||||
this.stroke = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setStrokeWidth(value : number) : Shape {
|
||||
this.strokeWidth = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setOpacity(value : number) : Shape {
|
||||
this.opacity = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setVisible(value : boolean) : Shape {
|
||||
this.visible = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setRotation(value : number) : Shape {
|
||||
this.rotation = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setScaleX(value : number) : Shape {
|
||||
this.scaleX = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setScaleY(value : number) : Shape {
|
||||
this.scaleY = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setOffsetX(value : number) : Shape {
|
||||
this.offsetX = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setOffsetY(value : number) : Shape {
|
||||
this.offsetY = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
setLineDash(value : number[]) : Shape {
|
||||
this.lineDash = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setDraggable(value : boolean) : Shape {
|
||||
this.draggable = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setBubbleEvent(value : boolean) : Shape {
|
||||
this.bubbleEvent = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
setZindex(value : number) : Shape {
|
||||
this.zIndex = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
setClip(value : boolean) : Shape {
|
||||
this.clip = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
setTextBgColor(value : string) : Shape {
|
||||
this.textBgColor = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setAttr(key : string, value : any) {
|
||||
this.needsUpdate = true;
|
||||
switch (key) {
|
||||
case 'x': { this.x = value as number; break; }
|
||||
case 'y': { this.y = value as number; break; }
|
||||
case 'width': { this.width = value as number; break; }
|
||||
case 'height': { this.height = value as number; break; }
|
||||
case 'fill': { this.fill = value as string; break; }
|
||||
case 'stroke': { this.stroke = value as string; break; }
|
||||
case 'strokeWidth': { this.strokeWidth = value as number; break; }
|
||||
case 'opacity': { this.opacity = value as number; break; }
|
||||
case 'visible': { this.visible = value as boolean; break; }
|
||||
case 'rotation': { this.rotation = value as number; break; }
|
||||
case 'scaleX': { this.scaleX = value as number; break; }
|
||||
case 'scaleY': { this.scaleY = value as number; break; }
|
||||
case 'offsetX': { this.offsetX = value as number; break; }
|
||||
case 'offsetY': { this.offsetY = value as number; break; }
|
||||
case 'draggable': { this.draggable = value as boolean; break; }
|
||||
case 'bubbleEvent': { this.bubbleEvent = value as boolean; break; }
|
||||
case 'lineJoin': { this.lineJoin = value as ILineJoinType; break; }
|
||||
case 'lineDashOffset': { this.lineDashOffset = value as number; break; }
|
||||
case 'lineDash': { this.lineDash = value as number[]; break; }
|
||||
case 'lineCap': { this.lineCap = value as ILineCapType; break; }
|
||||
case 'text': this.text = value as string;
|
||||
case 'src': this.src = value as string;
|
||||
case 'fontSize': this.fontSize = value as number;
|
||||
case 'fontFamily': this.fontFamily = value as string;
|
||||
case 'textAlign': this.textAlign = value as ITextAlignType;
|
||||
case 'textBaseline': this.textBaseline = value as ITextBaselineType;
|
||||
case 'padding': this.padding = value as number;
|
||||
case 'lineHeight': this.lineHeight = value as number;
|
||||
case 'innerRadius': { this.innerRadius = value as number; break; }
|
||||
case 'outerRadius': { this.outerRadius = value as number; break; }
|
||||
case 'startAngle': { this.startAngle = value as number; break; }
|
||||
case 'endAngle': { this.endAngle = value as number; break; }
|
||||
case 'radius': { this.radius = value as number; break; }
|
||||
case 'sides': { this.sides = value as number; break; }
|
||||
case 'points': { this.points = value as number[]; break; }
|
||||
case 'closed': { this.closed = value as boolean; break; }
|
||||
case 'tension': { this.tension = value as number; break; }
|
||||
case 'bezier': { this.bezier = value as boolean; break; }
|
||||
case 'radiusX': { this.radiusX = value as number; break; }
|
||||
case 'radiusY': { this.radiusY = value as number; break; }
|
||||
case 'numPoints': { this.numPoints = value as number; break; }
|
||||
case 'pointStart': { this.pointStart = value as IShapeVector2d; break; }
|
||||
case 'pointEnd': { this.pointEnd = value as IShapeVector2d; break; }
|
||||
case 'clip': { this.clip = value as boolean; break; }
|
||||
case 'zIndex': { this.zIndex = value as number; break; }
|
||||
case 'textBgColor': { this.textBgColor = value as string; break; }
|
||||
case 'foreground': { this.foreground = value as string; break; }
|
||||
case 'qrcodeText': { this.qrcodeText = value as string; break; }
|
||||
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
getAttr(key : string) : any | null {
|
||||
switch (key) {
|
||||
case 'x': return this.x;
|
||||
case 'y': return this.y;
|
||||
case 'src': return this.src;
|
||||
case 'width': return this.width;
|
||||
case 'height': return this.height;
|
||||
case 'fill': return this.fill;
|
||||
case 'stroke': return this.stroke;
|
||||
case 'strokeWidth': return this.strokeWidth;
|
||||
case 'opacity': return this.opacity;
|
||||
case 'visible': return this.visible;
|
||||
case 'rotation': return this.rotation;
|
||||
case 'scaleX': return this.scaleX;
|
||||
case 'scaleY': return this.scaleY;
|
||||
case 'offsetX': return this.offsetX;
|
||||
case 'offsetY': return this.offsetY;
|
||||
case 'draggable': return this.draggable;
|
||||
case 'bubbleEvent': return this.bubbleEvent;
|
||||
case 'lineJoin': return this.lineJoin;
|
||||
case 'lineDashOffset': return this.lineDashOffset;
|
||||
case 'lineDash': return this.lineDash;
|
||||
case 'lineCap': return this.lineCap;
|
||||
case 'text': return this.text;
|
||||
case 'fontSize': return this.fontSize;
|
||||
case 'fontFamily': return this.fontFamily;
|
||||
case 'textAlign': return this.textAlign;
|
||||
case 'textBaseline': return this.textBaseline;
|
||||
case 'padding': return this.padding;
|
||||
case 'lineHeight': return this.lineHeight;
|
||||
case 'innerRadius': return this.innerRadius;
|
||||
case 'outerRadius': return this.outerRadius;
|
||||
case 'startAngle': return this.startAngle;
|
||||
case 'endAngle': return this.endAngle;
|
||||
case 'radius': return this.radius;
|
||||
case 'sides': return this.sides;
|
||||
case 'closed': return this.closed;
|
||||
case 'points': return this.points;
|
||||
case 'tension': return this.tension;
|
||||
case 'bezier': return this.bezier;
|
||||
case 'radiusX': return this.radiusX;
|
||||
case 'radiusY': return this.radiusY;
|
||||
case 'numPoints': return this.numPoints;
|
||||
case 'pointStart': return this.pointStart;
|
||||
case 'pointEnd': return this.pointEnd;
|
||||
case 'clip': return this.pointEnd;
|
||||
case 'zIndex': return this.zIndex;
|
||||
case 'textBgColor': return this.textBgColor;
|
||||
case 'foreground': return this.foreground;
|
||||
case 'qrcodeText': return this.qrcodeText;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
getBoundRect() : IShapeBoundRect {
|
||||
return {
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
width: this.width,
|
||||
height: this.height
|
||||
} as IShapeBoundRect;
|
||||
}
|
||||
|
||||
|
||||
toggle(call : (status : boolean, target : any) => void) : void {
|
||||
this.toggleListener = call;
|
||||
}
|
||||
|
||||
|
||||
update() : Shape {
|
||||
if (!this.needsUpdate) return this;
|
||||
this.needsUpdate = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 克隆当前图形
|
||||
* @param count 要克隆的数量,默认 1
|
||||
* @returns 克隆出来的新图形数组(长度为 count)
|
||||
*/
|
||||
clone(count : number = 1,callActons:CloneCallActions|null = null) : Shape[] {
|
||||
const clones : Shape[] = [];
|
||||
|
||||
const baseConfig = {
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
fill: this.fill,
|
||||
stroke: this.stroke,
|
||||
strokeWidth: this.strokeWidth,
|
||||
opacity: this.opacity,
|
||||
visible: this.visible,
|
||||
rotation: this.rotation,
|
||||
scaleX: this.scaleX,
|
||||
scaleY: this.scaleY,
|
||||
offsetX: this.offsetX,
|
||||
offsetY: this.offsetY,
|
||||
draggable: this.draggable,
|
||||
bubbleEvent: this.bubbleEvent,
|
||||
rotateCenter: this.rotateCenter,
|
||||
lineJoin: this.lineJoin,
|
||||
lineDashOffset: this.lineDashOffset,
|
||||
lineDash: this.lineDash.slice(),
|
||||
lineCap: this.lineCap,
|
||||
text: this.text,
|
||||
fontSize: this.fontSize,
|
||||
fontFamily: this.fontFamily,
|
||||
textAlign: this.textAlign,
|
||||
textBaseline: this.textBaseline,
|
||||
padding: this.padding,
|
||||
lineHeight: this.lineHeight,
|
||||
innerRadius: this.innerRadius,
|
||||
outerRadius: this.outerRadius,
|
||||
startAngle: this.startAngle,
|
||||
endAngle: this.endAngle,
|
||||
radius: this.radius,
|
||||
sides: this.sides,
|
||||
points: this.points.slice(),
|
||||
closed: this.closed,
|
||||
tension: this.tension,
|
||||
bezier: this.bezier,
|
||||
radiusX: this.radiusX,
|
||||
radiusY: this.radiusY,
|
||||
numPoints: this.numPoints,
|
||||
pointStart: { x: this.pointStart.x, y: this.pointStart.y },
|
||||
pointEnd: { x: this.pointEnd.x, y: this.pointEnd.y },
|
||||
strokeGradient: this.strokeGradient.slice(),
|
||||
fillGradient: this.fillGradient.slice(),
|
||||
clip: this.clip,
|
||||
zIndex: this.zIndex,
|
||||
textBgColor: this.textBgColor,
|
||||
src: this.src,
|
||||
} as IShapeOptional;
|
||||
|
||||
for (let i = 0; i < Math.max(1, count); i++) {
|
||||
|
||||
let shape:any|null = null;
|
||||
|
||||
// #ifdef APP-ANDROID
|
||||
switch(this.type){
|
||||
case 'IArc':{
|
||||
shape = new IArc(baseConfig,this.canvas)
|
||||
break;
|
||||
}
|
||||
case 'ICircle':{
|
||||
shape = new ICircle(baseConfig,this.canvas)
|
||||
break;
|
||||
}
|
||||
case 'IEllipse':{
|
||||
shape = new IEllipse(baseConfig,this.canvas)
|
||||
break;
|
||||
}
|
||||
case 'ImageShape':{
|
||||
shape = new ImageShape(baseConfig,this.canvas)
|
||||
break;
|
||||
}
|
||||
case 'ILine':{
|
||||
shape = new ILine(baseConfig,this.canvas)
|
||||
break;
|
||||
}
|
||||
case 'ILinePolygon':{
|
||||
shape = new ILinePolygon(baseConfig,this.canvas)
|
||||
break;
|
||||
}
|
||||
case 'Path2DShape':{
|
||||
shape = new Path2DShape(baseConfig,this.canvas)
|
||||
break;
|
||||
}
|
||||
case 'IRect':{
|
||||
shape = new IRect(baseConfig,this.canvas)
|
||||
break;
|
||||
}
|
||||
case 'IRegularPolygon':{
|
||||
shape = new IRegularPolygon(baseConfig,this.canvas)
|
||||
break;
|
||||
}
|
||||
case 'IRing':{
|
||||
shape = new IRing(baseConfig,this.canvas)
|
||||
break;
|
||||
}
|
||||
case 'ISector':{
|
||||
shape = new ISector(baseConfig,this.canvas)
|
||||
break;
|
||||
}
|
||||
case 'IStar':{
|
||||
shape = new IStar(baseConfig,this.canvas)
|
||||
break;
|
||||
}
|
||||
case 'IText':{
|
||||
shape = new IText(baseConfig,this.canvas)
|
||||
break;
|
||||
}
|
||||
default:{
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-ANDROID
|
||||
const Ctor = (this as any).constructor;
|
||||
shape = new Ctor(baseConfig,this.canvas) as any
|
||||
// #endif
|
||||
|
||||
|
||||
if(shape!=null){
|
||||
const cloned : Shape = shape! as Shape;
|
||||
cloned.needsUpdate = true;
|
||||
if(callActons!=null){
|
||||
callActons!(cloned!,i)
|
||||
}
|
||||
clones.push(cloned);
|
||||
}
|
||||
|
||||
}
|
||||
return clones;
|
||||
}
|
||||
|
||||
private createLinearGradient(ctx : CanvasRenderingContext2D, gradientArray : string[]) : CanvasGradient {
|
||||
if (gradientArray.length < 2) return ctx.createLinearGradient(0, 0, 0, 0);
|
||||
// 解析角度
|
||||
let angle = 0;
|
||||
const firstItem = gradientArray[0];
|
||||
if (firstItem.includes('deg')) {
|
||||
angle = parseFloat(firstItem);
|
||||
gradientArray = gradientArray.slice(1);
|
||||
}
|
||||
|
||||
// 计算渐变的起点和终点
|
||||
const radian = angle * Math.PI / 180;
|
||||
const length = Math.max(this.width, this.height);
|
||||
let startX = this.x;
|
||||
let startY = this.y;
|
||||
let endX = this.x;
|
||||
let endY = this.y;
|
||||
|
||||
if (this.type == 'ICircle') {
|
||||
// 对于圆形,使用半径计算
|
||||
const dx = Math.cos(radian) * this.radius;
|
||||
const dy = Math.sin(radian) * this.radius;
|
||||
startX = this.x - dx;
|
||||
startY = this.y - dy;
|
||||
endX = this.x + dx;
|
||||
endY = this.y + dy;
|
||||
} else if (this.type == 'IEllipse') {
|
||||
// 对于椭圆,使用radiusX和radiusY计算
|
||||
const dx = Math.cos(radian) * this.radiusX;
|
||||
const dy = Math.sin(radian) * this.radiusY;
|
||||
startX = this.x - dx;
|
||||
startY = this.y - dy;
|
||||
endX = this.x + dx;
|
||||
endY = this.y + dy;
|
||||
} else if (this.type == 'IStar') {
|
||||
// 对于星形,使用外接圆半径计算
|
||||
const radius = this.outerRadius;
|
||||
const dx = Math.cos(radian) * radius;
|
||||
const dy = Math.sin(radian) * radius;
|
||||
startX = this.x - dx;
|
||||
startY = this.y - dy;
|
||||
endX = this.x + dx;
|
||||
endY = this.y + dy;
|
||||
} else if (this.type == 'IText') {
|
||||
// 对于文本,使用文本框的宽高计算
|
||||
const dx = Math.cos(radian) * this.width;
|
||||
const dy = Math.sin(radian) * this.height;
|
||||
endX = startX + dx;
|
||||
endY = startY + dy;
|
||||
} else if (this.type == 'IArc' || this.type == 'ISector') {
|
||||
// 对于圆弧和扇形,使用半径计算
|
||||
const dx = Math.cos(radian) * this.radius;
|
||||
const dy = Math.sin(radian) * this.radius;
|
||||
startX = this.x - dx;
|
||||
startY = this.y - dy;
|
||||
endX = this.x + dx;
|
||||
endY = this.y + dy;
|
||||
} else {
|
||||
// 默认使用矩形的宽高计算
|
||||
const dx = Math.cos(radian) * length;
|
||||
const dy = Math.sin(radian) * length;
|
||||
endX = startX + dx;
|
||||
endY = startY + dy;
|
||||
}
|
||||
|
||||
const gradient = ctx.createLinearGradient(startX, startY, endX, endY);
|
||||
// 添加渐变色停止点
|
||||
gradientArray.forEach(item => {
|
||||
const ar = item.split(' ');
|
||||
const color = ar[0];
|
||||
const stop = ar[1];
|
||||
const offset = parseFloat(stop) / 100;
|
||||
|
||||
gradient.addColorStop(offset, color);
|
||||
});
|
||||
|
||||
return gradient;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import { Shape } from './shape.uts';
|
||||
import { CanvasRotateCenter, IShapeBoundRect, IShapeOptional } from '../interface.uts';
|
||||
import { ICanvas } from '@/uni_modules/tmx-ui/core/canvas/ICanvas.uts';
|
||||
|
||||
export class IStar extends Shape {
|
||||
override type = 'IStar'
|
||||
constructor(config: IShapeOptional, canvas: ICanvas) {
|
||||
super(config, canvas);
|
||||
this.numPoints = config?.numPoints ?? 5;
|
||||
this.innerRadius = config?.innerRadius ?? 16;
|
||||
this.outerRadius = config?.outerRadius ?? 30;
|
||||
this.width = this.outerRadius * 2;
|
||||
this.height = this.outerRadius * 2;
|
||||
}
|
||||
|
||||
setInnerRadius(value: number): IStar {
|
||||
this.innerRadius = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setOuterRadius(value: number): IStar {
|
||||
this.outerRadius = value;
|
||||
this.width = value * 2;
|
||||
this.height = value * 2;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setNumPoints(value: number): IStar {
|
||||
this.numPoints = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override getBoundRect(): IShapeBoundRect {
|
||||
// 估算星形的外接矩形(以中心为圆心,外半径为界)
|
||||
const r = Math.max(0, this.outerRadius);
|
||||
const pad = (this.stroke != "" ? this.strokeWidth/2 : 0);
|
||||
const d = r*2 + pad*2;
|
||||
return {
|
||||
x: this.x - r - pad,
|
||||
y: this.y - r - pad,
|
||||
width: d,
|
||||
height: d
|
||||
} as IShapeBoundRect;
|
||||
}
|
||||
|
||||
override setWidth(value: number): IStar {
|
||||
this.height = value;
|
||||
this.width = value;
|
||||
this.outerRadius = value / 2;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override setHeight(value: number): IStar {
|
||||
this.height = value;
|
||||
this.width = value;
|
||||
this.outerRadius = value / 2;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override draw(ctx: CanvasRenderingContext2D) {
|
||||
if (this.visible == false) return;
|
||||
super.draw(ctx);
|
||||
ctx.beginPath();
|
||||
|
||||
// 计算每个角的角度增量
|
||||
const angleStep = Math.PI * 2 / this.numPoints;
|
||||
// 起始角度(使星形垂直向上)
|
||||
const startAngle = -Math.PI / 2;
|
||||
|
||||
// 绘制第一个点(外部点)
|
||||
let x = this.x + Math.cos(startAngle) * this.outerRadius;
|
||||
let y = this.y + Math.sin(startAngle) * this.outerRadius;
|
||||
ctx.moveTo(x, y);
|
||||
|
||||
// 绘制其余的点
|
||||
for (let i = 1; i <= this.numPoints * 2; i++) {
|
||||
const angle = startAngle + angleStep * i / 2;
|
||||
const radius = i % 2 === 0 ? this.outerRadius : this.innerRadius;
|
||||
x = this.x + Math.cos(angle) * radius;
|
||||
y = this.y + Math.sin(angle) * radius;
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
|
||||
ctx.closePath();
|
||||
if (this.fill != "") {
|
||||
ctx.fill();
|
||||
}
|
||||
if (this.stroke != "") {
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
override isPointInPath(x: number, y: number, shapeId: string): boolean {
|
||||
if (!this.visible || (shapeId != "" && shapeId != this.id)) return false;
|
||||
|
||||
// 计算点击位置相对于星形中心的实际坐标
|
||||
let realX = x - this.offsetX - this.x;
|
||||
let realY = y - this.offsetY - this.y;
|
||||
|
||||
// 如果有旋转,需要将坐标转换回未旋转状态
|
||||
if (this.rotation != 0) {
|
||||
const angle = -this.rotation * Math.PI / 180;
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
let centerX = 0;
|
||||
let centerY = 0;
|
||||
|
||||
// 根据不同的旋转中心点设置centerX和centerY
|
||||
switch(this.rotateCenter) {
|
||||
case 'topLeft':
|
||||
centerX = 0;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'topRight':
|
||||
centerX = this.width;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'bottomLeft':
|
||||
centerX = 0;
|
||||
centerY = this.height;
|
||||
break;
|
||||
case 'bottomRight':
|
||||
centerX = this.width;
|
||||
centerY = this.height;
|
||||
break;
|
||||
case 'center':
|
||||
default:
|
||||
centerX = this.width/2;
|
||||
centerY = this.height/2;
|
||||
break;
|
||||
}
|
||||
const dx = realX - centerX;
|
||||
const dy = realY - centerY;
|
||||
realX = centerX + dx * cos - dy * sin;
|
||||
realY = centerY + dx * sin + dy * cos;
|
||||
}
|
||||
|
||||
// 计算点到中心的距离
|
||||
const distance = Math.sqrt(realX * realX + realY * realY);
|
||||
|
||||
// 如果点击位置超出外半径,则不在星形内
|
||||
if (distance > this.outerRadius * Math.max(this.scaleX, this.scaleY)) return false;
|
||||
|
||||
// 计算点击位置的角度
|
||||
let angle = Math.atan2(realY, realX);
|
||||
if (angle < 0) angle += Math.PI * 2;
|
||||
|
||||
// 调整角度使其从垂直向上开始计算
|
||||
angle = (angle + Math.PI / 2) % (Math.PI * 2);
|
||||
|
||||
// 计算点所在的扇区
|
||||
const angleStep = Math.PI * 2 / this.numPoints;
|
||||
const sector = Math.floor(angle / angleStep);
|
||||
const sectorAngle = angle - sector * angleStep;
|
||||
|
||||
// 计算该角度对应的半径
|
||||
const ratio = sectorAngle / angleStep;
|
||||
const radius = this.innerRadius + (this.outerRadius - this.innerRadius) * Math.abs(0.5 - ratio) * 2;
|
||||
// 考虑缩放因素
|
||||
const scaledRadius = radius * Math.max(this.scaleX, this.scaleY);
|
||||
// 如果点击位置在计算出的半径内,则在星形内
|
||||
return distance <= scaledRadius;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
import { Shape } from './shape.uts';
|
||||
import { ShapeSetAttrType, ITextBaselineType, ITextAlignType, CanvasRotateCenter, IShapeBoundRect, IShapeOptional } from '../interface.uts';
|
||||
import { ICanvas } from '@/uni_modules/tmx-ui/core/canvas/ICanvas.uts';
|
||||
|
||||
|
||||
|
||||
export class IText extends Shape {
|
||||
|
||||
constructor(config : IShapeOptional,canvas:ICanvas) {
|
||||
super(config,canvas);
|
||||
this.type = 'IText'
|
||||
}
|
||||
|
||||
private wrapText(ctx : CanvasRenderingContext2D, text : string, maxWidth : number) : string[] {
|
||||
// 首先处理所有类型的换行符,统一转换为\n
|
||||
text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
||||
// 按换行符分割文本
|
||||
const paragraphs = text.split('\n');
|
||||
const lines : string[] = [];
|
||||
|
||||
// 处理每个段落
|
||||
for (let i = 0; i < paragraphs.length; i++) {
|
||||
let paragraph = paragraphs[i]
|
||||
if (paragraph == '') {
|
||||
// 保留空行
|
||||
lines.push('');
|
||||
continue;
|
||||
}
|
||||
|
||||
const words = paragraph.split('');
|
||||
let currentLine = '';
|
||||
|
||||
for (let i = 0; i < words.length; i++) {
|
||||
const testLine = currentLine + words[i];
|
||||
const metrics = ctx.measureText(testLine);
|
||||
const testWidth = metrics.width;
|
||||
|
||||
if (testWidth > maxWidth && i > 0) {
|
||||
lines.push(currentLine);
|
||||
currentLine = words[i];
|
||||
} else {
|
||||
currentLine = testLine;
|
||||
}
|
||||
}
|
||||
lines.push(currentLine);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
override draw(ctx : CanvasRenderingContext2D) {
|
||||
if (this.visible == false) return;
|
||||
|
||||
ctx.font = `${this.fontSize}px ${this.fontFamily}`;
|
||||
ctx.textAlign = this.textAlign;
|
||||
ctx.textBaseline = this.textBaseline;
|
||||
const maxWidth = this.width > 0 ? this.width - this.padding * 2 : ctx.canvas.offsetWidth - this.x - this.padding * 2;
|
||||
const lines = this.wrapText(ctx, this.text, maxWidth);
|
||||
const lineHeightPixels = this.fontSize * this.lineHeight;
|
||||
|
||||
this.width = maxWidth + this.padding * 2;
|
||||
this.height = lineHeightPixels * lines.length + this.padding * 2;
|
||||
if(this.textBgColor!=''){
|
||||
ctx.fillStyle = this.textBgColor
|
||||
// ctx.fillRect(this.x,this.y,this.width,this.height)
|
||||
ctx.beginPath();
|
||||
if (this.radius > 0) {
|
||||
let radiuss = Math.min(this.radius, this.width / 2, this.height / 2);
|
||||
const radius = Math.max(radiuss, 0)
|
||||
ctx.moveTo(this.x + radius, this.y);
|
||||
ctx.lineTo(this.x + this.width - radius, this.y);
|
||||
ctx.arcTo(this.x + this.width, this.y, this.x + this.width, this.y + radius, radius);
|
||||
ctx.lineTo(this.x + this.width, this.y + this.height - radius);
|
||||
ctx.arcTo(this.x + this.width, this.y + this.height, this.x + this.width - radius, this.y + this.height, radius);
|
||||
ctx.lineTo(this.x + radius, this.y + this.height);
|
||||
ctx.arcTo(this.x, this.y + this.height, this.x, this.y + this.height - radius, radius);
|
||||
ctx.lineTo(this.x, this.y + radius);
|
||||
ctx.arcTo(this.x, this.y, this.x + radius, this.y, radius);
|
||||
} else {
|
||||
ctx.beginPath();
|
||||
ctx.rect(this.x, this.y, this.width, this.height);
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.fill()
|
||||
ctx.fillStyle = this.fill
|
||||
}
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const y = this.y + this.padding + i * lineHeightPixels;
|
||||
let adjustedY = y;
|
||||
|
||||
// 根据textBaseline调整y坐标
|
||||
if (this.textBaseline === 'middle') {
|
||||
adjustedY = y + this.fontSize / 2;
|
||||
} else if (this.textBaseline === 'bottom') {
|
||||
adjustedY = y + this.fontSize;
|
||||
}
|
||||
|
||||
let x = this.x + this.padding;
|
||||
if (this.textAlign === 'center') {
|
||||
x = this.x + this.width / 2;
|
||||
} else if (this.textAlign === 'right') {
|
||||
x = this.x + this.width - this.padding;
|
||||
}
|
||||
if (this.fill != "") {
|
||||
ctx.fillStyle = this.fill;
|
||||
ctx.fillText(lines[i], x, adjustedY);
|
||||
}
|
||||
if (this.stroke != "") {
|
||||
ctx.strokeStyle = this.stroke;
|
||||
ctx.lineWidth = this.strokeWidth;
|
||||
ctx.strokeText(lines[i], x, adjustedY);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
setText(value : string) : IText {
|
||||
this.text = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setFontSize(value : number) : IText {
|
||||
this.fontSize = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setFontFamily(value : string) : IText {
|
||||
this.fontFamily = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setTextAlign(value : 'left' | 'center' | 'right') : IText {
|
||||
this.textAlign = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setTextBaseline(value : 'top' | 'middle' | 'bottom') : IText {
|
||||
this.textBaseline = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setPadding(value : number) : IText {
|
||||
this.padding = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setLineHeight(value : number) : IText {
|
||||
this.lineHeight = value;
|
||||
this.needsUpdate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
override getBoundRect() : IShapeBoundRect {
|
||||
const ctx = this.canvas.ctx!
|
||||
ctx.font = `${this.fontSize}px ${this.fontFamily}`;
|
||||
const maxWidth = this.width > 0 ? this.width - this.padding * 2 : ctx.canvas.offsetWidth - this.x - this.padding * 2;
|
||||
const lines = this.wrapText(ctx, this.text, maxWidth);
|
||||
const lineHeightPixels = this.fontSize * this.lineHeight;
|
||||
const measuredWidth = Math.max(...lines.map(line => ctx.measureText(line).width), 0);
|
||||
const effectiveWidth = (this.width > 0 ? Math.min(measuredWidth, maxWidth) : measuredWidth) + this.padding * 2;
|
||||
const width = effectiveWidth;
|
||||
const height = lineHeightPixels * lines.length + this.padding * 2;
|
||||
return {
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
width: width,
|
||||
height: height
|
||||
} as IShapeBoundRect;
|
||||
}
|
||||
|
||||
override isPointInPath(x : number, y : number, shapeId : string) : boolean {
|
||||
if (!this.visible || (shapeId != "" && shapeId != this.id)) return false;
|
||||
const textBounds = this.getBoundRect();
|
||||
const realX = x - textBounds.x - this.offsetX;
|
||||
const realY = y - textBounds.y - this.offsetY;
|
||||
|
||||
if (this.rotation != 0) {
|
||||
const angle = -this.rotation * Math.PI / 180;
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
let centerX = 0;
|
||||
let centerY = 0;
|
||||
|
||||
// 根据不同的旋转中心点设置centerX和centerY
|
||||
switch(this.rotateCenter) {
|
||||
case 'topLeft':
|
||||
centerX = 0;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'topRight':
|
||||
centerX = textBounds.width;
|
||||
centerY = 0;
|
||||
break;
|
||||
case 'bottomLeft':
|
||||
centerX = 0;
|
||||
centerY = textBounds.height;
|
||||
break;
|
||||
case 'bottomRight':
|
||||
centerX = textBounds.width;
|
||||
centerY = textBounds.height;
|
||||
break;
|
||||
case 'center':
|
||||
default:
|
||||
centerX = textBounds.width/2;
|
||||
centerY = textBounds.height/2;
|
||||
break;
|
||||
}
|
||||
|
||||
const dx = realX - centerX;
|
||||
const dy = realY - centerY;
|
||||
const rotatedX = centerX + dx * cos - dy * sin;
|
||||
const rotatedY = centerY + dx * sin + dy * cos;
|
||||
return rotatedX >= 0 && rotatedX <= textBounds.width * this.scaleX && rotatedY >= 0 && rotatedY <= textBounds.height * this.scaleY;
|
||||
}
|
||||
|
||||
return realX >= 0 && realX <= textBounds.width * this.scaleX && realY >= 0 && realY <= textBounds.height * this.scaleY;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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
|
||||
```
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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类型(请求参数配置数据)
|
||||
* 除了auth,before 函数体内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()
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user