This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
## 1.0.62025-08-17
* 兼容原生鸿蒙
## 1.0.52025-04-13
* xToast在安卓,ios,web布局已更新为宽自动,并设定最大宽(之前宽是固定),方便在平板手机内容超多时自动断行及限定最大宽
* 在安卓端并且会自动隐藏状态栏,并可以覆盖底部安全区域,还可以覆盖住官方的diaglo-page,做到真正的全覆盖
* 补上close事件
## 1.0.42025-02-23
* 安卓端重新设定了动画参数,让打开显示的更自然.同时关闭逻辑改成了:当页面卸载或者超时跳转页面时,不会自动关闭上页的问题.
* ios端添加了显示动画,显示更自然
## 1.0.32025-02-13
* 兼容微信
## 1.0.22024-09-12
* 增加了maskDisableClik事件穿透,false表示允许穿透默认是true禁止事件穿透遮罩.
## 1.0.12024-09-06
* 更新支持Uniapp
## 1.0.02024-08-06
安卓,ios,web可用。
+100
View File
@@ -0,0 +1,100 @@
{
"id": "x-toast-s",
"displayName": "TM-UI 4.0x全屏消息提醒覆盖任意界面",
"version": "1.0.6",
"description": "主要是用来代替uni.showToast",
"keywords": [
"uts,uniappx,tmui,原生"
],
"repository": "",
"engines": {
"HBuilderX": "^3.6.8",
"uni-app": "",
"uni-app-x": "^4.75"
},
"dcloudext": {
"type": "uts",
"sale": {
"regular": {
"price": "1.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"darkmode": "x",
"i18n": "x",
"widescreen": "√"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "√",
"aliyun": "√",
"alipay": "√"
},
"client": {
"uni-app": {
"vue": {
"vue2": "-",
"vue3": "-"
},
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"vue": "-",
"nvue": "-",
"android": "-",
"ios": "-",
"harmony": "-"
},
"mp": {
"weixin": "-",
"alipay": "-",
"toutiao": "-",
"baidu": "-",
"kuaishou": "-",
"jd": "-",
"harmony": "-",
"qq": "-",
"lark": "-"
},
"quickapp": {
"huawei": "-",
"union": "-"
}
},
"uni-app-x": {
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"android": {
"extVersion": "",
"minVersion": "21"
},
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√"
}
}
}
}
}
}
+74
View File
@@ -0,0 +1,74 @@
# x-toast-s
### 特性说明
主要是用来代替uni.showToast的,相比优化有:
1. 高自动适应,因此可以添加超长的title文字。
2. 图标可以任意更新,大小,颜色等
3. 可以添加title标题,允许改变字号,颜色 及背景
4. 精致美观,可以定制界面达到与品牌相符的设计
5. 可以重复打开,或者连续打开不用担心前一个是否关闭,我内部会检测关闭上一个提示。
**微信内使用官方的弹层,大多属性无效,只有同官方的属性会生效**
### 兼容性
| Harmony | IOS | Android | WEB | 小程序 |
| --- | --- | --- | --- | --- |
| 支持 | 支持 | 支持 | 支持 | 支持 |
### 开发文档
**浏览器端需要你把目录中的```remixicon.ttf```复制到static目录下**
调用:
```ts
import { showToast,XTOAST_TYPE } from "@/uni_modules/x-toast-s"
// 打开提示
showToast({title:"提示消息"} as XTOAST_TYPE)
// 手动隐藏
hideToast()
```
参数配置:
下面的图标iconCode,同组件库x-icon是相同的字体图标,请打开图标网站:[查看](https://remixicon.com/)
找到直接想要的图标后 复制 unicode ,不是名称。比如:我们只要取ED29
下面的参数图标iconCode就是ED29 **iconCode:'ED29'**
**XLOADINGS_TYPE**
```ts
// 所有参数都是可选的, 但标题是必选。
export type XTOAST_TYPE = {
iconColor?:string,
contentBgColor?:string,
maskBgColor?:string,
iconSize?:number,
/**
* 正常应该填写图标的16进制符号如:EEC4
* 本组件允许几个状态名称使用默认图标,为空是info图标。
* warn,error,success,info
*/
iconCode?:string,
/**
* 标题为空不显示。
*/
title:string,
titleSize?:number,
titleColor?:string,
/**
* 几秒后消失,0 表示永不消失
*/
duration?:number,
close?:()=>void,
/** 宽 */
size?:number,
/**
* 禁用遮罩穿透点击事件
* @default true
*/
maskDisableClik?:boolean
}
```
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

@@ -0,0 +1,3 @@
{
"minSdkVersion": "21"
}
@@ -0,0 +1,319 @@
import LinearLayout from 'android.widget.LinearLayout';
import Context from 'android.content.Context'
import RelativeLayout from 'android.widget.RelativeLayout';
import ViewGroup from 'android.view.ViewGroup';
import View from 'android.view.View';
import { XTOAST_TYPE, XTOAST_TYPE_PRIVATE } from "../interface.uts"
import Color from 'android.graphics.Color';
import Gravity from 'android.view.Gravity';
import GradientDrawable from 'android.graphics.drawable.GradientDrawable';
import TextView from 'android.widget.TextView';
import Typeface from 'android.graphics.Typeface';
import TextUtils from 'android.text.TextUtils';
import Uri from 'android.net.Uri';
import ObjectAnimator from 'android.animation.ObjectAnimator';
import ValueAnimator from 'android.animation.ValueAnimator';
import TimeInterpolator from 'android.animation.TimeInterpolator';
import MotionEvent from 'android.view.MotionEvent';
import { hexToRgb } from "../libs/color.uts"
import { defaultConfig, configCover } from "../libs/config.uts"
import ViewPropertyAnimator from 'android.view.ViewPropertyAnimator';
import AccelerateDecelerateInterpolator from 'android.view.animation.AccelerateDecelerateInterpolator';
// #ifdef UNI-APP-X
import getCurrentPages from 'io.dcloud.uniapp.framework.getCurrentPages';
import onReady from 'io.dcloud.uniapp.framework.onReady';
import onUnload from 'io.dcloud.uniapp.framework.onUnload';
import onShow from 'io.dcloud.uniapp.framework.onShow';
import MeasureSpec from 'android.view.View.MeasureSpec';
// #endif
import AttributeSet from 'android.util.AttributeSet';
import R from 'android.R';
import Bundle from "android.os.Bundle";
import Dialog from "android.app.Dialog";
import WindowManager from "android.view.WindowManager";
import Window from "android.view.Window";
import ColorDrawable from "android.graphics.drawable.ColorDrawable";
let tid : number | null = 0
let maskDomId = 588
class MaxWidthLinearLayout extends LinearLayout {
private mMaxWidth = 300;
constructor(context:Context) {
super(context);
}
setMaximumWidth(maxWidth:Int) {
this.mMaxWidth = maxWidth;
this.requestLayout();
}
override onMeasure(widthMeasureSpec:Int , heightMeasureSpec:Int ) {
let widthSize = MeasureSpec.getSize(widthMeasureSpec);
if (this.mMaxWidth > 0 && this.mMaxWidth < widthSize) {
let widthMode = MeasureSpec.getMode(widthMeasureSpec);
widthMeasureSpec = MeasureSpec.makeMeasureSpec(this.mMaxWidth.toInt(), widthMode);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
let dialogModal : FullScreenDialogFragment | null = null;
class FullScreenDialogFragment extends Dialog {
private mContext : Context;
private opts : XTOAST_TYPE|null = null;
private maskeDom:RelativeLayout|null = null;
constructor(context : Context, optsOwer : XTOAST_TYPE|null) {
super(context,R.style.Theme_Translucent_NoTitleBar_Fullscreen);
this.mContext = context;
this.opts = optsOwer
}
override onCreate(savedInstanceState ?: Bundle) {
super.onCreate(savedInstanceState)
this.requestWindowFeature(Window.FEATURE_NO_TITLE)
// 禁止按键返回及侧滑返回.
this.setCancelable(false)
this.maskeDom = _createToastView(this.getContext(),this.window!.decorView! as ViewGroup,this.opts)
// 设置窗口属性
let window = this.getWindow();
if (window != null) {
// 首先设置全屏标志
window.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
);
// 设置布局参数为全屏
window.setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT
);
// 设置背景透明
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.setGravity(Gravity.CENTER);
// 完整的沉浸式设置
let decorView = window.getDecorView();
let option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
decorView.setSystemUiVisibility(option);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.setStatusBarColor(Color.TRANSPARENT)
window.setNavigationBarColor(Color.TRANSPARENT)
window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_PANEL)
}
}
override dismiss(){
super.dismiss()
this.maskeDom = null;
if(this.opts!=null){
let closeEvent = this.opts?.close??null
if(closeEvent!=null){
closeEvent()
}
}
dialogModal = null
}
}
function px2dp(n : number) : number {
const mets = UTSAndroid.getAppContext()!.resources!.getDisplayMetrics()
return mets.density * n
}
export function hideToast() {
UTSAndroid.getDispatcher("main").async(function (_) {
dialogModal?.dismiss()
if (tid != null) {
clearTimeout(tid!)
}
})
}
function __hideToast__(mainDialog:FullScreenDialogFragment | null) {
UTSAndroid.getDispatcher("main").async(function (_) {
mainDialog?.dismiss()
if (tid != null) {
clearTimeout(tid!)
}
})
}
function _createToastView(context : Context, decorView : ViewGroup, opts : XTOAST_TYPE|null):RelativeLayout{
let realopts = opts == null ? ({ title: '' } as XTOAST_TYPE) : (opts!)
let config = configCover(realopts)
let rmbgColor = hexToRgb(config.maskBgColor);
let textColor = hexToRgb(config.iconColor);
let wrapColor = hexToRgb(config.contentBgColor);
let titleColorRgb = hexToRgb(config.titleColor);
let mbgColor = rmbgColor == null ? Color.parseColor(config.maskBgColor) : Color.argb((rmbgColor!.a * 255).toInt(), rmbgColor!.r.toInt(), rmbgColor!.g.toInt(), rmbgColor!.b.toInt())
let fontColor = textColor == null ? Color.parseColor(config.iconColor) : Color.argb((textColor!.a * 255).toInt(), textColor!.r.toInt(), textColor!.g.toInt(), textColor!.b.toInt())
let contentColor = rmbgColor == null ? Color.parseColor(config.contentBgColor) : Color.argb((wrapColor!.a * 255).toInt(), wrapColor!.r.toInt(), wrapColor!.g.toInt(), wrapColor!.b.toInt())
let titleColor = titleColorRgb == null ? Color.parseColor(config.titleColor) : Color.argb((titleColorRgb!.a * 255).toInt(), titleColorRgb!.r.toInt(), titleColorRgb!.g.toInt(), titleColorRgb!.b.toInt())
let maskerDom = new RelativeLayout(context)
class MaskerDomClickListsner extends View.OnTouchListener {
constructor() {
super();
}
override onTouch(view : View, event : MotionEvent) : Boolean {
// 阻止事件向下传递。
return config.maskDisableClik;
}
}
maskerDom.setOnTouchListener(new MaskerDomClickListsner())
let maskerDomLayrPrams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
maskerDom.setLayoutParams(maskerDomLayrPrams)
maskerDom.setBackgroundColor(mbgColor)
maskerDom.setGravity(Gravity.CENTER)
maskerDom.setId((maskDomId).toInt())
let contentWrapDiv = new MaxWidthLinearLayout(context);
// 设置圆角背景
let bg = new GradientDrawable()
bg.setColor(contentColor)
bg.setCornerRadius((px2dp(16)).toFloat())
contentWrapDiv.setBackground(bg)
// contentWrapDiv.setBackgroundColor(Color.parseColor(config.contentBgColor))
let wpx = px2dp(config.size).toInt();
let contentWrapDivParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
// contentWrapDivParams.width = px2dp(500).toInt();;
const maxwidthbody = UTSAndroid.getAppContext()!.resources!.getDisplayMetrics().widthPixels
contentWrapDiv.setMaximumWidth((Math.min((maxwidthbody-px2dp(80)),px2dp(320))).toInt())
contentWrapDiv.setMinimumHeight(wpx)
contentWrapDiv.setMinimumWidth(wpx)
contentWrapDiv.setLayoutParams(contentWrapDivParams)
if ((config.iconCode != '' && config.title != '') || (config.iconCode != '' && config.title == '')) {
contentWrapDiv.setMinimumHeight(wpx)
} else {
wpx = px2dp(32 + config.titleSize).toInt();
contentWrapDiv.setMinimumHeight(wpx)
}
let wpadingx = px2dp(16).toInt();
contentWrapDiv.setPadding(wpadingx, wpadingx, wpadingx, wpadingx)
contentWrapDiv.setGravity(Gravity.CENTER)
contentWrapDiv.setOrientation(LinearLayout.VERTICAL)
// 创建图标。
let iconDiv = new TextView(context)
// iconDiv.setLineHeight(1)
iconDiv.setTextColor(fontColor)
let assetManager = context!.getAssets();
let typeface = Typeface.createFromAsset(assetManager, "remixicon.ttf")
iconDiv.setTypeface(typeface)
iconDiv.setGravity(Gravity.CENTER)
iconDiv.setTextSize(config.iconSize.toFloat())
if (!TextUtils.isEmpty(config.iconCode) && config.iconCode != '') {
let codePoint = Integer.parseInt(config.iconCode, 16);
let charArray = Character.toChars(codePoint);
let text = new String(charArray);
iconDiv.setText(text);
contentWrapDiv.addView(iconDiv)
}
if (config.title != "") {
// 添加一个title
let titleDiv = new TextView(context)
titleDiv.setText(config.title)
titleDiv.setTextSize((config.titleSize).toFloat())
titleDiv.setTextColor(titleColor)
titleDiv.setGravity(Gravity.CENTER)
contentWrapDiv.addView(titleDiv)
}
maskerDom.setAlpha((0).toFloat())
maskerDom.addView(contentWrapDiv)
decorView.addView(maskerDom!)
try {
let s0 = (0).toFloat()
let s1 = (1).toFloat()
contentWrapDiv.setAlpha(s0)
contentWrapDiv.setScaleX(s0)
contentWrapDiv.setScaleY(s0)
// 遮罩渐变
const maskAni = maskerDom.animate() as ViewPropertyAnimator;
maskAni.alpha(s1)
.setDuration(300)
.setInterpolator(AccelerateDecelerateInterpolator())
.start()
let ani = contentWrapDiv.animate() as ViewPropertyAnimator;
ani.alpha(s1)
.scaleX(s1)
.scaleY(s1)
.setDuration(300)
.setInterpolator(AccelerateDecelerateInterpolator())
.start()
} catch (e) {
//TODO handle the exception
}
if (config.duration == 0) return maskerDom
tid = setTimeout(function () {
tid = null;
__hideToast__(dialogModal)
}, (config.duration + 300).toInt());
return maskerDom
}
function _showToast_(opts : XTOAST_TYPE | null) {
if (tid != null) {
clearTimeout(tid!)
}
dialogModal = new FullScreenDialogFragment(UTSAndroid.getUniActivity()!,opts)
UTSAndroid.getDispatcher("main").async(function (_) {
dialogModal?.show()
})
}
export function showToast(opts : XTOAST_TYPE | null) {
// hideToast()
dialogModal?.dismiss()
const pages = getCurrentPages();
if (pages.length > 0) {
const page = pages[pages.length - 1].vm!
const instance = page.$
if (page.$isReady) {
_showToast_(opts)
// onShow(()=>{
// console.log('on show page')
// },instance)
// onReady(() => {
// console.log('on ready page')
// }, instance)
} else {
hideToast()
}
onUnload(() => {
hideToast()
}, instance)
}else{
_showToast_(opts)
}
}
@@ -0,0 +1,5 @@
{
"dependencies": {
"x_toast_s": "./lib/x_toast_s.har"
}
}
@@ -0,0 +1,14 @@
import { XTOAST_TYPE, XTIPS_TYPE_PRIVATE } from "../interface.uts"
import { defaultConfig, configCover } from "../libs/config.uts"
import { hexToRgb } from "../libs/color.uts"
import { xshowtoasts,xhidetoasts } from "x_toast_s";
export const hideToast = () => {
xhidetoasts(UTSHarmony.getCurrentWindow()!.getUIContext())
}
export function showToast(opts : XTOAST_TYPE|null) {
let config = configCover(opts==null ? ({} as XTOAST_TYPE) : (opts!))
xshowtoasts(UTSHarmony.getCurrentWindow()!.getUIContext(),config)
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIAppFonts</key>
<array>
<string>remixicon.ttf</string>
</array>
</dict>
</plist>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
</dict>
</plist>
@@ -0,0 +1,3 @@
{
"deploymentTarget": "12"
}
@@ -0,0 +1,243 @@
import { XTOAST_TYPE, XTOAST_TYPE_PRIVATE } from "../interface.uts"
import { defaultConfig, configCover } from "../libs/config.uts"
import { UIView, UIViewController, NSLayoutConstraint,UILayoutPriority, UITextView, UIFont, UILabel, NSTextAlignment, NSLineBreakMode , Distribution } from 'UIKit';
import { CGRect, CGPoint, CGFloat, CGSize, CFDictionary } from 'CoreFoundation';
import { CGSizeMake } from 'CoreGraphics';
import { Alignment } from 'SwiftUI';
import { UInt32, KeyPath } from 'Swift';
import { NSString } from 'Foundation';
import { CABasicAnimation, CAMediaTimingFunction, CAMediaTimingFunctionName } from 'QuartzCore';
import { LessThanOrEqualToComparator } from 'AppIntents';
let maskerDom : null | UIView = null;
let tid : number | null = 0
let globalOpts: XTOAST_TYPE | null = null;
export function hideToast() {
DispatchQueue.main.async(execute = () : void => {
if (maskerDom != null) {
maskerDom!.removeFromSuperview()
}
if (tid != null) {
clearTimeout(tid!)
}
if(globalOpts!=null){
let closeEvent = globalOpts?.close??null;
if(closeEvent!=null){
closeEvent!()
}
}
})
}
export function showToast(opts : XTOAST_TYPE | null) {
globalOpts = opts;
DispatchQueue.main.async(execute = () : void => {
if (tid != null) {
clearTimeout(tid!)
tid = null;
}
if (maskerDom != null) {
maskerDom!.removeFromSuperview()
}
let parent = UTSiOS.getCurrentViewController() as UIViewController;
let parentView = parent.view as UIView
let realopts = opts == null ? ({} as XTOAST_TYPE) : (opts!)
let config = configCover(realopts)
maskerDom = new UIView()
// 允许遮罩穿透.
maskerDom!.isUserInteractionEnabled = !config.maskDisableClik;
if (maskerDom == null) return;
let cg = parentView.frame as CGRect
maskerDom!.frame = cg
maskerDom!.backgroundColor = UTSiOS.colorWithString(config.maskBgColor)
let contetDiv = new UIView();
contetDiv.layer.cornerRadius = 16
contetDiv.layer.masksToBounds = true;
contetDiv.backgroundColor = UTSiOS.colorWithString(config.contentBgColor)
let textView = new UILabel()
textView.frame = new CGRect(x = 0, y = 0, width = config.iconSize.toInt(), height = config.iconSize.toInt())
textView.font = new UIFont(name = "remixicon", size = new CGFloat(config.iconSize))
textView.textColor = UTSiOS.colorWithString(config.iconColor)
textView.textAlignment = NSTextAlignment.center
if (config.iconCode != "") {
let codePoint = new UInt32(config.iconCode, radix = 16)
let stricon = new NSString(format = "%C", codePoint!)
textView.text = new String(stricon);
// 添加动画图标旋转
let spinAni = new CABasicAnimation(keyPath = "transform.scale")
spinAni.fromValue = 0.4
spinAni.toValue = 1
spinAni.duration = 0.5
textView.layer.add(spinAni, forKey = "scaleAnimation")
// setTimeout(function () {
// spinAni.fromValue = 1.2
// spinAni.toValue = 1
// spinAni.duration = 0.5
// textView.layer.add(spinAni, forKey = "scaleAnimation")
// }, 200);
}
// 添加内容缩放动画
let scaleAni = new CABasicAnimation(keyPath = "transform.scale")
scaleAni.fromValue = 0
scaleAni.toValue = 1
scaleAni.duration = 0.3
scaleAni.timingFunction = new CAMediaTimingFunction(name = CAMediaTimingFunctionName.easeInEaseOut)
contetDiv.layer.add(scaleAni, forKey = "scaleAnimation")
let opacityAni = new CABasicAnimation(keyPath = "opacity")
opacityAni.fromValue = 0
opacityAni.toValue = 1
opacityAni.duration = 0.3
opacityAni.timingFunction = new CAMediaTimingFunction(name = CAMediaTimingFunctionName.easeInEaseOut)
maskerDom!.layer.add(opacityAni, forKey = "opacityAnimation")
// 两个都不空
if (config.title != "" && config.iconCode != "") {
textView.translatesAutoresizingMaskIntoConstraints = false
let titleView = new UILabel()
titleView.text = new String(config.title);
titleView.textAlignment = NSTextAlignment.center
titleView.lineBreakMode = NSLineBreakMode.byWordWrapping
titleView.numberOfLines = 0
titleView.font = UIFont.systemFont(ofSize = new CGFloat(config.titleSize))
titleView.textColor = UTSiOS.colorWithString(config.titleColor)
titleView.translatesAutoresizingMaskIntoConstraints = false
let contetDivWrap = new UIStackView(arrangedSubviews = [textView,titleView])
contetDivWrap.axis = NSLayoutConstraint.Axis.vertical
contetDivWrap.distribution = UIStackView.Distribution.fill
contetDivWrap.alignment = UIStackView.Alignment.center
contetDivWrap.spacing = 5
contetDivWrap.translatesAutoresizingMaskIntoConstraints = false
contetDiv.addSubview(contetDivWrap)
NSLayoutConstraint.activate([
// 中心约束
// contetDivWrap.centerXAnchor.constraint(equalTo = contetDiv.centerXAnchor),
contetDivWrap.centerYAnchor.constraint(equalTo = contetDiv.centerYAnchor),
// 左侧约束
contetDivWrap.leadingAnchor.constraint(equalTo = contetDiv.safeAreaLayoutGuide.leadingAnchor, constant = 16),
// 右侧约束
contetDivWrap.trailingAnchor.constraint(equalTo = contetDiv.safeAreaLayoutGuide.trailingAnchor, constant = -16),
contetDivWrap.topAnchor.constraint(equalTo = contetDiv.safeAreaLayoutGuide.topAnchor, constant = 16),
// contetDivWrap.bottomAnchor.constraint(equalTo = contetDiv.safeAreaLayoutGuide.bottomAnchor, constant = -16),
])
// 图标空,标题不空
} else if (config.iconCode == "" && config.title != "") {
let titleView = new UILabel()
titleView.text = new String(config.title);
titleView.textAlignment = NSTextAlignment.center
titleView.lineBreakMode = NSLineBreakMode.byWordWrapping
titleView.numberOfLines = 0
titleView.font = UIFont.systemFont(ofSize = new CGFloat(config.titleSize))
titleView.textColor = UTSiOS.colorWithString(config.titleColor)
titleView.translatesAutoresizingMaskIntoConstraints = false
contetDiv.addSubview(titleView)
NSLayoutConstraint.activate([
// 顶部约束
titleView.topAnchor.constraint(equalTo = contetDiv.safeAreaLayoutGuide.topAnchor, constant = 16),
titleView.bottomAnchor.constraint(equalTo = contetDiv.safeAreaLayoutGuide.bottomAnchor, constant = -16),
// 左侧约束
titleView.leadingAnchor.constraint(equalTo = contetDiv.safeAreaLayoutGuide.leadingAnchor, constant = 16),
// 右侧约束
titleView.trailingAnchor.constraint(equalTo = contetDiv.safeAreaLayoutGuide.trailingAnchor, constant = -16)
])
// 图标不空,文字空。
} else {
textView.translatesAutoresizingMaskIntoConstraints = false
contetDiv.addSubview(textView)
NSLayoutConstraint.activate([
// 中心约束
textView.centerXAnchor.constraint(equalTo = contetDiv.centerXAnchor),
textView.centerYAnchor.constraint(equalTo = contetDiv.centerYAnchor),
])
}
contetDiv.translatesAutoresizingMaskIntoConstraints = false
maskerDom!.addSubview(contetDiv)
if ((config.title != '' && config.iconCode != '')) {
NSLayoutConstraint.activate([
// 中心约束
contetDiv.centerXAnchor.constraint(equalTo = maskerDom!.centerXAnchor),
contetDiv.centerYAnchor.constraint(equalTo = maskerDom!.centerYAnchor),
// 宽大100
contetDiv.widthAnchor.constraint(lessThanOrEqualToConstant = new CGFloat(Math.min(Number(cg.size.width)-80,320))),
// 最小100
contetDiv.widthAnchor.constraint(greaterThanOrEqualToConstant = new CGFloat(config.size!)),
// 高为至少100
contetDiv.heightAnchor.constraint(greaterThanOrEqualToConstant = new CGFloat(config.size!))
])
}else if ((config.title == '' && config.iconCode != '')) {
NSLayoutConstraint.activate([
// 中心约束
contetDiv.centerXAnchor.constraint(equalTo = maskerDom!.centerXAnchor),
contetDiv.centerYAnchor.constraint(equalTo = maskerDom!.centerYAnchor),
// 宽大100
contetDiv.widthAnchor.constraint(lessThanOrEqualToConstant = new CGFloat(Math.min(Number(cg.size.width)-80,320))),
// 最小100
contetDiv.widthAnchor.constraint(greaterThanOrEqualToConstant = new CGFloat(config.size!)),
// 高为至少100
contetDiv.heightAnchor.constraint(greaterThanOrEqualToConstant = new CGFloat(config.size!))
])
} else {
NSLayoutConstraint.activate([
// 中心约束
contetDiv.centerXAnchor.constraint(equalTo = maskerDom!.centerXAnchor),
contetDiv.centerYAnchor.constraint(equalTo = maskerDom!.centerYAnchor),
// 宽大100
contetDiv.widthAnchor.constraint(lessThanOrEqualToConstant = new CGFloat(Math.min(Number(cg.size.width)-80,320))),
// 最小100
contetDiv.widthAnchor.constraint(greaterThanOrEqualToConstant = new CGFloat(config.size!)),
// 高为至少100
contetDiv.heightAnchor.constraint(greaterThanOrEqualToConstant = new CGFloat(32 + config.titleSize))
])
}
parentView.addSubview(maskerDom!)
if (config.duration == 0) return;
tid = setTimeout(function () {
tid = null;
hideToast()
}, (config.duration + 200).toInt());
})
}
@@ -0,0 +1,44 @@
export type XTOAST_TYPE = {
iconColor?:string,
contentBgColor?:string,
maskBgColor?:string,
iconSize?:number,
/**
* 正常应该填写图标的16进制符号如:EEC4
* 本组件允许几个状态名称使用默认图标,为空是info图标。
* warn,error,success,info
*/
iconCode?:string,
/**
* 标题为空不显示。
*/
title:string,
titleSize?:number,
titleColor?:string,
/**
* 几秒后消失,0 表示永不消失
*/
duration?:number,
close?:()=>void,
/** 宽 */
size?:number,
/**
* 禁用遮罩穿透点击事件
* @default true
*/
maskDisableClik?:boolean
}
export type XTOAST_TYPE_PRIVATE = {
iconColor:string,
contentBgColor:string,
maskBgColor:string,
iconSize:number,
iconCode:string,
title:string,
titleSize:number,
titleColor:string,
duration:number,
close:()=>void,
size:number,
maskDisableClik:boolean
}
@@ -0,0 +1,58 @@
type RGBA = { r : number, g : number, b : number, a : number }
export function hexToRgb(sColors : string) : RGBA | null {
if (sColors == "") {
return null
}
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
} as RGBA
} 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
} as RGBA
}
if (p.length == 3) {
p.add(1)
}
return {
r: p[0],
g: p[1],
b: p[2],
a: arr.length==3?p[3]:parseFloat(arr[3])
} as RGBA
}
return null;
}
@@ -0,0 +1,62 @@
import { XTOAST_TYPE, XTOAST_TYPE_PRIVATE } from "../interface.uts"
export const defaultConfig = {
iconColor:'rgb(5,121,255)',
contentBgColor:'rgba(255,255,255,1)',
maskBgColor:'rgba(0,0,0,0.6)',
iconSize:52,
iconCode:'F449',
title:'',
titleSize:14,
titleColor:'rgb(5,121,255)',
duration:2000,
size:130,
close:()=>{},
maskDisableClik:true
} as XTOAST_TYPE_PRIVATE
export const defaultStatusIcon = new Map<string,string>([
['warn','ECA1'],
['error','EB97'],
['success','EB79'],
['info','F449'],
])
export function configCover(opts : XTOAST_TYPE) : XTOAST_TYPE_PRIVATE {
let iconColor = opts.iconColor != null ? (opts.iconColor!) : defaultConfig.iconColor;
let contentBgColor = opts.contentBgColor != null ? (opts.contentBgColor!) : defaultConfig.contentBgColor;
let maskBgColor = opts.maskBgColor != null ? (opts.maskBgColor!) : defaultConfig.maskBgColor;
let iconSize = opts.iconSize != null ? (opts.iconSize!) : defaultConfig.iconSize;
let iconCode:string = opts.iconCode != null ? (opts.iconCode!) : defaultConfig.iconCode;
if(defaultStatusIcon.has(iconCode)){
iconCode = defaultStatusIcon.get(iconCode)!
}
let title = opts.title != null ? (opts.title!) : defaultConfig.title;
let titleSize = opts.titleSize != null ? (opts.titleSize!) : defaultConfig.titleSize;
let titleColor = opts.titleColor != null ? (opts.titleColor!) : defaultConfig.titleColor;
let duration = opts.duration != null ? (opts.duration!) : defaultConfig.duration;
let close = opts.close != null ? (opts.close!) : defaultConfig.close;
let size = opts.size != null ? (opts.size!) : defaultConfig.size;
let maskDisableClik = true;
// #ifdef APP-ANDROID
maskDisableClik = opts.maskDisableClik==null?defaultConfig.maskDisableClik:(opts.maskDisableClik!);
// #endif
// #ifdef WEB||MP
maskDisableClik = opts.maskDisableClik==null?(defaultConfig.maskDisableClik):(opts.maskDisableClik);
// #endif
// #ifdef APP-IOS
maskDisableClik = opts.maskDisableClik==null?(!defaultConfig.maskDisableClik):(!opts.maskDisableClik);
// #endif
return {
iconColor,
contentBgColor,
maskBgColor,
iconSize,
iconCode,title,titleSize,titleColor,duration,close,size,maskDisableClik
} as XTOAST_TYPE_PRIVATE
}
@@ -0,0 +1,33 @@
import { configCover } from "../libs/config.uts"
export { XTOAST_TYPE, XTOAST_TYPE_PRIVATE } from "../interface.uts"
let globalOpts:any | null = null;
export const hideToast = () => {
uni.hideToast()
globalOpts?.close();
}
export const showToast = (opts : any) => {
hideToast()
const config = configCover(opts)
globalOpts = config;
let title = config.title || config.content;
if(title){
wx.showToast({
title: config.title || config.content,
icon: config.iconCode || 'none',
duration: config.duration,
success(){
globalOpts?.close();
}
})
}else{
wx.showToast({
icon: config.iconCode || 'success',
duration: config.duration,
success(){
globalOpts?.close();
}
})
}
}
+144
View File
@@ -0,0 +1,144 @@
export { XTOAST_TYPE, XTOAST_TYPE_PRIVATE } from "../interface.uts"
import { defaultConfig, configCover } from "../libs/config.uts"
import { LoadFontFace } from "./loadFont.uts"
let maskerDom : null | HTMLDivElement = null;
let tid:number|null = 0
let globalOpts:XTOAST_TYPE | null = null;
export const hideToast = () => {
if (maskerDom != null) {
maskerDom.remove()
}
if(tid!=null){
clearTimeout(tid!)
}
globalOpts?.close?.()
}
export const showToast = (opts : XTOAST_TYPE | null) => {
hideToast()
globalOpts = opts;
LoadFontFace()
if(tid!=null){
clearTimeout(tid!)
tid=null;
}
if (maskerDom != null) {
maskerDom.remove()
}
let realopts = opts == null ? ({} as XTOAST_TYPE) : (opts!)
let config = configCover(realopts)
maskerDom = document.createElement("div")
if (maskerDom == null) return;
maskerDom.style.setProperty("background-color", config.maskBgColor)
maskerDom.style.setProperty("position", "fixed")
maskerDom.style.setProperty("left", "0px")
maskerDom.style.setProperty("top", "0px")
maskerDom.style.setProperty("width", "100%")
maskerDom.style.setProperty("height", "100%")
maskerDom.style.display = "flex"
maskerDom.style.flexDirection = "row"
maskerDom.style.justifyContent = "center"
maskerDom.style.alignItems = "center"
maskerDom.style.zIndex = "10000"
maskerDom.style.pointerEvents = config.maskDisableClik?'auto':"none"
let contentWrapDiv = document.createElement("div")
contentWrapDiv.style.padding = "16px"
contentWrapDiv.style.minWidth = config.size+"px"
let maxwidth = window.innerWidth;
contentWrapDiv.style.maxWidth = Math.min((maxwidth-40),320)+'px'
// contentWrapDiv.style.height = "100px"
if((config.iconCode!=''&&config.title!='')||(config.iconCode!=''&&config.title=='')){
contentWrapDiv.style.minHeight = config.size+"px"
}
contentWrapDiv.style.backgroundColor = config.contentBgColor
contentWrapDiv.style.borderRadius = "16px"
contentWrapDiv.style.boxSizing = "border-box"
contentWrapDiv.style.display = "flex"
contentWrapDiv.style.flexDirection = "column"
contentWrapDiv.style.justifyContent = "center"
contentWrapDiv.style.alignItems = "center"
contentWrapDiv.className = 'xShowToasScaleAnimation'
let textDiv = document.createElement("span")
if(config.iconCode!=''){
textDiv.innerText = String.fromCharCode(parseInt(config.iconCode, 16));
textDiv.style.fontFamily = "remixicon"
textDiv.style.fontSize = config.iconSize + "px"
textDiv.style.color = config.iconColor
textDiv.className = 'xShowToasAnimation'
}
var anistr = `
.xShowToasAnimation{
animation-name: xUiToastIconAni;
animation-duration: 1s;
animation-timing-function: cubic-bezier(.42,.38,.15,.93);
}
.xShowToasScaleAnimation{
animation-name: xUiToastIconAniScale;
animation-duration: 0.3s;
animation-timing-function: cubic-bezier(.42,.38,.15,.93);
}
@keyframes xUiToastIconAni {
0% {
transform: scale(0);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
@keyframes xUiToastIconAniScale {
0% {
transform: scale(0);
opacity:0;
}
100% {
transform: scale(1);
opacity:1;
}
}
`
var styleDIv = document.getElementById('xUiToastIcon');
if (!styleDIv) {
let style = document.createElement("style");
style.id = 'xUiLoadWeingIcon'
document.head.appendChild(style)
styleDIv = style;
}
let sheet = styleDIv.sheet;
if (sheet.cssRules.length == 0) {
styleDIv.append(anistr)
}
contentWrapDiv.appendChild(textDiv)
if (config.title != '') {
let titleDiv = document.createElement("span")
titleDiv.innerText = config.title;
titleDiv.style.fontSize = config.titleSize + "px"
titleDiv.style.color = config.titleColor
contentWrapDiv.appendChild(titleDiv)
titleDiv.style.textAlign = "center"
titleDiv.style.wordBreak = "break-all"
}
maskerDom!.appendChild(contentWrapDiv)
window.document.body.appendChild(maskerDom!)
if(config.duration==0) return;
tid = setTimeout(function() {
tid = null;
hideToast()
}, (config.duration));
}
@@ -0,0 +1,13 @@
export function LoadFontFace(){
// 定义字体
// @ts-ignore
const font = new FontFace("remixicon", "url(/static/tmui4xLibs/static/remixicon.ttf)", {
style: "italic",
weight: "800",
stretch: "condensed",
});
// 把字体添加到 document.fontsFontFaceSet)中
document.fonts.add(font);
font.load();
}