This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
## 1.0.62025-08-17
* 更新支持鸿蒙原生 Next,采用自己编译的 Har 包来支持
## 1.0.52025-04-30
* ios,安卓,web内容现在可以渲染html内容,不要太复杂.安卓和web会拦截a标签,ios目前拦截不了会直接跳转外链.
## 1.0.42025-04-13
* 安卓端弹层修改布局,现在可以覆盖官方的Dialog-page页面了,并且可以接受安卓的返回键及侧滑关闭弹层了.并且会自动隐藏状态栏,并可以覆盖底部安全区域
## 1.0.32025-02-23
* 安卓添加了显示背景遮罩动画,及关闭遮罩动画
* ios添加了显示背景遮罩动画,及关闭遮罩动画
* 上述添加后,不管是显示和关闭都更加的自然
## 1.0.22025-02-13
* 兼容微信
## 1.0.12024-08-15
* 新增contentAlign,可以让内容顶左对齐,还是让内容左右上下居中对齐 .
## 1.0.02024-08-06
安卓,ios,web可用。
Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

+100
View File
@@ -0,0 +1,100 @@
{
"id": "x-modal-s",
"displayName": "TM-UI 4.0x全屏对话框,可覆盖任意界面任意位置调用",
"version": "1.0.6",
"description": "主要是用来代替uni.showModal",
"keywords": [
"uts,原生插件,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": "√"
}
}
}
}
}
}
+171
View File
@@ -0,0 +1,171 @@
# x-modal-s
### 特性说明
主要是用来代替uni.showModal的,相比优化有:
1. 高自动适应,因此可以添加超长的title文字。
2. 图标可以任意更新,大小,颜色等
3. 可以添加title标题,允许改变字号,颜色 及背景
4. 精致美观,可以定制界面达到与品牌相符的设计
5. 可以重复打开,或者连续打开不用担心前一个是否关闭,我内部会检测关闭上一个对话框。
**微信内使用官方的弹层,大多属性无效,只有同官方的属性会生效**
### 兼容性
| Harmony | IOS | Android | WEB | 小程序 |
| --- | --- | --- | --- | --- |
| 支持 | 支持 | 支持 | 支持 | 支持 |
### 开发文档
**浏览器端需要你把目录中的```remixicon.ttf```复制到static目录下**
调用:
```ts
import { showModal,X_MODAL_TYPE} from "@/uni_modules/x-modal-s"
// 显示
showModal({
title: "提醒框",
content: "这是一条测试消息对话框,默认是按钮分离哦。",
confirmText: "确定",
cancelText: "取消",
// 按钮分离模式
isSplitBtn: true,
// 点击遮罩是否关闭
clickMaskClose: true,
// 确认按钮回调
confirm: () => {
console.log("点击确认")
},
// 取消按钮回调
cancel: () => {
console.log("点击取消")
},
// 关闭回调
close: () => {
console.log("对话框关闭")
}
} as X_MODAL_TYPE)
```
### 参数配置
下面的图标iconCode,同组件库x-icon是相同的字体图标,请打开图标网站:[查看](https://remixicon.com/)
找到直接想要的图标后 复制 unicode ,不是名称。比如:我们只要取ED29
下面的参数图标iconCode就是ED29 **iconCode:'ED29'**
**X_MODAL_TYPE参数说明**
```ts
export type X_MODAL_TYPE = {
// 标题文字
title?: string,
// 内容文字
content?: string,
// 内容对齐方式:'center'(上下左右居中) 或 'left'(顶左对齐)
contentAlign?: string,
// 取消按钮文字
cancelText?: string,
// 取消按钮背景色
cancelBgColor?: string,
// 取消按钮文字颜色
cancelColor?: string,
// 确认按钮文字
confirmText?: string,
// 确认按钮背景色
confirmBgColor?: string,
// 确认按钮文字颜色
confirmColor?: string,
// 圆角大小
radius?: number,
// 确认按钮图标(Remix图标unicode)
confirmIcon?: string,
// 取消按钮图标(Remix图标unicode)
cancelIcon?: string,
// 标题文字颜色
titleColor?: string,
// 内容文字颜色
contentColor?: string,
// 按钮是否分离显示
isSplitBtn?: boolean,
// 对话框内容背景色
contentBgColor?: string,
// 遮罩背景色
maskBgColor?: string,
// ios不支持,会直接打开对应的链接。只有安卓,鸿蒙,web支持。
clickLink?:(href:string)=>void,
// 确认按钮回调
confirm?: () => void,
// 取消按钮回调
cancel?: () => void,
// 关闭回调
close?: () => void,
// 背景模糊效果(仅web端生效)
isBlurMask?: boolean,
// 对话框高度
height?: number,
// 对话框宽度
width?: number,
// 是否允许点击遮罩关闭
clickMaskClose?: boolean,
// 是否显示取消按钮
showCancel?: boolean
}
```
### 平台差异说明
1. Web端
- 支持背景模糊效果(isBlurMask)
- 需将remixicon.ttf字体文件复制到static目录
2. 微信小程序
- 使用官方原生showModal组件
- 仅支持基础属性(title,content,confirmText,cancelText等)
- 不支持自定义样式
3. Android/iOS
- 完整支持所有特性
- 支持自定义按钮图标
- 支持按钮分离模式
- 支持遮罩点击关闭
### 使用示例
1. 基础用法
```ts
showModal({
title: "提示",
content: "这是一个基础对话框"
} as X_MODAL_TYPE)
```
2. 自定义按钮
```ts
showModal({
title: "自定义按钮",
content: "自定义按钮文字和颜色",
confirmText: "知道了",
confirmColor: "#2979ff",
showCancel: false
} as X_MODAL_TYPE)
```
3. 带图标按钮
```ts
showModal({
title: "带图标",
content: "使用Remix图标库",
confirmIcon: "ED29",
cancelIcon: "EAD8"
} as X_MODAL_TYPE)
```
4. 居中内容
```ts
showModal({
title: "居中内容",
content: "内容上下左右居中对齐",
contentAlign: "center"
} as X_MODAL_TYPE)
```
@@ -0,0 +1,3 @@
{
"minSdkVersion": "21"
}
@@ -0,0 +1,566 @@
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 { X_MODAL_TYPE, X_MODAL_TYPE_PRIVATE, RGBA } 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 Html from 'android.text.Html';
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 ScrollView from 'android.widget.ScrollView';
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';
// #endif
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";
import LinkMovementMethod from 'android.text.method.LinkMovementMethod';
import SpannableString from "android.text.SpannableString";
import Spannable from "android.text.Spannable";
import URLSpan from 'android.text.style.URLSpan';
import ClickableSpan from 'android.text.style.ClickableSpan';
import TextPaint from 'android.text.TextPaint'
import WindowInsetsController from "android.view.WindowInsetsController";
import WindowInsets from "android.view.WindowInsets";
import Build from 'android.os.Build';
import LayerDrawable from "android.graphics.drawable.LayerDrawable";
let masker : null | RelativeLayout = null;
let contentDom : null | LinearLayout = null;
let maskDomId = 586
let tid = 0
let isClsoing = false
function px2dp(n : number) : number {
const mets = UTSAndroid.getAppContext()!.resources!.getDisplayMetrics()
return mets.density * n
}
function setSpin(textView : TextView) {
try {
textView.clearAnimation();
} catch (e) {
//TODO handle the exception
}
let rotationStart = 0
let rotation = 360
let rotationSFD = 0.5
let rotationAnimator = ObjectAnimator.ofFloat(textView, "rotation", rotationStart.toFloat(), rotation.toFloat());
rotationAnimator.setDuration(1000);
class LinearInterpolator implements TimeInterpolator {
override getInterpolation(input : Float) : Float {
return input;
}
}
rotationAnimator.setInterpolator(new LinearInterpolator());
rotationAnimator.setRepeatCount(ValueAnimator.INFINITE);
rotationAnimator.start();
}
let dialogModal : FullScreenDialogFragment | null = null;
class FullScreenDialogFragment extends Dialog {
private mContext : Context;
private opts : X_MODAL_TYPE
constructor(context : Context, optsOwer : X_MODAL_TYPE) {
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)
// 设置窗口属性
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)
}
_createModalView(this.getContext(), this.window!.decorView! as ViewGroup, this.opts)
}
override dismiss() {
super.dismiss()
contentDom = null;
isClsoing = false;
dialogModal = null;
}
}
export function close(call : () => void, isClose : boolean) {
UTSAndroid.getDispatcher("main").async(function (_) {
if (!isClose) return;
let mk = masker
if (mk != null) {
clearTimeout(tid)
try {
let s0 = (0).toFloat()
let s1 = (0).toFloat()
let maskAni = mk.animate() as ViewPropertyAnimator;
maskAni.alpha(s1)
.setDuration(300)
.setInterpolator(AccelerateDecelerateInterpolator())
.start()
if (contentDom != null) {
let ani = contentDom!.animate() as ViewPropertyAnimator;
ani.alpha(s0)
.scaleX(s1)
.scaleY(s1)
.setDuration(300)
.setInterpolator(AccelerateDecelerateInterpolator())
.start()
tid = setTimeout(function () {
isClsoing = false
dialogModal?.dismiss()
mk = null;
contentDom = null;
masker = null;
dialogModal = null;
}, 300);
} else {
isClsoing = false;
dialogModal?.dismiss()
mk = null;
contentDom = null;
masker = null;
dialogModal = null;
}
} catch (e) {
//TODO handle the exception
}
call()
}
}, null)
}
function rgbToColorNumber(rgba : RGBA) : Int {
return Color.argb((rgba.a * 255).toInt(), rgba!.r.toInt(), rgba!.g.toInt(), rgba!.b.toInt())
}
function _clickLinksRender(contextText : TextView, config : X_MODAL_TYPE_PRIVATE) {
let spanned = Html.fromHtml(config.content)
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
spanned = Html.fromHtml(config.content, Html.FROM_HTML_MODE_LEGACY)
}
const spannableString = new SpannableString(spanned);
function addLinksClick() {
// 获取所有的 URLSpan(对应 <a> 标签)
const urlSpans = spanned.getSpans(0, spanned.length, UTSAndroid.getJavaClass(URLSpan));
// 遍历每个 URLSpan 并用自定义的 ClickableSpan 替换
for (item in urlSpans) {
const urlSpan = item as URLSpan
const start = spanned.getSpanStart(urlSpan);
const end = spanned.getSpanEnd(urlSpan);
// 创建自定义的 ClickableSpan
class clickableSpan extends ClickableSpan {
constructor() {
super()
}
override onClick(wv : View) {
// 当链接被点击时触发
const url : string = urlSpan.getURL();
config.clickLink(url)
console.log(url)
}
override updateDrawState(ds : TextPaint) {
super.updateDrawState(ds);
ds.setColor(rgbToColorNumber(hexToRgb(config.linkFontColor)!)); // 设置链接颜色
ds.setUnderlineText(true); // 设置下划线
}
};
// 替换 URLSpan 为自定义的 ClickableSpan
spannableString.setSpan(new clickableSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// 移除原始的 URLSpan
spannableString.removeSpan(urlSpan);
}
}
addLinksClick()
contextText.setText(spannableString);
contextText.setMovementMethod(new LinkMovementMethod());
}
function _createModalView(context : Context, decorView : ViewGroup, opts : X_MODAL_TYPE) {
//
let config = configCover(opts)
let titleHeight = 60;
let footerHeight = 74
let contentHeight = config.height;
let width = config.width;
let height = config.height
if (isClsoing) return;
let maskBgColorRgb = rgbToColorNumber(hexToRgb(config.maskBgColor)!);
let cancelColorRgb = rgbToColorNumber(hexToRgb(config.cancelColor)!);
let cancelBgColorRgb = rgbToColorNumber(hexToRgb(config.cancelBgColor)!);
let confirmBgColorRgb = rgbToColorNumber(hexToRgb(config.confirmBgColor)!);
let confirmColorRgb = rgbToColorNumber(hexToRgb(config.confirmColor)!);
let contentBgColorRgb = rgbToColorNumber(hexToRgb(config.contentBgColor)!);
let contentColorColorRgb = rgbToColorNumber(hexToRgb(config.contentColor)!);
let titleColorColorRgb = rgbToColorNumber(hexToRgb(config.titleColor)!);
let maskerDom = new RelativeLayout(context)
let maskerDomLayrPrams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
maskerDom.setLayoutParams(maskerDomLayrPrams)
maskerDom.setBackgroundColor(maskBgColorRgb)
maskerDom.setGravity(Gravity.CENTER)
maskerDom.setId((maskDomId).toInt())
maskerDom.setAlpha((0).toFloat())
// 内容容器
let container = new LinearLayout(context);
contentDom = container
container.setOrientation(LinearLayout.VERTICAL)
container.setGravity(Gravity.CENTER)
// 设置圆角背景
let bg = new GradientDrawable()
bg.setColor(contentBgColorRgb)
bg.setCornerRadius((px2dp(16)).toFloat())
container.setClipToOutline(true)
container.setBackground(bg)
let wpx = px2dp(config.width).toInt();
container.setLayoutParams(new LinearLayout.LayoutParams(wpx, ViewGroup.LayoutParams.WRAP_CONTENT))
let defaultNumScale = (0).toFloat()
container.setAlpha(defaultNumScale)
container.setScaleX(defaultNumScale)
container.setScaleY(defaultNumScale)
let titleDiv = new TextView(context)
let titleDivheight = px2dp(titleHeight).toInt();
titleDiv.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, titleDivheight))
titleDiv.setGravity(Gravity.CENTER)
titleDiv.setTextSize((18.0).toFloat())
titleDiv.setTextColor(titleColorColorRgb)
titleDiv.setText(config.title)
// 内容
let content = new ScrollView(context)
let contentDivheight = px2dp(contentHeight).toInt();
content.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, contentDivheight))
let contextText = new TextView(context);
if (config.contentAlign == 'center') {
contextText.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
contextText.setGravity(Gravity.CENTER)
contextText.setMinHeight(contentDivheight)
} else {
contextText.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
}
contextText.setTextSize((15.0).toFloat())
contextText.setTextColor(contentColorColorRgb)
_clickLinksRender(contextText, config)
let contentPadding = px2dp(16).toInt()
contextText.setPadding(contentPadding, 0, contentPadding, contentPadding)
// 边线
const lineColor = rgbToColorNumber(hexToRgb(config.lineColor=='transparent'?'rgba(255,255,255,0)':config.lineColor)!)
let borderBottomView = new View(context);
borderBottomView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1))
borderBottomView.setBackgroundColor(lineColor)
// 竖向边线
let borderRightView = new View(context);
borderRightView.setLayoutParams(new LinearLayout.LayoutParams(1,ViewGroup.LayoutParams.MATCH_PARENT))
borderRightView.setBackgroundColor(lineColor)
// 底部容器
let footerDiv = new LinearLayout(context)
footerDiv.setOrientation(LinearLayout.HORIZONTAL)
footerDiv.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
let footerDivPadding = px2dp(16).toInt()
if (config.isSplitBtn) {
footerDiv.setPadding(footerDivPadding, 0, contentPadding, footerDivPadding)
} else {
footerDiv.setPadding(0, 0, 0, 0)
}
// 取消按钮
let btnheight = px2dp(50).toInt()
let cancelBtnBG = new GradientDrawable()
cancelBtnBG.setColor(cancelBgColorRgb)
if (config.isSplitBtn) {
cancelBtnBG.setCornerRadius((px2dp(50)).toFloat())
}
let confirmBtnBG = new GradientDrawable()
confirmBtnBG.setColor(confirmBgColorRgb)
if (config.isSplitBtn) {
confirmBtnBG.setCornerRadius((px2dp(50)).toFloat())
}
let cancelDiv = new TextView(context);
cancelDiv.setLayoutParams(new LinearLayout.LayoutParams(0, btnheight, (1).toFloat()));
cancelDiv.setTextSize((16.0).toFloat());
cancelDiv.setTextColor(cancelColorRgb);
cancelDiv.setGravity(Gravity.CENTER);
cancelDiv.setBackground(cancelBtnBG);
if (config.showCancel) {
if (config.cancelIcon != '') {
let assetManager = context!.getAssets();
let typeface = Typeface.createFromAsset(assetManager, "remixicon.ttf")
cancelDiv.setTypeface(typeface)
if (!TextUtils.isEmpty(config.cancelIcon)) {
let codePoint = Integer.parseInt(config.cancelIcon, 16);
let charArray = Character.toChars(codePoint);
let text = new String(charArray);
let str = config.cancelText != "" ? (" " + config.cancelText) : config.cancelText
cancelDiv.setText(text + str);
}
} else {
cancelDiv.setText(config.cancelText);
}
}
let confirmDiv = new TextView(context);
confirmDiv.setLayoutParams(new LinearLayout.LayoutParams(0, btnheight, (1).toFloat()));
confirmDiv.setTextSize((16.0).toFloat());
confirmDiv.setTextColor(confirmColorRgb);
confirmDiv.setGravity(Gravity.CENTER);
confirmDiv.setBackground(confirmBtnBG);
if (config.confirmIcon != '') {
let assetManager = context!.getAssets();
let typeface = Typeface.createFromAsset(assetManager, "remixicon.ttf")
confirmDiv.setTypeface(typeface)
if (!TextUtils.isEmpty(config.confirmIcon)) {
let codePoint = Integer.parseInt(config.confirmIcon, 16);
let charArray = Character.toChars(codePoint);
let text = new String(charArray);
let str = config.confirmText != "" ? (" " + config.confirmText) : config.confirmText
confirmDiv.setText(text + str);
}
} else {
confirmDiv.setText(config.confirmText);
}
let confirmDivPadding = px2dp(16).toInt()
let spaceDiv = new TextView(context);
spaceDiv.setLayoutParams(new LinearLayout.LayoutParams(confirmDivPadding, btnheight));
// 添加遮罩事件.
class MaskerDomClickListsner extends View.OnTouchListener {
firstX = 0
firstY = 0
type = ""
constructor(type : string) {
super();
this.type = type
}
override onTouch(view : View, event : MotionEvent) : Boolean {
let action = event.getAction()
let t = this;
if (action == MotionEvent.ACTION_DOWN) {
this.firstX = event.getX()
this.firstY = event.getY()
}
if (action == MotionEvent.ACTION_UP) {
// 检查是否是点击动作
if (event.getDownTime() - event.getEventTime() < 300) {
// event.getX() == this.firstX && event.getY() == this.firstY
// 下面是兼容三星S21-23机型
if (Math.abs(event.getX() - this.firstX) < 10 && Math.abs(event.getY() - this.firstY) < 10 && !isClsoing) {
isClsoing = true;
close(() => {
if (t.type == 'cancel') {
config.cancel()
config.close()
}
if (t.type == 'confirm') {
config.confirm()
config.close()
}
if (t.type == 'mask') {
config.close()
}
}, (t.type == 'mask' && config.clickMaskClose) || t.type != 'mask')
}
}
}
return true;
}
}
maskerDom.setOnTouchListener(new MaskerDomClickListsner('mask'))
// 内容容器事件
class containerDomClickListsner extends View.OnTouchListener {
constructor() {
super();
}
override onTouch(view : View, event : MotionEvent) : Boolean {
return true;
}
}
container.setOnTouchListener(new containerDomClickListsner())
// 取消事件
cancelDiv.setOnTouchListener(new MaskerDomClickListsner('cancel'))
// 确认按钮事件
confirmDiv.setOnTouchListener(new MaskerDomClickListsner('confirm'))
if (config.showCancel) {
footerDiv.addView(cancelDiv)
if (!config.isSplitBtn) {
footerDiv.addView(borderRightView)
}
}
if (config.isSplitBtn && config.showCancel) {
footerDiv.addView(spaceDiv)
}
footerDiv.addView(confirmDiv)
content.addView(contextText)
container.addView(titleDiv)
container.addView(content)
if (!config.isSplitBtn) {
container.addView(borderBottomView)
}
container.addView(footerDiv)
maskerDom.addView(container)
decorView.addView(maskerDom!)
try {
let s0 = (0).toFloat()
let s1 = (1).toFloat()
container.setAlpha(s0)
container.setScaleX(s0)
container.setScaleY(s0)
// 遮罩渐变
const maskAni = maskerDom.animate() as ViewPropertyAnimator;
maskAni.alpha(s1)
.setDuration(350)
.setInterpolator(AccelerateDecelerateInterpolator())
.start()
let ani = container.animate() as ViewPropertyAnimator;
ani.alpha(s1)
.scaleX(s1)
.scaleY(s1)
.setDuration(350)
.setInterpolator(AccelerateDecelerateInterpolator())
.start()
} catch (e) {
console.error(e)
//TODO handle the exception
}
masker = maskerDom
}
function _showModal_(opts : X_MODAL_TYPE) {
dialogModal = new FullScreenDialogFragment(UTSAndroid.getUniActivity()!, opts)
UTSAndroid.getDispatcher("main").async(function (_) {
dialogModal?.show()
}, null)
}
export function showModal(opts : X_MODAL_TYPE) {
dialogModal?.dismiss()
const pages = getCurrentPages();
if (pages.length > 0) {
const page = pages[pages.length - 1].vm!
const instance = page.$
if (page.$isReady) {
_showModal_(opts)
}
onUnload(() => {
close(() => { }, true)
}, instance)
} else {
_showModal_(opts)
}
}
@@ -0,0 +1,5 @@
{
"dependencies": {
"x_modal_s": "./lib/x_modal_s.har"
}
}
@@ -0,0 +1,14 @@
import { X_MODAL_TYPE, X_MODAL_TYPE_PRIVATE } from "../interface.uts"
import { defaultConfig, configCover } from "../libs/config.uts"
import { hexToRgb } from "../libs/color.uts"
import {xcloseModal,xshowModal} from "x_modal_s"
export const close = (call : () => void, isClose : boolean) => {
if (!isClose) return;
xcloseModal(UTSHarmony.getCurrentWindow()!.getUIContext())
}
export function showModal(opts : X_MODAL_TYPE) {
let config = configCover(opts)
xshowModal(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,501 @@
import { X_MODAL_TYPE, X_MODAL_TYPE_PRIVATE } from "../interface.uts"
import { defaultConfig, configCover } from "../libs/config.uts"
import { UIView, UIViewController, NSLayoutConstraint, UIScrollView, NSLineBreakMode, UIFont, UILabel, UITapGestureRecognizer, UITextView, UITextItemInteraction, UITextViewDelegate, UIScrollViewDelegate, UITextItem, UIAction, UIEvent, UIUserInterfaceStyle, UIDataDetectorTypes, UIColor } from 'UIKit';
import { CGRect, CGPoint, CGFloat, CGAffineTransform } from 'CoreFoundation';
import { Alignment , Color } from 'SwiftUI';
import { UInt32, KeyPath } from 'Swift';
import { NSString, Data, NSAttributedString, URL, AttributedString } from 'Foundation';
import { CABasicAnimation, CAAnimationDelegate, CAMediaTimingFillMode, CAAnimation, CAMediaTimingFunction, CAMediaTimingFunctionName, CALayer } from 'QuartzCore';
import { Selector, NSObject, NSObjectProtocol } from "ObjectiveC";
import { String } from "Swift";
type callTYPE = (str:string)=>void
let maskerDom : null | UIView = null;
let tid = 0
class AllModalObjectLisen {
private callback : () => void
constructor(callback : () => void) {
this.callback = callback;
super.init()
}
@objc cancelClick() {
// console.log("取消被点击")
this.callback()
}
@objc confirmClick() {
// console.log("确认被点击")
this.callback()
}
@objc maskClick() {
// console.log("遮罩被点击")
this.callback()
}
@objc containerClick() {
// console.log("内容区域点击了")
}
}
class AnimaionStop implements CAAnimationDelegate {
animationDidStop(_ : CAAnimation, @argumentLabel("finished") flag : boolean) {
console.log("动画关闭2", flag)
closeModal()
}
}
class contentTextDeleage18 implements UITextViewDelegate {
private callback :callTYPE
constructor(callback : callTYPE) {
this.callback = callback;
super.init()
}
textView(tv : UITextView, url : URL, characterRange : NSRange, interaction : UITextItemInteraction) : boolean {
// this.optsRealConfig.clickLink(url.absoluteString)
console.log('点击')
return false;
}
}
// new AllModalObjectLisen()
let cancellListen : AllModalObjectLisen | null = null;
let confirmlListen : AllModalObjectLisen | null = null;
let masklListen : AllModalObjectLisen | null = null;
let containerlListen : AllModalObjectLisen | null = null;
let closeinganimationListen : AnimaionStop | null = null;
let linkDelegage : UITextViewDelegate | null = null;
function closeModal() {
DispatchQueue.main.async(execute = () : void => {
if (maskerDom != null) {
maskerDom!.removeFromSuperview()
maskerDom = null;
masklListen = null;
cancellListen = null;
confirmlListen = null;
containerlListen = null;
}
})
}
export const close = (call : () => void, isClose : boolean) => {
if (!isClose) return;
closeModal()
call()
}
export function showModal(opts : X_MODAL_TYPE) {
DispatchQueue.main.async(execute = () : void => {
if (maskerDom != null) {
clearTimeout(tid)
maskerDom!.removeFromSuperview()
}
let parent = UTSiOS.getCurrentViewController() as UIViewController;
let parentView = parent.view as UIView
let config = configCover(opts)
let titleHeight = 50;
let footerHeight = 74
let contentHeight = config.height - titleHeight;
let width = config.width;
let height = config.height + titleHeight
maskerDom = new UIView()
if (maskerDom == null) return;
let cg = parentView.frame as CGRect
maskerDom!.frame = cg
maskerDom!.backgroundColor = UTSiOS.colorWithString(config.maskBgColor)
// 内容区域外框。
let container = new UIView();
container.layer.cornerRadius = 16
container.layer.masksToBounds = true;
container.backgroundColor = UTSiOS.colorWithString(config.contentBgColor)
// 内容区域外框
let scaleAni = new CABasicAnimation(keyPath = "transform.scale")
scaleAni.fromValue = 0
scaleAni.toValue = 1
scaleAni.duration = 0.3
scaleAni.autoreverses = true
scaleAni.timingFunction = new CAMediaTimingFunction(name = CAMediaTimingFunctionName.easeInEaseOut)
let opacityAni = new CABasicAnimation(keyPath = "opacity")
opacityAni.fromValue = 0
opacityAni.toValue = 1
opacityAni.duration = 0.3
opacityAni.timingFunction = new CAMediaTimingFunction(name = CAMediaTimingFunctionName.easeInEaseOut)
container.layer.add(scaleAni, forKey = "scaleAnimation")
// UIView.animate(withDuration = 0.3, animations = ()=>{
// container.transform = new CGAffineTransform(scaleX= 1.5, y= 1.5)
// })
container.layer.add(opacityAni, forKey = "opacityAnimation")
maskerDom!.layer.add(opacityAni, forKey = "opacityAnimation")
container.translatesAutoresizingMaskIntoConstraints = false
maskerDom!.addSubview(container)
NSLayoutConstraint.activate([
// 中心约束
container.centerXAnchor.constraint(equalTo = maskerDom!.centerXAnchor),
container.centerYAnchor.constraint(equalTo = maskerDom!.centerYAnchor),
// 宽约束100
container.widthAnchor.constraint(equalToConstant = new CGFloat(width!)),
// 高为至少100
container.heightAnchor.constraint(greaterThanOrEqualToConstant = new CGFloat(height))
])
// 添加标题
let titleDiv = new UILabel();
titleDiv.textColor = UTSiOS.colorWithString(config.titleColor)
titleDiv.text = new String(config.title)
titleDiv.font = UIFont.systemFont(ofSize = new CGFloat(18))
titleDiv.frame = new CGRect(x = 0, y = 0, width = (width!).toInt(), height = titleHeight.toInt())
titleDiv.textAlignment = NSTextAlignment.center
container.addSubview(titleDiv)
// 添加scroll
let content = new UIScrollView();
content.frame = new CGRect(x = 0, y = titleHeight.toInt(), width = (width!).toInt(), height = config.height!.toInt())
content.isScrollEnabled = true;
content.bounces = true
container.addSubview(content)
let contextText = new UITextView();
contextText.isEditable = false;
contextText.isSelectable = true;
// contextText.isUserInteractionEnabled = true;
// contextText.delaysContentTouches = true;
// contextText.dataDetectorTypes = [UIDataDetectorTypes.link,UIDataDetectorTypes.phoneNumber]
if (UTSiOS.available("iOS 13.0, *")) {
contextText.overrideUserInterfaceStyle = UIUserInterfaceStyle.light
}
// config.content
const htmlContentData : Data = new String(config.content).data(using = String.Encoding.utf8)!
const opts = new Map<NSAttributedString.DocumentReadingOptionKey,any>([
[NSAttributedString.DocumentReadingOptionKey.documentType, NSAttributedString.DocumentType.html],
[NSAttributedString.DocumentReadingOptionKey.characterEncoding, String.Encoding.utf8.rawValue]
])
let attributedString = UTSiOS.try(new NSAttributedString(data = htmlContentData, options = opts, documentAttributes = null), "?")
let mutableAttrStr = NSMutableAttributedString(attributedString = attributedString!)
mutableAttrStr.addAttribute(NSAttributedString.Key.foregroundColor, value=config.contentColor, range= NSRange(location= 0, length= attributedString!.length))
contextText.attributedText = mutableAttrStr
contextText.textColor = UTSiOS.colorWithString(config.contentColor)
contextText.backgroundColor = UTSiOS.colorWithString(config.contentBgColor)
linkDelegage = new contentTextDeleage18(config.clickLink as callTYPE) as UITextViewDelegate
contextText.delegate = linkDelegage!
// contextText.text = new String(config.content)
contextText.font = UIFont.systemFont(ofSize = new CGFloat(15))
// contextText.lineBreakMode = NSLineBreakMode.byWordWrapping
// contextText.backgroundColor = UTSiOS.colorWithString("red")
// contextText.numberOfLines = 0
// 根据内容调整UILabel的大小
contextText.sizeToFit()
contextText.translatesAutoresizingMaskIntoConstraints = false
content.addSubview(contextText)
if (config.contentAlign == 'center') {
contextText.textAlignment = NSTextAlignment.center
NSLayoutConstraint.activate([
// 顶部约束
contextText.topAnchor.constraint(equalTo = content.topAnchor, constant = 0),
contextText.bottomAnchor.constraint(equalTo = content.bottomAnchor, constant = 0),
// contextText.widthAnchor.constraint(equalToConstant = new CGFloat((width!-32))),
// 左侧约束
contextText.leadingAnchor.constraint(equalTo = content.leadingAnchor, constant = 16),
// 右侧约束
contextText.trailingAnchor.constraint(equalTo = content.safeAreaLayoutGuide.trailingAnchor, constant = -16),
contextText.centerXAnchor.constraint(equalTo = content.centerXAnchor),
contextText.centerYAnchor.constraint(equalTo = content.centerYAnchor),
])
} else {
NSLayoutConstraint.activate([
// 顶部约束
contextText.topAnchor.constraint(equalTo = content.topAnchor, constant = 0),
contextText.bottomAnchor.constraint(equalTo = content.bottomAnchor, constant = 0),
// contextText.widthAnchor.constraint(equalToConstant = new CGFloat((width!-32))),
// 左侧约束
contextText.leadingAnchor.constraint(equalTo = content.leadingAnchor, constant = 16),
// 右侧约束
contextText.trailingAnchor.constraint(equalTo = content.safeAreaLayoutGuide.trailingAnchor, constant = -16)
])
}
if (!config.isSplitBtn&&config.lineColor!=''&&config.lineColor!='transparent'){
// 添加边线顶。
let footerDivTopBorder = new UIView()
footerDivTopBorder.translatesAutoresizingMaskIntoConstraints = false
footerDivTopBorder.backgroundColor = UTSiOS.colorWithString(config.lineColor)
container.addSubview(footerDivTopBorder)
NSLayoutConstraint.activate([
footerDivTopBorder.heightAnchor.constraint(equalToConstant = 0.5),
footerDivTopBorder.widthAnchor.constraint(equalTo = container.safeAreaLayoutGuide.widthAnchor),
// 顶部约束
footerDivTopBorder.topAnchor.constraint(equalTo = content.bottomAnchor, constant = 15),
])
}
// 底部容器
let footerDiv = new UIView()
footerDiv.translatesAutoresizingMaskIntoConstraints = false
// footerDiv.backgroundColor = UTSiOS.colorWithString("#333333")
container.addSubview(footerDiv)
if (config.isSplitBtn) {
NSLayoutConstraint.activate([
footerDiv.heightAnchor.constraint(equalToConstant = 50),
// footerDiv.widthAnchor.constraint(equalTo = container.safeAreaLayoutGuide.widthAnchor),
// 顶部约束
footerDiv.topAnchor.constraint(equalTo = content.bottomAnchor, constant = 16),
footerDiv.bottomAnchor.constraint(equalTo = container.bottomAnchor, constant = -16),
// 左侧约束
footerDiv.leadingAnchor.constraint(equalTo = container.leadingAnchor, constant = 16),
// 右侧约束
footerDiv.trailingAnchor.constraint(equalTo = container.trailingAnchor, constant = -16)
])
} else {
NSLayoutConstraint.activate([
footerDiv.heightAnchor.constraint(equalToConstant = 50),
// 顶部约束
footerDiv.topAnchor.constraint(equalTo = content.bottomAnchor, constant = 16),
footerDiv.bottomAnchor.constraint(equalTo = container.bottomAnchor, constant = 0),
// 左侧约束
footerDiv.leadingAnchor.constraint(equalTo = container.leadingAnchor, constant = 0),
// 右侧约束
footerDiv.trailingAnchor.constraint(equalTo = container.trailingAnchor, constant = 0)
])
}
// 添加取消按钮。
let cancelDiv = new UILabel();
cancelDiv.font = new UIFont(name = "remixicon", size = new CGFloat(15))
let cancelIcontext = ""
if (config.cancelIcon != '') {
let codePoint = new UInt32(config.cancelIcon, radix = 16)
let stricon = new NSString(format = "%C", codePoint!)
cancelIcontext = new String(stricon);
}
cancelDiv.textColor = UTSiOS.colorWithString(config.cancelColor)
cancelDiv.text = new String(cancelIcontext != '' ? (cancelIcontext + ' ' + config.cancelText) : config.cancelText)
// cancelDiv.font = UIFont.systemFont(ofSize = new CGFloat(15))
cancelDiv.textAlignment = NSTextAlignment.center
cancelDiv.backgroundColor = UTSiOS.colorWithString(config.cancelBgColor)
if (config.isSplitBtn) {
cancelDiv.layer.cornerRadius = 25
cancelDiv.layer.masksToBounds = true;
}
// 添加确认按钮。
let confirmDiv = new UILabel();
confirmDiv.font = new UIFont(name = "remixicon", size = new CGFloat(15))
let confirmIcontext = ""
if (config.confirmIcon != '') {
let codePoint = new UInt32(config.confirmIcon, radix = 16)
let stricon = new NSString(format = "%C", codePoint!)
confirmIcontext = new String(stricon);
}
confirmDiv.textColor = UTSiOS.colorWithString(config.confirmColor)
confirmDiv.text = new String(confirmIcontext != '' ? (confirmIcontext + ' ' + config.confirmText) : config.confirmText)
confirmDiv.textAlignment = NSTextAlignment.center
confirmDiv.backgroundColor = UTSiOS.colorWithString(config.confirmBgColor)
if (config.isSplitBtn) {
confirmDiv.layer.cornerRadius = 25
confirmDiv.layer.masksToBounds = true;
}
cancelDiv.translatesAutoresizingMaskIntoConstraints = false
confirmDiv.translatesAutoresizingMaskIntoConstraints = false
if (config.isSplitBtn) {
if (config.showCancel) {
footerDiv.addSubview(cancelDiv)
footerDiv.addSubview(confirmDiv)
NSLayoutConstraint.activate([
// 顶部约束
cancelDiv.topAnchor.constraint(equalTo = footerDiv.topAnchor, constant = 0),
cancelDiv.bottomAnchor.constraint(equalTo = footerDiv.bottomAnchor, constant = 0),
// 左侧约束
cancelDiv.leadingAnchor.constraint(equalTo = content.safeAreaLayoutGuide.leadingAnchor, constant = 16),
cancelDiv.trailingAnchor.constraint(equalTo = confirmDiv.safeAreaLayoutGuide.trailingAnchor, constant = 8),
cancelDiv.widthAnchor.constraint(equalTo = footerDiv.safeAreaLayoutGuide.widthAnchor, multiplier = 0.5),
// 顶部约束
confirmDiv.topAnchor.constraint(equalTo = footerDiv.topAnchor, constant = 0),
confirmDiv.bottomAnchor.constraint(equalTo = footerDiv.bottomAnchor, constant = 0),
// 左侧约束
confirmDiv.leadingAnchor.constraint(equalTo = cancelDiv.safeAreaLayoutGuide.trailingAnchor, constant = 8),
confirmDiv.trailingAnchor.constraint(equalTo = footerDiv.safeAreaLayoutGuide.trailingAnchor, constant = 0),
// confirmDiv.widthAnchor.constraint(equalTo = footerDiv.safeAreaLayoutGuide.widthAnchor,multiplier=0.5),
// confirmDiv.heightAnchor.constraint(equalTo = footerDiv.safeAreaLayoutGuide.heightAnchor)
])
} else {
footerDiv.addSubview(confirmDiv)
NSLayoutConstraint.activate([
// 顶部约束
confirmDiv.topAnchor.constraint(equalTo = footerDiv.topAnchor, constant = 0),
confirmDiv.bottomAnchor.constraint(equalTo = footerDiv.bottomAnchor, constant = 0),
// 左侧约束
confirmDiv.leadingAnchor.constraint(equalTo = footerDiv.safeAreaLayoutGuide.leadingAnchor, constant = 0),
confirmDiv.trailingAnchor.constraint(equalTo = footerDiv.safeAreaLayoutGuide.trailingAnchor, constant = 0),
])
}
} else {
if (config.showCancel) {
footerDiv.addSubview(cancelDiv)
footerDiv.addSubview(confirmDiv)
NSLayoutConstraint.activate([
// 顶部约束
cancelDiv.topAnchor.constraint(equalTo = footerDiv.topAnchor, constant = 0),
cancelDiv.bottomAnchor.constraint(equalTo = footerDiv.bottomAnchor, constant = 0),
// 左侧约束
cancelDiv.leadingAnchor.constraint(equalTo = content.safeAreaLayoutGuide.leadingAnchor, constant = 0),
cancelDiv.trailingAnchor.constraint(equalTo = confirmDiv.safeAreaLayoutGuide.trailingAnchor, constant = 0),
cancelDiv.widthAnchor.constraint(equalTo = footerDiv.safeAreaLayoutGuide.widthAnchor, multiplier = 0.5),
// 顶部约束
confirmDiv.topAnchor.constraint(equalTo = footerDiv.topAnchor, constant = 0),
confirmDiv.bottomAnchor.constraint(equalTo = footerDiv.bottomAnchor, constant = 0),
// 左侧约束
confirmDiv.leadingAnchor.constraint(equalTo = cancelDiv.safeAreaLayoutGuide.trailingAnchor, constant = 0),
confirmDiv.trailingAnchor.constraint(equalTo = footerDiv.safeAreaLayoutGuide.trailingAnchor, constant = 0),
// confirmDiv.widthAnchor.constraint(equalTo = footerDiv.safeAreaLayoutGuide.widthAnchor,multiplier=0.5),
// confirmDiv.heightAnchor.constraint(equalTo = footerDiv.safeAreaLayoutGuide.heightAnchor)
])
} else {
footerDiv.addSubview(confirmDiv)
NSLayoutConstraint.activate([
// 顶部约束
confirmDiv.topAnchor.constraint(equalTo = footerDiv.topAnchor, constant = 0),
confirmDiv.bottomAnchor.constraint(equalTo = footerDiv.bottomAnchor, constant = 0),
// 左侧约束
confirmDiv.leadingAnchor.constraint(equalTo = footerDiv.safeAreaLayoutGuide.leadingAnchor, constant = 0),
confirmDiv.trailingAnchor.constraint(equalTo = footerDiv.safeAreaLayoutGuide.trailingAnchor, constant = 0),
])
}
// 添加按钮的右边线。
if (!config.isSplitBtn&&config.lineColor!=''&&config.lineColor!='transparent'&&config.showCancel){
// 添加边线顶。
let cancelRightBorder = new UIView()
cancelRightBorder.translatesAutoresizingMaskIntoConstraints = false
cancelRightBorder.backgroundColor = UTSiOS.colorWithString(config.lineColor)
footerDiv.addSubview(cancelRightBorder)
NSLayoutConstraint.activate([
cancelRightBorder.heightAnchor.constraint(equalTo = cancelDiv.heightAnchor),
cancelRightBorder.widthAnchor.constraint(equalToConstant = 0.5),
// 顶部约束
cancelDiv.topAnchor.constraint(equalTo = content.bottomAnchor, constant = 16),
cancelRightBorder.leadingAnchor.constraint(equalTo = confirmDiv.safeAreaLayoutGuide.leadingAnchor, constant = 1),
])
}
}
cancelDiv.isUserInteractionEnabled = true;
confirmDiv.isUserInteractionEnabled = true;
container.isUserInteractionEnabled = true;
maskerDom!.isUserInteractionEnabled = true;
function closeanimation() {
if (maskerDom == null) return;
let scaleAni = new CABasicAnimation(keyPath = "transform.scale")
scaleAni.fromValue = 1
scaleAni.toValue = 0
scaleAni.duration = 0.3
scaleAni.fillMode = CAMediaTimingFillMode.forwards
scaleAni.timingFunction = new CAMediaTimingFunction(name = CAMediaTimingFunctionName.easeInEaseOut)
scaleAni.isRemovedOnCompletion = false
let opactiyAni = new CABasicAnimation(keyPath = "opacity")
opactiyAni.fromValue = 1
opactiyAni.toValue = 0
opactiyAni.duration = 0.3
opactiyAni.timingFunction = new CAMediaTimingFunction(name = CAMediaTimingFunctionName.easeInEaseOut);
opactiyAni.isRemovedOnCompletion = false
opacityAni.fillMode = CAMediaTimingFillMode.forwards
container.layer.add(scaleAni, forKey = "scaleAnimation")
container.layer.add(opactiyAni, forKey = "opacityAnimation")
let opactiyAniBymsk = new CABasicAnimation(keyPath = "opacity")
opactiyAniBymsk.fromValue = 1
opactiyAniBymsk.toValue = 0
opactiyAniBymsk.duration = 0.3
opactiyAniBymsk.timingFunction = new CAMediaTimingFunction(name = CAMediaTimingFunctionName.easeInEaseOut);
opactiyAniBymsk.isRemovedOnCompletion = false
opactiyAniBymsk.fillMode = CAMediaTimingFillMode.forwards
maskerDom!.layer.add(opactiyAniBymsk, forKey = "opacityAnimation")
class AniDelete implements NSObject, CAAnimationDelegate {
animationDidStop(_ : CAAnimation, @argumentLabel("finished") flag : boolean) {
console.log("动画关闭2", flag)
closeModal()
}
}
scaleAni.delegate = new AniDelete()
// closeinganimationListen = new AnimaionStop()
tid = setTimeout(function () {
container.layer.opacity = 0
closeModal()
}, 280);
}
function callbackFun(type : string) {
if (type == 'container') return;
if (type == 'cancel') {
config.cancel()
config.close()
closeanimation()
}
if (type == 'confirm') {
config.confirm()
config.close()
closeanimation()
}
if (type == 'mask' && config.clickMaskClose) {
config.close()
closeanimation()
}
}
cancellListen = new AllModalObjectLisen(() => { callbackFun('cancel') });
confirmlListen = new AllModalObjectLisen(() => { callbackFun('confirm') });
masklListen = new AllModalObjectLisen(() => { callbackFun('mask') });
containerlListen = new AllModalObjectLisen(() => { callbackFun('container') });
let tapGestureCancel = new UITapGestureRecognizer()
tapGestureCancel.addTarget(cancellListen, action = new Selector("cancelClick"))
let tapGestureConfirm = new UITapGestureRecognizer()
tapGestureConfirm.addTarget(confirmlListen, action = new Selector("confirmClick"))
let tapGestureMask = new UITapGestureRecognizer()
tapGestureMask.addTarget(masklListen, action = new Selector("maskClick"))
let tapGestureContainer = new UITapGestureRecognizer()
tapGestureContainer.addTarget(containerlListen, action = new Selector("containerClick"))
cancelDiv.addGestureRecognizer(tapGestureCancel)
confirmDiv.addGestureRecognizer(tapGestureConfirm)
maskerDom!.addGestureRecognizer(tapGestureMask)
container.addGestureRecognizer(tapGestureContainer)
parentView.addSubview(maskerDom!)
})
}
@@ -0,0 +1,91 @@
export type RGBA = { r : number, g : number, b : number, a : number }
export type X_MODAL_TYPE = {
title?:string,
content?:string,
/**
* 内容居中方式:
* left:就是正常的顶左对齐.
* center:就是左右上下居中.
*/
contentAlign?:string,
cancelText?:string,
cancelBgColor?:string,
cancelColor?:string,
confirmText?:string,
confirmBgColor?:string,
confirmColor?:string,
linkFontColor?:string,
radius?:number,
confirmIcon?:string,
cancelIcon?:string,
titleColor?:string,
contentColor?:string,
/**
* 按钮是否分离
* @default true
*/
isSplitBtn?:boolean,
/**
* 当isSplitBtn为false时,如果按钮都为白时需要添加上边线和按钮中间分割线时可以启用分割线颜色
* 默认为透明。
*/
lineColor?:string,
// 框体内容背景
contentBgColor?:string,
// 遮罩背景
maskBgColor?:string,
confirm?:()=>void,
cancel?:()=>void,
close?:()=>void,
clickLink?:(href:string)=>void,
/**
* 背景模糊,目前仅web生效
* @default true
*/
isBlurMask?:boolean,
height?:number,
width?:number,
/**
* 是否允许点击遮罩关闭
* @default true
*/
clickMaskClose?:boolean,
/**
* 是否显示取消按钮
* @default true
*/
showCancel?:boolean
}
export type X_MODAL_TYPE_PRIVATE = {
title:string,
content:string,
contentAlign:string,
cancelText:string,
cancelBgColor:string,
cancelColor:string,
titleColor:string,
contentColor:string,
confirmText:string,
confirmBgColor:string,
confirmColor:string,
linkFontColor:string,
lineColor:string,
radius:number,
confirmIcon:string,
cancelIcon:string,
isSplitBtn:boolean,
// 框体内容背景
contentBgColor:string,
// 遮罩背景
maskBgColor:string,
confirm:()=>void,
cancel:()=>void,
close:()=>void,
clickLink:(href:string)=>void,
isBlurMask:boolean,
height:number,
width:number,
clickMaskClose:boolean,
showCancel:boolean
}
@@ -0,0 +1,59 @@
import {RGBA} from "../interface.uts"
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,109 @@
import {X_MODAL_TYPE,X_MODAL_TYPE_PRIVATE} from "../interface.uts"
export const defaultConfig = {
title:"提醒",
content:"",
contentAlign:'center',
cancelText:"取消",
cancelColor:"#333",
cancelBgColor:"#f5f5f5",
confirmText:"确认",
confirmBgColor:"#0579FF",
confirmColor:"#FFF",
titleColor:'#000000',
contentColor:"#333333",
lineColor:"transparent",
linkFontColor:"rgb(5, 121, 255)",
radius:16,
confirmIcon:"",
cancelIcon:"",
isSplitBtn:true,
contentBgColor:"#fff",
maskBgColor:"rgba(0,0,0,0.6)",
confirm:()=>{},
cancel:()=>{},
close:()=>{},
clickLink:(href:string)=>{},
isBlurMask:true,
height:80,
width:320,
clickMaskClose:true,
showCancel:true
} as X_MODAL_TYPE_PRIVATE
export const configCover = (opts : X_MODAL_TYPE) : X_MODAL_TYPE_PRIVATE => {
let title = opts.title==null?defaultConfig.title:(opts.title!);
let content = opts.content==null?defaultConfig.content:(opts.content!);
let cancelText = opts.cancelText==null?defaultConfig.cancelText:(opts.cancelText!);
let cancelBgColor = opts.cancelBgColor==null?defaultConfig.cancelBgColor:(opts.cancelBgColor!);
let cancelColor = opts.cancelColor==null?defaultConfig.cancelColor:(opts.cancelColor!);
let confirmText = opts.confirmText==null?defaultConfig.confirmText:(opts.confirmText!);
let confirmBgColor = opts.confirmBgColor==null?defaultConfig.confirmBgColor:(opts.confirmBgColor!);
let confirmColor = opts.confirmColor==null?defaultConfig.confirmColor:(opts.confirmColor!);
let radius = opts.radius==null?defaultConfig.radius:(opts.radius!);
let confirmIcon = opts.confirmIcon==null?defaultConfig.confirmIcon:(opts.confirmIcon!);
let cancelIcon = opts.cancelIcon==null?defaultConfig.cancelIcon:(opts.cancelIcon!);
let contentBgColor = opts.contentBgColor==null?defaultConfig.contentBgColor:(opts.contentBgColor!);
let maskBgColor = opts.maskBgColor==null?defaultConfig.maskBgColor:(opts.maskBgColor!);
let confirm = opts.confirm==null?defaultConfig.confirm:(opts.confirm!);
let cancel = opts.cancel==null?defaultConfig.cancel:(opts.cancel!);
let close = opts.close==null?defaultConfig.close:(opts.close!);
let height = opts.height==null?defaultConfig.height:(opts.height!);
let width = opts.width==null?defaultConfig.width:(opts.width!);
let titleColor = opts.titleColor==null?defaultConfig.titleColor:(opts.titleColor!);
let contentColor = opts.contentColor==null?defaultConfig.contentColor:(opts.contentColor!);
let contentAlign = opts.contentAlign==null?defaultConfig.contentAlign:(opts.contentAlign!);
let clickLink = opts.clickLink==null?defaultConfig.clickLink:(opts.clickLink!);
let linkFontColor = opts.linkFontColor==null?defaultConfig.linkFontColor:(opts.linkFontColor!);
let lineColor = opts.lineColor==null?defaultConfig.lineColor:(opts.lineColor!);
// #ifdef APP-ANDROID || APP-HARMONY
let clickMaskClose = opts.clickMaskClose==null?defaultConfig.clickMaskClose:(opts.clickMaskClose!);
let showCancel = opts.showCancel==null?defaultConfig.showCancel:(opts.showCancel!);
let isBlurMask = opts.isBlurMask==null?defaultConfig.isBlurMask:(opts.isBlurMask!);
let isSplitBtn = opts.isSplitBtn==null?defaultConfig.isSplitBtn:(opts.isSplitBtn!);
// #endif
// #ifdef WEB || APP-IOS || MP
let clickMaskClose = opts.clickMaskClose==null?defaultConfig.clickMaskClose:(opts.clickMaskClose);
let showCancel = opts.showCancel==null?defaultConfig.showCancel:(opts.showCancel);
let isBlurMask = opts.isBlurMask==null?defaultConfig.isBlurMask:(opts.isBlurMask);
let isSplitBtn = opts.isSplitBtn==null?defaultConfig.isSplitBtn:(opts.isSplitBtn);
// #endif
return {
title,
content,
cancelText,
cancelBgColor,
cancelColor,
confirmText,
confirmBgColor,
confirmColor,
linkFontColor,
lineColor,
radius,
confirmIcon,
cancelIcon,
isSplitBtn,
// 框体内容背景
contentBgColor,
// 遮罩背景
maskBgColor,
confirm,
cancel,
close,
clickLink,
isBlurMask,
height,
width,
clickMaskClose,
showCancel,
titleColor,
contentColor,
contentAlign
} as X_MODAL_TYPE_PRIVATE
}
@@ -0,0 +1,25 @@
export {X_MODAL_TYPE,X_MODAL_TYPE_PRIVATE} from "../interface.uts"
import {configCover} from "../libs/config.uts"
export const close = (call : () => void, isClose : boolean)=>{
if (!isClose) return;
call()
console.error("微信无法关闭showModal")
}
export const showModal = (opts : any)=>{
let config = configCover(opts)
uni.showModal({
title:config.title,
content:config.content,
confirmText:config.confirmText,
cancelText:config.cancelText,
success(e){
if(e.confirm){
config.confirm()
}else{
config.cancel()
}
},
})
}
+220
View File
@@ -0,0 +1,220 @@
export {X_MODAL_TYPE,X_MODAL_TYPE_PRIVATE} from "../interface.uts"
import {defaultConfig,configCover} from "../libs/config.uts"
import { LoadFontFace } from "./loadFont.uts"
let maskerDom : null | HTMLDivElement = null;
let tid = 0
export const close = (call : () => void, isClose : boolean)=>{
if (!isClose) return;
if (maskerDom != null) {
clearTimeout(tid)
maskerDom.remove()
}
call()
}
// @ts-ignore
export const showModal = (opts : X_MODAL_TYPE) => {
LoadFontFace()
if (maskerDom != null) {
clearTimeout(tid)
maskerDom.remove()
}
let config = configCover(opts)
let titleHeight = 60;
let footerHeight = 74
let contentHeight = config.height;
let width = config.width;
let height = config.height
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 = "301"
// 内容层
let container = document.createElement("div")
// container.style.padding = "30px"
container.style.width = width+"px"
// container.style.height = height+"px"
container.style.backgroundColor = config.contentBgColor
container.style.borderRadius = config.radius+"px"
container.style.boxSizing = "border-box"
container.style.display = "flex"
container.style.overflow = "hidden"
container.style.flexDirection = "column"
container.style.transition = 'all 0.3s cubic-bezier(.42,.38,.15,.93)'
container.style.scale = "0"
container.style.opacity = "0"
// 标题
let titleDiv = document.createElement("div");
titleDiv.style.textAlign = "center";
titleDiv.style.height = titleHeight + "px";
titleDiv.style.display = "flex"
titleDiv.style.flexDirection = "row"
titleDiv.style.alignItems = "center"
titleDiv.style.justifyContent = "center"
titleDiv.style.fontSize = "18px"
titleDiv.style.fontWeight = "bold"
titleDiv.style.color = config.titleColor
titleDiv.innerText = config.title
// 内容
let content = document.createElement("div");
content.style.height = contentHeight + "px";
content.style.display = "flex"
content.style.fontSize = "16px"
content.style.color = config.contentColor
content.innerHTML = config.content
content.style.overflowY = 'auto';
content.style.boxSizing = "border-box"
content.style.padding = "0 16px"
if(config.contentAlign=='center'){
content.style.flexDirection = 'column'
content.style.justifyContent = 'center'
content.style.alignItems = 'center'
content.style.textAlign = 'center'
}
content.addEventListener('click',(e:PointerEvent)=>{
e.preventDefault()
e.stopPropagation()
// @ts-ignore
if(e.target?.href){
// @ts-ignore
let href = e.target?.href??""
if(href){
config.clickLink(href)
}
}
})
// 底部容器
let footerDiv = document.createElement("div");
footerDiv.style.display = "flex"
footerDiv.style.flexDirection = "row"
footerDiv.style.justifyContent = "center"
footerDiv.style.alignItems = "center"
// 取消按钮
let cancelDiv = document.createElement("div");
cancelDiv.style.flex = "1"
cancelDiv.style.height = "55px"
cancelDiv.style.backgroundColor = config.cancelBgColor
cancelDiv.style.color = config.cancelColor
cancelDiv.style.fontSize = "16px"
cancelDiv.style.display = "flex"
cancelDiv.style.flexDirection = "row"
cancelDiv.style.justifyContent = "center"
cancelDiv.style.alignItems = "center"
cancelDiv.style.fontFamily = "remixicon"
if(config.cancelIcon!=""){
let iconstr = String.fromCharCode(parseInt(config.cancelIcon, 16));
let str = config.cancelText==""?iconstr:(iconstr+" "+config.cancelText)
cancelDiv.innerHTML = str
}else{
cancelDiv.innerText = config.cancelText
}
let confirmDiv = document.createElement("div");
confirmDiv.style.flex = "1"
confirmDiv.style.height = "55px"
confirmDiv.style.backgroundColor = config.confirmBgColor
confirmDiv.style.color = config.confirmColor
confirmDiv.style.fontSize = "16px"
confirmDiv.style.display = "flex"
confirmDiv.style.flexDirection = "row"
confirmDiv.style.justifyContent = "center"
confirmDiv.style.alignItems = "center"
confirmDiv.style.fontFamily = "remixicon"
if(config.confirmIcon!=""){
let iconstr = String.fromCharCode(parseInt(config.confirmIcon, 16));
let str = config.confirmText==""?iconstr:(iconstr+" "+config.confirmText)
confirmDiv.innerHTML = str
}else{
confirmDiv.innerText = config.confirmText
}
if(config.isSplitBtn){
footerDiv.style.padding = "16px"
cancelDiv.style.marginRight = "16px"
confirmDiv.style.borderRadius = "55px"
cancelDiv.style.borderRadius = "55px"
}else{
content.style.paddingBottom = "16px"
}
if(!config.isSplitBtn&&config.showCancel){
footerDiv.style.borderTop = `0.5px solid ${config.lineColor}`
cancelDiv.style.borderRight = `0.5px solid ${config.lineColor}`
}
if(config.isBlurMask){
maskerDom.style.backdropFilter = "blur(6px)"
}
function close(){
try{
container.style.scale = "0"
container.style.opacity = "0"
tid = setTimeout(function() {
maskerDom?.remove()
}, 200);
}catch(e){
//TODO handle the exception
}
}
cancelDiv.addEventListener("click",function(){
close()
config.close()
config.cancel()
})
confirmDiv.addEventListener("click",function(){
close()
config.close()
config.confirm()
})
container.addEventListener("click",function(evt:PointerEvent){
evt.preventDefault()
evt.stopPropagation()
})
maskerDom.addEventListener("click",function(){
if(config.clickMaskClose){
close()
config.close()
}
})
if(config.showCancel){
footerDiv.appendChild(cancelDiv)
}
footerDiv.appendChild(confirmDiv)
container.appendChild(titleDiv)
container.appendChild(content)
container.appendChild(footerDiv)
maskerDom!.appendChild(container)
window.document.body.appendChild(maskerDom!)
try{
setTimeout(function() {
container.style.scale = "1"
container.style.opacity = "1"
}, 10);
}catch(e){
//TODO handle the exception
}
}
@@ -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();
}