feat:转账插件
This commit is contained in:
@@ -3,27 +3,95 @@
|
||||
"displayName": "HashMall 前台客服",
|
||||
"version": "0.1.0",
|
||||
"description": "内嵌于 HashMall App、H5 和微信小程序的客服聊天插件",
|
||||
"keywords": ["客服", "聊天", "WebSocket", "uni-app"],
|
||||
"keywords": [
|
||||
"客服",
|
||||
"聊天",
|
||||
"WebSocket",
|
||||
"uni-app"
|
||||
],
|
||||
"engines": {
|
||||
"HBuilderX": "^4.0.0"
|
||||
"HBuilderX": "^4.0.0",
|
||||
"uni-app": "^3.1.0",
|
||||
"uni-app-x": "^3.1.0"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": ["前端组件", "通用组件"],
|
||||
"sale": { "regular": { "price": "0.00" }, "sourcecode": { "price": "0.00" } },
|
||||
"contact": { "qq": "" },
|
||||
"declaration": { "ads": "无", "data": "插件使用商城现有用户登录态和客服消息接口", "permissions": "相册、相机(仅在用户主动发送图片时)" }
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "插件使用商城现有用户登录态和客服消息接口",
|
||||
"permissions": "相册、相机(仅在用户主动发送图片时)"
|
||||
},
|
||||
"type": "component-vue",
|
||||
"darkmode": "-",
|
||||
"i18n": "-",
|
||||
"widescreen": "-"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": { "tcb": "u", "aliyun": "u" },
|
||||
"cloud": {
|
||||
"tcb": "-",
|
||||
"aliyun": "-"
|
||||
},
|
||||
"client": {
|
||||
"App": { "app-vue": "y", "app-nvue": "u" },
|
||||
"H5-mobile": { "Safari": "y", "Android Browser": "y", "微信浏览器(Android)": "y", "QQ浏览器(Android)": "y" },
|
||||
"H5-pc": { "Chrome": "y", "IE": "u", "Edge": "y", "Firefox": "y", "Safari": "y" },
|
||||
"小程序": { "微信": "y", "阿里": "u", "百度": "u", "字节跳动": "u", "QQ": "u", "京东": "u" },
|
||||
"Vue": { "vue2": "u", "vue3": "y" }
|
||||
"uni-app": {
|
||||
"vue": {
|
||||
"vue2": "-",
|
||||
"vue3": "-"
|
||||
},
|
||||
"web": {
|
||||
"safari": "-",
|
||||
"chrome": "-"
|
||||
},
|
||||
"app": {
|
||||
"vue": "-",
|
||||
"nvue": "-",
|
||||
"android": "-",
|
||||
"ios": "-",
|
||||
"harmony": "-"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": "-",
|
||||
"alipay": "-",
|
||||
"toutiao": "-",
|
||||
"baidu": "-",
|
||||
"kuaishou": "-",
|
||||
"jd": "-",
|
||||
"harmony": "-",
|
||||
"qq": "-",
|
||||
"lark": "-",
|
||||
"xhs": "-"
|
||||
},
|
||||
"quickapp": {
|
||||
"huawei": "-",
|
||||
"union": "-"
|
||||
}
|
||||
},
|
||||
"uni-app-x": {
|
||||
"web": {
|
||||
"safari": "-",
|
||||
"chrome": "-"
|
||||
},
|
||||
"app": {
|
||||
"android": "-",
|
||||
"ios": "-",
|
||||
"harmony": "-"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": "-"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,11 @@
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.0"
|
||||
"HBuilderX": "^3.1.0",
|
||||
"uni-app": "^3.1.0",
|
||||
"uni-app-x": "^3.1.0"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
],
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
@@ -31,48 +29,67 @@
|
||||
"data": "",
|
||||
"permissions": ""
|
||||
},
|
||||
"npmurl": ""
|
||||
"npmurl": "",
|
||||
"type": "component-vue",
|
||||
"darkmode": "-",
|
||||
"i18n": "-",
|
||||
"widescreen": "-"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "u",
|
||||
"aliyun": "u"
|
||||
"tcb": "-",
|
||||
"aliyun": "-"
|
||||
},
|
||||
"client": {
|
||||
"Vue": {
|
||||
"vue2": "u",
|
||||
"vue3": "u"
|
||||
"uni-app": {
|
||||
"vue": {
|
||||
"vue2": "-",
|
||||
"vue3": "-"
|
||||
},
|
||||
"App": {
|
||||
"app-vue": "u",
|
||||
"app-nvue": "u"
|
||||
"web": {
|
||||
"safari": "-",
|
||||
"chrome": "-"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "u",
|
||||
"Android Browser": "u",
|
||||
"微信浏览器(Android)": "u",
|
||||
"QQ浏览器(Android)": "u"
|
||||
"app": {
|
||||
"vue": "-",
|
||||
"nvue": "-",
|
||||
"android": "-",
|
||||
"ios": "-",
|
||||
"harmony": "-"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "u",
|
||||
"IE": "u",
|
||||
"Edge": "u",
|
||||
"Firefox": "u",
|
||||
"Safari": "u"
|
||||
"mp": {
|
||||
"weixin": "-",
|
||||
"alipay": "-",
|
||||
"toutiao": "-",
|
||||
"baidu": "-",
|
||||
"kuaishou": "-",
|
||||
"jd": "-",
|
||||
"harmony": "-",
|
||||
"qq": "-",
|
||||
"lark": "-",
|
||||
"xhs": "-"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "u",
|
||||
"阿里": "u",
|
||||
"百度": "u",
|
||||
"字节跳动": "u",
|
||||
"QQ": "u"
|
||||
"quickapp": {
|
||||
"huawei": "-",
|
||||
"union": "-"
|
||||
}
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
"uni-app-x": {
|
||||
"web": {
|
||||
"safari": "-",
|
||||
"chrome": "-"
|
||||
},
|
||||
"app": {
|
||||
"android": "-",
|
||||
"ios": "-",
|
||||
"harmony": "-"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": "-"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
## 1.0.3(2024-10-18)
|
||||
修复4.25版引起的插件回调只触发一次的问题。
|
||||
## 1.0.2(2024-09-05)
|
||||
修复uni.chooseImage或者其他部分情况下弹窗不显示的bug。
|
||||
## 1.0.1(2024-05-30)
|
||||
修复云打包可能报错的bug
|
||||
## 1.0.0(2024-03-09)
|
||||
支持全局监听权限申请。当申请权限时,会在页面顶部显示申请权限的目的。
|
||||
@@ -1,115 +0,0 @@
|
||||
{
|
||||
"id": "uni-registerRequestPermissionTips",
|
||||
"displayName": "uni-registerRequestPermissionTips",
|
||||
"version": "1.0.3",
|
||||
"description": "支持android平台全局监听权限的申请。当申请权限时,会在页面顶部显示申请权限的目的。主要解决上架华为应用市场审核要求:APP在调用终端权限时,应同步告知用户申请该权限的目的。",
|
||||
"keywords": [
|
||||
"权限",
|
||||
"权限申请",
|
||||
"上架",
|
||||
"华为"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^4.0"
|
||||
},
|
||||
"dcloudext": {
|
||||
"type": "uts",
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": ""
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [
|
||||
],
|
||||
"uni-ext-api": {
|
||||
"uni": {
|
||||
"registerRequestPermissionTipsListener": {
|
||||
"name": "registerRequestPermissionTipsListener",
|
||||
"app": {
|
||||
"js": false,
|
||||
"kotlin": true,
|
||||
"swift": false
|
||||
}
|
||||
},
|
||||
"unregisterRequestPermissionTipsListener": {
|
||||
"name": "unregisterRequestPermissionTipsListener",
|
||||
"app": {
|
||||
"js": false,
|
||||
"kotlin": true,
|
||||
"swift": false
|
||||
}
|
||||
},
|
||||
"setRequestPermissionTips": {
|
||||
"name": "setRequestPermissionTips",
|
||||
"app": {
|
||||
"js": false,
|
||||
"kotlin": true,
|
||||
"swift": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y",
|
||||
"alipay": "y"
|
||||
},
|
||||
"client": {
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
},
|
||||
"App": {
|
||||
"app-android": "y",
|
||||
"app-ios": "n",
|
||||
"app-harmony": "u"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "n",
|
||||
"Android Browser": "n",
|
||||
"微信浏览器(Android)": "n",
|
||||
"QQ浏览器(Android)": "n"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "n",
|
||||
"IE": "n",
|
||||
"Edge": "n",
|
||||
"Firefox": "n",
|
||||
"Safari": "n"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "n",
|
||||
"阿里": "n",
|
||||
"百度": "n",
|
||||
"字节跳动": "n",
|
||||
"QQ": "n",
|
||||
"钉钉": "n",
|
||||
"快手": "n",
|
||||
"飞书": "n",
|
||||
"京东": "n"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "n",
|
||||
"联盟": "n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
## registerRequestPermissionTipsListener(listener?)
|
||||
注册权限监听事件
|
||||
## unregisterRequestPermissionTipsListener(listener?)
|
||||
取消注册权限监听事件
|
||||
|
||||
## RequestPermissionTipsListener的属性值
|
||||
|名称 |类型 |描述 |必填 |
|
||||
|:-- |:-- |:-- |:-- |
|
||||
|onRequest |(permissions:Array<string>)=>void |申请系统权限回调,permissions为触发权限申请的所有权限 |否 |
|
||||
|onConfirm |(permissions:Array<string>)=>void |弹出系统权限授权框回调,permissions为触发弹出权限授权框的所有权限 |否 |
|
||||
|onComplete |(permissions:UTSJSONObject)=>void |权限申请完成回调,permissions包括权限及权限的状态。`grant`为权限已获取,`denied`为权限已拒绝 |否 |
|
||||
|
||||
## setRequestPermissionTips(UTSJSONObject)
|
||||
设置权限监听的说明。支持针对权限设置具体的说明。
|
||||
|
||||
参考:`{"android.permission.CAMERA":"<p>相机权限申请说明</p>"}`
|
||||
|
||||
安卓权限列表可参考[谷歌官方文档](https://developer.android.com/reference/android/Manifest.permission)。
|
||||
|
||||
权限申请说明基于原生TextView实现,可以实现加载html内容,支持的标签及属性可参考:
|
||||
```
|
||||
<b> 或 <strong>:加粗文本。
|
||||
<i> 或 <em>:斜体文本。
|
||||
<u>:下划线文本。
|
||||
<sup>:上标文本。
|
||||
<sub>:下标文本。
|
||||
<tt>:等宽字体文本。
|
||||
<big>:放大字体。
|
||||
<small>:缩小字体。
|
||||
<strike> 或 <s> 或 <del>:带有删除线的文本。
|
||||
<p>:段落。
|
||||
<div>:块级容器。
|
||||
<h1>,<h2>,<h3>,<h4>,<h5>,<h6>:区域标题元素。
|
||||
<ul>, <ol>, <li>:无序列表和有序列表。
|
||||
<br>:换行。
|
||||
<font color="..."> 和 <font size="...">:设置文本颜色和大小。
|
||||
```
|
||||
|
||||
## 示例
|
||||
|
||||
```
|
||||
<script>
|
||||
import {
|
||||
registerRequestPermissionTipsListener,
|
||||
unregisterRequestPermissionTipsListener,
|
||||
setRequestPermissionTips
|
||||
} from "@/uni_modules/uni-registerRequestPermissionTips"
|
||||
var PermissionTips = {
|
||||
"android.permission.CAMERA": "<h4 style=\"font-size:40px;\">正在读取通讯录权限</h4><font color=#cccccc>通讯录权限不会获取任何信息,请注意</font>",
|
||||
"android.permission.READ_PHONE_STATE": "<h4 style=\"font-size:40px;\">正在读取网络状态权限</h4><font color=#cccccc>通讯录权限不会获取任何信息,请注意通讯录权限不会获取任何信息,请注意通讯录权限不会获取任何信息,请注意</font>"
|
||||
}
|
||||
export default {
|
||||
onLaunch: function() {
|
||||
var brand = uni.getSystemInfoSync().deviceBrand
|
||||
setRequestPermissionTips(PermissionTips)
|
||||
registerRequestPermissionTipsListener({
|
||||
onRequest: (e) => {
|
||||
console.log(e)
|
||||
},
|
||||
onConfirm: (e) => {
|
||||
console.log(e)
|
||||
},
|
||||
onComplete: (e) => {
|
||||
// 华为手机在权限禁止之后,再次申请权限不会出现权限申请框。此时应该引导用户去系统设置开启此权限,不应该频繁申请。
|
||||
if (brand.toLowerCase() == "huawei") {
|
||||
var tips = {}
|
||||
var hasDeniedPermission = false
|
||||
for (var k in PermissionTips) {
|
||||
if (e[k] != "denied") {
|
||||
tips[k] = PermissionTips[k]
|
||||
} else {
|
||||
hasDeniedPermission = true
|
||||
}
|
||||
}
|
||||
setRequestPermissionTips(tips) // 更新弹框提醒,防止华为手机不出现权限申请框时权限提醒框闪烁的情况
|
||||
if (hasDeniedPermission)
|
||||
uni.showModal({
|
||||
content: "权限已经被拒绝,请前往设置中开启"
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onShow: function() {
|
||||
console.log('App Show')
|
||||
},
|
||||
onHide: function() {
|
||||
console.log('App Hide')
|
||||
},
|
||||
onExit: function() {
|
||||
unregisterRequestPermissionTipsListener(null)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="io.dcloud.uts.permissionrequest">
|
||||
</manifest>
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"minSdkVersion": "21"
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
import { UnregisterRequestPermissionTipsListener, RegisterRequestPermissionTipsListener, RequestPermissionTipsListener, SetRequestPermissionTips } from "../interface";
|
||||
import RelativeLayout from 'android.widget.RelativeLayout';
|
||||
import LinearLayout from 'android.widget.LinearLayout';
|
||||
import Color from 'android.graphics.Color';
|
||||
import TextView from 'android.widget.TextView';
|
||||
import ViewGroup from 'android.view.ViewGroup';
|
||||
import Activity from 'android.app.Activity';
|
||||
import HashMap from 'java.util.HashMap';
|
||||
import AnimationUtils from 'android.view.animation.AnimationUtils';
|
||||
import R from 'io.dcloud.uts.permissionrequest.R'
|
||||
import Html from 'android.text.Html';
|
||||
import View from 'android.view.View';
|
||||
import Runnable from "java.lang.Runnable"
|
||||
|
||||
let PermissionTipsView : View | null = null
|
||||
let permissionTips : HashMap<String, String> = new HashMap<String, String>()
|
||||
var permissionListener : RequestPermissionListener | null = null
|
||||
var listener : RequestPermissionTipsListener | null = null
|
||||
|
||||
@UTSJS.keepAlive
|
||||
export function unregisterRequestPermissionTipsListener(e : RequestPermissionTipsListener | null) {
|
||||
listener = null;
|
||||
if (permissionListener != null) {
|
||||
permissionListener!.stop()
|
||||
permissionListener = null
|
||||
}
|
||||
if (PermissionTipsView != null) {
|
||||
if (PermissionTipsView!.getParent() != null) {
|
||||
PermissionTipsView!.setAnimation(null);
|
||||
((PermissionTipsView!.getParent()) as ViewGroup).removeView(PermissionTipsView)
|
||||
}
|
||||
PermissionTipsView = null
|
||||
}
|
||||
}
|
||||
|
||||
@UTSJS.keepAlive
|
||||
export function registerRequestPermissionTipsListener(l : RequestPermissionTipsListener | null) {
|
||||
listener = l
|
||||
if (permissionListener == null) {
|
||||
permissionListener = uni.createRequestPermissionListener()
|
||||
permissionListener!.onRequest((permissions : Array<string>) => {
|
||||
if (listener != null)
|
||||
listener!.onRequest?.invoke(permissions)
|
||||
})
|
||||
permissionListener!.onConfirm((permissions : Array<string>) => {
|
||||
UTSAndroid.getUniActivity()!.runOnUiThread(new ConfirmRunnable(permissions))
|
||||
})
|
||||
permissionListener!.onComplete((permissions : Array<string>) => {
|
||||
UTSAndroid.getUniActivity()!.runOnUiThread(new CompleteRunnable(permissions))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class ConfirmRunnable implements Runnable {
|
||||
permissions : Array<string>
|
||||
constructor(permissions : Array<string>) {
|
||||
this.permissions = permissions
|
||||
}
|
||||
override run() {
|
||||
let activity = UTSAndroid.getUniActivity()!
|
||||
if (PermissionTipsView != null && PermissionTipsView!.getParent() != null) {
|
||||
PermissionTipsView!.setAnimation(null);
|
||||
((PermissionTipsView!.getParent()) as ViewGroup).removeView(PermissionTipsView)
|
||||
}
|
||||
if (this.permissions.length > 0) {
|
||||
try {
|
||||
PermissionTipsView = createPermissionWindow(activity, this.permissions);
|
||||
if (PermissionTipsView != null) {
|
||||
(activity.findViewById(android.R.id.content) as ViewGroup).addView(PermissionTipsView!)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
if (listener != null)
|
||||
listener!.onConfirm?.invoke(this.permissions)
|
||||
}
|
||||
}
|
||||
|
||||
class CompleteRunnable implements Runnable {
|
||||
permissions : Array<string>
|
||||
constructor(permissions : Array<string>) {
|
||||
this.permissions = permissions
|
||||
}
|
||||
override run() {
|
||||
let activity = UTSAndroid.getUniActivity()!
|
||||
if (PermissionTipsView != null) {
|
||||
PermissionTipsView!.setAnimation(AnimationUtils.loadAnimation(activity, R.anim.popupwindow_exit));
|
||||
((PermissionTipsView!.getParent()) as ViewGroup).removeView(PermissionTipsView!)
|
||||
PermissionTipsView = null
|
||||
}
|
||||
if (listener != null) {
|
||||
var permissionStatus = {}
|
||||
for (var p in this.permissions) {
|
||||
permissionStatus[p] = UTSAndroid.checkSystemPermissionGranted(UTSAndroid.getUniActivity()!, [p]) ? "grant" : "denied"
|
||||
}
|
||||
listener!.onComplete?.invoke(permissionStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const setRequestPermissionTips : SetRequestPermissionTips = (tips : UTSJSONObject) => {
|
||||
permissionTips.clear()
|
||||
for (var k in tips) {
|
||||
permissionTips.put(k, tips[k] != null ? tips[k].toString() : "")
|
||||
}
|
||||
}
|
||||
|
||||
function createPermissionWindow(activity : Activity, permissions : Array<string>) : ViewGroup | null {
|
||||
let rootView = new RelativeLayout(activity);
|
||||
rootView.setBackgroundColor(Color.TRANSPARENT);
|
||||
let backgroundView = new LinearLayout(activity);
|
||||
backgroundView.setPadding(30, 0, 30, 30);
|
||||
backgroundView.setOrientation(1)
|
||||
backgroundView.setBackgroundResource(R.drawable.dcloud_permission_background);
|
||||
let permissionTipsList : Array<string> = new Array<string>()
|
||||
for (var p in permissions) {
|
||||
if (permissionTips.containsKey(p) && permissionTipsList.indexOf(permissionTips.get(p)) == -1) {
|
||||
permissionTipsList.push(permissionTips.get(p)!)
|
||||
}
|
||||
}
|
||||
for (var p in permissionTipsList) {
|
||||
let text = new TextView(activity);
|
||||
text.setText(Html.fromHtml(p, Html.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING))
|
||||
text.setPadding(0, 30, 0, 0)
|
||||
text.setTextSize((5 * getScale()).toFloat())
|
||||
text.setTextColor(Color.BLACK)
|
||||
backgroundView.addView(text)
|
||||
}
|
||||
if (backgroundView.getChildCount() == 0) {
|
||||
return null;
|
||||
}
|
||||
let rll = new RelativeLayout.LayoutParams(-1, -2)
|
||||
rll.topMargin = (UTSAndroid.getStatusBarHeight() * getScale()).toInt();
|
||||
rll.leftMargin = 30;
|
||||
rll.rightMargin = 30;
|
||||
rll.bottomMargin = 30;
|
||||
rootView.addView(backgroundView, rll)
|
||||
rootView.setAnimation(AnimationUtils.loadAnimation(activity, R.anim.popupwindow_enter));
|
||||
return rootView;
|
||||
}
|
||||
|
||||
function getScale() : Float {
|
||||
if (UTSAndroid.getUniActivity() != null) {
|
||||
return UTSAndroid.getUniActivity()!.resources.displayMetrics.scaledDensity
|
||||
}
|
||||
return (0 as number).toFloat();
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="200"
|
||||
android:fromYDelta="-100%"
|
||||
android:toYDelta="0" >
|
||||
</translate>
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="200"
|
||||
android:fromYDelta="0"
|
||||
android:toYDelta="-100%" />
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<corners android:radius="8dp" />
|
||||
<!-- <stroke android:color="@color/black" android:width="2dp"/>-->
|
||||
<solid android:color="#ffffff"/>
|
||||
</shape>
|
||||
@@ -1,19 +0,0 @@
|
||||
export type RequestPermissionTipsListener = {
|
||||
onRequest ?: ((permissions : Array<string>) => void) | null,
|
||||
onConfirm ?: ((permission : Array<string>) => void) | null,
|
||||
onComplete ?: ((permissions : UTSJSONObject) => void) | null
|
||||
}
|
||||
|
||||
|
||||
export type RegisterRequestPermissionTipsListener = (listener : RequestPermissionTipsListener | null) => void
|
||||
export type UnregisterRequestPermissionTipsListener = (listener : RequestPermissionTipsListener | null) => void
|
||||
export type SetRequestPermissionTips = (tips : UTSJSONObject) => void
|
||||
|
||||
export interface Uni {
|
||||
|
||||
registerRequestPermissionTipsListener : RegisterRequestPermissionTipsListener,
|
||||
|
||||
unregisterRequestPermissionTipsListener : UnregisterRequestPermissionTipsListener
|
||||
|
||||
setRequestPermissionTips : SetRequestPermissionTips
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
## 1.1.1(2026-07-11)
|
||||
修复 uni-requestMerchantTransfer 商家转账回调解析细节问题
|
||||
## 1.1.0(2026-07-10)
|
||||
新增 harmony 平台支持
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
{
|
||||
"id": "uni-requestMerchantTransfer",
|
||||
"displayName": "uni-requestMerchantTransfer",
|
||||
"version": "1.1.1",
|
||||
"description": "商家转账用户确认模式下,拉起页面请求用户确认收款Transfer",
|
||||
"keywords": [
|
||||
"确认收款",
|
||||
"免确认收款授权"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.6.8",
|
||||
"uni-app": "^4.61",
|
||||
"uni-app-x": "^4.61"
|
||||
},
|
||||
"dcloudext": {
|
||||
"type": "uts",
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "插件不采集任何数据",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "",
|
||||
"darkmode": "x",
|
||||
"i18n": "x",
|
||||
"widescreen": "x"
|
||||
},
|
||||
"uni_modules": {
|
||||
"uni-ext-api": {
|
||||
"uni": {
|
||||
"requestMerchantTransfer": {
|
||||
"name": "requestMerchantTransfer",
|
||||
"app": {
|
||||
"js": false,
|
||||
"kotlin": true,
|
||||
"swift": true,
|
||||
"arkts": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "x",
|
||||
"aliyun": "x",
|
||||
"alipay": "x"
|
||||
},
|
||||
"client": {
|
||||
"uni-app": {
|
||||
"vue": {
|
||||
"vue2": "x",
|
||||
"vue3": "√"
|
||||
},
|
||||
"web": {
|
||||
"safari": "-",
|
||||
"chrome": "-"
|
||||
},
|
||||
"app": {
|
||||
"vue": "-",
|
||||
"nvue": "-",
|
||||
"android": {
|
||||
"extVersion": "1.1.0",
|
||||
"minVersion": "34"
|
||||
},
|
||||
"ios": {
|
||||
"extVersion": "1.1.0",
|
||||
"minVersion": "12"
|
||||
},
|
||||
"harmony": {
|
||||
"extVersion": "1.1.0",
|
||||
"minVersion": ""
|
||||
}
|
||||
},
|
||||
"mp": {
|
||||
"weixin": "-",
|
||||
"alipay": "-",
|
||||
"toutiao": "-",
|
||||
"baidu": "-",
|
||||
"kuaishou": "-",
|
||||
"jd": "-",
|
||||
"harmony": "-",
|
||||
"qq": "-",
|
||||
"lark": "-",
|
||||
"xhs": "-"
|
||||
},
|
||||
"quickapp": {
|
||||
"huawei": "-",
|
||||
"union": "-"
|
||||
}
|
||||
},
|
||||
"uni-app-x": {
|
||||
"web": {
|
||||
"safari": "-",
|
||||
"chrome": "-"
|
||||
},
|
||||
"app": {
|
||||
"android": {
|
||||
"extVersion": "1.1.0",
|
||||
"minVersion": "34"
|
||||
},
|
||||
"ios": {
|
||||
"extVersion": "1.1.0",
|
||||
"minVersion": "12"
|
||||
},
|
||||
"harmony": {
|
||||
"extVersion": "1.1.0",
|
||||
"minVersion": ""
|
||||
}
|
||||
},
|
||||
"mp": {
|
||||
"weixin": "-"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
# uni-requestMerchantTransfer
|
||||
实现打开微信请求用户确认收款功能。
|
||||
[API规范文档](https://doc.dcloud.net.cn/uni-app-x/api/request-merchant-transfer.html)
|
||||
|
||||
|
||||
## uts 语言介绍
|
||||
uts,全称 uni type script,是一门跨平台的、高性能的、强类型的现代编程语言。
|
||||
|
||||
它可以被编译为不同平台的编程语言,如:
|
||||
|
||||
Android平台:编译为Kotlin
|
||||
iOS平台:编译Swift
|
||||
鸿蒙OS平台:编译为ArkTS
|
||||
web平台/小程序:编译为JavaScript
|
||||
|
||||
uts 采用了与 ts 基本一致的语法规范,支持绝大部分 ES6 API。
|
||||
|
||||
但为了跨端,uts进行了一些约束和特定平台的增补。
|
||||
|
||||
过去在js引擎下运行支持的语法,大部分在uts的处理下也可以平滑的在kotlin和swift中使用。但有一些无法抹平,需要使用条件编译。
|
||||
|
||||
和uni-app的条件编译类似,uts也支持条件编译。写在条件编译里的,可以调用平台特有的扩展语法。
|
||||
|
||||
|
||||
## uts 插件介绍
|
||||
UTS 插件是一种特定的 uni_modules 插件,其核心目的是允许 uni-app/uni-app x 开发者使用 UTS 语法来调用扩展 API(封装原生系统的API或三方SDK)。
|
||||
|
||||
UTS 插件的实现代码主要位于 utssdk 目录下,并按平台进行分离和组织:
|
||||
| 目录/文件 | 目标平台 | 实现语言 | 作用描述 |
|
||||
| -- | -- | -- | -- |
|
||||
|utssdk/app-android|Android|UTS, Kotlin, Java|存放 UTS 插件在 Android 平台上的具体实现源码|
|
||||
|utssdk/app-ios|iOS|UTS, Swift|存放 UTS 插件在 iOS 平台上的具体实现源码|
|
||||
|utssdk/app-harmony|HarmonyOS (鸿蒙)|UTS, ArkTS|存放 UTS 插件在 HarmonyOS 平台上的具体实现源码|
|
||||
|utssdk/*.uts|多平台共用|UTS|存放使用 UTS 语言编写的、可供 所有平台 共用的实现源码|
|
||||
|
||||
|
||||
## 参考文档
|
||||
- [uts 语言介绍](https://doc.dcloud.net.cn/uni-app-x/uts/)
|
||||
- [uts 和 ts 的差异](https://doc.dcloud.net.cn/uni-app-x/uts/uts_diff_ts.html)
|
||||
- [uts 插件开发文档](https://doc.dcloud.net.cn/uni-app-x/plugin/uts-plugin.html)
|
||||
- [uts 插件原生语言混编开发文档](https://doc.dcloud.net.cn/uni-app-x/plugin/uts-plugin-hybrid.html)
|
||||
- [uts 插件 Android 平台开发注意事项](https://doc.dcloud.net.cn/uni-app-x/plugin/uts-for-android.html)
|
||||
- [uts 插件 iOS 平台开发注意事项](https://doc.dcloud.net.cn/uni-app-x/plugin/uts-for-ios.html)
|
||||
- [uts 插件 HarmonyOS 平台开发注意事项](https://doc.dcloud.net.cn/uni-app-x/plugin/uts-for-ios.html)
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"minSdkVersion": "21",
|
||||
"dependencies": [
|
||||
"com.tencent.mm.opensdk:wechat-sdk-android:6.8.0"
|
||||
],
|
||||
"thirdSDKs": [
|
||||
"weixin"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
|
||||
import { RequestMerchantTransfer, RequestMerchantTransferOptions, RequestMerchantTransferGeneralCallbackResult } from '../interface.uts';
|
||||
import WXAPIFactory from 'com.tencent.mm.opensdk.openapi.WXAPIFactory'
|
||||
import URLEncoder from 'java.net.URLEncoder'
|
||||
import StandardCharsets from 'java.nio.charset.StandardCharsets'
|
||||
import WXOpenBusinessView from 'com.tencent.mm.opensdk.modelbiz.WXOpenBusinessView'
|
||||
import Build from 'com.tencent.mm.opensdk.constants.Build'
|
||||
export const requestMerchantTransfer : RequestMerchantTransfer = function (options : RequestMerchantTransferOptions) {
|
||||
var appId = options.appId
|
||||
var api = WXAPIFactory.createWXAPI(UTSAndroid.getAppContext(), appId, false)
|
||||
var wxSdkVersion = api.wxAppSupportAPI
|
||||
if (wxSdkVersion >= Build.OPEN_BUSINESS_VIEW_SDK_INT) {
|
||||
var req = WXOpenBusinessView.Req()
|
||||
req.businessType = "requestMerchantTransfer"
|
||||
|
||||
// 通过 URL 编码处理参数,确保特殊字符不会影响请求
|
||||
var query = ""
|
||||
query += 'mchId=' + encodeParams(options.mchId) + '&'
|
||||
query += 'package=' + encodeParams(options.package) + '&'
|
||||
if (appId != null) {
|
||||
query += 'appId=' + encodeParams(options.appId) + '&'
|
||||
}
|
||||
if (options.openId != null) {
|
||||
query += 'openId=' + encodeParams(options.openId) + '&'
|
||||
}
|
||||
if (options.subAppId != null) {
|
||||
query += 'subAppId=' + encodeParams(options.subAppId) + '&'
|
||||
}
|
||||
if (options.subMchId != null) {
|
||||
query += 'subMchId=' + encodeParams(options.subMchId) + '&'
|
||||
}
|
||||
query = query.substring(0, query.length - 1)
|
||||
req.query = query
|
||||
// 发送请求并检查返回值
|
||||
var ret = api.sendReq(req)
|
||||
if (ret) {
|
||||
var result : RequestMerchantTransferGeneralCallbackResult = {
|
||||
errMsg: 'ok'
|
||||
}
|
||||
options.success?.(result)
|
||||
options.complete?.(result)
|
||||
}else{
|
||||
var result : RequestMerchantTransferGeneralCallbackResult = {
|
||||
errMsg: 'fail'
|
||||
}
|
||||
options.fail?.(result)
|
||||
options.complete?.(result)
|
||||
}
|
||||
} else {
|
||||
var result : RequestMerchantTransferGeneralCallbackResult = {
|
||||
errMsg: '当前微信版本过低,请升级微信以使用该功能'
|
||||
}
|
||||
options.fail?.(result)
|
||||
options.complete?.(result)
|
||||
}
|
||||
}
|
||||
function encodeParams(params : string | null) : string | null {
|
||||
return URLEncoder.encode(params, StandardCharsets.UTF_8.toString())
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@tencent/wechat_open_sdk": "1.0.15"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import { RequestMerchantTransfer, RequestMerchantTransferGeneralCallbackResult, RequestMerchantTransferOptions } from '../interface.uts'
|
||||
import {
|
||||
handleWechatMerchantTransferWant,
|
||||
requestWechatMerchantTransfer,
|
||||
WechatMerchantTransferNativeResult
|
||||
} from './wechatMerchantTransfer.ets'
|
||||
import {
|
||||
REQUEST_MERCHANT_TRANSFER_APP_ID_REQUIRED,
|
||||
REQUEST_MERCHANT_TRANSFER_BUSINESS_FAILED,
|
||||
REQUEST_MERCHANT_TRANSFER_CANCEL,
|
||||
REQUEST_MERCHANT_TRANSFER_MCH_ID_REQUIRED,
|
||||
REQUEST_MERCHANT_TRANSFER_OK,
|
||||
REQUEST_MERCHANT_TRANSFER_OPEN_SDK_ERROR,
|
||||
REQUEST_MERCHANT_TRANSFER_PACKAGE_REQUIRED,
|
||||
REQUEST_MERCHANT_TRANSFER_PARSE_RESPONSE_ERROR,
|
||||
REQUEST_MERCHANT_TRANSFER_WECHAT_NOT_INSTALLED,
|
||||
REQUEST_MERCHANT_TRANSFER_WECHAT_VERSION_LOW
|
||||
} from '../unierror.uts'
|
||||
|
||||
let isWechatMerchantTransferHandlerRegistered = false
|
||||
|
||||
function ensureWechatMerchantTransferHandlerRegistered() : void {
|
||||
if (isWechatMerchantTransferHandlerRegistered) {
|
||||
return
|
||||
}
|
||||
isWechatMerchantTransferHandlerRegistered = true
|
||||
UTSHarmony.onAppAbilityCreate((want, _launchParam) => {
|
||||
handleWechatMerchantTransferWant(want)
|
||||
})
|
||||
UTSHarmony.onAppAbilityNewWant((want, _launchParam) => {
|
||||
handleWechatMerchantTransferWant(want)
|
||||
})
|
||||
}
|
||||
|
||||
function invokeCallback(options : RequestMerchantTransferOptions, errMsg : string, isSuccess : boolean) : void {
|
||||
const result : RequestMerchantTransferGeneralCallbackResult = {
|
||||
errMsg: errMsg
|
||||
}
|
||||
if (isSuccess) {
|
||||
options.success?.(result)
|
||||
} else {
|
||||
options.fail?.(result)
|
||||
}
|
||||
options.complete?.(result)
|
||||
}
|
||||
|
||||
function isEmptyString(value : string | null) : boolean {
|
||||
return value == null || value.length == 0
|
||||
}
|
||||
|
||||
function appendQueryParam(query : string, key : string, value : string) : string {
|
||||
const param = key + '=' + encodeURIComponent(value)
|
||||
if (query.length > 0) {
|
||||
return query + '&' + param
|
||||
}
|
||||
return param
|
||||
}
|
||||
|
||||
function getValidateErrorMsg(options : RequestMerchantTransferOptions) : string {
|
||||
if (isEmptyString(options.mchId)) {
|
||||
return REQUEST_MERCHANT_TRANSFER_MCH_ID_REQUIRED.errMsg
|
||||
}
|
||||
if (isEmptyString(options.package)) {
|
||||
return REQUEST_MERCHANT_TRANSFER_PACKAGE_REQUIRED.errMsg
|
||||
}
|
||||
if (isEmptyString(options.appId)) {
|
||||
return REQUEST_MERCHANT_TRANSFER_APP_ID_REQUIRED.errMsg
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function buildRequestMerchantTransferQuery(options : RequestMerchantTransferOptions, appId : string) : string {
|
||||
let query = ''
|
||||
// 微信官方文档上只有下方3个参数,没有 openId、subAppId、subMchId
|
||||
// 相关文档:
|
||||
// 用户确认收款:https://pay.weixin.qq.com/doc/v3/merchant/4019670774
|
||||
// 免确认收款授权:https://pay.weixin.qq.com/doc/v3/merchant/4020320137
|
||||
query = appendQueryParam(query, 'mchId', options.mchId)
|
||||
query = appendQueryParam(query, 'package', options.package)
|
||||
query = appendQueryParam(query, 'appId', appId)
|
||||
return query
|
||||
}
|
||||
|
||||
function handleNativeResult(options : RequestMerchantTransferOptions, nativeResult : WechatMerchantTransferNativeResult) : void {
|
||||
if (nativeResult.result == 'success') {
|
||||
invokeCallback(options, REQUEST_MERCHANT_TRANSFER_OK, true)
|
||||
return
|
||||
}
|
||||
|
||||
let errMsg = REQUEST_MERCHANT_TRANSFER_BUSINESS_FAILED.errMsg
|
||||
if (nativeResult.result == 'cancel') {
|
||||
errMsg = REQUEST_MERCHANT_TRANSFER_CANCEL.errMsg
|
||||
} else if (nativeResult.result == 'wechat_not_installed') {
|
||||
errMsg = REQUEST_MERCHANT_TRANSFER_WECHAT_NOT_INSTALLED.errMsg
|
||||
} else if (nativeResult.result == 'wechat_version_low') {
|
||||
errMsg = REQUEST_MERCHANT_TRANSFER_WECHAT_VERSION_LOW.errMsg
|
||||
} else if (nativeResult.result == 'parse_response_error') {
|
||||
errMsg = REQUEST_MERCHANT_TRANSFER_PARSE_RESPONSE_ERROR.errMsg
|
||||
} else if (nativeResult.result == 'open_sdk_error' || nativeResult.result == 'timeout' || nativeResult.result == 'request_interrupted') {
|
||||
errMsg = REQUEST_MERCHANT_TRANSFER_OPEN_SDK_ERROR.errMsg
|
||||
}
|
||||
invokeCallback(options, errMsg, false)
|
||||
}
|
||||
|
||||
export const requestMerchantTransfer : RequestMerchantTransfer = function (options : RequestMerchantTransferOptions) {
|
||||
const validateErrorMsg = getValidateErrorMsg(options)
|
||||
if (validateErrorMsg.length > 0) {
|
||||
invokeCallback(options, validateErrorMsg, false)
|
||||
return
|
||||
}
|
||||
|
||||
ensureWechatMerchantTransferHandlerRegistered()
|
||||
const appId : string = options.appId ?? ''
|
||||
const query = buildRequestMerchantTransferQuery(options, appId)
|
||||
requestWechatMerchantTransfer(appId, query).then((nativeResult : WechatMerchantTransferNativeResult) => {
|
||||
handleNativeResult(options, nativeResult)
|
||||
}).catch((_error : Error) => {
|
||||
invokeCallback(options, REQUEST_MERCHANT_TRANSFER_OPEN_SDK_ERROR.errMsg, false)
|
||||
})
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
import { common, Want } from '@kit.AbilityKit';
|
||||
import * as wxopensdk from '@tencent/wechat_open_sdk';
|
||||
|
||||
export class WechatMerchantTransferNativeResult {
|
||||
result: string = '';
|
||||
errMsg: string = '';
|
||||
extMsg: string = '';
|
||||
|
||||
constructor(result: string, errMsg: string, extMsg: string) {
|
||||
this.result = result;
|
||||
this.errMsg = errMsg;
|
||||
this.extMsg = extMsg;
|
||||
}
|
||||
}
|
||||
|
||||
const MERCHANT_TRANSFER_BUSINESS_TYPE: string = 'requestMerchantTransfer';
|
||||
const REQUEST_TIMEOUT_MS: number = 120000;
|
||||
|
||||
let currentAppId: string = '';
|
||||
let currentWxApi: wxopensdk.WXApi | null = null;
|
||||
let pendingResolve: ((result: WechatMerchantTransferNativeResult) => void) | null = null;
|
||||
let timeoutId: number = -1;
|
||||
|
||||
class MerchantTransferRespPayload {
|
||||
result: string | null = null;
|
||||
}
|
||||
|
||||
function buildResult(result: string, errMsg: string, extMsg: string): WechatMerchantTransferNativeResult {
|
||||
return new WechatMerchantTransferNativeResult(result, errMsg, extMsg);
|
||||
}
|
||||
|
||||
function getWxApi(appId: string): wxopensdk.WXApi {
|
||||
let wxApi = currentWxApi;
|
||||
if (wxApi == null || currentAppId != appId) {
|
||||
currentAppId = appId;
|
||||
wxApi = wxopensdk.WXAPIFactory.createWXAPI(appId);
|
||||
currentWxApi = wxApi;
|
||||
}
|
||||
return wxApi;
|
||||
}
|
||||
|
||||
function clearPendingTimeout(): void {
|
||||
if (timeoutId != -1) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
function finishPending(result: WechatMerchantTransferNativeResult): void {
|
||||
const resolve = pendingResolve;
|
||||
pendingResolve = null;
|
||||
clearPendingTimeout();
|
||||
if (resolve != null) {
|
||||
resolve(result);
|
||||
}
|
||||
}
|
||||
|
||||
function parseMerchantTransferResp(extMsg: string): WechatMerchantTransferNativeResult {
|
||||
if (extMsg.length == 0) {
|
||||
return buildResult('parse_response_error', 'parse response failed', extMsg);
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = JSON.parse(extMsg) as MerchantTransferRespPayload;
|
||||
const result: string = payload.result ?? '';
|
||||
|
||||
if (result == 'success') {
|
||||
return buildResult('success', '', extMsg);
|
||||
}
|
||||
if (result == 'fail') {
|
||||
return buildResult('fail', 'business failed', extMsg);
|
||||
}
|
||||
if (result == 'cancel') {
|
||||
return buildResult('cancel', 'business canceled', extMsg);
|
||||
}
|
||||
return buildResult('business_failed', 'unknown business result', extMsg);
|
||||
} catch (_error) {
|
||||
return buildResult('parse_response_error', 'parse response failed', extMsg);
|
||||
}
|
||||
}
|
||||
|
||||
class WechatMerchantTransferEventHandler implements wxopensdk.WXApiEventHandler {
|
||||
onResp(resp: wxopensdk.BaseResp): void {
|
||||
if (resp instanceof wxopensdk.OpenBusinessViewResp) {
|
||||
if ("requestMerchantTransfer" === resp.businessType) {
|
||||
if (resp.errCode == wxopensdk.ErrCode.ERR_UNSUPPORTED) {
|
||||
finishPending(buildResult('wechat_version_low', resp.errStr ?? 'wechat version is too low', ''));
|
||||
return;
|
||||
}
|
||||
|
||||
const businessType: string = resp.businessType ?? '';
|
||||
if (businessType != MERCHANT_TRANSFER_BUSINESS_TYPE) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extMsg: string = resp.extMsg ?? '';
|
||||
if (resp.errCode == wxopensdk.ErrCode.ERR_USER_CANCEL && extMsg.length == 0) {
|
||||
finishPending(buildResult('cancel', resp.errStr ?? 'business canceled', ''));
|
||||
return;
|
||||
}
|
||||
finishPending(parseMerchantTransferResp(extMsg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onReq(_req: wxopensdk.BaseReq): void {
|
||||
}
|
||||
}
|
||||
|
||||
const wxEventHandler = new WechatMerchantTransferEventHandler();
|
||||
|
||||
export async function requestWechatMerchantTransfer(appId: string, query: string): Promise<WechatMerchantTransferNativeResult> {
|
||||
if (appId == '') {
|
||||
return buildResult('invalid_app_id', 'appId is empty', '');
|
||||
}
|
||||
if (query == '') {
|
||||
return buildResult('invalid_query', 'query is empty', '');
|
||||
}
|
||||
|
||||
const wxApi: wxopensdk.WXApi = getWxApi(appId);
|
||||
|
||||
if (!wxApi.isWXAppInstalled()) {
|
||||
return buildResult('wechat_not_installed', 'wechat is not installed', '');
|
||||
}
|
||||
|
||||
const req = new wxopensdk.OpenBusinessViewReq();
|
||||
req.businessType = MERCHANT_TRANSFER_BUSINESS_TYPE;
|
||||
req.query = query;
|
||||
|
||||
const context = getContext() as common.UIAbilityContext;
|
||||
const responsePromise = new Promise<WechatMerchantTransferNativeResult>((resolve) => {
|
||||
if (pendingResolve != null) {
|
||||
finishPending(buildResult('request_interrupted', 'another merchant transfer request started', ''));
|
||||
}
|
||||
|
||||
pendingResolve = resolve;
|
||||
timeoutId = setTimeout(() => {
|
||||
finishPending(buildResult('timeout', 'wait wechat response timeout', ''));
|
||||
}, REQUEST_TIMEOUT_MS);
|
||||
});
|
||||
|
||||
try {
|
||||
const sendResult: boolean = await wxApi.sendReq(context, req);
|
||||
if (!sendResult) {
|
||||
finishPending(buildResult('open_sdk_error', 'sendReq returned false', ''));
|
||||
}
|
||||
} catch (_error) {
|
||||
finishPending(buildResult('open_sdk_error', 'sendReq failed', ''));
|
||||
}
|
||||
|
||||
return responsePromise;
|
||||
}
|
||||
|
||||
export function handleWechatMerchantTransferWant(want: Want): void {
|
||||
const wxApi = currentWxApi;
|
||||
if (wxApi == null || currentAppId.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
wxApi.handleWant(want, wxEventHandler);
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
重要!
|
||||
SDK2.0.5
|
||||
1. 支持模块化集成:XCFramework 头文件引用改为标准化格式 #import <WechatOpenSDK/WXApi.h>,解决路径冲突并支持 Swift/ObjC 混合开发
|
||||
2. 修复openWXApp偶现失败的问题
|
||||
|
||||
SDK2.0.4
|
||||
1.增加privacy manifest文件
|
||||
2.修复跳微信时可能卡顿的问题
|
||||
|
||||
SDK2.0.2
|
||||
1. 优化XCFramework打包方式
|
||||
|
||||
SDK2.0.1
|
||||
1. SDK支持XCFramework
|
||||
|
||||
SDK2.0.0
|
||||
1. 分享能力支持内容防篡改校验
|
||||
|
||||
SDK1.9.9
|
||||
1. 授权登录支持关闭自动授权
|
||||
2. 分享支持添加签名,防止篡改
|
||||
|
||||
SDK1.9.7
|
||||
1. 适配CocoaPods
|
||||
|
||||
SDK1.9.6
|
||||
1. 适配iOS 16,减少读写剪切板
|
||||
|
||||
SDK1.9.4
|
||||
1. 修复授权登录取消/拒绝时state字段没有带回
|
||||
|
||||
SDK1.9.3
|
||||
1. 新增发起二维码支付能力
|
||||
|
||||
SDK1.9.2
|
||||
1. 新增发起企微客服会话能力
|
||||
|
||||
SDK1.9.1
|
||||
1. 音乐视频分享类型增加运营H5字段
|
||||
|
||||
SDK1.8.9
|
||||
1. 增加音乐视频分享类型
|
||||
|
||||
SDK1.8.8
|
||||
1. 增加游戏直播消息类型
|
||||
|
||||
SDK1.8.7.1
|
||||
1. 修复Xcode11以下编译不通过
|
||||
|
||||
SDK1.8.7
|
||||
1. 修复iPadOS,未安装微信的情况下,因为UA问题无法授权登录
|
||||
2. 修复未安装微信的情况下, 适配了UIScene的App因为UIAlertView Crash
|
||||
3. 增加Universal Link检测函数
|
||||
|
||||
SDK1.8.6.2
|
||||
1. 修改包含"UIWebView"字符的类名
|
||||
|
||||
SDK1.8.6.1
|
||||
1.短信授权登录使用的UIWebview切换成WKWebview
|
||||
|
||||
SDK1.8.6
|
||||
1. 支持Universal Link拉起微信以及返回App
|
||||
2. SDK移除MTA库
|
||||
|
||||
SDK1.8.5
|
||||
1. 更换MTA库:取消对剪切板的访问, 防止和其他SDK竞争导致crash
|
||||
2. NSMutableArray的MTA分类方法改名,减少命名冲突
|
||||
3. 不含支付功能版本移除非税支付和医保支付接口
|
||||
4. 分享音乐支持填写歌词和高清封面图
|
||||
|
||||
SDK1.8.4
|
||||
1. 调整分享图片大小限制
|
||||
2. 新增openBusinessView接口
|
||||
|
||||
SDK1.8.3
|
||||
1. SDK增加调起微信刷卡支付接口
|
||||
2. SDK增加小程序订阅消息接口
|
||||
3. 修复小程序订阅消息接口没有resp的问题
|
||||
|
||||
SDK1.8.2
|
||||
1. SDK增加开发票授权 WXInvoiceAuthInsert
|
||||
2. SDK增加非税接口 WXNontaxPay
|
||||
3. SDK增加医保接口 WXPayInsurance
|
||||
4. 更换MTA库
|
||||
|
||||
SDK1.8.1
|
||||
1. SDK打开小程序支持指定版本(体验,开发,正式版)
|
||||
2. SDK分享小程序支持指定版本(体验,开发,正式版)
|
||||
3. SDK支持输出log日志
|
||||
|
||||
SDK1.8.0
|
||||
1. SDK支持打开小程序
|
||||
2. SDK分享小程序支持shareTicket
|
||||
|
||||
SDK1.7.9
|
||||
1. SDK订阅一次性消息
|
||||
|
||||
SDK1.7.8
|
||||
1 SDK分享小程序支持大图
|
||||
|
||||
SDK1.7.7
|
||||
1 增加SDK分享小程序
|
||||
2 增加选择发票接口
|
||||
|
||||
SDK1.7.6
|
||||
1. 提高稳定性
|
||||
1 修复mta崩溃
|
||||
2 新增接口支持开发者关闭mta数据统计上报
|
||||
|
||||
SDK1.7.5
|
||||
1. 提高稳定性
|
||||
2. 加快registerApp接口启动速度
|
||||
|
||||
SDK1.7.4
|
||||
1. 更新支持iOS启用 ATS(App Transport Security)
|
||||
2. 需要在工程中链接CFNetwork.framework
|
||||
3. 在工程配置中的”Other Linker Flags”中加入”-Objc -all_load”
|
||||
|
||||
SDK1.7.3
|
||||
1. 增强稳定性,适配iOS10
|
||||
2. 修复小于32K的jpg格式缩略图设置失败的问题
|
||||
|
||||
SDK1.7.2
|
||||
1. 修复因CTTeleponyNetworkInfo引起的崩溃问题
|
||||
|
||||
SDK1.7.1
|
||||
1. 支持兼容ipv6(提升稳定性)
|
||||
2. xCode Version 7.3.1 (7D1014) 编译
|
||||
|
||||
SDK1.7
|
||||
1. 支持兼容ipv6
|
||||
2. 修复若干问题增强稳定性
|
||||
|
||||
SDK1.6.3
|
||||
1. xCode7.2 构建的sdk包。
|
||||
2. 请使用xCode7.2进行编译。
|
||||
3. 需要在Build Phases中Link Security.framework
|
||||
4. 修复若干小问题。
|
||||
|
||||
SDK1.6.2
|
||||
1、xCode7.1 构建的sdk包
|
||||
2、请使用xCode7.1进行编译
|
||||
|
||||
SDK1.6.1
|
||||
1、修复armv7s下,bitcode可能编译不过
|
||||
2、解决warning
|
||||
|
||||
SDK1.6
|
||||
1、iOS 9系统策略更新,限制了http协议的访问,此外应用需要在“Info.plist”中将要使用的URL Schemes列为白名单,才可正常检查其他应用是否安装。
|
||||
受此影响,当你的应用在iOS 9中需要使用微信SDK的相关能力(分享、收藏、支付、登录等)时,需要在“Info.plist”里增加如下代码:
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>weixin</string>
|
||||
</array>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
2、开发者需要在工程中链接上 CoreTelephony.framework
|
||||
3、解决bitcode编译不过问题
|
||||
|
||||
SDK1.5
|
||||
1、废弃safeSendReq:接口,使用sendReq:即可。
|
||||
2、新增+(BOOL) sendAuthReq:(SendAuthReq*) req viewController : (UIViewController*) viewController delegate:(id<WXApiDelegate>) delegate;
|
||||
支持未安装微信情况下Auth,具体见WXApi.h接口描述
|
||||
3、微信开放平台新增了微信模块用户统计功能,便于开发者统计微信功能模块的用户使用和活跃情况。开发者需要在工程中链接上:SystemConfiguration.framework,libz.dylib,libsqlite3.0.dylib。
|
||||
@@ -0,0 +1,231 @@
|
||||
//
|
||||
// WXApi.h
|
||||
// 所有Api接口
|
||||
//
|
||||
// Created by Wechat on 12-2-28.
|
||||
// Copyright (c) 2012年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "WXApiObject.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
typedef BOOL(^WXGrantReadPasteBoardPermissionCompletion)(void);
|
||||
|
||||
|
||||
#pragma mark - WXApiDelegate
|
||||
/*! @brief 接收并处理来自微信终端程序的事件消息
|
||||
*
|
||||
* 接收并处理来自微信终端程序的事件消息,期间微信界面会切换到第三方应用程序。
|
||||
* WXApiDelegate 会在handleOpenURL:delegate:中使用并触发。
|
||||
*/
|
||||
@protocol WXApiDelegate <NSObject>
|
||||
@optional
|
||||
|
||||
/*! @brief 收到一个来自微信的请求,第三方应用程序处理完后调用sendResp向微信发送结果
|
||||
*
|
||||
* 收到一个来自微信的请求,异步处理完成后必须调用sendResp发送处理结果给微信。
|
||||
* 可能收到的请求有GetMessageFromWXReq、ShowMessageFromWXReq等。
|
||||
* @param req 具体请求内容,是自动释放的
|
||||
*/
|
||||
- (void)onReq:(BaseReq*)req;
|
||||
|
||||
|
||||
|
||||
/*! @brief 发送一个sendReq后,收到微信的回应
|
||||
*
|
||||
* 收到一个来自微信的处理结果。调用一次sendReq后会收到onResp。
|
||||
* 可能收到的处理结果有SendMessageToWXResp、SendAuthResp等。
|
||||
* @param resp具体的回应内容,是自动释放的
|
||||
*/
|
||||
- (void)onResp:(BaseResp*)resp;
|
||||
|
||||
/* ! @brief 用于在iOS16以及以上系统上,控制OpenSDK是否读取剪切板中微信传递的数据以及读取的时机
|
||||
* 在iOS16以及以上系统,在SDK需要读取剪切板中微信写入的数据时,会回调该方法。没有实现默认会直接读取微信通过剪切板传递过来的数据
|
||||
* 注意:
|
||||
* 1. 只在iOS16以及以上的系统版本上回调;
|
||||
* 2. 不实现时,OpenSDK会直接调用读取剪切板接口,读取微信传递过来的数据;
|
||||
* 3. 若实现该方法:开发者需要通过调用completion(), 支持异步,通知SDK允许读取剪切板中微信传递的数据,
|
||||
* 不调用completion()则代表不授权OpenSDK读取剪切板,会导致收不到onReq:, onResp:回调,无法后续业务流程。请谨慎使用
|
||||
* 4. 不要长时间持有completion不释放,可能会导致内存泄漏。
|
||||
*/
|
||||
- (void)onNeedGrantReadPasteBoardPermissionWithURL:(nonnull NSURL *)openURL completion:(nonnull WXGrantReadPasteBoardPermissionCompletion)completion;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - WXApiLogDelegate
|
||||
|
||||
@protocol WXApiLogDelegate <NSObject>
|
||||
|
||||
- (void)onLog:(NSString*)log logLevel:(WXLogLevel)level;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark - WXApi
|
||||
|
||||
/*! @brief 微信Api接口函数类
|
||||
*
|
||||
* 该类封装了微信终端SDK的所有接口
|
||||
*/
|
||||
@interface WXApi : NSObject
|
||||
|
||||
/*! @brief WXApi的成员函数,向微信终端程序注册第三方应用。
|
||||
*
|
||||
* 需要在每次启动第三方应用程序时调用。
|
||||
* @attention 请保证在主线程中调用此函数
|
||||
* @param appid 微信开发者ID
|
||||
* @param universalLink 微信开发者Universal Link
|
||||
* @return 成功返回YES,失败返回NO。
|
||||
*/
|
||||
+ (BOOL)registerApp:(NSString *)appid universalLink:(NSString *)universalLink;
|
||||
|
||||
|
||||
/*! @brief 处理旧版微信通过URL启动App时传递的数据
|
||||
*
|
||||
* 需要在 application:openURL:sourceApplication:annotation:或者application:handleOpenURL中调用。
|
||||
* @param url 微信启动第三方应用时传递过来的URL
|
||||
* @param delegate WXApiDelegate对象,用来接收微信触发的消息。
|
||||
* @return 成功返回YES,失败返回NO。
|
||||
*/
|
||||
+ (BOOL)handleOpenURL:(NSURL *)url delegate:(nullable id<WXApiDelegate>)delegate;
|
||||
|
||||
|
||||
/*! @brief 处理微信通过Universal Link启动App时传递的数据
|
||||
*
|
||||
* 需要在 application:continueUserActivity:restorationHandler:中调用。
|
||||
* @param userActivity 微信启动第三方应用时系统API传递过来的userActivity
|
||||
* @param delegate WXApiDelegate对象,用来接收微信触发的消息。
|
||||
* @return 成功返回YES,失败返回NO。
|
||||
*/
|
||||
+ (BOOL)handleOpenUniversalLink:(NSUserActivity *)userActivity delegate:(nullable id<WXApiDelegate>)delegate;
|
||||
|
||||
|
||||
/*! @brief 检查微信是否已被用户安装
|
||||
*
|
||||
* @return 微信已安装返回YES,未安装返回NO。
|
||||
*/
|
||||
+ (BOOL)isWXAppInstalled;
|
||||
|
||||
|
||||
|
||||
/*! @brief 判断当前微信的版本是否支持OpenApi
|
||||
*
|
||||
* @return 支持返回YES,不支持返回NO。
|
||||
*/
|
||||
+ (BOOL)isWXAppSupportApi;
|
||||
|
||||
|
||||
/*! @brief 判断当前微信的版本是否支持分享微信状态功能
|
||||
*
|
||||
* @attention 需在工程LSApplicationQueriesSchemes配置中添加weixinStateAPI
|
||||
* @return 支持返回YES,不支持返回NO。
|
||||
*/
|
||||
+ (BOOL)isWXAppSupportStateAPI;
|
||||
|
||||
|
||||
#ifndef BUILD_WITHOUT_PAY
|
||||
/*! @brief 判断当前微信的版本是否支持二维码拉起微信支付
|
||||
*
|
||||
* @attention 需在工程LSApplicationQueriesSchemes配置中添加weixinQRCodePayAPI
|
||||
* @return 支持返回YES,不支持返回NO。
|
||||
*/
|
||||
+ (BOOL)isWXAppSupportQRCodePayAPI;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*! @brief 获取微信的itunes安装地址
|
||||
*
|
||||
* @return 微信的安装地址字符串。
|
||||
*/
|
||||
+ (NSString *)getWXAppInstallUrl;
|
||||
|
||||
|
||||
|
||||
/*! @brief 获取当前微信SDK的版本号
|
||||
*
|
||||
* @return 返回当前微信SDK的版本号
|
||||
*/
|
||||
+ (NSString *)getApiVersion;
|
||||
|
||||
|
||||
|
||||
/*! @brief 打开微信
|
||||
*
|
||||
* @return 成功返回YES,失败返回NO。
|
||||
*/
|
||||
+ (BOOL)openWXApp;
|
||||
|
||||
|
||||
|
||||
/*! @brief 发送请求到微信,等待微信返回onResp
|
||||
*
|
||||
* 函数调用后,会切换到微信的界面。第三方应用程序等待微信返回onResp。微信在异步处理完成后一定会调用onResp。支持以下类型
|
||||
* SendAuthReq、SendMessageToWXReq、PayReq等。
|
||||
* @param req 具体的发送请求。
|
||||
* @param completion 调用结果回调block
|
||||
*/
|
||||
+ (void)sendReq:(BaseReq *)req completion:(void (^ __nullable)(BOOL success))completion;
|
||||
|
||||
/*! @brief 收到微信onReq的请求,发送对应的应答给微信,并切换到微信界面
|
||||
*
|
||||
* 函数调用后,会切换到微信的界面。第三方应用程序收到微信onReq的请求,异步处理该请求,完成后必须调用该函数。可能发送的相应有
|
||||
* GetMessageFromWXResp、ShowMessageFromWXResp等。
|
||||
* @param resp 具体的应答内容
|
||||
* @param completion 调用结果回调block
|
||||
*/
|
||||
+ (void)sendResp:(BaseResp*)resp completion:(void (^ __nullable)(BOOL success))completion;
|
||||
|
||||
|
||||
/*! @brief 发送Auth请求到微信,支持用户没安装微信,等待微信返回onResp
|
||||
*
|
||||
* 函数调用后,会切换到微信的界面。第三方应用程序等待微信返回onResp。微信在异步处理完成后一定会调用onResp。支持SendAuthReq类型。
|
||||
* @param req 具体的发送请求。
|
||||
* @param viewController 当前界面对象。
|
||||
* @param delegate WXApiDelegate对象,用来接收微信触发的消息。
|
||||
* @param completion 调用结果回调block
|
||||
*/
|
||||
+ (void)sendAuthReq:(SendAuthReq *)req viewController:(UIViewController*)viewController delegate:(nullable id<WXApiDelegate>)delegate completion:(void (^ __nullable)(BOOL success))completion;
|
||||
|
||||
|
||||
/*! @brief 测试函数,用于排查当前App通过Universal Link方式分享到微信的流程
|
||||
注意1: 调用自检函数之前必须要先调用registerApp:universalLink接口, 并确认调用成功
|
||||
注意2: 自检过程中会有Log产生,可以先调用startLogByLevel函数,根据Log排查问题
|
||||
注意3: 会多次回调block
|
||||
注意4: 仅用于新接入SDK时调试使用,请勿在正式环境的调用
|
||||
*
|
||||
* 当completion回调的step为WXULCheckStepFinal时,表示检测通过,Universal Link接入成功
|
||||
* @param completion 回调Block
|
||||
*/
|
||||
+ (void)checkUniversalLinkReady:(nonnull WXCheckULCompletion)completion;
|
||||
|
||||
|
||||
/*! @brief WXApi的成员函数,接受微信的log信息。byBlock
|
||||
注意1:SDK会强引用这个block,注意不要导致内存泄漏,注意不要导致内存泄漏
|
||||
注意2:调用过一次startLog by block之后,如果再调用一次任意方式的startLoad,会释放上一次logBlock,不再回调上一个logBlock
|
||||
*
|
||||
* @param level 打印log的级别
|
||||
* @param logBlock 打印log的回调block
|
||||
*/
|
||||
|
||||
+ (void)startLogByLevel:(WXLogLevel)level logBlock:(WXLogBolock)logBlock;
|
||||
|
||||
/*! @brief WXApi的成员函数,接受微信的log信息。byDelegate
|
||||
注意1:sdk会弱引用这个delegate,这里可加任意对象为代理,不需要与WXApiDelegate同一个对象
|
||||
注意2:调用过一次startLog by delegate之后,再调用一次任意方式的startLoad,不会再回调上一个logDelegate对象
|
||||
* @param level 打印log的级别
|
||||
* @param logDelegate 打印log的回调代理,
|
||||
*/
|
||||
+ (void)startLogByLevel:(WXLogLevel)level logDelegate:(id<WXApiLogDelegate>)logDelegate;
|
||||
|
||||
/*! @brief 停止打印log,会清理block或者delegate为空,释放block
|
||||
* @param
|
||||
*/
|
||||
+ (void)stopLog;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// WechatAuthSDK.h
|
||||
// WechatAuthSDK
|
||||
//
|
||||
// Created by 李凯 on 13-11-29.
|
||||
// Copyright (c) 2013年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
enum AuthErrCode {
|
||||
WechatAuth_Err_Ok = 0, //Auth成功
|
||||
WechatAuth_Err_NormalErr = -1, //普通错误
|
||||
WechatAuth_Err_NetworkErr = -2, //网络错误
|
||||
WechatAuth_Err_GetQrcodeFailed = -3, //获取二维码失败
|
||||
WechatAuth_Err_Cancel = -4, //用户取消授权
|
||||
WechatAuth_Err_Timeout = -5, //超时
|
||||
};
|
||||
|
||||
@protocol WechatAuthAPIDelegate<NSObject>
|
||||
@optional
|
||||
|
||||
- (void)onAuthGotQrcode:(UIImage *)image; //得到二维码
|
||||
- (void)onQrcodeScanned; //二维码被扫描
|
||||
- (void)onAuthFinish:(int)errCode AuthCode:(nullable NSString *)authCode; //成功登录
|
||||
|
||||
@end
|
||||
|
||||
@interface WechatAuthSDK : NSObject{
|
||||
NSString *_sdkVersion;
|
||||
__weak id<WechatAuthAPIDelegate> _delegate;
|
||||
}
|
||||
|
||||
@property(nonatomic, weak, nullable) id<WechatAuthAPIDelegate> delegate;
|
||||
@property(nonatomic, readonly) NSString *sdkVersion; //authSDK版本号
|
||||
|
||||
/*! @brief 发送登录请求,等待WechatAuthAPIDelegate回调
|
||||
*
|
||||
* @param appId 微信开发者ID
|
||||
* @param nonceStr 一个随机的尽量不重复的字符串,用来使得每次的signature不同
|
||||
* @param timeStamp 时间戳
|
||||
* @param scope 应用授权作用域,拥有多个作用域用逗号(,)分隔
|
||||
* @param signature 签名
|
||||
* @param schemeData 会在扫码后拼在scheme后
|
||||
* @return 成功返回YES,失败返回NO
|
||||
注:该实现只保证同时只有一个Auth在运行,Auth未完成或未Stop再次调用Auth接口时会返回NO。
|
||||
*/
|
||||
|
||||
- (BOOL)Auth:(NSString *)appId
|
||||
nonceStr:(NSString *)nonceStr
|
||||
timeStamp:(NSString *)timeStamp
|
||||
scope:(NSString *)scope
|
||||
signature:(NSString *)signature
|
||||
schemeData:(nullable NSString *)schemeData;
|
||||
|
||||
|
||||
/*! @brief 暂停登录请求
|
||||
*
|
||||
* @return 成功返回YES,失败返回NO。
|
||||
*/
|
||||
- (BOOL)StopAuth;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
Binary file not shown.
@@ -0,0 +1,17 @@
|
||||
<?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>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>com.apple.developer.associated-domains</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"deploymentTarget": "12.0",
|
||||
"simulatorArchitectures": [
|
||||
"x86_64"
|
||||
],
|
||||
"frameworks": [
|
||||
"CoreGraphics.framework",
|
||||
"WebKit.framework",
|
||||
"Security.framework"
|
||||
],
|
||||
"thirdSDKs": [
|
||||
"weixin"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
import { UIApplication } from "UIKit";
|
||||
import { URL, NSUserActivity, Bundle, URLComponents, URLQueryItem, Script } from "Foundation";
|
||||
import { RequestMerchantTransfer, RequestMerchantTransferOptions, RequestMerchantTransferGeneralCallbackResult } from '../interface.uts';
|
||||
|
||||
export const requestMerchantTransfer : RequestMerchantTransfer = function (options : RequestMerchantTransferOptions) {
|
||||
WxpayManager.requestMerchantTransfer(options)
|
||||
}
|
||||
|
||||
export class WxpayManagerHookProxy implements UTSiOSHookProxy {
|
||||
// 应用正常启动时 (不包括已在后台转到前台的情况)的回调函数。
|
||||
applicationDidFinishLaunchingWithOptions(application : UIApplication | null, launchOptions : Map<UIApplication.LaunchOptionsKey, any> | null = null) : boolean {
|
||||
WxpayManager.registerApp()
|
||||
return false
|
||||
}
|
||||
|
||||
// 通过 url scheme 方式唤起 app 时的回调函数。
|
||||
applicationOpenURLOptions(app : UIApplication | null, url : URL, options : Map<UIApplication.OpenURLOptionsKey, any> | null = null) : boolean {
|
||||
WxpayManager.handleOpen(url)
|
||||
return true
|
||||
}
|
||||
|
||||
// 当应用程序接收到与用户活动相关的数据时调用此方法,例如,当用户使用 Universal Link 唤起应用时。
|
||||
applicationContinueUserActivityRestorationHandler(application : UIApplication | null, userActivity : NSUserActivity | null, restorationHandler : ((res : [any] | null) => void) | null = null) : boolean {
|
||||
WxpayManager.handleOpenUniversalLink(userActivity)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class WxpayManager implements WXApiDelegate {
|
||||
private static shared = new WxpayManager()
|
||||
private static options ?: RequestMerchantTransferOptions
|
||||
|
||||
@UTSiOS.keyword("fileprivate")
|
||||
static registerApp() {
|
||||
const scheme = this.getApplicationScheme()
|
||||
const universalLinkTmp = this.getApplicationUniversalLink()
|
||||
if (scheme != null && universalLinkTmp != null && scheme != "" && universalLinkTmp != "") {
|
||||
WXApi.registerApp(scheme!, universalLink = universalLinkTmp!)
|
||||
}
|
||||
}
|
||||
|
||||
@UTSiOS.keyword("fileprivate")
|
||||
static handleOpen(url : URL) {
|
||||
WXApi.handleOpen(url, delegate = shared)
|
||||
}
|
||||
|
||||
@UTSiOS.keyword("fileprivate")
|
||||
static handleOpenUniversalLink(userActivity : NSUserActivity | null) {
|
||||
if (userActivity != null) {
|
||||
WXApi.handleOpenUniversalLink(userActivity!, delegate = shared)
|
||||
}
|
||||
}
|
||||
|
||||
private static getApplicationScheme() : string | null {
|
||||
let scheme : string | null = null
|
||||
const infoDictionary = Bundle.main.infoDictionary
|
||||
if (infoDictionary != null) {
|
||||
const wechat = infoDictionary!['WeChat'] as Map<string, any> | null
|
||||
if (wechat != null) {
|
||||
scheme = wechat!['appid'] as string | null
|
||||
}
|
||||
}
|
||||
|
||||
return scheme
|
||||
}
|
||||
|
||||
private static getApplicationUniversalLink() : string | null {
|
||||
let universalLink : string | null = null
|
||||
const infoDictionary = Bundle.main.infoDictionary
|
||||
if (infoDictionary != null) {
|
||||
const wechat = infoDictionary!['WeChat'] as Map<string, any> | null
|
||||
if (wechat != null) {
|
||||
universalLink = wechat!['universalLink'] as string | null
|
||||
}
|
||||
}
|
||||
return universalLink
|
||||
}
|
||||
|
||||
//@brief 检查微信是否已被用户安装
|
||||
@UTSiOS.keyword("fileprivate")
|
||||
static isWXAppInstalled() : boolean {
|
||||
const url = URL(string = "weixin://")!
|
||||
if (UIApplication.shared.canOpenURL(url)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
static buildQueryString(parameters : Map<string, string>) : string | null {
|
||||
let components = URLComponents()
|
||||
let queryItems : [URLQueryItem] = []
|
||||
parameters.forEach((value, key) => {
|
||||
queryItems.append(URLQueryItem(name = key, value = value))
|
||||
})
|
||||
components.queryItems = queryItems
|
||||
return components.percentEncodedQuery
|
||||
}
|
||||
|
||||
static requestMerchantTransfer(options : RequestMerchantTransferOptions) {
|
||||
WxpayManager.options = options
|
||||
if (WxpayManager.isWXAppInstalled() == false) {
|
||||
var res : RequestMerchantTransferGeneralCallbackResult = {
|
||||
errMsg: '微信没有安装'
|
||||
}
|
||||
options.fail?.(res)
|
||||
options.complete?.(res)
|
||||
}
|
||||
|
||||
if (WxpayManager.getApplicationScheme() == null || WxpayManager.getApplicationScheme() == "") {
|
||||
var res : RequestMerchantTransferGeneralCallbackResult = {
|
||||
errMsg: '没有配置对应的URL Scheme'
|
||||
}
|
||||
options.fail?.(res)
|
||||
options.complete?.(res)
|
||||
return
|
||||
}
|
||||
|
||||
if (WxpayManager.getApplicationUniversalLink() == null || WxpayManager.getApplicationUniversalLink() == "") {
|
||||
var res : RequestMerchantTransferGeneralCallbackResult = {
|
||||
errMsg: '没有配置对应的Universal Link'
|
||||
}
|
||||
options.fail?.(res)
|
||||
options.complete?.(res)
|
||||
return
|
||||
}
|
||||
|
||||
if (WxpayManager.options != null) {
|
||||
const parameters = new Map<string, string>()
|
||||
parameters.set('mchId', options.mchId)
|
||||
parameters.set('package', options.package)
|
||||
if (options.appId != null) {
|
||||
parameters.set('appId', options.appId!)
|
||||
}
|
||||
if (options.openId != null) {
|
||||
parameters.set('openId', options.openId!)
|
||||
}
|
||||
if (options.subAppId != null) {
|
||||
parameters.set('subAppId', options.subAppId!)
|
||||
}
|
||||
if (options.subMchId != null) {
|
||||
parameters.set('subMchId', options.subMchId!)
|
||||
}
|
||||
|
||||
let reg = new WXOpenBusinessViewReq();
|
||||
reg.businessType = "requestMerchantTransfer"
|
||||
|
||||
let query = WxpayManager.buildQueryString(parameters) ?? ""
|
||||
reg.query = query
|
||||
|
||||
//函数调用后,会切换到微信的界面。第三方应用程序等待微信返回onResp。微信在异步处理完成后一定会调onResp
|
||||
WXApi.send(reg)
|
||||
}
|
||||
}
|
||||
|
||||
//@brief 发送一个sendReq后,收到微信的回应
|
||||
onResp(resp : BaseResp) {
|
||||
if (resp instanceof WXOpenBusinessViewResp) {
|
||||
let resp = resp as WXOpenBusinessViewResp
|
||||
|
||||
if (resp.extMsg?.includes("cancel") == true) {
|
||||
var res : RequestMerchantTransferGeneralCallbackResult = {
|
||||
errMsg: '用户取消'
|
||||
}
|
||||
WxpayManager.options?.fail?.(res)
|
||||
WxpayManager.options?.complete?.(res)
|
||||
}
|
||||
if (resp.extMsg?.includes("fail") == true) {
|
||||
var res : RequestMerchantTransferGeneralCallbackResult = {
|
||||
errMsg: '展示页面失败'
|
||||
}
|
||||
WxpayManager.options?.fail?.(res)
|
||||
WxpayManager.options?.complete?.(res)
|
||||
}
|
||||
if (resp.extMsg?.includes("success") == true) {
|
||||
var res : RequestMerchantTransferGeneralCallbackResult = {
|
||||
errMsg: '确认收款'
|
||||
}
|
||||
WxpayManager.options?.success?.(res)
|
||||
WxpayManager.options?.complete?.(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,713 @@
|
||||
export interface Uni {
|
||||
/**
|
||||
商家转账用户确认模式下,拉起页面请求用户确认收款
|
||||
@tutorial https://doc.dcloud.net.cn/uni-app-x/api/request-merchant-transfer.html
|
||||
@tutorial_uni_app_x https://doc.dcloud.net.cn/uni-app-x/api/request-merchant-transfer.html
|
||||
@tutorial_uni_app https://uniapp.dcloud.net.cn/api/plugins/request-merchant-transfer.html
|
||||
@uniPlatform {
|
||||
"app": {
|
||||
"android": {
|
||||
"osVer": "5.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"ios": {
|
||||
"osVer": "12.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"harmony": {
|
||||
"osVer": "5.0",
|
||||
"uniVer": "5.14",
|
||||
"unixVer": "5.14"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": {
|
||||
"hostVer": "√",
|
||||
"uniVer": "√",
|
||||
"unixVer": "4.41"
|
||||
},
|
||||
"alipay": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"baidu": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"toutiao": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"lark": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"qq": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"kuaishou": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"jd": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
requestMerchantTransfer(options : RequestMerchantTransferOptions) : void;
|
||||
}
|
||||
|
||||
export type RequestMerchantTransfer = (options : RequestMerchantTransferOptions) => void;
|
||||
export type RequestMerchantTransferOptions = {
|
||||
/**
|
||||
商户号
|
||||
@uniPlatform {
|
||||
"app": {
|
||||
"android": {
|
||||
"osVer": "5.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"ios": {
|
||||
"osVer": "12.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"harmony": {
|
||||
"osVer": "5.0",
|
||||
"uniVer": "5.14",
|
||||
"unixVer": "5.14"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": {
|
||||
"hostVer": "√",
|
||||
"uniVer": "√",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"alipay": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"baidu": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"toutiao": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"lark": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"qq": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"kuaishou": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"jd": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
mchId : string
|
||||
/**
|
||||
商家转账付款单跳转收款页 pkg 信息,商家转账付款单受理成功时返回给商户
|
||||
@uniPlatform {
|
||||
"app": {
|
||||
"android": {
|
||||
"osVer": "5.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"ios": {
|
||||
"osVer": "12.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"harmony": {
|
||||
"osVer": "5.0",
|
||||
"uniVer": "5.14",
|
||||
"unixVer": "5.14"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": {
|
||||
"hostVer": "√",
|
||||
"uniVer": "√",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"alipay": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"baidu": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"toutiao": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"lark": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"qq": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"kuaishou": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"jd": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
package : string
|
||||
/**
|
||||
商户 appId(微信平台appid),普通模式下必填,服务商模式下,appId 和 subAppId 二选一填写
|
||||
@uniPlatform {
|
||||
"app": {
|
||||
"android": {
|
||||
"osVer": "5.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"ios": {
|
||||
"osVer": "12.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"harmony": {
|
||||
"osVer": "5.0",
|
||||
"uniVer": "5.14",
|
||||
"unixVer": "5.14"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": {
|
||||
"hostVer": "√",
|
||||
"uniVer": "√",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"alipay": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"baidu": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"toutiao": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"lark": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"qq": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"kuaishou": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"jd": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
appId ?: string | null
|
||||
/**
|
||||
收款用户 openId, 对应传入的商户 appId 下,某用户的 openId
|
||||
@uniPlatform {
|
||||
"app": {
|
||||
"android": {
|
||||
"osVer": "5.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"ios": {
|
||||
"osVer": "12.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"harmony": {
|
||||
"osVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": {
|
||||
"hostVer": "√",
|
||||
"uniVer": "√",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"alipay": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"baidu": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"toutiao": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"lark": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"qq": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"kuaishou": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"jd": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
openId ?: string | null
|
||||
/**
|
||||
子商户 appId(微信平台子appid),服务商模式下,appId 和 subAppId 二选一填写
|
||||
@uniPlatform {
|
||||
"app": {
|
||||
"android": {
|
||||
"osVer": "5.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"ios": {
|
||||
"osVer": "12.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"harmony": {
|
||||
"osVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": {
|
||||
"hostVer": "√",
|
||||
"uniVer": "√",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"alipay": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"baidu": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"toutiao": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"lark": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"qq": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"kuaishou": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"jd": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
subAppId ?: string | null
|
||||
/**
|
||||
子商户号,服务商模式下必填
|
||||
@uniPlatform {
|
||||
"app": {
|
||||
"android": {
|
||||
"osVer": "5.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"ios": {
|
||||
"osVer": "12.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"harmony": {
|
||||
"osVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": {
|
||||
"hostVer": "√",
|
||||
"uniVer": "√",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"alipay": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"baidu": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"toutiao": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"lark": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"qq": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"kuaishou": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"jd": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
subMchId ?: string | null
|
||||
/**
|
||||
接口调用成功的回调函数
|
||||
@uniPlatform {
|
||||
"app": {
|
||||
"android": {
|
||||
"osVer": "5.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"ios": {
|
||||
"osVer": "12.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"harmony": {
|
||||
"osVer": "5.0",
|
||||
"uniVer": "5.14",
|
||||
"unixVer": "5.14"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": {
|
||||
"hostVer": "√",
|
||||
"uniVer": "√",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"alipay": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"baidu": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"toutiao": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"lark": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"qq": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"kuaishou": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"jd": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
success ?: RequestMerchantTransferSuccessCallback | null
|
||||
/**
|
||||
接口调用失败的回调函数
|
||||
@uniPlatform {
|
||||
"app": {
|
||||
"android": {
|
||||
"osVer": "5.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"ios": {
|
||||
"osVer": "12.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"harmony": {
|
||||
"osVer": "5.0",
|
||||
"uniVer": "5.14",
|
||||
"unixVer": "5.14"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": {
|
||||
"hostVer": "√",
|
||||
"uniVer": "√",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"alipay": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"baidu": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"toutiao": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"lark": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"qq": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"kuaishou": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"jd": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
fail ?: RequestMerchantTransferFailCallback | null
|
||||
/**
|
||||
接口调用结束的回调函数(调用成功、失败都会执行)
|
||||
@uniPlatform {
|
||||
"app": {
|
||||
"android": {
|
||||
"osVer": "5.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"ios": {
|
||||
"osVer": "12.0",
|
||||
"uniVer": "4.61",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"harmony": {
|
||||
"osVer": "5.0",
|
||||
"uniVer": "5.14",
|
||||
"unixVer": "5.14"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": {
|
||||
"hostVer": "√",
|
||||
"uniVer": "√",
|
||||
"unixVer": "4.61"
|
||||
},
|
||||
"alipay": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"baidu": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"toutiao": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"lark": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"qq": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"kuaishou": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
},
|
||||
"jd": {
|
||||
"hostVer": "x",
|
||||
"uniVer": "x",
|
||||
"unixVer": "x"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
complete ?: RequestMerchantTransferCompleteCallback | null
|
||||
}
|
||||
export type RequestMerchantTransferCompleteCallback = (
|
||||
res : RequestMerchantTransferGeneralCallbackResult
|
||||
) => void
|
||||
/** 接口调用失败的回调函数 */
|
||||
export type RequestMerchantTransferFailCallback = (
|
||||
res : RequestMerchantTransferGeneralCallbackResult
|
||||
) => void
|
||||
/** 接口调用成功的回调函数 */
|
||||
export type RequestMerchantTransferSuccessCallback = (
|
||||
res : RequestMerchantTransferGeneralCallbackResult
|
||||
) => void
|
||||
export type RequestMerchantTransferGeneralCallbackResult = {
|
||||
errMsg : string
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
export type RequestMerchantTransferErrorCode = 9301102 | 9301103 | 9301104 | 9301105 | 9301106 | 9301107 | 9301108 | 9301109 | 9301110
|
||||
|
||||
export type RequestMerchantTransferError = {
|
||||
errCode : RequestMerchantTransferErrorCode
|
||||
errMsg : string
|
||||
}
|
||||
|
||||
export const REQUEST_MERCHANT_TRANSFER_MCH_ID_REQUIRED : RequestMerchantTransferError = {
|
||||
errCode: 9301102,
|
||||
errMsg: 'requestMerchantTransfer:fail mchId is required'
|
||||
}
|
||||
|
||||
export const REQUEST_MERCHANT_TRANSFER_PACKAGE_REQUIRED : RequestMerchantTransferError = {
|
||||
errCode: 9301103,
|
||||
errMsg: 'requestMerchantTransfer:fail package is required'
|
||||
}
|
||||
|
||||
export const REQUEST_MERCHANT_TRANSFER_APP_ID_REQUIRED : RequestMerchantTransferError = {
|
||||
errCode: 9301104,
|
||||
errMsg: 'requestMerchantTransfer:fail appId is required'
|
||||
}
|
||||
|
||||
export const REQUEST_MERCHANT_TRANSFER_WECHAT_NOT_INSTALLED : RequestMerchantTransferError = {
|
||||
errCode: 9301105,
|
||||
errMsg: 'requestMerchantTransfer:fail wechat is not installed'
|
||||
}
|
||||
|
||||
export const REQUEST_MERCHANT_TRANSFER_WECHAT_VERSION_LOW : RequestMerchantTransferError = {
|
||||
errCode: 9301106,
|
||||
errMsg: 'requestMerchantTransfer:fail wechat version is too low'
|
||||
}
|
||||
|
||||
export const REQUEST_MERCHANT_TRANSFER_CANCEL : RequestMerchantTransferError = {
|
||||
errCode: 9301107,
|
||||
errMsg: 'requestMerchantTransfer:fail cancel'
|
||||
}
|
||||
|
||||
export const REQUEST_MERCHANT_TRANSFER_PARSE_RESPONSE_ERROR : RequestMerchantTransferError = {
|
||||
errCode: 9301108,
|
||||
errMsg: 'requestMerchantTransfer:fail parse response error'
|
||||
}
|
||||
|
||||
export const REQUEST_MERCHANT_TRANSFER_OPEN_SDK_ERROR : RequestMerchantTransferError = {
|
||||
errCode: 9301109,
|
||||
errMsg: 'requestMerchantTransfer:fail open sdk error'
|
||||
}
|
||||
|
||||
export const REQUEST_MERCHANT_TRANSFER_BUSINESS_FAILED : RequestMerchantTransferError = {
|
||||
errCode: 9301110,
|
||||
errMsg: 'requestMerchantTransfer:fail business failed'
|
||||
}
|
||||
|
||||
export const REQUEST_MERCHANT_TRANSFER_OK = 'requestMerchantTransfer:ok'
|
||||
@@ -12,7 +12,19 @@
|
||||
|
||||
## 说明
|
||||
|
||||
uview-plus,是uni-app全面兼容vue3/nvue/鸿蒙/uni-app-x的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水。uview-plus是基于uView2.x移植的支持vue3的版本,感谢uView。
|
||||
【文档有广告,不喜勿入谢谢】
|
||||
|
||||
uview-plus,是uni-app全面兼容vue3/nvue/鸿蒙/uni-app-x(已经发布https://ext.dcloud.net.cn/plugin?name=uview-ultra)的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水。uview-plus是基于uView2.x移植的支持vue3的版本,感谢uView。
|
||||
|
||||
## 版本
|
||||
|
||||
### uview-plus v3 (uni-app版本)
|
||||
1. 保持选项式API,尽可能兼容原有uview2各种API,比如mixins,方便大家升级更容易;
|
||||
2. 新手友好,保持原汁原味js,二次开发方便,未使用typescript重写,但带有独立的类型申明包;
|
||||
|
||||
### uview-ultra v4 (uni-app-x版本)
|
||||
1. 尽可能保持原有组件的API一致,方便大家升级;
|
||||
2. 使用uts + 组合式API架构级全新重构,但仍然保持源码的简易,不封装过于复杂,便于修改。
|
||||
|
||||
## 可视化设计
|
||||
|
||||
@@ -32,7 +44,13 @@ uview-plus现已推出免费可视化设计,可以方便的进行页面可视
|
||||
您可以通过**微信**扫码,查看最佳的演示效果。
|
||||
<br>
|
||||
<br>
|
||||
<img src="https://uview-plus.jiangruyi.com/common/h5_qrcode.png" width="220" height="220" >
|
||||
|
||||
| H5 | 微信小程序 | 安卓 | 鸿蒙Next(已上架) |
|
||||
| --- | --- | --- | --- |
|
||||
| <img src="https://uview-plus.jiangruyi.com/common/h5_qrcode.png" width="160" height="160" /> | <img src="https://uview-plus.jiangruyi.com/common/weixin_mini_qrcode.jpg" width="160" height="160" /> | <img src="https://uview-plus.jiangruyi.com/common/android_qrcode.png" width="160" height="160" /> | <img src="https://uview-plus.jiangruyi.com/common/hos_qrcode.jpg" width="160" height="160" /> |
|
||||
<br>
|
||||
<img src="https://uview-plus.jiangruyi.com/common/hos.png">
|
||||
|
||||
|
||||
## 链接
|
||||
|
||||
@@ -41,9 +59,6 @@ uview-plus现已推出免费可视化设计,可以方便的进行页面可视
|
||||
- [升级指南](https://uview-plus.jiangruyi.com/components/changeGuide.html)
|
||||
- [关于我们](https://uview-plus.jiangruyi.com/cooperation/about.html)
|
||||
|
||||
## 交流反馈
|
||||
|
||||
欢迎加入我们的QQ群交流反馈:[点此跳转](https://uview-plus.jiangruyi.com/components/addQQGroup.html)
|
||||
|
||||
## 关于PR
|
||||
|
||||
@@ -72,3 +87,4 @@ uview-plus现已推出免费可视化设计,可以方便的进行页面可视
|
||||
## 版权信息
|
||||
uview-plus遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uview-plus应用到您的产品中。
|
||||
|
||||
Root 组件基于上游项目 `uni-ku/root` 二次开发,已按 MIT 要求保留其原始授权文件:`libs/root/LICENSE`。
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<view class="u-action-sheet-data">
|
||||
<view class="u-action-sheet-data__trigger">
|
||||
<slot name="trigger"></slot>
|
||||
<up-input
|
||||
v-if="!$slots['trigger']"
|
||||
:modelValue="current"
|
||||
disabled
|
||||
disabledColor="#ffffff"
|
||||
:placeholder="title"
|
||||
border="none"
|
||||
></up-input>
|
||||
<view @click="show = true"
|
||||
class="u-action-sheet-data__trigger__cover"></view>
|
||||
</view>
|
||||
<up-action-sheet
|
||||
:show="show"
|
||||
:actions="options"
|
||||
:title="title"
|
||||
safeAreaInsetBottom
|
||||
:description="description"
|
||||
@close="show = false"
|
||||
@select="select"
|
||||
>
|
||||
</up-action-sheet>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
valueKey: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'name'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
current: '',
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.modelValue) {
|
||||
this.options.forEach((ele) => {
|
||||
if (ele[this.valueKey] == this.modelValue) {
|
||||
this.current = ele[this.labelKey]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
watch: {
|
||||
modelValue() {
|
||||
this.options.forEach((ele) => {
|
||||
if (ele[this.valueKey] == this.modelValue) {
|
||||
this.current = ele[this.labelKey]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hideKeyboard() {
|
||||
uni.hideKeyboard()
|
||||
},
|
||||
select(e) {
|
||||
this.$emit('update:modelValue', e[this.valueKey])
|
||||
this.current = e[this.labelKey]
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.u-action-sheet-data {
|
||||
&__trigger {
|
||||
position: relative;
|
||||
&__cover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-20 16:44:35
|
||||
* @FilePath : /u-view2.0/uview-ui/libs/config/props/actionSheet.js
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-08-16 10:52:35
|
||||
* @FilePath : /uview-plus/libs/config/props/actionSheet.js
|
||||
*/
|
||||
export default {
|
||||
// action-sheet组件
|
||||
@@ -14,6 +14,8 @@ export default {
|
||||
title: '',
|
||||
description: '',
|
||||
actions: [],
|
||||
nameKey: 'name',
|
||||
subnameKey: 'subnameKey',
|
||||
index: '',
|
||||
cancelText: '',
|
||||
closeOnClickAction: true,
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-08-16 10:52:35
|
||||
* @FilePath : /uview-plus/libs/config/props/props.js
|
||||
*/
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import ActionSheetDefaultProps from './actionSheet'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(ActionSheetDefaultProps)
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 操作菜单是否展示 (默认false)
|
||||
@@ -23,6 +33,16 @@ export const props = defineMixin({
|
||||
type: Array,
|
||||
default: () => defProps.actionSheet.actions
|
||||
},
|
||||
// 选项名称key
|
||||
nameKey: {
|
||||
type: String,
|
||||
default: () => defProps.actionSheet.nameKey
|
||||
},
|
||||
// 选项子名称key
|
||||
subnameKey: {
|
||||
type: String,
|
||||
default: () => defProps.actionSheet.subnameKey
|
||||
},
|
||||
// 取消按钮的文字,不为空时显示按钮
|
||||
cancelText: {
|
||||
type: String,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
<template>
|
||||
<u-popup
|
||||
:show="show"
|
||||
@@ -8,32 +7,39 @@
|
||||
:round="round"
|
||||
>
|
||||
<view class="u-action-sheet">
|
||||
<!-- 顶部标题区域 -->
|
||||
<view
|
||||
class="u-action-sheet__header"
|
||||
v-if="title"
|
||||
>
|
||||
<text class="u-action-sheet__header__title u-line-1">{{title}}</text>
|
||||
<text
|
||||
class="u-action-sheet__header__title u-line-1"
|
||||
:style="titleDynamicStyle"
|
||||
>{{title}}</text>
|
||||
<view
|
||||
class="u-action-sheet__header__icon-wrap"
|
||||
@tap.stop="cancel"
|
||||
>
|
||||
<u-icon
|
||||
<up-icon
|
||||
name="close"
|
||||
size="17"
|
||||
color="#c8c9cc"
|
||||
:color="closeIconColor"
|
||||
bold
|
||||
></u-icon>
|
||||
></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 描述信息 -->
|
||||
<text
|
||||
class="u-action-sheet__description"
|
||||
:style="[{
|
||||
marginTop: `${title && description ? 0 : '18px'}`
|
||||
}]"
|
||||
}, descriptionDynamicStyle]"
|
||||
v-if="description"
|
||||
>{{description}}</text>
|
||||
<slot>
|
||||
<u-line v-if="description"></u-line>
|
||||
<!-- 分割线 -->
|
||||
<u-line v-if="description" :color="dividerColor"></u-line>
|
||||
<!-- 操作项列表 -->
|
||||
<scroll-view scroll-y class="u-action-sheet__item-wrap" :style="{maxHeight: wrapMaxHeight}">
|
||||
<view :key="index" v-for="(item, index) in actions">
|
||||
<!-- #ifdef MP -->
|
||||
@@ -62,17 +68,20 @@
|
||||
@tap.stop="selectHandler(index)"
|
||||
:hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
|
||||
:hover-stay-time="150"
|
||||
:style="getItemHoverStyle(index)"
|
||||
>
|
||||
<template v-if="!item.loading">
|
||||
<text
|
||||
class="u-action-sheet__item-wrap__item__name"
|
||||
:style="[itemStyle(index)]"
|
||||
>{{ item.name }}</text>
|
||||
>{{ item[nameKey] }}</text>
|
||||
<text
|
||||
v-if="item.subname"
|
||||
v-if="item[subnameKey]"
|
||||
class="u-action-sheet__item-wrap__item__subname"
|
||||
>{{ item.subname }}</text>
|
||||
:style="[subnameStyle(index)]"
|
||||
>{{ item[subnameKey] }}</text>
|
||||
</template>
|
||||
<!-- 加载状态图标 -->
|
||||
<u-loading-icon
|
||||
v-else
|
||||
custom-class="van-action-sheet__loading"
|
||||
@@ -83,21 +92,25 @@
|
||||
<!-- #ifdef MP -->
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
<u-line v-if="index !== actions.length - 1"></u-line>
|
||||
<!-- 选项间分割线 -->
|
||||
<u-line v-if="index !== actions.length - 1" :color="dividerColor"></u-line>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</slot>
|
||||
<!-- 取消按钮前的分割区域 -->
|
||||
<u-gap
|
||||
bgColor="#eaeaec"
|
||||
:bgColor="cancelGapColor"
|
||||
height="6"
|
||||
v-if="cancelText"
|
||||
></u-gap>
|
||||
<!-- 取消按钮 -->
|
||||
<view class="u-action-sheet__item-wrap__item u-action-sheet__cancel"
|
||||
hover-class="u-action-sheet--hover" @tap="cancel" v-if="cancelText">
|
||||
<text
|
||||
@touchmove.stop.prevent
|
||||
:hover-stay-time="150"
|
||||
class="u-action-sheet__cancel-text"
|
||||
:style="cancelTextDynamicStyle"
|
||||
>{{cancelText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -114,7 +127,7 @@
|
||||
/**
|
||||
* ActionSheet 操作菜单
|
||||
* @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
|
||||
* @tutorial https://ijry.github.io/uview-plus/components/actionSheet.html
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/actionSheet.html
|
||||
*
|
||||
* @property {Boolean} show 操作菜单是否展示 (默认 false )
|
||||
* @property {String} title 操作菜单标题
|
||||
@@ -154,20 +167,54 @@
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
titleDynamicStyle() {
|
||||
return {
|
||||
color: this.upThemeVar('--up-main-color', '#303133')
|
||||
}
|
||||
},
|
||||
descriptionDynamicStyle() {
|
||||
return {
|
||||
color: this.upThemeVar('--up-tips-color', '#909193')
|
||||
}
|
||||
},
|
||||
closeIconColor() {
|
||||
return this.upThemeVar('--up-content-color', '#606266')
|
||||
},
|
||||
dividerColor() {
|
||||
return this.upThemeVar('--up-border-color', this.upThemeIsDark ? '#3a3a3c' : '#dadbde')
|
||||
},
|
||||
cancelGapColor() {
|
||||
return this.upThemeVar('--up-gap-bg-color', this.upThemeIsDark ? '#111111' : '#eaeaec')
|
||||
},
|
||||
cancelTextDynamicStyle() {
|
||||
return {
|
||||
color: this.upThemeVar('--up-main-color', '#303133')
|
||||
}
|
||||
},
|
||||
// 操作项目的样式
|
||||
itemStyle() {
|
||||
return (index) => {
|
||||
let style = {};
|
||||
const style = {
|
||||
color: this.upThemeVar('--up-main-color', '#303133')
|
||||
};
|
||||
if (this.actions[index].color) style.color = this.actions[index].color
|
||||
if (this.actions[index].fontSize) style.fontSize = addUnit(this.actions[index].fontSize)
|
||||
// 选项被禁用的样式
|
||||
if (this.actions[index].disabled) style.color = '#c0c4cc'
|
||||
if (this.actions[index].disabled) style.color = this.upThemeVar('--up-light-color', '#c0c4cc')
|
||||
return style;
|
||||
}
|
||||
},
|
||||
subnameStyle() {
|
||||
return (index) => ({
|
||||
color: this.actions[index].disabled
|
||||
? this.upThemeVar('--up-light-color', '#c0c4cc')
|
||||
: this.upThemeVar('--up-tips-color', '#909193')
|
||||
})
|
||||
}
|
||||
},
|
||||
emits: ["close", "select", "update:show"],
|
||||
methods: {
|
||||
// 关闭操作菜单事件处理
|
||||
closeHandler() {
|
||||
// 允许点击遮罩关闭时,才发出close事件
|
||||
if(this.closeOnClickOverlay) {
|
||||
@@ -180,6 +227,7 @@
|
||||
this.$emit('update:show', false)
|
||||
this.$emit('close')
|
||||
},
|
||||
// 选择操作项处理
|
||||
selectHandler(index) {
|
||||
const item = this.actions[index]
|
||||
if (item && !item.disabled && !item.loading) {
|
||||
@@ -190,30 +238,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
// 动态处理Hover时候第一个item的圆角
|
||||
getItemHoverStyle(index) {
|
||||
if (index === 0 && this.round && !this.title && !this.description) {
|
||||
return {
|
||||
borderTopLeftRadius: `${this.round}px`,
|
||||
borderTopRightRadius: `${this.round}px`,
|
||||
}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
$u-action-sheet-reset-button-width:100% !default;
|
||||
$u-action-sheet-title-font-size: 16px !default;
|
||||
$u-action-sheet-title-padding: 12px 30px !default;
|
||||
$u-action-sheet-title-color: $u-main-color !default;
|
||||
$u-action-sheet-title-color: var(--up-main-color, #303133) !default;
|
||||
$u-action-sheet-header-icon-wrap-right:15px !default;
|
||||
$u-action-sheet-header-icon-wrap-top:15px !default;
|
||||
$u-action-sheet-description-font-size:13px !default;
|
||||
$u-action-sheet-description-color:14px !default;
|
||||
$u-action-sheet-description-color: var(--up-tips-color, #909193) !default;
|
||||
$u-action-sheet-description-margin: 18px 15px !default;
|
||||
$u-action-sheet-item-wrap-item-padding:17px !default;
|
||||
$u-action-sheet-item-wrap-name-font-size:16px !default;
|
||||
$u-action-sheet-item-wrap-subname-font-size:13px !default;
|
||||
$u-action-sheet-item-wrap-subname-color: #c0c4cc !default;
|
||||
$u-action-sheet-item-wrap-subname-color: var(--up-tips-color, #909193) !default;
|
||||
$u-action-sheet-item-wrap-subname-margin-top:10px !default;
|
||||
$u-action-sheet-cancel-text-font-size:16px !default;
|
||||
$u-action-sheet-cancel-text-color:$u-content-color !default;
|
||||
$u-action-sheet-cancel-text-color: var(--up-main-color, #303133) !default;
|
||||
$u-action-sheet-cancel-text-font-size:15px !default;
|
||||
$u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default;
|
||||
$u-action-sheet-cancel-text-hover-background-color: var(--up-hover-bg-color, rgb(242, 243, 245)) !default;
|
||||
|
||||
.u-reset-button {
|
||||
width: $u-action-sheet-reset-button-width;
|
||||
@@ -239,8 +296,9 @@
|
||||
}
|
||||
|
||||
&__description {
|
||||
display: block;
|
||||
font-size: $u-action-sheet-description-font-size;
|
||||
color: $u-tips-color;
|
||||
color: $u-action-sheet-description-color;
|
||||
margin: $u-action-sheet-description-margin;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -256,7 +314,7 @@
|
||||
|
||||
&__name {
|
||||
font-size: $u-action-sheet-item-wrap-name-font-size;
|
||||
color: $u-main-color;
|
||||
color: var(--up-main-color, #303133);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<style scoped lang="scss">
|
||||
.agreement-content {
|
||||
width: 100%;;
|
||||
display: inline-block;
|
||||
flex-direction: column;
|
||||
color: var(--up-main-color, #303133);
|
||||
.agreement-url {
|
||||
display: inline-block;
|
||||
color: var(--up-primary, #2979ff);
|
||||
// #ifdef H5
|
||||
cursor: pointer;
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<view class="up-agreement">
|
||||
<up-modal v-model:show="show" showCancelButton @confirm="confirm" @cancel="close" confirmText="阅读并同意">
|
||||
<view class="agreement-content">
|
||||
<slot>
|
||||
我们非常重视您的个人信息和隐私保护。为了更好地保障您的个人权益,在您使用我们的产品前,
|
||||
请务必审慎阅读《<text class="agreement-url" @click="urlClick('urlProtocol')">用户协议</text>》
|
||||
和《<text class="agreement-url" @click="urlClick('urlPrivacy')">隐私政策</text>》内的所有条款,
|
||||
尤其是:1.我们对您的个人信息的收集/保存/使用/对外提供/保护等规则条款,以及您的用户权利等条款;2. 约定我们的限制责任、免责
|
||||
条款;3.其他以颜色或加粗进行标识的重要条款。如您对以上协议有任何疑问,请先不要同意,您点击“同意并继续”的行为即表示您已阅读
|
||||
完毕并同意以上协议的全部内容。
|
||||
</slot>
|
||||
</view>
|
||||
</up-modal>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'up-agreement',
|
||||
props: {
|
||||
urlProtocol: {
|
||||
type: String,
|
||||
default: '/pages/user_agreement/agreement/info?title=用户协议'
|
||||
},
|
||||
urlPrivacy: {
|
||||
type: String,
|
||||
default: '/pages/user_agreement/agreement/info?title=隐私政策'
|
||||
},
|
||||
},
|
||||
emits: ['confirm'],
|
||||
data() {
|
||||
return {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
// #ifdef H5
|
||||
window.opener = null;
|
||||
window.close();
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
plus.runtime.quit();
|
||||
// #endif
|
||||
},
|
||||
confirm() {
|
||||
this.show = false;
|
||||
this.$emit('confirm', 1);
|
||||
},
|
||||
showModal() {
|
||||
this.show = true;
|
||||
},
|
||||
urlClick(type) {
|
||||
uni.navigateTo({
|
||||
url: this[type]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-20 16:47:24
|
||||
* @FilePath : /u-view2.0/uview-ui/libs/config/props/album.js
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-08-16 16:32:24
|
||||
* @FilePath : /uview-plus/libs/config/props/album.js
|
||||
*/
|
||||
export default {
|
||||
// album 组件
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
/*
|
||||
* @Author : jry
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-08-16 16:35:24
|
||||
* @FilePath : /uview-plus/components/u-album/props.js
|
||||
*/
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import AlbumDefaultProps from './album'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(AlbumDefaultProps)
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 图片地址,Array<String>|Array<Object>形式
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="u-album">
|
||||
<!-- 相册行容器,每行显示 rowCount 个图片 -->
|
||||
<view
|
||||
class="u-album__row"
|
||||
ref="u-album__row"
|
||||
@@ -8,13 +9,15 @@
|
||||
:key="index"
|
||||
:style="{flexWrap: autoWrap ? 'wrap' : 'nowrap'}"
|
||||
>
|
||||
<!-- 图片包装容器 -->
|
||||
<view
|
||||
class="u-album__row__wrapper"
|
||||
v-for="(item, index1) in arr"
|
||||
:key="index1"
|
||||
:style="[imageStyle(index + 1, index1 + 1)]"
|
||||
@tap="previewFullImage ? onPreviewTap($event, getSrc(item)) : ''"
|
||||
@tap="onPreviewTap($event, getSrc(item))"
|
||||
>
|
||||
<!-- 图片显示 -->
|
||||
<image
|
||||
:src="getSrc(item)"
|
||||
:mode="
|
||||
@@ -32,6 +35,7 @@
|
||||
}
|
||||
]"
|
||||
></image>
|
||||
<!-- 超出最大显示数量时的更多提示 -->
|
||||
<view
|
||||
v-if="
|
||||
showMore &&
|
||||
@@ -64,14 +68,14 @@ import { mixin } from '../../libs/mixin/mixin';
|
||||
import { addUnit, sleep } from '../../libs/function/index';
|
||||
import test from '../../libs/function/test';
|
||||
// #ifdef APP-NVUE
|
||||
// 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
|
||||
// 不支持百分比单位,这里需要通过dom查询组件的宽度
|
||||
const dom = uni.requireNativePlugin('dom')
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* Album 相册
|
||||
* @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码
|
||||
* @tutorial https://ijry.github.io/uview-plus/components/album.html
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/album.html
|
||||
*
|
||||
* @property {Array} urls 图片地址列表 Array<String>|Array<Object>形式
|
||||
* @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
|
||||
@@ -108,14 +112,20 @@ export default {
|
||||
urls: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
// 当只有一张图片时,获取图片尺寸信息
|
||||
if (newVal.length === 1) {
|
||||
this.getImageRect()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
emits: ["albumWidth"],
|
||||
computed: {
|
||||
/**
|
||||
* 计算图片样式
|
||||
* @param {Number} index1 - 行索引
|
||||
* @param {Number} index2 - 列索引
|
||||
* @returns {Object} 图片样式对象
|
||||
*/
|
||||
imageStyle() {
|
||||
return (index1, index2) => {
|
||||
const { space, rowCount, multipleSize, urls } = this,
|
||||
@@ -139,11 +149,16 @@ export default {
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 将数组划分为二维数组
|
||||
/**
|
||||
* 将图片地址数组划分为二维数组,用于按行显示
|
||||
* @returns {Array} 二维数组,每个子数组代表一行图片
|
||||
*/
|
||||
showUrls() {
|
||||
if (this.autoWrap) {
|
||||
// 自动换行模式下,所有图片放在一行中显示
|
||||
return [ this.urls.slice(0, this.maxCount) ];
|
||||
} else {
|
||||
// 固定行数模式下,按 rowCount 分割图片
|
||||
const arr = []
|
||||
this.urls.map((item, index) => {
|
||||
// 限制最大展示数量
|
||||
@@ -160,18 +175,29 @@ export default {
|
||||
return arr
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 计算图片宽度
|
||||
* @returns {String} 图片宽度样式值
|
||||
*/
|
||||
imageWidth() {
|
||||
return addUnit(
|
||||
this.urls.length === 1 ? this.singleWidth : this.multipleSize, this.unit
|
||||
)
|
||||
},
|
||||
/**
|
||||
* 计算图片高度
|
||||
* @returns {String} 图片高度样式值
|
||||
*/
|
||||
imageHeight() {
|
||||
return addUnit(
|
||||
this.urls.length === 1 ? this.singleHeight : this.multipleSize, this.unit
|
||||
)
|
||||
},
|
||||
// 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度
|
||||
// 因为用户在某些特殊的情况下,需要让文字与相册的宽度相等,所以这里事件的形式对外发送
|
||||
/**
|
||||
* 计算相册总宽度,用于外部组件对齐
|
||||
* 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度
|
||||
* @returns {Number} 相册宽度
|
||||
*/
|
||||
albumWidth() {
|
||||
let width = 0
|
||||
if (this.urls.length === 1) {
|
||||
@@ -185,59 +211,100 @@ export default {
|
||||
return width
|
||||
}
|
||||
},
|
||||
emits: ['preview', 'albumWidth'],
|
||||
methods: {
|
||||
addUnit,
|
||||
// 预览图片
|
||||
/**
|
||||
* 点击图片预览
|
||||
* @param {Event} e - 点击事件对象
|
||||
* @param {String} url - 当前点击图片的地址
|
||||
*/
|
||||
onPreviewTap(e, url) {
|
||||
// 获取所有图片地址
|
||||
const urls = this.urls.map((item) => {
|
||||
return this.getSrc(item)
|
||||
})
|
||||
if (this.previewFullImage) {
|
||||
// 使用系统默认预览图片功能
|
||||
uni.previewImage({
|
||||
current: url,
|
||||
urls
|
||||
})
|
||||
// 是否阻止事件传播
|
||||
this.stop && this.preventEvent(e)
|
||||
} else {
|
||||
// 发送自定义预览事件
|
||||
this.$emit('preview', {
|
||||
urls,
|
||||
currentIndex: urls.indexOf(url)
|
||||
})
|
||||
}
|
||||
},
|
||||
// 获取图片的路径
|
||||
/**
|
||||
* 获取图片地址
|
||||
* @param {String|Object} item - 图片项,可以是字符串或对象
|
||||
* @returns {String} 图片地址
|
||||
*/
|
||||
getSrc(item) {
|
||||
return test.object(item)
|
||||
? (this.keyName && item[this.keyName]) || item.src
|
||||
: item
|
||||
},
|
||||
// 单图时,获取图片的尺寸
|
||||
// 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸
|
||||
// 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent)
|
||||
/**
|
||||
* 单图时,获取图片的尺寸
|
||||
* 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸
|
||||
* 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent)
|
||||
*/
|
||||
getImageRect() {
|
||||
const src = this.getSrc(this.urls[0])
|
||||
uni.getImageInfo({
|
||||
src,
|
||||
success: (res) => {
|
||||
let singleSize = this.singleSize;
|
||||
// 单位
|
||||
let unit = '';
|
||||
if (Number.isNaN(Number(this.singleSize))) {
|
||||
// 大小中有字符 则记录字符
|
||||
unit = this.singleSize.replace(/\d+/g, ''); // 单位
|
||||
singleSize = Number(this.singleSize.replace(/\D+/g, ''), 10); // 具体值
|
||||
}
|
||||
|
||||
// 判断图片横向还是竖向展示方式
|
||||
const isHorizotal = res.width >= res.height
|
||||
this.singleWidth = isHorizotal
|
||||
? this.singleSize
|
||||
: (res.width / res.height) * this.singleSize
|
||||
? singleSize
|
||||
: (res.width / res.height) * singleSize
|
||||
this.singleHeight = !isHorizotal
|
||||
? this.singleSize
|
||||
? singleSize
|
||||
: (res.height / res.width) * this.singleWidth
|
||||
|
||||
// 如果有单位统一设置单位
|
||||
if(unit != null && unit !== ''){
|
||||
this.singleWidth = this.singleWidth + unit
|
||||
this.singleHeight = this.singleHeight + unit
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
// 获取图片信息失败时,通过组件宽度计算
|
||||
this.getComponentWidth()
|
||||
}
|
||||
})
|
||||
},
|
||||
// 获取组件的宽度
|
||||
/**
|
||||
* 获取组件的宽度,用于计算单图显示尺寸
|
||||
*/
|
||||
async getComponentWidth() {
|
||||
// 延时一定时间,以获取dom尺寸
|
||||
await sleep(30)
|
||||
// #ifndef APP-NVUE
|
||||
// H5、小程序等平台通过 $uGetRect 获取组件宽度
|
||||
this.$uGetRect('.u-album__row').then((size) => {
|
||||
this.singleWidth = size.width * this.singlePercent
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
// NVUE 平台通过 dom 插件获取组件宽度
|
||||
// 这里ref="u-album__row"所在的标签为通过for循环出来,导致this.$refs['u-album__row']是一个数组
|
||||
const ref = this.$refs['u-album__row'][0]
|
||||
ref &&
|
||||
@@ -251,8 +318,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../libs/css/components.scss';
|
||||
|
||||
.u-album {
|
||||
@include flex(column);
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-20 16:48:53
|
||||
* @FilePath : /u-view2.0/uview-ui/libs/config/props/alert.js
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-08-17 17:23:53
|
||||
* @FilePath : /uview-plus/libs/config/props/alert.js
|
||||
*/
|
||||
export default {
|
||||
// alert警告组件
|
||||
@@ -17,6 +17,10 @@ export default {
|
||||
showIcon: false,
|
||||
effect: 'light',
|
||||
center: false,
|
||||
fontSize: 14
|
||||
fontSize: 14,
|
||||
transitionMode: 'fade',
|
||||
duration: 0,
|
||||
icon: '',
|
||||
value: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
/*
|
||||
* @Author : jry
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-08-17 17:23:53
|
||||
* @FilePath : /uview-plus/libs/config/props/props.js
|
||||
*/
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import AlertDefaultProps from './alert'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(AlertDefaultProps)
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 显示文字
|
||||
@@ -41,6 +52,26 @@ export const props = defineMixin({
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.alert.fontSize
|
||||
},
|
||||
// 动画类型
|
||||
transitionMode: {
|
||||
type: [String],
|
||||
default: () => defProps.alert.transitionMode
|
||||
},
|
||||
// 自动定时关闭毫秒
|
||||
duration: {
|
||||
type: [Number],
|
||||
default: () => defProps.alert.duration
|
||||
},
|
||||
// 自定义图标
|
||||
icon: {
|
||||
type: [String],
|
||||
default: () => defProps.alert.icon
|
||||
},
|
||||
// 是否显示
|
||||
modelValue: {
|
||||
type: [Boolean],
|
||||
default: () => defProps.alert.value
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<u-transition
|
||||
mode="fade"
|
||||
<up-transition
|
||||
:mode="transitionMode"
|
||||
:show="show"
|
||||
>
|
||||
<view
|
||||
@@ -9,22 +9,25 @@
|
||||
@tap.stop="clickHandler"
|
||||
:style="[addStyle(customStyle)]"
|
||||
>
|
||||
<!-- 左侧图标 -->
|
||||
<view
|
||||
class="u-alert__icon"
|
||||
v-if="showIcon"
|
||||
>
|
||||
<u-icon
|
||||
<up-icon
|
||||
:name="iconName"
|
||||
size="18"
|
||||
:color="iconColor"
|
||||
></u-icon>
|
||||
></up-icon>
|
||||
</view>
|
||||
<!-- 内容区域 -->
|
||||
<view
|
||||
class="u-alert__content"
|
||||
:style="[{
|
||||
paddingRight: closable ? '20px' : 0
|
||||
}]"
|
||||
>
|
||||
<!-- 标题 -->
|
||||
<text
|
||||
class="u-alert__content__title"
|
||||
v-if="title"
|
||||
@@ -34,6 +37,7 @@
|
||||
}]"
|
||||
:class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
|
||||
>{{ title }}</text>
|
||||
<!-- 描述信息 -->
|
||||
<text
|
||||
class="u-alert__content__desc"
|
||||
v-if="description"
|
||||
@@ -44,19 +48,22 @@
|
||||
:class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
|
||||
>{{ description }}</text>
|
||||
</view>
|
||||
<!-- 关闭按钮 -->
|
||||
<view
|
||||
class="u-alert__close"
|
||||
v-if="closable"
|
||||
@tap.stop="closeHandler"
|
||||
>
|
||||
<u-icon
|
||||
<slot name="close">
|
||||
<up-icon
|
||||
name="close"
|
||||
:color="iconColor"
|
||||
size="15"
|
||||
></u-icon>
|
||||
></up-icon>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</u-transition>
|
||||
</up-transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -67,7 +74,7 @@
|
||||
/**
|
||||
* Alert 警告提示
|
||||
* @description 警告提示,展现需要关注的信息。
|
||||
* @tutorial https://ijry.github.io/uview-plus/components/alertTips.html
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/alertTips.html
|
||||
*
|
||||
* @property {String} title 显示的文字
|
||||
* @property {String} type 使用预设的颜色 (默认 'warning' )
|
||||
@@ -78,24 +85,34 @@
|
||||
* @property {Boolean} center 文字是否居中 (默认 false )
|
||||
* @property {String | Number} fontSize 字体大小 (默认 14 )
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
* @property {String} transitionMode 过渡动画模式 (默认 'fade' )
|
||||
* @property {String | Number} duration 自动关闭延时(毫秒),设置为0或负数则不自动关闭 (默认 0 )
|
||||
* @property {String} icon 自定义图标名称,优先级高于type默认图标
|
||||
* @property {Boolean} modelValue/v-model 绑定值,控制是否显示 (默认 true )
|
||||
* @event {Function} click 点击组件时触发
|
||||
* @event {Function} close 点击关闭按钮时触发
|
||||
* @example <u-alert :title="title" type = "warning" :closable="closable" :description = "description"></u-alert>
|
||||
* @event {Function} closed 关闭动画结束时触发
|
||||
* @example <up-alert :title="title" type = "warning" :closable="closable" :description = "description"></up-alert>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-alert',
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
// 控制组件显示隐藏
|
||||
show: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 根据不同的主题类型返回对应的图标颜色
|
||||
iconColor() {
|
||||
return this.effect === 'light' ? this.type : '#fff'
|
||||
},
|
||||
// 不同主题对应不同的图标
|
||||
iconName() {
|
||||
// 如果用户自定义了图标,则优先使用自定义图标
|
||||
if (this.icon) return this.icon;
|
||||
|
||||
switch (this.type) {
|
||||
case 'success':
|
||||
return 'checkmark-circle-fill';
|
||||
@@ -117,25 +134,50 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
emits: ["click","close"],
|
||||
emits: ["click","close", "closed", "update:modelValue"],
|
||||
watch: {
|
||||
modelValue: {
|
||||
handler(newVal) {
|
||||
this.show = newVal;
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
show: {
|
||||
handler(newVal) {
|
||||
this.$emit('update:modelValue', newVal);
|
||||
|
||||
// 如果是从显示到隐藏,且启用了自动关闭功能
|
||||
if (!newVal && this.duration > 0) {
|
||||
this.$emit('closed');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 如果设置了自动关闭时间,则在指定时间后自动关闭
|
||||
if (this.duration > 0) {
|
||||
setTimeout(() => {
|
||||
this.closeHandler();
|
||||
}, this.duration);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addUnit,
|
||||
addStyle,
|
||||
// 点击内容
|
||||
// 点击内容区域触发click事件
|
||||
clickHandler() {
|
||||
this.$emit('click')
|
||||
},
|
||||
// 点击关闭按钮
|
||||
// 点击关闭按钮触发close事件并隐藏组件
|
||||
closeHandler() {
|
||||
this.show = false
|
||||
this.$emit('close')
|
||||
this.$emit('close');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-alert {
|
||||
position: relative;
|
||||
@@ -153,7 +195,7 @@
|
||||
}
|
||||
|
||||
&--primary--light {
|
||||
background-color: #ecf5ff;
|
||||
background-color: var(--up-primary-light, #ecf5ff);
|
||||
}
|
||||
|
||||
&--error--dark {
|
||||
@@ -161,7 +203,7 @@
|
||||
}
|
||||
|
||||
&--error--light {
|
||||
background-color: #FEF0F0;
|
||||
background-color: var(--up-error-light, #FEF0F0);
|
||||
}
|
||||
|
||||
&--success--dark {
|
||||
@@ -169,7 +211,7 @@
|
||||
}
|
||||
|
||||
&--success--light {
|
||||
background-color: #f5fff0;
|
||||
background-color: var(--up-success-light, #f5fff0);
|
||||
}
|
||||
|
||||
&--warning--dark {
|
||||
@@ -177,7 +219,7 @@
|
||||
}
|
||||
|
||||
&--warning--light {
|
||||
background-color: #FDF6EC;
|
||||
background-color: var(--up-warning-light, #FDF6EC);
|
||||
}
|
||||
|
||||
&--info--dark {
|
||||
@@ -185,7 +227,7 @@
|
||||
}
|
||||
|
||||
&--info--light {
|
||||
background-color: #f4f4f5;
|
||||
background-color: var(--up-info-light, #f4f4f5);
|
||||
}
|
||||
|
||||
&__icon {
|
||||
@@ -197,24 +239,22 @@
|
||||
flex: 1;
|
||||
|
||||
&__title {
|
||||
color: $u-main-color;
|
||||
color: var(--up-main-color, $u-main-color);
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
color: $u-main-color;
|
||||
color: var(--up-main-color, $u-main-color);
|
||||
font-size: 14px;
|
||||
flex-wrap: wrap;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&__title--dark,
|
||||
&__desc--dark {
|
||||
color: #FFFFFF;
|
||||
color: var(--up-white, #FFFFFF);
|
||||
}
|
||||
|
||||
&__text--primary--light,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-20 16:49:55
|
||||
* @FilePath : /u-view2.0/uview-ui/libs/config/props/avatarGroup.js
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/avatarGroup.js
|
||||
*/
|
||||
export default {
|
||||
// avatarGroup 组件
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import AvatarGroupDefaultProps from './avatarGroup'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(AvatarGroupDefaultProps)
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 头像图片组
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
/**
|
||||
* AvatarGroup 头像组
|
||||
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
|
||||
* @tutorial https://ijry.github.io/uview-plus/components/avatar.html
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/avatar.html
|
||||
*
|
||||
* @property {Array} urls 头像图片组 (默认 [] )
|
||||
* @property {String | Number} maxCount 最多展示的头像数量 ( 默认 5 )
|
||||
@@ -79,7 +79,6 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-avatar-group {
|
||||
@include flex;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-20 16:49:22
|
||||
* @FilePath : /u-view2.0/uview-ui/libs/config/props/avatar.js
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/avatar.js
|
||||
*/
|
||||
export default {
|
||||
// avatar 组件
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import AvatarDefaultProps from './avatar'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(AvatarDefaultProps)
|
||||
import test from '../../libs/function/test';
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
<!-- #ifndef MP-WEIXIN && MP-QQ && MP-BAIDU -->
|
||||
<template v-if="mpAvatar && allowMp"></template>
|
||||
<!-- #endif -->
|
||||
<u-icon
|
||||
<up-icon
|
||||
v-else-if="icon"
|
||||
:name="icon"
|
||||
:size="fontSize"
|
||||
:color="color"
|
||||
></u-icon>
|
||||
></up-icon>
|
||||
<up-text
|
||||
v-else-if="text"
|
||||
:text="text"
|
||||
@@ -63,7 +63,7 @@
|
||||
/**
|
||||
* Avatar 头像
|
||||
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
|
||||
* @tutorial https://ijry.github.io/uview-plus/components/avatar.html
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/avatar.html
|
||||
*
|
||||
* @property {String} src 头像路径,如加载失败,将会显示默认头像(不能为相对路径)
|
||||
* @property {String} shape 头像形状 ( circle (默认) | square)
|
||||
@@ -151,7 +151,6 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-avatar {
|
||||
@include flex;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-20 16:50:18
|
||||
* @FilePath : /u-view2.0/uview-ui/libs/config/props/backtop.js
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/backtop.js
|
||||
*/
|
||||
export default {
|
||||
// backtop组件
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import BackTopDefaultProps from './backtop'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(BackTopDefaultProps)
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 返回顶部的形状,circle-圆形,square-方形
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
v-if="!$slots.default && !$slots.$default"
|
||||
@click="backToTop"
|
||||
>
|
||||
<u-icon
|
||||
<up-icon
|
||||
:name="icon"
|
||||
:custom-style="iconStyle"
|
||||
></u-icon>
|
||||
></up-icon>
|
||||
<text
|
||||
v-if="text"
|
||||
class="u-back-top__text"
|
||||
@@ -111,7 +111,6 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../libs/css/components.scss';
|
||||
$u-back-top-flex:1 !default;
|
||||
$u-back-top-height:100% !default;
|
||||
$u-back-top-background-color:#E1E1E1 !default;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-23 19:51:50
|
||||
* @FilePath : /u-view2.0/uview-ui/libs/config/props/badge.js
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/badge.js
|
||||
*/
|
||||
export default {
|
||||
// 徽标数组件
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import BadgeDefaultProps from './badge'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(BadgeDefaultProps)
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 是否显示圆点
|
||||
|
||||
@@ -89,7 +89,6 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
$u-badge-primary: $u-primary !default;
|
||||
$u-badge-error: $u-error !default;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* @Author : ly
|
||||
* @Description :
|
||||
* @version : 4.0
|
||||
* @Date : 2026-02-02 23:54:58
|
||||
* @LastAuthor : ly
|
||||
* @lastTime : 2026-02-02 23:54:58
|
||||
* @FilePath : /uview-ultra/components/up-box/box.js
|
||||
*/
|
||||
export default {
|
||||
// box 组件
|
||||
box: {
|
||||
bgColors: ['#EEFCFF', '#FCF8FF', '#FDF8F2'],
|
||||
height: '160px',
|
||||
borderRadius: '6px',
|
||||
gap: '15px',
|
||||
leftIcon: '',
|
||||
leftTitle: '左',
|
||||
rightTopIcon: '',
|
||||
rightTopTitle: '右上',
|
||||
rightBottomIcon: '',
|
||||
rightBottomTitle: '右下'
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,60 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import { defineMixin } from '../../libs/vue.js'
|
||||
import BoxDefaultProps from './box.js'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(BoxDefaultProps)
|
||||
|
||||
export const propsBox = defineMixin({
|
||||
props: {
|
||||
// 背景色
|
||||
bgColors: {
|
||||
type: [Array],
|
||||
default: ['#EEFCFF', '#FCF8FF', '#FDF8F2']
|
||||
default: () => defProps.box.bgColors
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: [String],
|
||||
default: "160px"
|
||||
default: () => defProps.box.height
|
||||
},
|
||||
// 圆角
|
||||
borderRadius: {
|
||||
type: [String],
|
||||
default: "6px"
|
||||
default: () => defProps.box.borderRadius
|
||||
},
|
||||
// 间隔
|
||||
gap: {
|
||||
type: [String],
|
||||
default: "15px"
|
||||
default: () => defProps.box.gap
|
||||
},
|
||||
// 左侧图标
|
||||
leftIcon: {
|
||||
type: [String],
|
||||
default: () => defProps.box.leftIcon
|
||||
},
|
||||
// 左侧文案
|
||||
leftTitle: {
|
||||
type: [String],
|
||||
default: () => defProps.box.leftTitle
|
||||
},
|
||||
// 右上图标
|
||||
rightTopIcon: {
|
||||
type: [String],
|
||||
default: () => defProps.box.rightTopIcon
|
||||
},
|
||||
// 右上文案
|
||||
rightTopTitle: {
|
||||
type: [String],
|
||||
default: () => defProps.box.rightTopTitle
|
||||
},
|
||||
// 右下图标
|
||||
rightBottomIcon: {
|
||||
type: [String],
|
||||
default: () => defProps.box.rightBottomIcon
|
||||
},
|
||||
// 右下文案
|
||||
rightBottomTitle: {
|
||||
type: [String],
|
||||
default: () => defProps.box.rightBottomTitle
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,16 +1,31 @@
|
||||
<template>
|
||||
<view class="u-box" :style="[{height: height}, addStyle(customStyle)]">
|
||||
<view class="u-box__left" :style="{borderRadius: borderRadius, backgroundColor: bgColors[0]}">
|
||||
<slot name="left">左</slot>
|
||||
<slot name="left">
|
||||
<view class="flex flex-row items-center justify-center">
|
||||
<u-icon size="36" :name="leftIcon"></u-icon>
|
||||
<text class="ml-2 text-16px">{{leftTitle}}</text>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
<view class="u-box__gap" :style="{width: gap, height: height}"></view>
|
||||
<view class="u-box__right">
|
||||
<view class="u-box__right-top" :style="{borderRadius: borderRadius, backgroundColor: bgColors[1]}">
|
||||
<slot name="rightTop">右上</slot>
|
||||
<slot name="rightTop">
|
||||
<view class="flex flex-row items-center justify-center">
|
||||
<u-icon size="36" :name="rightTopIcon"></u-icon>
|
||||
<text class="ml-2 text-15px">{{rightTopTitle}}</text>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
<view class="u-box__right-gap" :style="{height: gap}"></view>
|
||||
<view class="u-box__right-bottom" :style="{borderRadius: borderRadius, backgroundColor: bgColors[2]}">
|
||||
<slot name="rightBottom">右下</slot>
|
||||
<slot name="rightBottom">
|
||||
<view class="flex flex-row items-center justify-center">
|
||||
<u-icon size="36" :name="rightBottomIcon"></u-icon>
|
||||
<text class="ml-2 text-15px">{{rightBottomTitle}}</text>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -51,7 +66,6 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-20 16:51:27
|
||||
* @FilePath : /u-view2.0/uview-ui/libs/config/props/button.js
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/button.js
|
||||
*/
|
||||
export default {
|
||||
// button组件
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import ButtonDefaultProps from './button'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(ButtonDefaultProps)
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 是否细边框
|
||||
|
||||
@@ -39,13 +39,13 @@
|
||||
>
|
||||
</template>
|
||||
<template v-else>
|
||||
<u-icon
|
||||
<up-icon
|
||||
v-if="icon"
|
||||
:name="icon"
|
||||
:color="iconColorCom"
|
||||
:size="textSize * 1.35"
|
||||
:customStyle="{ marginRight: '2px' }"
|
||||
></u-icon>
|
||||
></up-icon>
|
||||
<slot>
|
||||
<text
|
||||
class="u-button__text"
|
||||
@@ -87,12 +87,12 @@
|
||||
>
|
||||
</template>
|
||||
<template v-else>
|
||||
<u-icon
|
||||
<up-icon
|
||||
v-if="icon"
|
||||
:name="icon"
|
||||
:color="iconColorCom"
|
||||
:size="textSize * 1.35"
|
||||
></u-icon>
|
||||
></up-icon>
|
||||
<text
|
||||
class="u-button__text"
|
||||
:style="[
|
||||
@@ -117,11 +117,11 @@ import { mixin } from '../../libs/mixin/mixin';
|
||||
import { props } from "./props";
|
||||
import { addStyle } from '../../libs/function/index';
|
||||
import { throttle } from '../../libs/function/throttle';
|
||||
import color from '../../libs/config/color';
|
||||
import { getThemeVar } from '../../libs/theme/runtime.js';
|
||||
/**
|
||||
* button 按钮
|
||||
* @description Button 按钮
|
||||
* @tutorial https://ijry.github.io/uview-plus/components/button.html
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/button.html
|
||||
*
|
||||
* @property {Boolean} hairline 是否显示按钮的细边框 (默认 true )
|
||||
* @property {String} type 按钮的预置样式,info,primary,error,warning,success (默认 'info' )
|
||||
@@ -192,26 +192,67 @@ export default {
|
||||
);
|
||||
}
|
||||
},
|
||||
isDarkTheme() {
|
||||
return this.$u.theme && this.$u.theme.mode === 'dark';
|
||||
},
|
||||
nvueMainColor() {
|
||||
return this.resolveNvueColor(this.$u.color.mainColor, this.isDarkTheme ? '#f5f5f5' : '#303133');
|
||||
},
|
||||
nvueBorderColor() {
|
||||
return this.resolveNvueColor(this.$u.color.borderColor, this.isDarkTheme ? '#3a3a3c' : '#dadbde');
|
||||
},
|
||||
themeTypeColor() {
|
||||
const fallbackMap = {
|
||||
primary: '#3c9cff',
|
||||
success: '#5ac725',
|
||||
warning: '#f9ae3d',
|
||||
error: '#f56c6c',
|
||||
info: '#909399',
|
||||
};
|
||||
const type = fallbackMap[this.type] ? this.type : 'info';
|
||||
return this.resolveNvueColor(
|
||||
this.upThemeVar(`--up-${type}`, fallbackMap[type]),
|
||||
fallbackMap[type]
|
||||
);
|
||||
},
|
||||
nvueInfoBackgroundColor() {
|
||||
return this.upThemeVar(
|
||||
'--up-button-info-background-color',
|
||||
this.upThemeVar(
|
||||
'--up-card-bg-color',
|
||||
this.isDarkTheme ? '#1c1c1e' : '#ffffff'
|
||||
)
|
||||
);
|
||||
},
|
||||
nvuePlainBackgroundColor() {
|
||||
return this.upThemeVar(
|
||||
'--up-button-plain-background-color',
|
||||
this.upThemeVar(
|
||||
'--up-card-bg-color',
|
||||
this.isDarkTheme ? '#1c1c1e' : '#ffffff'
|
||||
)
|
||||
);
|
||||
},
|
||||
loadingColor() {
|
||||
if (this.plain) {
|
||||
// 如果有设置color值,则用color值,否则使用type主题颜色
|
||||
return this.color
|
||||
? this.color
|
||||
: color[`u-${this.type}`];
|
||||
: this.themeTypeColor;
|
||||
}
|
||||
if (this.type === "info") {
|
||||
return "#c9c9c9";
|
||||
return this.isDarkTheme ? "#9ca3af" : "#c9c9c9";
|
||||
}
|
||||
return "rgb(200, 200, 200)";
|
||||
},
|
||||
iconColorCom() {
|
||||
// 如果是镂空状态,设置了color就用color值,否则使用主题颜色,
|
||||
// u-icon的color能接受一个主题颜色的值
|
||||
// up-icon的color能接受一个主题颜色的值
|
||||
if (this.iconColor) return this.iconColor;
|
||||
if (this.plain) {
|
||||
return this.color ? this.color : this.type;
|
||||
} else {
|
||||
return this.type === "info" ? "#000000" : "#ffffff";
|
||||
return this.type === "info" ? this.nvueMainColor : "#ffffff";
|
||||
}
|
||||
},
|
||||
baseColor() {
|
||||
@@ -240,7 +281,30 @@ export default {
|
||||
style.borderWidth = "1px";
|
||||
style.borderStyle = "solid";
|
||||
}
|
||||
return style;
|
||||
}
|
||||
// #ifdef APP-NVUE
|
||||
const typeColor = this.themeTypeColor;
|
||||
if (this.plain) {
|
||||
style.color = this.type === 'info' ? this.nvueMainColor : typeColor;
|
||||
style.backgroundColor = this.nvuePlainBackgroundColor;
|
||||
style.borderColor = this.type === 'info' ? this.nvueBorderColor : typeColor;
|
||||
style.borderWidth = this.hairline ? '0.5px' : '1px';
|
||||
style.borderStyle = 'solid';
|
||||
} else {
|
||||
style.borderWidth = this.hairline ? '0.5px' : '1px';
|
||||
style.borderStyle = 'solid';
|
||||
if (this.type === 'info') {
|
||||
style.color = this.nvueMainColor;
|
||||
style.backgroundColor = this.nvueInfoBackgroundColor;
|
||||
style.borderColor = this.nvueBorderColor;
|
||||
} else {
|
||||
style.color = '#ffffff';
|
||||
style.backgroundColor = typeColor;
|
||||
style.borderColor = typeColor;
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
return style;
|
||||
},
|
||||
// nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置
|
||||
@@ -248,10 +312,12 @@ export default {
|
||||
let style = {};
|
||||
// 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
|
||||
if (this.type === "info") {
|
||||
style.color = "#323233";
|
||||
style.color = this.nvueMainColor;
|
||||
}
|
||||
if (this.color) {
|
||||
style.color = this.plain ? this.color : "white";
|
||||
} else if (this.plain && this.type !== 'info') {
|
||||
style.color = this.themeTypeColor;
|
||||
}
|
||||
style.fontSize = this.textSize + "px";
|
||||
return style;
|
||||
@@ -271,6 +337,14 @@ export default {
|
||||
'error', 'opensetting', 'launchapp', 'agreeprivacyauthorization'],
|
||||
methods: {
|
||||
addStyle,
|
||||
resolveNvueColor(colorValue, fallbackColor) {
|
||||
if (typeof colorValue !== 'string') return fallbackColor;
|
||||
if (colorValue.indexOf('var(') > -1) return fallbackColor;
|
||||
return colorValue;
|
||||
},
|
||||
upThemeVar(varName, fallbackColor) {
|
||||
return getThemeVar(varName, fallbackColor, this.$u);
|
||||
},
|
||||
clickHandler(e: any) {
|
||||
// 非禁止并且非加载中,才能点击
|
||||
if (!this.disabled && !this.loading) {
|
||||
@@ -306,8 +380,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
@import "./vue.scss";
|
||||
/* #endif */
|
||||
@@ -334,9 +406,9 @@ $u-button-mini-height: 22px !default;
|
||||
$u-button-mini-font-size: 10px !default;
|
||||
$u-button-mini-min-width: 50px !default;
|
||||
$u-button-disabled-opacity: 0.5 !default;
|
||||
$u-button-info-color: #323233 !default;
|
||||
$u-button-info-background-color: #fff !default;
|
||||
$u-button-info-border-color: #ebedf0 !default;
|
||||
$u-button-info-color: $u-main-color !default;
|
||||
$u-button-info-background-color: var(--up-button-info-background-color, var(--up-card-bg-color, #fff)) !default;
|
||||
$u-button-info-border-color: $u-border-color !default;
|
||||
$u-button-info-border-width: 1px !default;
|
||||
$u-button-info-border-style: solid !default;
|
||||
$u-button-success-color: #fff !default;
|
||||
@@ -369,7 +441,7 @@ $u-button-square-border-top-left-radius: 3px !default;
|
||||
$u-button-square-border-bottom-left-radius: 3px !default;
|
||||
$u-button-square-border-bottom-right-radius: 3px !default;
|
||||
$u-button-icon-min-width: 1em !default;
|
||||
$u-button-plain-background-color: #fff !default;
|
||||
$u-button-plain-background-color: var(--up-button-plain-background-color, var(--up-card-bg-color, #fff)) !default;
|
||||
$u-button-hairline-border-width: 0.5px !default;
|
||||
|
||||
.u-button {
|
||||
|
||||
@@ -15,7 +15,7 @@ $u-button-plain-u-button-error-color:$u-error;
|
||||
$u-button-plain-u-button-warning-color:$u-warning;
|
||||
|
||||
.u-button {
|
||||
width: 100%;
|
||||
width: auto;
|
||||
white-space: nowrap;
|
||||
|
||||
&__text {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* @Author : uview-plus
|
||||
* @Description : calendarStrip default props
|
||||
* @version : 3.0
|
||||
* @Date : 2026-05-29 14:40:00
|
||||
*/
|
||||
import { t } from '../../libs/i18n'
|
||||
|
||||
export default {
|
||||
calendarStrip: {
|
||||
modelValue: null,
|
||||
minDate: 0,
|
||||
maxDate: 0,
|
||||
color: '#3c9cff',
|
||||
weekText: [
|
||||
t('up.week.one'),
|
||||
t('up.week.two'),
|
||||
t('up.week.three'),
|
||||
t('up.week.four'),
|
||||
t('up.week.five'),
|
||||
t('up.week.six'),
|
||||
t('up.week.seven')
|
||||
],
|
||||
fullCalendar: true,
|
||||
fullCalendarProps: {},
|
||||
fullMonthNum: 24,
|
||||
pullDownThreshold: 40,
|
||||
collapseAfterSelect: true,
|
||||
readonly: false,
|
||||
showToday: true,
|
||||
monthFormat: '',
|
||||
expandHint: '下拉展开月历',
|
||||
collapseHint: '上拉收起月历'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import CalendarStripDefaultProps from './calendarStrip'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(CalendarStripDefaultProps)
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [String, Number, Date, null],
|
||||
default: () => defProps.calendarStrip.modelValue
|
||||
},
|
||||
minDate: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.calendarStrip.minDate
|
||||
},
|
||||
maxDate: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.calendarStrip.maxDate
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: () => defProps.calendarStrip.color
|
||||
},
|
||||
weekText: {
|
||||
type: Array,
|
||||
default: () => defProps.calendarStrip.weekText
|
||||
},
|
||||
fullCalendar: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendarStrip.fullCalendar
|
||||
},
|
||||
fullCalendarProps: {
|
||||
type: Object,
|
||||
default: () => defProps.calendarStrip.fullCalendarProps
|
||||
},
|
||||
fullMonthNum: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.calendarStrip.fullMonthNum
|
||||
},
|
||||
pullDownThreshold: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.calendarStrip.pullDownThreshold
|
||||
},
|
||||
collapseAfterSelect: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendarStrip.collapseAfterSelect
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendarStrip.readonly
|
||||
},
|
||||
showToday: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendarStrip.showToday
|
||||
},
|
||||
monthFormat: {
|
||||
type: String,
|
||||
default: () => defProps.calendarStrip.monthFormat
|
||||
},
|
||||
expandHint: {
|
||||
type: String,
|
||||
default: () => defProps.calendarStrip.expandHint
|
||||
},
|
||||
collapseHint: {
|
||||
type: String,
|
||||
default: () => defProps.calendarStrip.collapseHint
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,507 @@
|
||||
<template>
|
||||
<view class="u-calendar-strip">
|
||||
<view v-if="!fullCalendar || !innerShowFull">
|
||||
<view class="u-calendar-strip__header u-border-bottom">
|
||||
<text
|
||||
class="u-calendar-strip__header__switch"
|
||||
:class="{ 'u-calendar-strip__header__switch--disabled': switchPrevDisabled }"
|
||||
@tap="prevMonth"
|
||||
>‹</text>
|
||||
<text class="u-calendar-strip__header__title">{{ monthLabel }}</text>
|
||||
<text
|
||||
class="u-calendar-strip__header__switch"
|
||||
:class="{ 'u-calendar-strip__header__switch--disabled': switchNextDisabled }"
|
||||
@tap="nextMonth"
|
||||
>›</text>
|
||||
<text
|
||||
v-if="fullCalendar"
|
||||
class="u-calendar-strip__header__toggle"
|
||||
@click="toggleFull('button')"
|
||||
>▾</text>
|
||||
</view>
|
||||
<view
|
||||
class="u-calendar-strip__scroll-wrap"
|
||||
@touchstart="onTouchStart"
|
||||
@touchend="onTouchEnd"
|
||||
>
|
||||
<scroll-view
|
||||
class="u-calendar-strip__scroll"
|
||||
scroll-x
|
||||
enable-flex
|
||||
:show-scrollbar="false"
|
||||
:scroll-into-view="scrollIntoView"
|
||||
>
|
||||
<view class="u-calendar-strip__scroll__inner">
|
||||
<view
|
||||
v-for="(item, index) in monthDays"
|
||||
:key="index"
|
||||
class="u-calendar-strip__day"
|
||||
:id="getDateId(item.date)"
|
||||
:class="[
|
||||
item.disabled && 'u-calendar-strip__day--disabled',
|
||||
item.selected && 'u-calendar-strip__day--selected',
|
||||
item.today && showToday && !item.selected && 'u-calendar-strip__day--today',
|
||||
]"
|
||||
:style="[dayStyle(item)]"
|
||||
@tap="onDayTap(item)"
|
||||
>
|
||||
<text class="u-calendar-strip__day__date">{{ item.day }}</text>
|
||||
<text class="u-calendar-strip__day__week">{{ getWeekLabel(item.week) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="fullCalendar && innerShowFull"
|
||||
class="u-calendar-strip__panel-wrap"
|
||||
@touchstart="onTouchStart"
|
||||
@touchend="onTouchEnd"
|
||||
>
|
||||
<view class="u-calendar-strip__panel">
|
||||
<u-calendar
|
||||
:show="true"
|
||||
:pageInline="true"
|
||||
:showTitle="false"
|
||||
:showConfirm="false"
|
||||
:closeOnClickOverlay="false"
|
||||
:monthSwitch="true"
|
||||
mode="single"
|
||||
:defaultDate="innerSelectedDate"
|
||||
:minDate="panelMinDate"
|
||||
:maxDate="panelMaxDate"
|
||||
:monthNum="panelMonthNum"
|
||||
:readonly="readonly"
|
||||
:showToday="showToday"
|
||||
:color="color"
|
||||
v-bind="fullCalendarProps"
|
||||
@confirm="onPanelConfirm"
|
||||
></u-calendar>
|
||||
</view>
|
||||
<view class="u-calendar-strip__hint u-calendar-strip__hint--panel">
|
||||
<text class="u-calendar-strip__hint__text" @click="toggleFull('hint')">{{ collapseHint }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { props } from './props'
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin'
|
||||
import { mixin } from '../../libs/mixin/mixin'
|
||||
import dayjs from '../u-datetime-picker/dayjs.esm.min.js'
|
||||
import test from '../../libs/function/test'
|
||||
|
||||
/**
|
||||
* CalendarStrip 单行日历
|
||||
* @description 单行横向日期日历,支持切月、下拉展开完整月历
|
||||
*/
|
||||
export default {
|
||||
name: 'u-calendar-strip',
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
innerSelectedDate: '',
|
||||
currentMonth: '',
|
||||
scrollIntoView: '',
|
||||
innerShowFull: false,
|
||||
touchStartX: 0,
|
||||
touchStartY: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
innerMaxDate() {
|
||||
return test.number(this.maxDate) ? Number(this.maxDate) : this.maxDate
|
||||
},
|
||||
innerMinDate() {
|
||||
return test.number(this.minDate) ? Number(this.minDate) : this.minDate
|
||||
},
|
||||
rangeChange() {
|
||||
return [this.innerMinDate, this.innerMaxDate]
|
||||
},
|
||||
hasMaxDate() {
|
||||
return !!this.innerMaxDate && dayjs(this.innerMaxDate).isValid()
|
||||
},
|
||||
hasMinDate() {
|
||||
return !!this.innerMinDate && dayjs(this.innerMinDate).isValid()
|
||||
},
|
||||
minDateDay() {
|
||||
if (!this.hasMinDate) return ''
|
||||
return dayjs(this.innerMinDate).format('YYYY-MM-DD')
|
||||
},
|
||||
maxDateDay() {
|
||||
if (!this.hasMaxDate) return ''
|
||||
return dayjs(this.innerMaxDate).format('YYYY-MM-DD')
|
||||
},
|
||||
todayDate() {
|
||||
return dayjs().format('YYYY-MM-DD')
|
||||
},
|
||||
monthLabel() {
|
||||
if (!this.currentMonth) return ''
|
||||
const date = dayjs(`${this.currentMonth}-01`)
|
||||
if (this.monthFormat) return date.format(this.monthFormat)
|
||||
if (uni.getLocale() == 'zh-Hans' || uni.getLocale() == 'zh-Hant') {
|
||||
return date.format('YYYY年MM月')
|
||||
}
|
||||
return date.format('MM/YYYY')
|
||||
},
|
||||
monthDays() {
|
||||
if (!this.currentMonth) return []
|
||||
const start = dayjs(`${this.currentMonth}-01`)
|
||||
const days = start.daysInMonth()
|
||||
return new Array(days).fill(0).map((item, index) => {
|
||||
const date = start.date(index + 1).format('YYYY-MM-DD')
|
||||
return {
|
||||
day: index + 1,
|
||||
date,
|
||||
week: start.date(index + 1).day(),
|
||||
disabled: this.isDateDisabled(date),
|
||||
selected: this.dateSame(date, this.innerSelectedDate),
|
||||
today: this.dateSame(date, this.todayDate)
|
||||
}
|
||||
})
|
||||
},
|
||||
switchPrevDisabled() {
|
||||
if (!this.hasMinDate || !this.currentMonth) return false
|
||||
const current = dayjs(`${this.currentMonth}-01`)
|
||||
const minMonth = dayjs(this.minDateDay).startOf('month')
|
||||
return current.isSame(minMonth, 'month') || current.isBefore(minMonth, 'month')
|
||||
},
|
||||
switchNextDisabled() {
|
||||
if (!this.hasMaxDate || !this.currentMonth) return false
|
||||
const current = dayjs(`${this.currentMonth}-01`)
|
||||
const maxMonth = dayjs(this.maxDateDay).startOf('month')
|
||||
return current.isSame(maxMonth, 'month') || current.isAfter(maxMonth, 'month')
|
||||
},
|
||||
panelMinDate() {
|
||||
if (this.hasMinDate) return this.minDateDay
|
||||
const monthNum = Math.max(1, Number(this.fullMonthNum) || 24)
|
||||
const anchor = this.currentMonth || dayjs().format('YYYY-MM')
|
||||
return dayjs(`${anchor}-01`).subtract(monthNum - 1, 'month').startOf('month').format('YYYY-MM-DD')
|
||||
},
|
||||
panelMaxDate() {
|
||||
if (this.hasMaxDate) return this.maxDateDay
|
||||
const monthNum = Math.max(1, Number(this.fullMonthNum) || 24)
|
||||
const anchor = this.currentMonth || dayjs().format('YYYY-MM')
|
||||
return dayjs(`${anchor}-01`).add(monthNum - 1, 'month').endOf('month').format('YYYY-MM-DD')
|
||||
},
|
||||
panelMonthNum() {
|
||||
return this.getMonths(this.panelMinDate, this.panelMaxDate)
|
||||
},
|
||||
pullHintText() {
|
||||
return this.innerShowFull ? this.collapseHint : this.expandHint
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue: {
|
||||
immediate: true,
|
||||
handler(n) {
|
||||
this.syncByValue(n, false, 'sync')
|
||||
}
|
||||
},
|
||||
rangeChange() {
|
||||
this.syncByValue(this.innerSelectedDate || this.modelValue, true, 'range')
|
||||
}
|
||||
},
|
||||
emits: ['change', 'confirm', 'monthChange', 'toggleFull', 'update:modelValue'],
|
||||
methods: {
|
||||
dateSame(date1, date2) {
|
||||
if (!date1 || !date2) return false
|
||||
return dayjs(date1).isSame(dayjs(date2), 'day')
|
||||
},
|
||||
normalizeDate(value) {
|
||||
if (!value) return ''
|
||||
const parsed = dayjs(value)
|
||||
if (!parsed.isValid()) return ''
|
||||
return parsed.format('YYYY-MM-DD')
|
||||
},
|
||||
clampDate(date) {
|
||||
let next = this.normalizeDate(date)
|
||||
if (!next) return ''
|
||||
if (this.hasMinDate && dayjs(next).isBefore(dayjs(this.minDateDay), 'day')) {
|
||||
next = this.minDateDay
|
||||
}
|
||||
if (this.hasMaxDate && dayjs(next).isAfter(dayjs(this.maxDateDay), 'day')) {
|
||||
next = this.maxDateDay
|
||||
}
|
||||
return next
|
||||
},
|
||||
isDateDisabled(date) {
|
||||
if (!date) return true
|
||||
if (this.hasMinDate && dayjs(date).isBefore(dayjs(this.minDateDay), 'day')) {
|
||||
return true
|
||||
}
|
||||
if (this.hasMaxDate && dayjs(date).isAfter(dayjs(this.maxDateDay), 'day')) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
getDateId(date) {
|
||||
return `u-calendar-strip-day-${dayjs(date).format('YYYYMMDD')}`
|
||||
},
|
||||
getWeekLabel(week) {
|
||||
const index = week === 0 ? 6 : week - 1
|
||||
return this.weekText[index] || ''
|
||||
},
|
||||
dayStyle(item) {
|
||||
const style = {}
|
||||
if (item.selected) {
|
||||
style.backgroundColor = this.color
|
||||
}
|
||||
if (!item.selected && item.today && this.showToday) {
|
||||
style.borderColor = this.color
|
||||
}
|
||||
return style
|
||||
},
|
||||
scrollToDate(date) {
|
||||
if (!date) {
|
||||
this.scrollIntoView = ''
|
||||
return
|
||||
}
|
||||
const target = this.getDateId(date)
|
||||
this.scrollIntoView = ''
|
||||
this.$nextTick(() => {
|
||||
this.scrollIntoView = target
|
||||
})
|
||||
},
|
||||
syncByValue(value, emit = false, scene = 'sync') {
|
||||
let next = this.clampDate(value)
|
||||
if (!next) {
|
||||
next = this.clampDate(this.todayDate)
|
||||
}
|
||||
if (!next) return
|
||||
this.setSelectedDate(next, scene, emit)
|
||||
},
|
||||
setSelectedDate(date, scene = 'tap', emit = true) {
|
||||
const next = this.clampDate(date)
|
||||
if (!next || this.isDateDisabled(next)) return
|
||||
const prevDate = this.innerSelectedDate
|
||||
const prevMonth = this.currentMonth
|
||||
this.innerSelectedDate = next
|
||||
this.currentMonth = dayjs(next).format('YYYY-MM')
|
||||
this.scrollToDate(next)
|
||||
if (!emit) return
|
||||
const payload = {
|
||||
date: next,
|
||||
month: this.currentMonth,
|
||||
scene
|
||||
}
|
||||
if (!this.dateSame(prevDate, next)) {
|
||||
this.$emit('update:modelValue', next)
|
||||
}
|
||||
this.$emit('change', payload)
|
||||
this.$emit('confirm', payload)
|
||||
if (prevMonth !== this.currentMonth) {
|
||||
this.$emit('monthChange', {
|
||||
month: this.currentMonth,
|
||||
scene
|
||||
})
|
||||
}
|
||||
},
|
||||
getMonths(minDate, maxDate) {
|
||||
const minYear = dayjs(minDate).year()
|
||||
const minMonth = dayjs(minDate).month() + 1
|
||||
const maxYear = dayjs(maxDate).year()
|
||||
const maxMonth = dayjs(maxDate).month() + 1
|
||||
return Math.max(1, (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1)
|
||||
},
|
||||
findFirstEnabledDate(month) {
|
||||
const start = dayjs(`${month}-01`)
|
||||
const days = start.daysInMonth()
|
||||
for (let i = 1; i <= days; i++) {
|
||||
const date = start.date(i).format('YYYY-MM-DD')
|
||||
if (!this.isDateDisabled(date)) {
|
||||
return date
|
||||
}
|
||||
}
|
||||
return ''
|
||||
},
|
||||
switchMonth(step = 0) {
|
||||
if (step < 0 && this.switchPrevDisabled) return
|
||||
if (step > 0 && this.switchNextDisabled) return
|
||||
const baseMonth = this.currentMonth || dayjs(this.todayDate).format('YYYY-MM')
|
||||
const target = dayjs(`${baseMonth}-01`).add(step, 'month')
|
||||
const targetMonth = target.format('YYYY-MM')
|
||||
const selectedDay = dayjs(this.innerSelectedDate || this.todayDate).date()
|
||||
const dayInTargetMonth = Math.min(selectedDay, target.daysInMonth())
|
||||
let next = target.date(dayInTargetMonth).format('YYYY-MM-DD')
|
||||
next = this.clampDate(next)
|
||||
if (!next || !dayjs(next).isSame(target, 'month') || this.isDateDisabled(next)) {
|
||||
next = this.findFirstEnabledDate(targetMonth)
|
||||
}
|
||||
if (!next) return
|
||||
this.setSelectedDate(next, 'switch', true)
|
||||
},
|
||||
prevMonth() {
|
||||
this.switchMonth(-1)
|
||||
},
|
||||
nextMonth() {
|
||||
this.switchMonth(1)
|
||||
},
|
||||
onDayTap(item) {
|
||||
if (this.readonly || item.disabled) return
|
||||
this.setSelectedDate(item.date, 'tap', true)
|
||||
},
|
||||
setFullVisible(show, source = 'button') {
|
||||
if (!this.fullCalendar) return
|
||||
if (this.innerShowFull === show) return
|
||||
this.innerShowFull = show
|
||||
this.$emit('toggleFull', {
|
||||
show,
|
||||
source
|
||||
})
|
||||
},
|
||||
toggleFull(source = 'button') {
|
||||
this.setFullVisible(!this.innerShowFull, source)
|
||||
},
|
||||
onPanelConfirm(e) {
|
||||
if (!Array.isArray(e) || !e.length) return
|
||||
this.setSelectedDate(e[0], 'full', true)
|
||||
if (this.collapseAfterSelect) {
|
||||
this.setFullVisible(false, 'auto')
|
||||
}
|
||||
},
|
||||
onTouchStart(event) {
|
||||
if (!this.fullCalendar) return
|
||||
const point = event?.changedTouches?.[0] || event?.touches?.[0]
|
||||
if (!point) return
|
||||
this.touchStartX = point.clientX
|
||||
this.touchStartY = point.clientY
|
||||
},
|
||||
onTouchEnd(event) {
|
||||
if (!this.fullCalendar) return
|
||||
const point = event?.changedTouches?.[0] || event?.touches?.[0]
|
||||
if (!point) return
|
||||
const deltaX = point.clientX - this.touchStartX
|
||||
const deltaY = point.clientY - this.touchStartY
|
||||
const threshold = Number(this.pullDownThreshold) || 40
|
||||
if (Math.abs(deltaY) < threshold || Math.abs(deltaY) <= Math.abs(deltaX)) {
|
||||
return
|
||||
}
|
||||
if (deltaY > 0 && !this.innerShowFull) {
|
||||
this.setFullVisible(true, 'pull-down')
|
||||
}
|
||||
if (deltaY < 0 && this.innerShowFull) {
|
||||
this.setFullVisible(false, 'pull-up')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.u-calendar-strip {
|
||||
background-color: var(--up-card-bg-color, #ffffff);
|
||||
border-radius: 10px;
|
||||
margin: 0 8px;
|
||||
|
||||
&__header {
|
||||
@include flex;
|
||||
align-items: center;
|
||||
padding: 0 4px;
|
||||
height: 40px;
|
||||
|
||||
&__switch {
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
line-height: 40px;
|
||||
color: var(--up-main-color, $u-main-color);
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.35;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: var(--up-main-color, $u-main-color);
|
||||
}
|
||||
|
||||
&__toggle {
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
line-height: 40px;
|
||||
color: var(--up-content-color, $u-content-color);
|
||||
}
|
||||
}
|
||||
|
||||
&__scroll-wrap {
|
||||
padding: 10px 0 6px;
|
||||
}
|
||||
|
||||
&__scroll {
|
||||
&__inner {
|
||||
@include flex;
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&__day {
|
||||
width: 54px;
|
||||
height: 62px;
|
||||
margin-right: 8px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
@include flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
&__date {
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
color: var(--up-main-color, $u-main-color);
|
||||
}
|
||||
|
||||
&__week {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--up-content-color, $u-content-color);
|
||||
}
|
||||
|
||||
&--selected {
|
||||
.u-calendar-strip__day__date,
|
||||
.u-calendar-strip__day__week {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
&--today {
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
&__hint {
|
||||
padding: 4px 0 10px;
|
||||
text-align: center;
|
||||
|
||||
&__text {
|
||||
font-size: 12px;
|
||||
color: var(--up-tips-color, $u-tips-color);
|
||||
}
|
||||
}
|
||||
|
||||
&__panel {
|
||||
padding: 0 8px 8px;
|
||||
}
|
||||
|
||||
&__panel-wrap {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
&__hint--panel {
|
||||
padding: 0 0 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,21 +1,22 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-20 16:52:43
|
||||
* @FilePath : /u-view2.0/uview-ui/libs/config/props/calendar.js
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/calendar.js
|
||||
*/
|
||||
import { t } from '../../libs/i18n'
|
||||
export default {
|
||||
// calendar 组件
|
||||
calendar: {
|
||||
title: '日期选择',
|
||||
title: t("up.calendar.chooseDates"),
|
||||
showTitle: true,
|
||||
showSubtitle: true,
|
||||
mode: 'single',
|
||||
startText: '开始',
|
||||
endText: '结束',
|
||||
startText: t("up.common.start"),
|
||||
endText: t("up.common.end"),
|
||||
customList: [],
|
||||
color: '#3c9cff',
|
||||
minDate: 0,
|
||||
@@ -26,8 +27,8 @@ export default {
|
||||
formatter: null,
|
||||
showLunar: false,
|
||||
showMark: true,
|
||||
confirmText: '确定',
|
||||
confirmDisabledText: '确定',
|
||||
confirmText: t("up.common.confirm"),
|
||||
confirmDisabledText: t("up.common.confirm"),
|
||||
show: false,
|
||||
closeOnClickOverlay: false,
|
||||
readonly: false,
|
||||
@@ -36,10 +37,27 @@ export default {
|
||||
rangePrompt: '',
|
||||
showRangePrompt: true,
|
||||
allowSameDay: false,
|
||||
rangeResultMode: 'all',
|
||||
enableTime: false,
|
||||
timePrecision: 'minute',
|
||||
defaultTime: '',
|
||||
round: 0,
|
||||
overlay: true,
|
||||
duration: 300,
|
||||
overlayStyle: {},
|
||||
overlayOpacity: 0.5,
|
||||
zIndex: 10075,
|
||||
safeAreaInsetBottom: true,
|
||||
safeAreaInsetTop: false,
|
||||
bgColor: '',
|
||||
monthNum: 3,
|
||||
weekText: ['一', '二', '三', '四', '五', '六', '日'],
|
||||
monthSwitch: false,
|
||||
showToday: true,
|
||||
todayColor: '',
|
||||
weekText: [t("up.week.one"), t("up.week.two"), t("up.week.three"), t("up.week.four"), t("up.week.five"), t("up.week.six"), t("up.week.seven")],
|
||||
forbidDays: [],
|
||||
forbidDaysToast: '该日期已禁用',
|
||||
forbidDaysToast: t("up.calendar.disabled"),
|
||||
monthFormat: '',
|
||||
pageInline: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,42 @@
|
||||
class="u-calendar-header__title"
|
||||
v-if="showTitle"
|
||||
>{{ title }}</text>
|
||||
<text
|
||||
class="u-calendar-header__subtitle"
|
||||
<view
|
||||
class="u-calendar-header__subtitle-wrap"
|
||||
v-if="showSubtitle"
|
||||
>{{ subtitle }}</text>
|
||||
>
|
||||
<text
|
||||
v-if="showSwitch"
|
||||
class="u-calendar-header__switch"
|
||||
:class="{ 'u-calendar-header__switch--disabled': prevYearDisabled }"
|
||||
@click="prevYear"
|
||||
>«</text>
|
||||
<text
|
||||
v-if="showSwitch"
|
||||
class="u-calendar-header__switch"
|
||||
:class="{ 'u-calendar-header__switch--disabled': prevDisabled }"
|
||||
@click="prev"
|
||||
>‹</text>
|
||||
<text class="u-calendar-header__subtitle">{{ subtitle }}</text>
|
||||
<text
|
||||
v-if="showSwitch"
|
||||
class="u-calendar-header__switch"
|
||||
:class="{ 'u-calendar-header__switch--disabled': nextDisabled }"
|
||||
@click="next"
|
||||
>›</text>
|
||||
<text
|
||||
v-if="showSwitch"
|
||||
class="u-calendar-header__switch"
|
||||
:class="{ 'u-calendar-header__switch--disabled': nextYearDisabled }"
|
||||
@click="nextYear"
|
||||
>»</text>
|
||||
<text
|
||||
v-if="showToday"
|
||||
class="u-calendar-header__today"
|
||||
:class="{ 'u-calendar-header__today--disabled': todayDisabled }"
|
||||
@click="today"
|
||||
>{{ todayText }}</text>
|
||||
</view>
|
||||
<view class="u-calendar-header__weekdays">
|
||||
<text class="u-calendar-header__weekdays__weekday">{{ weekText[0] }}</text>
|
||||
<text class="u-calendar-header__weekdays__weekday">{{ weekText[1] }}</text>
|
||||
@@ -51,9 +83,49 @@
|
||||
weekText: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return ['一', '二', '三', '四', '五', '六', '日']
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 是否显示月份切换按钮
|
||||
showSwitch: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 上个月按钮是否禁用
|
||||
prevDisabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 下个月按钮是否禁用
|
||||
nextDisabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 上一年按钮是否禁用
|
||||
prevYearDisabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 下一年按钮是否禁用
|
||||
nextYearDisabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示今天按钮
|
||||
showToday: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 今天按钮文案
|
||||
todayText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 今天按钮是否禁用
|
||||
todayDisabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -61,15 +133,36 @@
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
name() {
|
||||
|
||||
prev() {
|
||||
if (!this.prevDisabled) {
|
||||
this.$emit('prev')
|
||||
}
|
||||
},
|
||||
next() {
|
||||
if (!this.nextDisabled) {
|
||||
this.$emit('next')
|
||||
}
|
||||
},
|
||||
prevYear() {
|
||||
if (!this.prevYearDisabled) {
|
||||
this.$emit('prevYear')
|
||||
}
|
||||
},
|
||||
nextYear() {
|
||||
if (!this.nextYearDisabled) {
|
||||
this.$emit('nextYear')
|
||||
}
|
||||
},
|
||||
today() {
|
||||
if (!this.todayDisabled) {
|
||||
this.$emit('today')
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-calendar-header {
|
||||
display: flex;
|
||||
@@ -78,20 +171,41 @@
|
||||
|
||||
&__title {
|
||||
font-size: 16px;
|
||||
color: $u-main-color;
|
||||
color: var(--up-main-color, $u-main-color);
|
||||
text-align: center;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__subtitle-wrap {
|
||||
@include flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
font-size: 14px;
|
||||
color: $u-main-color;
|
||||
height: 40px;
|
||||
color: var(--up-main-color, $u-main-color);
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
font-weight: bold;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
&__switch {
|
||||
width: 44px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
color: var(--up-main-color, $u-main-color);
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
flex-shrink: 0;
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.35;
|
||||
}
|
||||
}
|
||||
|
||||
&__weekdays {
|
||||
@@ -100,11 +214,27 @@
|
||||
|
||||
&__weekday {
|
||||
font-size: 13px;
|
||||
color: $u-main-color;
|
||||
color: var(--up-main-color, $u-main-color);
|
||||
line-height: 30px;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__today {
|
||||
margin-left: 4px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
padding: 0 10px;
|
||||
border-radius: 14px;
|
||||
font-size: 13px;
|
||||
color: var(--up-primary, $u-primary);
|
||||
border: 1px solid var(--up-primary, $u-primary);
|
||||
flex-shrink: 0;
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.35;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<view class="u-calendar-month-wrapper" ref="u-calendar-month-wrapper">
|
||||
<view v-for="(item, index) in months" :key="index" :class="[`u-calendar-month-${index}`]"
|
||||
:ref="`u-calendar-month-${index}`" :id="`month-${index}`">
|
||||
<text v-if="index !== 0" class="u-calendar-month__title">{{ item.year }}年{{ item.month }}月</text>
|
||||
<text v-if="index !== 0" class="u-calendar-month__title">{{ monthTitle(item) }}</text>
|
||||
<view class="u-calendar-month__days">
|
||||
<view v-if="showMark" class="u-calendar-month__days__month-mark-wrapper">
|
||||
<text class="u-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text>
|
||||
@@ -33,11 +33,12 @@
|
||||
// #endif
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { addUnit, deepClone, toast, sleep } from '../../libs/function/index';
|
||||
import { addUnit, deepClone, toast, sleep, getWindowInfo } from '../../libs/function/index';
|
||||
import { colorGradient } from '../../libs/function/colorGradient';
|
||||
import test from '../../libs/function/test';
|
||||
import defProps from '../../libs/config/props';
|
||||
import dayjs from 'dayjs/esm/index'
|
||||
import dayjs from '../u-datetime-picker/dayjs.esm.min.js';
|
||||
import { t } from '../../libs/i18n'
|
||||
export default {
|
||||
name: 'u-calendar-month',
|
||||
mixins: [mpMixin, mixin],
|
||||
@@ -134,6 +135,16 @@
|
||||
forbidDaysToast: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 今天日期,用于独立高亮
|
||||
todayDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 今天日期的独立高亮颜色
|
||||
todayColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -162,17 +173,24 @@
|
||||
return (index1, index2, item) => {
|
||||
const style = {}
|
||||
let week = item.week
|
||||
// 隐藏挂载场景下节点宽度可能拿到0,兜底使用窗口宽度避免首日偏移归零
|
||||
const wrapperWidth = this.width > 0 ? this.width : (getWindowInfo().windowWidth || 0)
|
||||
// 不进行四舍五入的形式保留2位小数
|
||||
const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1))
|
||||
const dayWidth = Number(parseFloat(wrapperWidth / 7).toFixed(3).slice(0, -1))
|
||||
// 得出每个日期的宽度
|
||||
// #ifdef APP-NVUE
|
||||
style.width = addUnit(dayWidth, 'px')
|
||||
// #endif
|
||||
style.height = addUnit(this.rowHeight)
|
||||
style.height = addUnit(this.rowHeight, 'px')
|
||||
if (index2 === 0) {
|
||||
// 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数
|
||||
week = (week === 0 ? 7 : week) - 1
|
||||
// #ifdef APP-NVUE
|
||||
style.marginLeft = addUnit(week * dayWidth, 'px')
|
||||
// #endif
|
||||
// #ifndef APP-NVUE
|
||||
style.marginLeft = `${(week / 7) * 100}%`
|
||||
// #endif
|
||||
}
|
||||
if (this.mode === 'range') {
|
||||
// 之所以需要这么写,是因为DCloud公司的iOS客户端导致的bug
|
||||
@@ -192,6 +210,10 @@
|
||||
if (this.selected.some(item => this.dateSame(item, date))) {
|
||||
style.backgroundColor = this.color
|
||||
}
|
||||
if (this.todayDate && this.dateSame(date, this.todayDate)) {
|
||||
style.border = `1px solid ${this.resolvedTodayColor}`
|
||||
style.boxSizing = 'border-box'
|
||||
}
|
||||
if (this.mode === 'single') {
|
||||
if (date === this.selected[0]) {
|
||||
// 因为需要对nvue的兼容,只能这么写,无法缩写,也无法通过类名控制等等
|
||||
@@ -216,12 +238,13 @@
|
||||
// 处于第一和最后一个之间的日期,背景色设置为浅色,通过将对应颜色进行等分,再取其尾部的颜色值
|
||||
if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
|
||||
.selected[len]))) {
|
||||
style.backgroundColor = colorGradient(this.color, '#ffffff', 100)[90]
|
||||
const rangeEndColor = this.upThemeVar('--up-card-bg-color', this.upThemeIsDark ? '#1c1c1e' : '#ffffff')
|
||||
style.backgroundColor = colorGradient(this.color, rangeEndColor, 100)[90]
|
||||
// 增加一个透明度,让范围区间的背景色也能看到底部的mark水印字符
|
||||
style.opacity = 0.7
|
||||
style.opacity = this.upThemeIsDark ? 0.85 : 0.7
|
||||
}
|
||||
} else if (this.selected.length === 1) {
|
||||
// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
|
||||
// 之所以需要这么写,是因为uni-app的iOS客户端的bug
|
||||
// 进行还原操作,否则在nvue的iOS,uni-app有bug,会导致诡异的表现
|
||||
style.borderTopLeftRadius = '3px'
|
||||
style.borderBottomLeftRadius = '3px'
|
||||
@@ -237,6 +260,9 @@
|
||||
return style
|
||||
}
|
||||
},
|
||||
resolvedTodayColor() {
|
||||
return this.todayColor || this.color
|
||||
},
|
||||
// 某个日期是否被选中
|
||||
textStyle() {
|
||||
return (item) => {
|
||||
@@ -254,6 +280,9 @@
|
||||
style.color = this.color
|
||||
}
|
||||
}
|
||||
if (this.todayDate && this.dateSame(date, this.todayDate) && !this.isSelectedDate(date)) {
|
||||
style.color = this.resolvedTodayColor
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
@@ -292,6 +321,7 @@
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
emits: ['monthSelected', 'updateMonthTop'],
|
||||
methods: {
|
||||
init() {
|
||||
// 初始化默认选中
|
||||
@@ -305,6 +335,13 @@
|
||||
})
|
||||
})
|
||||
},
|
||||
monthTitle(item) {
|
||||
if (uni.getLocale() == 'zh-Hans' || uni.getLocale() == 'zh-Hant') {
|
||||
return item.year + '年' + (item.month < 10 ? '0' + item.month : item.month) + '月'
|
||||
} else {
|
||||
return (item.month < 10 ? '0' + item.month : item.month) + '/' + item.year
|
||||
}
|
||||
},
|
||||
isForbid(item) {
|
||||
let date = dayjs(item.date).format("YYYY-MM-DD")
|
||||
if (this.mode !== 'range' && this.forbidDays.includes(date)) {
|
||||
@@ -316,16 +353,21 @@
|
||||
dateSame(date1, date2) {
|
||||
return dayjs(date1).isSame(dayjs(date2))
|
||||
},
|
||||
isSelectedDate(date) {
|
||||
return this.selected.some(item => this.dateSame(item, date))
|
||||
},
|
||||
// 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度
|
||||
getWrapperWidth() {
|
||||
// #ifdef APP-NVUE
|
||||
dom.getComponentRect(this.$refs['u-calendar-month-wrapper'], res => {
|
||||
this.width = res.size.width
|
||||
const width = res && res.size ? Number(res.size.width) : 0
|
||||
this.width = width > 0 ? width : (getWindowInfo().windowWidth || 0)
|
||||
})
|
||||
// #endif
|
||||
// #ifndef APP-NVUE
|
||||
this.$uGetRect('.u-calendar-month-wrapper').then(size => {
|
||||
this.width = size.width
|
||||
const width = size ? Number(size.width) : 0
|
||||
this.width = width > 0 ? width : (getWindowInfo().windowWidth || 0)
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
@@ -350,7 +392,7 @@
|
||||
// 获取每个月份区域的尺寸
|
||||
getMonthRectByPromise(el) {
|
||||
// #ifndef APP-NVUE
|
||||
// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://ijry.github.io/uview-plus/js/getRect.html
|
||||
// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://uview-plus.jiangruyi.com/js/getRect.html
|
||||
// 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同
|
||||
return new Promise(resolve => {
|
||||
this.$uGetRect(`.${el}`).then(size => {
|
||||
@@ -414,7 +456,7 @@
|
||||
if(this.rangePrompt) {
|
||||
toast(this.rangePrompt)
|
||||
} else {
|
||||
toast(`选择天数不能超过 ${this.maxRange} 天`)
|
||||
toast(t("up.calendar.daysExceed", { days: this.maxRange }))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -474,13 +516,24 @@
|
||||
setSelected(selected, event = true) {
|
||||
this.selected = selected
|
||||
event && this.$emit('monthSelected', this.selected,'tap')
|
||||
},
|
||||
selectDate(date) {
|
||||
const targetDate = dayjs(date).format("YYYY-MM-DD")
|
||||
for (let monthIndex = 0; monthIndex < this.months.length; monthIndex++) {
|
||||
const dayIndex = this.months[monthIndex].date.findIndex(item => {
|
||||
return this.dateSame(item.date, targetDate)
|
||||
})
|
||||
if (dayIndex !== -1) {
|
||||
this.clickHandler(monthIndex, dayIndex, this.months[monthIndex].date[dayIndex])
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-calendar-month-wrapper {
|
||||
margin-top: 4px;
|
||||
@@ -494,7 +547,7 @@
|
||||
font-size: 14px;
|
||||
line-height: 42px;
|
||||
height: 42px;
|
||||
color: $u-main-color;
|
||||
color: var(--up-main-color, $u-main-color);
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -516,7 +569,7 @@
|
||||
|
||||
&__text {
|
||||
font-size: 155px;
|
||||
color: rgba(231, 232, 234, 0.83);
|
||||
color: var(--up-calendar-month-mark-color, rgba(231, 232, 234, 0.83));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,7 +600,7 @@
|
||||
}
|
||||
|
||||
&__buttom-info {
|
||||
color: $u-content-color;
|
||||
color: var(--up-content-color, $u-content-color);
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
@@ -561,20 +614,21 @@
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
color: #cacbcd;
|
||||
color: var(--up-disabled-color, #cacbcd);
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
color: var(--up-main-color, $u-main-color);
|
||||
|
||||
&--selected {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
color: #cacbcd;
|
||||
color: var(--up-disabled-color, #cacbcd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,4 +659,5 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import CalendarDefaultProps from './calendar'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(CalendarDefaultProps)
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 日历顶部标题
|
||||
@@ -98,6 +100,46 @@ export const props = defineMixin({
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.show
|
||||
},
|
||||
// 是否显示遮罩
|
||||
overlay: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.overlay
|
||||
},
|
||||
// 动画时长,单位ms
|
||||
duration: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.calendar.duration
|
||||
},
|
||||
// 自定义遮罩的样式
|
||||
overlayStyle: {
|
||||
type: [Object, String],
|
||||
default: () => defProps.calendar.overlayStyle
|
||||
},
|
||||
// 遮罩的透明度,0-1之间
|
||||
overlayOpacity: {
|
||||
type: [Number, String],
|
||||
default: () => defProps.calendar.overlayOpacity
|
||||
},
|
||||
// 层级
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.calendar.zIndex
|
||||
},
|
||||
// 是否为iPhoneX留出底部安全距离
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.safeAreaInsetBottom
|
||||
},
|
||||
// 是否留出顶部安全距离(状态栏高度)
|
||||
safeAreaInsetTop: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.safeAreaInsetTop
|
||||
},
|
||||
// 弹窗背景色,设置为transparent可去除白色背景
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: () => defProps.calendar.bgColor
|
||||
},
|
||||
// 是否允许点击遮罩关闭日历
|
||||
closeOnClickOverlay: {
|
||||
type: Boolean,
|
||||
@@ -132,6 +174,26 @@ export const props = defineMixin({
|
||||
allowSameDay: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.allowSameDay
|
||||
},
|
||||
// 区间模式下确认返回值格式,all-返回区间内所有日期,boundary-仅返回起止日期
|
||||
rangeResultMode: {
|
||||
type: String,
|
||||
default: () => defProps.calendar.rangeResultMode
|
||||
},
|
||||
// 是否开启时分秒选择
|
||||
enableTime: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.enableTime
|
||||
},
|
||||
// 时间精度:hour-仅时,minute-时分,second-时分秒
|
||||
timePrecision: {
|
||||
type: String,
|
||||
default: () => defProps.calendar.timePrecision
|
||||
},
|
||||
// 默认时间,格式支持 HH / HH:mm / HH:mm:ss
|
||||
defaultTime: {
|
||||
type: String,
|
||||
default: () => defProps.calendar.defaultTime
|
||||
},
|
||||
// 圆角值
|
||||
round: {
|
||||
@@ -143,6 +205,21 @@ export const props = defineMixin({
|
||||
type: [Number, String],
|
||||
default: 3
|
||||
},
|
||||
// 是否启用非滚动的单月切换模式
|
||||
monthSwitch: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.monthSwitch
|
||||
},
|
||||
// 是否显示今天按钮
|
||||
showToday: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.showToday
|
||||
},
|
||||
// 今天日期的独立高亮颜色,默认跟随主题色
|
||||
todayColor: {
|
||||
type: String,
|
||||
default: () => defProps.calendar.todayColor
|
||||
},
|
||||
// 星期文案
|
||||
weekText: {
|
||||
type: Array,
|
||||
@@ -156,5 +233,14 @@ export const props = defineMixin({
|
||||
type: String,
|
||||
default: defProps.calendar.forbidDaysToast
|
||||
},
|
||||
monthFormat:{
|
||||
type: String,
|
||||
default: defProps.calendar.monthFormat
|
||||
},
|
||||
// 是否页面内展示
|
||||
pageInline:{
|
||||
type: Boolean,
|
||||
default: defProps.calendar.pageInline
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,9 +2,18 @@
|
||||
<u-popup
|
||||
:show="show"
|
||||
mode="bottom"
|
||||
closeable
|
||||
:overlay="overlay"
|
||||
:duration="duration"
|
||||
:overlayStyle="overlayStyle"
|
||||
:overlayOpacity="overlayOpacity"
|
||||
:zIndex="zIndex"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom"
|
||||
:safeAreaInsetTop="safeAreaInsetTop"
|
||||
:bgColor="bgColor"
|
||||
:closeable="!pageInline"
|
||||
@close="close"
|
||||
:round="round"
|
||||
:pageInline="pageInline"
|
||||
:closeOnClickOverlay="closeOnClickOverlay"
|
||||
>
|
||||
<view class="u-calendar">
|
||||
@@ -14,10 +23,46 @@
|
||||
:showSubtitle="showSubtitle"
|
||||
:showTitle="showTitle"
|
||||
:weekText="weekText"
|
||||
:showSwitch="monthSwitch"
|
||||
:showToday="showToday"
|
||||
:todayText="todayText"
|
||||
:todayDisabled="todayDisabled"
|
||||
:prevDisabled="switchPrevDisabled"
|
||||
:nextDisabled="switchNextDisabled"
|
||||
:prevYearDisabled="switchPrevYearDisabled"
|
||||
:nextYearDisabled="switchNextYearDisabled"
|
||||
@prev="prevMonth"
|
||||
@next="nextMonth"
|
||||
@prevYear="prevYear"
|
||||
@nextYear="nextYear"
|
||||
@today="jumpToToday"
|
||||
></uHeader>
|
||||
<view v-if="showTimePanel" class="u-calendar__time-panel">
|
||||
<view v-if="mode === 'single'" class="u-calendar__time-row">
|
||||
<text class="u-calendar__time-date">{{ singleDateLabel }}</text>
|
||||
<view class="u-calendar__time-trigger" @click="openTimePicker('single')">
|
||||
<text class="u-calendar__time-text">{{ singleTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else-if="mode === 'range' && rangeResultMode === 'boundary'">
|
||||
<view class="u-calendar__time-row">
|
||||
<text class="u-calendar__time-date">{{ rangeStartDateLabel }}</text>
|
||||
<view class="u-calendar__time-trigger" @click="openTimePicker('start')">
|
||||
<text class="u-calendar__time-text">{{ rangeStartTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-calendar__time-row">
|
||||
<text class="u-calendar__time-date">{{ rangeEndDateLabel }}</text>
|
||||
<view class="u-calendar__time-trigger" @click="openTimePicker('end')">
|
||||
<text class="u-calendar__time-text">{{ rangeEndTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view
|
||||
v-if="!monthSwitch"
|
||||
:style="{
|
||||
height: addUnit(listHeight)
|
||||
height: addUnit(listHeight, 'px')
|
||||
}"
|
||||
scroll-y
|
||||
@scroll="onScroll"
|
||||
@@ -44,11 +89,48 @@
|
||||
:allowSameDay="allowSameDay"
|
||||
:forbidDays="forbidDays"
|
||||
:forbidDaysToast="forbidDaysToast"
|
||||
:monthFormat="monthFormat"
|
||||
:todayDate="todayDate"
|
||||
:todayColor="todayColor"
|
||||
ref="month"
|
||||
@monthSelected="monthSelected"
|
||||
@updateMonthTop="updateMonthTop"
|
||||
@updateMonthTop="onUpdateMonthTop"
|
||||
></uMonth>
|
||||
</scroll-view>
|
||||
<view
|
||||
v-else
|
||||
:style="{
|
||||
height: addUnit(listHeight, 'px')
|
||||
}"
|
||||
>
|
||||
<uMonth
|
||||
:color="color"
|
||||
:rowHeight="rowHeight"
|
||||
:showMark="showMark"
|
||||
:months="currentMonths"
|
||||
:mode="mode"
|
||||
:maxCount="maxCount"
|
||||
:startText="startText"
|
||||
:endText="endText"
|
||||
:defaultDate="defaultDate"
|
||||
:minDate="innerMinDate"
|
||||
:maxDate="innerMaxDate"
|
||||
:maxMonth="monthNum"
|
||||
:readonly="readonly"
|
||||
:maxRange="maxRange"
|
||||
:rangePrompt="rangePrompt"
|
||||
:showRangePrompt="showRangePrompt"
|
||||
:allowSameDay="allowSameDay"
|
||||
:forbidDays="forbidDays"
|
||||
:forbidDaysToast="forbidDaysToast"
|
||||
:monthFormat="monthFormat"
|
||||
:todayDate="todayDate"
|
||||
:todayColor="todayColor"
|
||||
ref="month"
|
||||
@monthSelected="monthSelected"
|
||||
@updateMonthTop="onUpdateMonthTop"
|
||||
></uMonth>
|
||||
</view>
|
||||
<slot name="footer" v-if="showConfirm">
|
||||
<view class="u-calendar__confirm">
|
||||
<u-button
|
||||
@@ -64,6 +146,32 @@
|
||||
</slot>
|
||||
</view>
|
||||
</u-popup>
|
||||
<u-popup
|
||||
:show="timePickerShow"
|
||||
mode="center"
|
||||
:round="8"
|
||||
:closeOnClickOverlay="true"
|
||||
@close="closeTimePicker"
|
||||
>
|
||||
<view class="u-calendar__time-picker">
|
||||
<view class="u-calendar__time-picker__header">
|
||||
<text class="u-calendar__time-picker__cancel" @click="closeTimePicker">取消</text>
|
||||
<text class="u-calendar__time-picker__title">选择时间</text>
|
||||
<text class="u-calendar__time-picker__confirm" @click="confirmTimePicker">确定</text>
|
||||
</view>
|
||||
<picker-view class="u-calendar__time-picker__body" :value="timePickerValue" @change="onTimePickerChange">
|
||||
<picker-view-column>
|
||||
<view v-for="(item, index) in hourOptions" :key="`h-${index}`" class="u-calendar__time-picker__item">{{ item }}</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column v-if="timePrecision !== 'hour'">
|
||||
<view v-for="(item, index) in minuteOptions" :key="`m-${index}`" class="u-calendar__time-picker__item">{{ item }}</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column v-if="timePrecision === 'second'">
|
||||
<view v-for="(item, index) in secondOptions" :key="`s-${index}`" class="u-calendar__time-picker__item">{{ item }}</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -71,16 +179,17 @@ import uHeader from './header.vue'
|
||||
import uMonth from './month.vue'
|
||||
import { props } from './props.js'
|
||||
import util from './util.js'
|
||||
import dayjs from 'dayjs/esm/index'
|
||||
import dayjs from '../u-datetime-picker/dayjs.esm.min.js';
|
||||
import Calendar from '../../libs/util/calendar.js'
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin.js'
|
||||
import { mixin } from '../../libs/mixin/mixin.js'
|
||||
import { addUnit, range, error, padZero } from '../../libs/function/index';
|
||||
import { addUnit, getPx, range, error, padZero } from '../../libs/function/index';
|
||||
import test from '../../libs/function/test';
|
||||
import { t } from '../../libs/i18n'
|
||||
/**
|
||||
* Calendar 日历
|
||||
* @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中.
|
||||
* @tutorial https://ijry.github.io/uview-plus/components/calendar.html
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/calendar.html
|
||||
*
|
||||
* @property {String} title 标题内容 (默认 日期选择 )
|
||||
* @property {Boolean} showTitle 是否显示标题 (默认 true )
|
||||
@@ -101,14 +210,28 @@ import test from '../../libs/function/test';
|
||||
* @property {String} confirmText 确定按钮的文字 (默认 '确定' )
|
||||
* @property {String} confirmDisabledText 确认按钮处于禁用状态时的文字 (默认 '确定' )
|
||||
* @property {Boolean} show 是否显示日历弹窗 (默认 false )
|
||||
* @property {Boolean} overlay 是否显示遮罩 (默认 true )
|
||||
* @property {String | Number} duration 动画时长,单位ms (默认 300 )
|
||||
* @property {Object | String} overlayStyle 自定义遮罩的样式
|
||||
* @property {Number | String} overlayOpacity 遮罩透明度,0-1之间 (默认 0.5 )
|
||||
* @property {String | Number} zIndex 弹出层的z-index值 (默认 10075 )
|
||||
* @property {Boolean} safeAreaInsetBottom 是否留出底部安全距离 (默认 true )
|
||||
* @property {Boolean} safeAreaInsetTop 是否留出顶部安全距离(状态栏高度) (默认 false )
|
||||
* @property {String} bgColor 弹窗背景色,设置为transparent可去除白色背景
|
||||
* @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭日历 (默认 false )
|
||||
* @property {Boolean} readonly 是否为只读状态,只读状态下禁止选择日期 (默认 false )
|
||||
* @property {String | Number} maxRange 日期区间最多可选天数,默认无限制,mode = range时有效
|
||||
* @property {String} rangePrompt 范围选择超过最多可选天数时的提示文案,mode = range时有效
|
||||
* @property {Boolean} showRangePrompt 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 (默认 true )
|
||||
* @property {Boolean} allowSameDay 是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false )
|
||||
* @property {String} rangeResultMode 区间模式下确认返回值格式,all-返回区间内所有日期,boundary-仅返回起止日期 (默认 'all' )
|
||||
* @property {Boolean} enableTime 是否开启时分秒选择 (默认 false )
|
||||
* @property {String} timePrecision 时间精度,hour-仅时,minute-时分,second-时分秒 (默认 'minute' )
|
||||
* @property {String} defaultTime 默认时间,支持HH/HH:mm/HH:mm:ss,不传则按00:00补齐
|
||||
* @property {Number|String} round 圆角值,默认无圆角 (默认 0 )
|
||||
* @property {Number|String} monthNum 最多展示的月份数量 (默认 3 )
|
||||
* @property {Boolean} monthSwitch 是否启用非滚动的单月切换模式 (默认 false )
|
||||
* @property {String} todayColor 今天日期的独立高亮颜色,默认跟随主题色
|
||||
* @property {Array} weekText 星期文案 (默认 ['一', '二', '三', '四', '五', '六', '日'] )
|
||||
*
|
||||
* @event {Function()} confirm 点击确定按钮时触发 选择日期相关的返回参数
|
||||
@@ -136,6 +259,15 @@ export default {
|
||||
scrollIntoView: '',
|
||||
scrollIntoViewScroll: '',
|
||||
scrollTop:0,
|
||||
timePickerShow: false,
|
||||
timePickerTarget: 'single',
|
||||
timePickerValue: [0, 0, 0],
|
||||
hourOptions: [],
|
||||
minuteOptions: [],
|
||||
secondOptions: [],
|
||||
singleTime: '00:00',
|
||||
rangeStartTime: '00:00',
|
||||
rangeEndTime: '00:00',
|
||||
// 过滤处理方法
|
||||
innerFormatter: (value) => value
|
||||
}
|
||||
@@ -153,6 +285,13 @@ export default {
|
||||
this.setMonth()
|
||||
}
|
||||
},
|
||||
timePrecision() {
|
||||
this.initTimeOptions()
|
||||
this.initTimeValues()
|
||||
},
|
||||
defaultTime() {
|
||||
this.initTimeValues()
|
||||
},
|
||||
// 打开弹窗时,设置月份数据
|
||||
show: {
|
||||
immediate: true,
|
||||
@@ -179,6 +318,28 @@ export default {
|
||||
? Number(this.minDate)
|
||||
: this.minDate
|
||||
},
|
||||
todayDate() {
|
||||
return dayjs().format('YYYY-MM-DD')
|
||||
},
|
||||
todayText() {
|
||||
return t('up.calendar.today')
|
||||
},
|
||||
todayDisabled() {
|
||||
const today = dayjs(this.todayDate)
|
||||
const minDate = this.innerMinDate
|
||||
? dayjs(this.innerMinDate)
|
||||
: null
|
||||
const maxDate = this.innerMaxDate
|
||||
? dayjs(this.innerMaxDate)
|
||||
: null
|
||||
if (minDate && today.isBefore(minDate, 'day')) {
|
||||
return true
|
||||
}
|
||||
if (maxDate && today.isAfter(maxDate, 'day')) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
// 多个条件的变化,会引起选中日期的变化,这里统一管理监听
|
||||
selectedChange() {
|
||||
return [this.innerMinDate, this.innerMaxDate, this.defaultDate]
|
||||
@@ -186,13 +347,53 @@ export default {
|
||||
subtitle() {
|
||||
// 初始化时,this.months为空数组,所以需要特别判断处理
|
||||
if (this.months.length) {
|
||||
return `${this.months[this.monthIndex].year}年${
|
||||
this.months[this.monthIndex].month
|
||||
}月`
|
||||
if (uni.getLocale() == 'zh-Hans' || uni.getLocale() == 'zh-Hant') {
|
||||
return this.months[this.monthIndex].year + '年' + (this.months[this.monthIndex].month < 10 ? '0' + this.months[this.monthIndex].month : this.months[this.monthIndex].month) + '月'
|
||||
} else {
|
||||
return (this.months[this.monthIndex].month < 10 ? '0' + this.months[this.monthIndex].month : this.months[this.monthIndex].month) + '/' + this.months[this.monthIndex].year
|
||||
}
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
currentMonths() {
|
||||
if (this.monthSwitch && this.months.length) {
|
||||
return [this.months[this.monthIndex]]
|
||||
}
|
||||
return this.months
|
||||
},
|
||||
switchPrevDisabled() {
|
||||
return this.monthIndex <= 0
|
||||
},
|
||||
switchNextDisabled() {
|
||||
return this.monthIndex >= this.months.length - 1
|
||||
},
|
||||
switchPrevYearDisabled() {
|
||||
return this.monthIndex - 12 < 0
|
||||
},
|
||||
switchNextYearDisabled() {
|
||||
return this.monthIndex + 12 > this.months.length - 1
|
||||
},
|
||||
showTimePanel() {
|
||||
if (!this.enableTime) return false
|
||||
if (this.mode === 'single') return true
|
||||
if (this.mode === 'range' && this.rangeResultMode === 'boundary') {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
singleDateLabel() {
|
||||
return this.selected[0] || '--'
|
||||
},
|
||||
rangeStartDateLabel() {
|
||||
return this.selected[0] || '--'
|
||||
},
|
||||
rangeEndDateLabel() {
|
||||
if (this.selected.length >= 2) {
|
||||
return this.selected[this.selected.length - 1]
|
||||
}
|
||||
return '--'
|
||||
},
|
||||
buttonDisabled() {
|
||||
// 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态
|
||||
if (this.mode === 'range') {
|
||||
@@ -213,6 +414,130 @@ export default {
|
||||
emits: ["confirm", "close"],
|
||||
methods: {
|
||||
addUnit,
|
||||
padTime(num) {
|
||||
return String(num).padStart(2, '0')
|
||||
},
|
||||
initTimeOptions() {
|
||||
this.hourOptions = Array.from({ length: 24 }, (_, i) => this.padTime(i))
|
||||
this.minuteOptions = Array.from({ length: 60 }, (_, i) => this.padTime(i))
|
||||
this.secondOptions = Array.from({ length: 60 }, (_, i) => this.padTime(i))
|
||||
},
|
||||
parseTimeValue(value = '') {
|
||||
const raw = String(value || '').trim()
|
||||
const parts = raw.length ? raw.split(':') : []
|
||||
const getNumber = (index, max) => {
|
||||
const current = Number(parts[index] || 0)
|
||||
if (Number.isNaN(current)) return 0
|
||||
return Math.max(0, Math.min(max, current))
|
||||
}
|
||||
const hour = getNumber(0, 23)
|
||||
const minute = getNumber(1, 59)
|
||||
const second = getNumber(2, 59)
|
||||
return [hour, minute, second]
|
||||
},
|
||||
getDefaultTimeValue() {
|
||||
const [hour, minute, second] = this.parseTimeValue(this.defaultTime)
|
||||
if (this.timePrecision === 'hour') {
|
||||
return this.padTime(hour)
|
||||
}
|
||||
if (this.timePrecision === 'second') {
|
||||
return `${this.padTime(hour)}:${this.padTime(minute)}:${this.padTime(second)}`
|
||||
}
|
||||
return `${this.padTime(hour)}:${this.padTime(minute)}`
|
||||
},
|
||||
initTimeValues() {
|
||||
const value = this.getDefaultTimeValue()
|
||||
this.singleTime = value
|
||||
this.rangeStartTime = value
|
||||
this.rangeEndTime = value
|
||||
},
|
||||
timeToPickerValue(value = '') {
|
||||
const [hour, minute, second] = this.parseTimeValue(value)
|
||||
return [hour, minute, second]
|
||||
},
|
||||
pickerValueToTime(value = []) {
|
||||
const hour = Number(value[0] || 0)
|
||||
const minute = Number(value[1] || 0)
|
||||
const second = Number(value[2] || 0)
|
||||
if (this.timePrecision === 'hour') {
|
||||
return this.padTime(hour)
|
||||
}
|
||||
if (this.timePrecision === 'second') {
|
||||
return `${this.padTime(hour)}:${this.padTime(minute)}:${this.padTime(second)}`
|
||||
}
|
||||
return `${this.padTime(hour)}:${this.padTime(minute)}`
|
||||
},
|
||||
openTimePicker(target) {
|
||||
this.timePickerTarget = target
|
||||
let currentValue = this.singleTime
|
||||
if (target === 'start') currentValue = this.rangeStartTime
|
||||
if (target === 'end') currentValue = this.rangeEndTime
|
||||
this.timePickerValue = this.timeToPickerValue(currentValue)
|
||||
this.timePickerShow = true
|
||||
},
|
||||
onTimePickerChange(e) {
|
||||
this.timePickerValue = e.detail.value
|
||||
},
|
||||
closeTimePicker() {
|
||||
this.timePickerShow = false
|
||||
},
|
||||
confirmTimePicker() {
|
||||
const value = this.pickerValueToTime(this.timePickerValue)
|
||||
if (this.timePickerTarget === 'single') this.singleTime = value
|
||||
if (this.timePickerTarget === 'start') this.rangeStartTime = value
|
||||
if (this.timePickerTarget === 'end') this.rangeEndTime = value
|
||||
this.timePickerShow = false
|
||||
},
|
||||
timeToSecond(timeText = '') {
|
||||
const [hour, minute, second] = this.parseTimeValue(timeText)
|
||||
return hour * 3600 + minute * 60 + second
|
||||
},
|
||||
validateSameDayRangeTime() {
|
||||
if (!this.enableTime || this.mode !== 'range' || this.rangeResultMode !== 'boundary') {
|
||||
return true
|
||||
}
|
||||
if (this.selected.length < 2) return true
|
||||
const startDate = this.selected[0]
|
||||
const endDate = this.selected[this.selected.length - 1]
|
||||
if (startDate !== endDate) return true
|
||||
const startSeconds = this.timeToSecond(this.rangeStartTime)
|
||||
const endSeconds = this.timeToSecond(this.rangeEndTime)
|
||||
if (endSeconds < startSeconds) {
|
||||
uni.showToast({
|
||||
title: '结束时间不能早于开始时间',
|
||||
icon: 'none'
|
||||
})
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
appendTime(dateText, timeText) {
|
||||
return `${dateText} ${timeText}`
|
||||
},
|
||||
getConfirmValue(selected = this.selected) {
|
||||
let result = selected
|
||||
if (
|
||||
this.mode === 'range' &&
|
||||
this.rangeResultMode === 'boundary' &&
|
||||
selected.length >= 2
|
||||
) {
|
||||
const len = selected.length - 1
|
||||
result = [selected[0], selected[len]]
|
||||
}
|
||||
if (!this.showTimePanel || !this.enableTime) {
|
||||
return result
|
||||
}
|
||||
if (this.mode === 'single' && result.length >= 1) {
|
||||
return [this.appendTime(result[0], this.singleTime)]
|
||||
}
|
||||
if (this.mode === 'range' && this.rangeResultMode === 'boundary' && result.length >= 2) {
|
||||
return [
|
||||
this.appendTime(result[0], this.rangeStartTime),
|
||||
this.appendTime(result[1], this.rangeEndTime)
|
||||
]
|
||||
}
|
||||
return result
|
||||
},
|
||||
// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
|
||||
setFormatter(e) {
|
||||
this.innerFormatter = e
|
||||
@@ -231,7 +556,8 @@ export default {
|
||||
return
|
||||
}
|
||||
if( scene === 'tap') {
|
||||
this.$emit('confirm', this.selected)
|
||||
if (!this.validateSameDayRangeTime()) return
|
||||
this.$emit('confirm', this.getConfirmValue())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,7 +572,15 @@ export default {
|
||||
return error('maxDate不能小于minDate时间')
|
||||
}
|
||||
// 滚动区域的高度
|
||||
this.listHeight = this.rowHeight * 5 + 30
|
||||
let bottomPadding = 0;
|
||||
if (this.pageInline) {
|
||||
bottomPadding = 0
|
||||
} else {
|
||||
bottomPadding = 30
|
||||
}
|
||||
this.listHeight = this.rowHeight * (this.monthSwitch ? 6 : 5) + bottomPadding
|
||||
this.initTimeOptions()
|
||||
this.initTimeValues()
|
||||
this.setMonth()
|
||||
},
|
||||
close() {
|
||||
@@ -255,7 +589,8 @@ export default {
|
||||
// 点击确定按钮
|
||||
confirm() {
|
||||
if (!this.buttonDisabled) {
|
||||
this.$emit('confirm', this.selected)
|
||||
if (!this.validateSameDayRangeTime()) return
|
||||
this.$emit('confirm', this.getConfirmValue())
|
||||
}
|
||||
},
|
||||
// 获得两个日期之间的月份数
|
||||
@@ -340,6 +675,67 @@ export default {
|
||||
year: dayjs(minDate).add(i, 'month').year()
|
||||
})
|
||||
}
|
||||
if (this.monthSwitch) {
|
||||
this.monthIndex = this.getDefaultMonthIndex()
|
||||
}
|
||||
},
|
||||
getDefaultMonthIndex() {
|
||||
let selected = dayjs().format('YYYY-MM')
|
||||
if (this.defaultDate) {
|
||||
if (!test.array(this.defaultDate)) {
|
||||
selected = dayjs(this.defaultDate).format('YYYY-MM')
|
||||
} else if (this.defaultDate.length) {
|
||||
selected = dayjs(this.defaultDate[0]).format('YYYY-MM')
|
||||
}
|
||||
}
|
||||
const index = this.months.findIndex(({ year, month }) => {
|
||||
return `${year}-${padZero(month)}` === selected
|
||||
})
|
||||
return index === -1 ? 0 : index
|
||||
},
|
||||
prevMonth() {
|
||||
if (!this.switchPrevDisabled) {
|
||||
this.monthIndex -= 1
|
||||
}
|
||||
},
|
||||
nextMonth() {
|
||||
if (!this.switchNextDisabled) {
|
||||
this.monthIndex += 1
|
||||
}
|
||||
},
|
||||
prevYear() {
|
||||
if (!this.switchPrevYearDisabled) {
|
||||
this.monthIndex -= 12
|
||||
}
|
||||
},
|
||||
nextYear() {
|
||||
if (!this.switchNextYearDisabled) {
|
||||
this.monthIndex += 12
|
||||
}
|
||||
},
|
||||
jumpToToday() {
|
||||
if (this.todayDisabled) {
|
||||
return
|
||||
}
|
||||
const targetMonth = dayjs(this.todayDate).format('YYYY-MM')
|
||||
const selectToday = () => {
|
||||
if (this.mode === 'range') {
|
||||
return
|
||||
}
|
||||
this.$refs.month && this.$refs.month.selectDate(this.todayDate)
|
||||
}
|
||||
if (this.monthSwitch) {
|
||||
const todayMonthIndex = this.months.findIndex(({ year, month }) => {
|
||||
return `${year}-${padZero(month)}` === targetMonth
|
||||
})
|
||||
if (todayMonthIndex !== -1) {
|
||||
this.monthIndex = todayMonthIndex
|
||||
this.$nextTick(selectToday)
|
||||
}
|
||||
return
|
||||
}
|
||||
this.scrollIntoDefaultMonth(targetMonth)
|
||||
this.$nextTick(selectToday)
|
||||
},
|
||||
// 滚动到默认设置的月份
|
||||
scrollIntoDefaultMonth(selected) {
|
||||
@@ -354,6 +750,7 @@ export default {
|
||||
if (_index !== -1) {
|
||||
// #ifndef MP-WEIXIN
|
||||
this.$nextTick(() => {
|
||||
this.scrollIntoView = ''
|
||||
this.scrollIntoView = `month-${_index}`
|
||||
this.scrollIntoViewScroll = this.scrollIntoView
|
||||
})
|
||||
@@ -376,6 +773,12 @@ export default {
|
||||
}
|
||||
},
|
||||
// 更新月份的top值
|
||||
onUpdateMonthTop(topArr = []) {
|
||||
if (this.monthSwitch) {
|
||||
return
|
||||
}
|
||||
this.updateMonthTop(topArr)
|
||||
},
|
||||
updateMonthTop(topArr = []) {
|
||||
// 设置对应月份的top值,用于onScroll方法更新月份
|
||||
topArr.map((item, index) => {
|
||||
@@ -403,9 +806,88 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../libs/css/components.scss';
|
||||
|
||||
.u-calendar {
|
||||
color: var(--up-main-color, $u-main-color);
|
||||
background-color: var(--up-card-bg-color, #ffffff);
|
||||
|
||||
&__time-panel {
|
||||
padding: 8px 16px 0;
|
||||
}
|
||||
|
||||
&__time-row {
|
||||
height: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
&__time-date {
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
&__time-trigger {
|
||||
min-width: 92px;
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
border-radius: 15px;
|
||||
border: 1px solid #dcdfe6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__time-text {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
&__time-picker {
|
||||
width: 320px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__time-picker__header {
|
||||
height: 44px;
|
||||
padding: 0 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
&__time-picker__cancel {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
&__time-picker__title {
|
||||
font-size: 15px;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__time-picker__confirm {
|
||||
font-size: 14px;
|
||||
color: #3c9cff;
|
||||
}
|
||||
|
||||
&__time-picker__body {
|
||||
width: 320px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
&__time-picker__item {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
&__confirm {
|
||||
padding: 7px 18px;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import dayjs from 'dayjs/esm/index'
|
||||
import dayjs from '../u-datetime-picker/dayjs.esm.min.js';
|
||||
export default {
|
||||
methods: {
|
||||
// 设置月份数据
|
||||
|
||||
@@ -0,0 +1,734 @@
|
||||
<template>
|
||||
<view
|
||||
class="u-canvas"
|
||||
:id="rootId"
|
||||
:style="{
|
||||
width: useRootHeightAndWidth ? '100%' : actualWidth + unit,
|
||||
height: useRootHeightAndWidth ? '100%' : actualHeight + unit
|
||||
}"
|
||||
>
|
||||
<!-- #ifdef MP || H5 -->
|
||||
<canvas
|
||||
class="u-canvas__canvas"
|
||||
:id="canvasId"
|
||||
:canvas-id="canvasId"
|
||||
type="2d"
|
||||
:disable-scroll="disableScroll"
|
||||
:style="{ width: actualWidth + unit, height: actualHeight + unit }"
|
||||
@touchstart="onTouchStart"
|
||||
@touchmove="onTouchMove"
|
||||
@touchend="onTouchEnd"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<canvas
|
||||
class="u-canvas__canvas"
|
||||
:id="canvasId"
|
||||
:canvas-id="canvasId"
|
||||
:disable-scroll="disableScroll"
|
||||
:style="{ width: actualWidth + unit, height: actualHeight + unit }"
|
||||
@touchstart="onTouchStart"
|
||||
@touchmove="onTouchMove"
|
||||
@touchend="onTouchEnd"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<gcanvas
|
||||
class="u-canvas__canvas"
|
||||
ref="gcanvas"
|
||||
:style="{ width: actualWidth + unit, height: actualHeight + unit }"
|
||||
@touchstart="onTouchStart"
|
||||
@touchmove="onTouchMove"
|
||||
@touchend="onTouchEnd"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifdef APP-NVUE
|
||||
import {
|
||||
enable,
|
||||
WeexBridge,
|
||||
Image as GImage
|
||||
} from '../../libs/util/gcanvas/index.js';
|
||||
// #endif
|
||||
|
||||
export default {
|
||||
name: 'u-canvas',
|
||||
emits: ['ready', 'touchstart', 'touchmove', 'touchend'],
|
||||
props: {
|
||||
canvasId: {
|
||||
type: String,
|
||||
default: () => `u-canvas${Math.floor(Math.random() * 1000000)}`
|
||||
},
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: 300
|
||||
},
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: 300
|
||||
},
|
||||
unit: {
|
||||
type: String,
|
||||
default: 'px'
|
||||
},
|
||||
useRootHeightAndWidth: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
},
|
||||
disableScroll: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rootId: `u-canvas-root-${Math.floor(Math.random() * 1000000)}`,
|
||||
ctx: null,
|
||||
widthLocal: this.parseSize(this.width),
|
||||
heightLocal: this.parseSize(this.height),
|
||||
fontSize: 12,
|
||||
fontFamily: 'sans-serif',
|
||||
fontWeight: 'normal',
|
||||
dpr: 1
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
actualWidth() {
|
||||
return this.useRootHeightAndWidth ? this.widthLocal : this.parseSize(this.width);
|
||||
},
|
||||
actualHeight() {
|
||||
return this.useRootHeightAndWidth ? this.heightLocal : this.parseSize(this.height);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
width() {
|
||||
this.refresh();
|
||||
},
|
||||
height() {
|
||||
this.refresh();
|
||||
},
|
||||
bgColor() {
|
||||
this.clearCanvas();
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this._canvasNode = null;
|
||||
this._selectorResult = null;
|
||||
this._canvasElement = null;
|
||||
this._imageCache = Object.create(null);
|
||||
this._isNvue = false;
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initCanvas();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
parseSize(value) {
|
||||
if (typeof value === 'number') {
|
||||
return value;
|
||||
}
|
||||
if (typeof value !== 'string') {
|
||||
return 0;
|
||||
}
|
||||
if (value.endsWith('rpx') || value.endsWith('upx')) {
|
||||
return uni.upx2px(parseFloat(value));
|
||||
}
|
||||
if (value.endsWith('px')) {
|
||||
return parseFloat(value) || 0;
|
||||
}
|
||||
return parseFloat(value) || 0;
|
||||
},
|
||||
onTouchStart(event) {
|
||||
this.$emit('touchstart', event);
|
||||
},
|
||||
onTouchMove(event) {
|
||||
this.$emit('touchmove', event);
|
||||
},
|
||||
onTouchEnd(event) {
|
||||
this.$emit('touchend', event);
|
||||
},
|
||||
async getCanvasNode(id = this.canvasId, isCanvas = true) {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
// #ifdef APP-NVUE
|
||||
setTimeout(() => {
|
||||
const gcanvas = this.$refs.gcanvas;
|
||||
if (!gcanvas) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
this._isNvue = true;
|
||||
resolve(enable(gcanvas, { bridge: WeexBridge }));
|
||||
}, 100);
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-NVUE
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(`#${id}`)
|
||||
.fields(
|
||||
{
|
||||
node: isCanvas,
|
||||
size: true
|
||||
},
|
||||
(res) => {
|
||||
resolve(res || false);
|
||||
}
|
||||
)
|
||||
.exec();
|
||||
// #endif
|
||||
} catch (error) {
|
||||
console.error('获取画布节点失败:', error);
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
getCanvasElement() {
|
||||
return this._canvasElement || null;
|
||||
},
|
||||
getRawContext() {
|
||||
return this.ctx;
|
||||
},
|
||||
getCanvasContext() {
|
||||
// #ifdef APP-PLUS
|
||||
return uni.createCanvasContext(this.canvasId, this);
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
return this._canvasElement && typeof this._canvasElement.getContext === 'function'
|
||||
? this._canvasElement.getContext('2d')
|
||||
: null;
|
||||
// #endif
|
||||
|
||||
// #ifdef MP || H5
|
||||
return this._canvasElement && typeof this._canvasElement.getContext === 'function'
|
||||
? this._canvasElement.getContext('2d')
|
||||
: null;
|
||||
// #endif
|
||||
},
|
||||
async setNewSize() {
|
||||
const rootNode = await this.getCanvasNode(this.rootId, false);
|
||||
if (!rootNode) {
|
||||
return;
|
||||
}
|
||||
if (rootNode.width) {
|
||||
this.widthLocal = rootNode.width;
|
||||
}
|
||||
if (rootNode.height) {
|
||||
this.heightLocal = rootNode.height;
|
||||
}
|
||||
},
|
||||
async initCanvas(force = false) {
|
||||
try {
|
||||
if (this.useRootHeightAndWidth) {
|
||||
await this.setNewSize();
|
||||
}
|
||||
if (this.ctx && !force) {
|
||||
this.$emit('ready', {
|
||||
width: this.actualWidth,
|
||||
height: this.actualHeight
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
this._canvasNode = await this.getCanvasNode(this.canvasId);
|
||||
if (!this._canvasNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._selectorResult = this._canvasNode;
|
||||
this._canvasElement = this._canvasNode.node || this._canvasNode;
|
||||
this.dpr = uni.getSystemInfoSync().pixelRatio || 1;
|
||||
|
||||
// #ifdef MP || H5
|
||||
if (this._canvasElement) {
|
||||
this._canvasElement.width = Math.ceil(this.actualWidth * this.dpr);
|
||||
this._canvasElement.height = Math.ceil(this.actualHeight * this.dpr);
|
||||
}
|
||||
// #endif
|
||||
|
||||
this.ctx = this.getCanvasContext();
|
||||
if (!this.ctx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// #ifdef MP || H5
|
||||
if (typeof this.ctx.setTransform === 'function') {
|
||||
this.ctx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);
|
||||
} else if (typeof this.ctx.scale === 'function') {
|
||||
this.ctx.scale(this.dpr, this.dpr);
|
||||
}
|
||||
// #endif
|
||||
|
||||
this.applyFont();
|
||||
this.clearCanvas();
|
||||
this.$emit('ready', {
|
||||
width: this.actualWidth,
|
||||
height: this.actualHeight
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('初始化Canvas失败:', error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
refresh() {
|
||||
return this.initCanvas(true);
|
||||
},
|
||||
getWidth() {
|
||||
return this.actualWidth;
|
||||
},
|
||||
getHeight() {
|
||||
return this.actualHeight;
|
||||
},
|
||||
clearCanvas() {
|
||||
if (!this.ctx) return;
|
||||
this.clearRect(0, 0, this.actualWidth, this.actualHeight);
|
||||
if (this.bgColor && this.bgColor !== 'transparent') {
|
||||
this.beginPath();
|
||||
this.rect(0, 0, this.actualWidth, this.actualHeight);
|
||||
this.setFillStyle(this.bgColor);
|
||||
this.fill();
|
||||
}
|
||||
this.draw();
|
||||
},
|
||||
callContext(method, ...args) {
|
||||
if (this.ctx && typeof this.ctx[method] === 'function') {
|
||||
return this.ctx[method](...args);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
rect(x, y, width, height) {
|
||||
return this.callContext('rect', x, y, width, height);
|
||||
},
|
||||
clearRect(x, y, width, height) {
|
||||
return this.callContext('clearRect', x, y, width, height);
|
||||
},
|
||||
fillRect(x, y, width, height) {
|
||||
return this.callContext('fillRect', x, y, width, height);
|
||||
},
|
||||
strokeRect(x, y, width, height) {
|
||||
return this.callContext('strokeRect', x, y, width, height);
|
||||
},
|
||||
fill() {
|
||||
return this.callContext('fill');
|
||||
},
|
||||
stroke() {
|
||||
return this.callContext('stroke');
|
||||
},
|
||||
beginPath() {
|
||||
return this.callContext('beginPath');
|
||||
},
|
||||
closePath() {
|
||||
return this.callContext('closePath');
|
||||
},
|
||||
moveTo(x, y) {
|
||||
return this.callContext('moveTo', x, y);
|
||||
},
|
||||
lineTo(x, y) {
|
||||
return this.callContext('lineTo', x, y);
|
||||
},
|
||||
arc(x, y, radius, startAngle, endAngle, anticlockwise = false) {
|
||||
return this.callContext('arc', x, y, radius, startAngle, endAngle, anticlockwise);
|
||||
},
|
||||
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
|
||||
return this.callContext('bezierCurveTo', cp1x, cp1y, cp2x, cp2y, x, y);
|
||||
},
|
||||
quadraticCurveTo(cpx, cpy, x, y) {
|
||||
return this.callContext('quadraticCurveTo', cpx, cpy, x, y);
|
||||
},
|
||||
clip() {
|
||||
return this.callContext('clip');
|
||||
},
|
||||
save() {
|
||||
return this.callContext('save');
|
||||
},
|
||||
restore() {
|
||||
return this.callContext('restore');
|
||||
},
|
||||
translate(x, y) {
|
||||
return this.callContext('translate', x, y);
|
||||
},
|
||||
rotate(angle) {
|
||||
return this.callContext('rotate', angle);
|
||||
},
|
||||
scale(x, y) {
|
||||
return this.callContext('scale', x, y);
|
||||
},
|
||||
setFillStyle(color) {
|
||||
if (!this.ctx) return;
|
||||
if (typeof this.ctx.setFillStyle === 'function') {
|
||||
this.ctx.setFillStyle(color);
|
||||
} else {
|
||||
this.ctx.fillStyle = color;
|
||||
}
|
||||
},
|
||||
setStrokeStyle(color) {
|
||||
if (!this.ctx) return;
|
||||
if (typeof this.ctx.setStrokeStyle === 'function') {
|
||||
this.ctx.setStrokeStyle(color);
|
||||
} else {
|
||||
this.ctx.strokeStyle = color;
|
||||
}
|
||||
},
|
||||
setLineWidth(width) {
|
||||
if (!this.ctx) return;
|
||||
if (typeof this.ctx.setLineWidth === 'function') {
|
||||
this.ctx.setLineWidth(width);
|
||||
} else {
|
||||
this.ctx.lineWidth = width;
|
||||
}
|
||||
},
|
||||
setLineCap(lineCap = 'round') {
|
||||
if (!this.ctx) return;
|
||||
if (typeof this.ctx.setLineCap === 'function') {
|
||||
this.ctx.setLineCap(lineCap);
|
||||
} else {
|
||||
this.ctx.lineCap = lineCap;
|
||||
}
|
||||
},
|
||||
setLineJoin(lineJoin = 'round') {
|
||||
if (!this.ctx) return;
|
||||
if (typeof this.ctx.setLineJoin === 'function') {
|
||||
this.ctx.setLineJoin(lineJoin);
|
||||
} else {
|
||||
this.ctx.lineJoin = lineJoin;
|
||||
}
|
||||
},
|
||||
setTextAlign(align = 'left') {
|
||||
if (!this.ctx) return;
|
||||
if (typeof this.ctx.setTextAlign === 'function') {
|
||||
this.ctx.setTextAlign(align);
|
||||
} else {
|
||||
this.ctx.textAlign = align;
|
||||
}
|
||||
},
|
||||
setTextBaseline(baseline = 'alphabetic') {
|
||||
if (!this.ctx) return;
|
||||
if (typeof this.ctx.setTextBaseline === 'function') {
|
||||
this.ctx.setTextBaseline(baseline);
|
||||
} else {
|
||||
this.ctx.textBaseline = baseline;
|
||||
}
|
||||
},
|
||||
setFontSize(fontSize) {
|
||||
this.fontSize = Number(fontSize) || this.fontSize;
|
||||
if (this.ctx && typeof this.ctx.setFontSize === 'function') {
|
||||
this.ctx.setFontSize(this.fontSize);
|
||||
}
|
||||
this.applyFont();
|
||||
},
|
||||
setFont(font) {
|
||||
if (!this.ctx) return;
|
||||
if ('font' in this.ctx) {
|
||||
this.ctx.font = font;
|
||||
return;
|
||||
}
|
||||
const matched = String(font).match(/(\d+(?:\.\d+)?)px/);
|
||||
if (matched) {
|
||||
this.fontSize = Number(matched[1]);
|
||||
}
|
||||
this.applyFont();
|
||||
},
|
||||
setGlobalAlpha(alpha) {
|
||||
if (!this.ctx) return;
|
||||
if (typeof this.ctx.setGlobalAlpha === 'function') {
|
||||
this.ctx.setGlobalAlpha(alpha);
|
||||
} else {
|
||||
this.ctx.globalAlpha = alpha;
|
||||
}
|
||||
},
|
||||
setShadow(offsetX = 0, offsetY = 0, blur = 0, color = 'rgba(0,0,0,0)') {
|
||||
if (!this.ctx) return;
|
||||
if (typeof this.ctx.setShadow === 'function') {
|
||||
this.ctx.setShadow(offsetX, offsetY, blur, color);
|
||||
return;
|
||||
}
|
||||
this.ctx.shadowOffsetX = offsetX;
|
||||
this.ctx.shadowOffsetY = offsetY;
|
||||
this.ctx.shadowBlur = blur;
|
||||
this.ctx.shadowColor = color;
|
||||
},
|
||||
setLineStyle(lineColor, lineWidth) {
|
||||
this.setLineCap('round');
|
||||
this.setLineJoin('round');
|
||||
this.setStrokeStyle(lineColor);
|
||||
this.setLineWidth(lineWidth);
|
||||
},
|
||||
applyFont() {
|
||||
if (!this.ctx) return;
|
||||
const font = `${this.fontWeight === 'normal' ? '' : `${this.fontWeight} `}${this.fontSize}px ${this.fontFamily}`.trim();
|
||||
if ('font' in this.ctx) {
|
||||
this.ctx.font = font;
|
||||
} else if (typeof this.ctx.setFont === 'function') {
|
||||
this.ctx.setFont(font);
|
||||
} else if (typeof this.ctx.setFontSize === 'function') {
|
||||
this.ctx.setFontSize(this.fontSize);
|
||||
}
|
||||
},
|
||||
fillText(text, x, y) {
|
||||
return this.callContext('fillText', String(text), x, y);
|
||||
},
|
||||
measureText(text) {
|
||||
if (this.ctx && typeof this.ctx.measureText === 'function') {
|
||||
return this.ctx.measureText(String(text));
|
||||
}
|
||||
return {
|
||||
width: String(text).length * this.fontSize * 0.6
|
||||
};
|
||||
},
|
||||
createLinearGradient(x0, y0, x1, y1) {
|
||||
if (this.ctx && typeof this.ctx.createLinearGradient === 'function') {
|
||||
return this.ctx.createLinearGradient(x0, y0, x1, y1);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
createRadialGradient(x0, y0, r0, x1, y1, r1) {
|
||||
if (this.ctx && typeof this.ctx.createRadialGradient === 'function') {
|
||||
return this.ctx.createRadialGradient(x0, y0, r0, x1, y1, r1);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
loadImage(src) {
|
||||
if (this._imageCache[src]) {
|
||||
return Promise.resolve(this._imageCache[src]);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
let image = null;
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
image = new GImage();
|
||||
// #endif
|
||||
|
||||
// #ifdef MP
|
||||
const canvas = this.getCanvasElement();
|
||||
if (canvas && typeof canvas.createImage === 'function') {
|
||||
image = canvas.createImage();
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
image = new Image();
|
||||
image.crossOrigin = 'anonymous';
|
||||
// #endif
|
||||
|
||||
if (!image) {
|
||||
resolve(src);
|
||||
return;
|
||||
}
|
||||
|
||||
image.onload = () => {
|
||||
this._imageCache[src] = image;
|
||||
resolve(image);
|
||||
};
|
||||
image.onerror = reject;
|
||||
image.src = src;
|
||||
});
|
||||
},
|
||||
async drawImage(source, ...args) {
|
||||
if (!this.ctx || typeof this.ctx.drawImage !== 'function') {
|
||||
return false;
|
||||
}
|
||||
if (typeof source !== 'string' || (typeof this.ctx.setFillStyle === 'function' && !this._isNvue)) {
|
||||
this.ctx.drawImage(source, ...args);
|
||||
return true;
|
||||
}
|
||||
const image = await this.loadImage(source);
|
||||
this.ctx.drawImage(image, ...args);
|
||||
return true;
|
||||
},
|
||||
draw(isLastDraw = false, callback) {
|
||||
if (this.ctx && typeof this.ctx.draw === 'function') {
|
||||
return this.ctx.draw(isLastDraw, callback);
|
||||
}
|
||||
if (typeof callback === 'function') {
|
||||
setTimeout(callback, 0);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
toTempFilePath(options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const width = options.width || this.actualWidth;
|
||||
const height = options.height || this.actualHeight;
|
||||
const request = {
|
||||
x: options.x || 0,
|
||||
y: options.y || 0,
|
||||
width,
|
||||
height,
|
||||
destWidth: options.destWidth || width,
|
||||
destHeight: options.destHeight || height,
|
||||
fileType: options.fileType || 'png',
|
||||
quality: options.quality === undefined ? 1 : options.quality
|
||||
};
|
||||
const success = (res) => {
|
||||
if (typeof options.success === 'function') options.success(res);
|
||||
resolve(res);
|
||||
};
|
||||
const fail = (err) => {
|
||||
if (typeof options.fail === 'function') options.fail(err);
|
||||
reject(err);
|
||||
};
|
||||
const complete = (res) => {
|
||||
if (typeof options.complete === 'function') options.complete(res);
|
||||
};
|
||||
|
||||
// #ifdef H5
|
||||
const canvas = this.getCanvasElement() || (this.ctx && this.ctx.canvas);
|
||||
if (canvas && typeof canvas.toDataURL === 'function') {
|
||||
try {
|
||||
let exportCanvas = canvas;
|
||||
if (
|
||||
request.x !== 0 ||
|
||||
request.y !== 0 ||
|
||||
request.width !== this.actualWidth ||
|
||||
request.height !== this.actualHeight ||
|
||||
request.destWidth !== request.width ||
|
||||
request.destHeight !== request.height
|
||||
) {
|
||||
exportCanvas = document.createElement('canvas');
|
||||
exportCanvas.width = request.destWidth;
|
||||
exportCanvas.height = request.destHeight;
|
||||
const exportCtx = exportCanvas.getContext('2d');
|
||||
exportCtx.drawImage(
|
||||
canvas,
|
||||
request.x * this.dpr,
|
||||
request.y * this.dpr,
|
||||
request.width * this.dpr,
|
||||
request.height * this.dpr,
|
||||
0,
|
||||
0,
|
||||
request.destWidth,
|
||||
request.destHeight
|
||||
);
|
||||
}
|
||||
const mime = request.fileType === 'jpg' || request.fileType === 'jpeg'
|
||||
? 'image/jpeg'
|
||||
: 'image/png';
|
||||
const res = {
|
||||
tempFilePath: exportCanvas.toDataURL(mime, request.quality)
|
||||
};
|
||||
success(res);
|
||||
complete(res);
|
||||
return;
|
||||
} catch (error) {
|
||||
fail(error);
|
||||
complete(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
if (this.ctx && typeof this.ctx.toTempFilePath === 'function') {
|
||||
this.ctx.toTempFilePath(
|
||||
request.x,
|
||||
request.y,
|
||||
request.width,
|
||||
request.height,
|
||||
request.destWidth,
|
||||
request.destHeight,
|
||||
request.fileType,
|
||||
request.quality,
|
||||
(res) => {
|
||||
success(res);
|
||||
complete(res);
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
// #endif
|
||||
|
||||
const canvasNode = this.getCanvasElement();
|
||||
const uniOptions = {
|
||||
...request,
|
||||
canvasId: this.canvasId,
|
||||
success,
|
||||
fail,
|
||||
complete
|
||||
};
|
||||
if (canvasNode) {
|
||||
uniOptions.canvas = canvasNode;
|
||||
}
|
||||
uni.canvasToTempFilePath(uniOptions, this);
|
||||
});
|
||||
},
|
||||
async exportImage(fileType = 'png', quality = 1) {
|
||||
const res = await this.toTempFilePath({
|
||||
fileType,
|
||||
quality,
|
||||
width: this.actualWidth,
|
||||
height: this.actualHeight,
|
||||
destWidth: this.actualWidth,
|
||||
destHeight: this.actualHeight
|
||||
});
|
||||
return res.tempFilePath || res.apFilePath;
|
||||
},
|
||||
getImageData(options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = {
|
||||
canvasId: this.canvasId,
|
||||
x: options.x || 0,
|
||||
y: options.y || 0,
|
||||
width: options.width || this.actualWidth,
|
||||
height: options.height || this.actualHeight,
|
||||
success: (res) => {
|
||||
if (typeof options.success === 'function') options.success(res);
|
||||
resolve(res);
|
||||
},
|
||||
fail: (err) => {
|
||||
if (typeof options.fail === 'function') options.fail(err);
|
||||
reject(err);
|
||||
},
|
||||
complete: options.complete
|
||||
};
|
||||
uni.canvasGetImageData(request, this);
|
||||
});
|
||||
},
|
||||
putImageData(options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = {
|
||||
canvasId: this.canvasId,
|
||||
x: options.x || 0,
|
||||
y: options.y || 0,
|
||||
width: options.width || this.actualWidth,
|
||||
height: options.height || this.actualHeight,
|
||||
data: options.data,
|
||||
success: (res) => {
|
||||
if (typeof options.success === 'function') options.success(res);
|
||||
resolve(res);
|
||||
},
|
||||
fail: (err) => {
|
||||
if (typeof options.fail === 'function') options.fail(err);
|
||||
reject(err);
|
||||
},
|
||||
complete: options.complete
|
||||
};
|
||||
uni.canvasPutImageData(request, this);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.u-canvas {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-canvas__canvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-20 16:53:20
|
||||
* @FilePath : /u-view2.0/uview-ui/libs/config/props/carKeyboard.js
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/carKeyboard.js
|
||||
*/
|
||||
export default {
|
||||
// 车牌号键盘
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import CarKeyboardDefaultProps from './carKeyboard'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(CarKeyboardDefaultProps)
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 是否打乱键盘按键的顺序
|
||||
|
||||
@@ -56,11 +56,11 @@
|
||||
hover-class="u-hover-class"
|
||||
:hover-stay-time="200"
|
||||
>
|
||||
<u-icon
|
||||
<up-icon
|
||||
size="28"
|
||||
name="backspace"
|
||||
color="#303133"
|
||||
></u-icon>
|
||||
:color="upThemeVar('--up-main-color', '#303133')"
|
||||
></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -222,26 +222,25 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
$u-car-keyboard-background-color: rgb(224, 228, 230) !default;
|
||||
$u-car-keyboard-background-color: var(--up-bg-color, rgb(224, 228, 230)) !default;
|
||||
$u-car-keyboard-padding:6px 0 6px !default;
|
||||
$u-car-keyboard-button-inner-width:64rpx !default;
|
||||
$u-car-keyboard-button-inner-background-color:#FFFFFF !default;
|
||||
$u-car-keyboard-button-inner-background-color:var(--up-card-bg-color, #ffffff) !default;
|
||||
$u-car-keyboard-button-height:80rpx !default;
|
||||
$u-car-keyboard-button-inner-box-shadow:0 1px 0px #999992 !default;
|
||||
$u-car-keyboard-button-border-radius:4px !default;
|
||||
$u-car-keyboard-button-inner-margin:8rpx 5rpx !default;
|
||||
$u-car-keyboard-button-text-font-size:16px !default;
|
||||
$u-car-keyboard-button-text-color:$u-main-color !default;
|
||||
$u-car-keyboard-button-text-color:var(--up-main-color, #303133) !default;
|
||||
$u-car-keyboard-center-inner-margin: 0 4rpx !default;
|
||||
$u-car-keyboard-special-button-width:134rpx !default;
|
||||
$u-car-keyboard-lang-font-size:16px !default;
|
||||
$u-car-keyboard-lang-color:$u-main-color !default;
|
||||
$u-car-keyboard-lang-color:var(--up-main-color, #303133) !default;
|
||||
$u-car-keyboard-active-color:$u-primary !default;
|
||||
$u-car-keyboard-line-font-size:15px !default;
|
||||
$u-car-keyboard-line-color:$u-main-color !default;
|
||||
$u-car-keyboard-line-color:var(--up-main-color, #303133) !default;
|
||||
$u-car-keyboard-line-margin:0 1px !default;
|
||||
$u-car-keyboard-u-hover-class-background-color:#BBBCC6 !default;
|
||||
$u-car-keyboard-u-hover-class-background-color:var(--up-border-color, #BBBCC6) !default;
|
||||
|
||||
.u-keyboard {
|
||||
@include flex(column);
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* @Author : jry
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2025-04-26 16:37:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-04-26 16:37:21
|
||||
* @FilePath : /uview-plus/libs/config/props/card.js
|
||||
*/
|
||||
export default {
|
||||
// card组件的props
|
||||
card: {
|
||||
full: false,
|
||||
title: '',
|
||||
titleColor: '#303133',
|
||||
titleSize: '15px',
|
||||
subTitle: '',
|
||||
subTitleColor: '#909399',
|
||||
subTitleSize: '13px',
|
||||
border: true,
|
||||
index: '',
|
||||
margin: '15px',
|
||||
borderRadius: '8px',
|
||||
headStyle: {},
|
||||
bodyStyle: {},
|
||||
footStyle: {},
|
||||
headBorderBottom: true,
|
||||
footBorderTop: true,
|
||||
thumb: '',
|
||||
thumbWidth: '30px',
|
||||
thumbCircle: false,
|
||||
padding: '15px',
|
||||
paddingHead: '',
|
||||
paddingBody: '',
|
||||
paddingFoot: '',
|
||||
showHead: true,
|
||||
showFoot: true,
|
||||
boxShadow: 'none'
|
||||
}
|
||||
}
|
||||
@@ -1,140 +1,136 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import CardDefaultProps from './card'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(CardDefaultProps)
|
||||
export const propsCard = defineMixin({
|
||||
props: {
|
||||
// 与屏幕两侧是否留空隙
|
||||
full: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: () => defProps.card.full
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: () => defProps.card.title
|
||||
},
|
||||
// 标题颜色
|
||||
titleColor: {
|
||||
type: String,
|
||||
default: '#303133'
|
||||
default: () => defProps.card.titleColor
|
||||
},
|
||||
// 标题字体大小
|
||||
titleSize: {
|
||||
type: [Number, String],
|
||||
default: '15px'
|
||||
default: () => defProps.card.titleSize
|
||||
},
|
||||
// 副标题
|
||||
subTitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: () => defProps.card.subTitle
|
||||
},
|
||||
// 副标题颜色
|
||||
subTitleColor: {
|
||||
type: String,
|
||||
default: '#909399'
|
||||
default: () => defProps.card.subTitleColor
|
||||
},
|
||||
// 副标题字体大小
|
||||
subTitleSize: {
|
||||
type: [Number, String],
|
||||
default: '13'
|
||||
default: () => defProps.card.subTitleSize
|
||||
},
|
||||
// 是否显示外部边框,只对full=false时有效(卡片与边框有空隙时)
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: () => defProps.card.border
|
||||
},
|
||||
// 用于标识点击了第几个
|
||||
index: {
|
||||
type: [Number, String, Object],
|
||||
default: ''
|
||||
default: () => defProps.card.index
|
||||
},
|
||||
// 用于隔开上下左右的边距,带单位的写法,如:"30px 30px","20px 20px 30px 30px"
|
||||
margin: {
|
||||
type: String,
|
||||
default: '15px'
|
||||
default: () => defProps.card.margin
|
||||
},
|
||||
// card卡片的圆角
|
||||
borderRadius: {
|
||||
type: [Number, String],
|
||||
default: '8px'
|
||||
default: () => defProps.card.borderRadius
|
||||
},
|
||||
// 头部自定义样式,对象形式
|
||||
headStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
default: () => defProps.card.headStyle
|
||||
},
|
||||
// 主体自定义样式,对象形式
|
||||
bodyStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
default: () => defProps.card.bodyStyle
|
||||
},
|
||||
// 底部自定义样式,对象形式
|
||||
footStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
default: () => defProps.card.footStyle
|
||||
},
|
||||
// 头部是否下边框
|
||||
headBorderBottom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: () => defProps.card.headBorderBottom
|
||||
},
|
||||
// 底部是否有上边框
|
||||
footBorderTop: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: () => defProps.card.footBorderTop
|
||||
},
|
||||
// 标题左边的缩略图
|
||||
thumb: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: () => defProps.card.thumb
|
||||
},
|
||||
// 缩略图宽高
|
||||
thumbWidth: {
|
||||
type: [String, Number],
|
||||
default: '30px'
|
||||
default: () => defProps.card.thumbWidth
|
||||
},
|
||||
// 缩略图是否为圆形
|
||||
thumbCircle: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: () => defProps.card.thumbCircle
|
||||
},
|
||||
// 给head,body,foot的内边距
|
||||
padding: {
|
||||
type: [String, Number],
|
||||
default: '15px'
|
||||
default: () => defProps.card.padding
|
||||
},
|
||||
paddingHead: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
default: () => defProps.card.paddingHead
|
||||
},
|
||||
paddingBody: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
default: () => defProps.card.paddingBody
|
||||
},
|
||||
paddingFoot: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
default: () => defProps.card.paddingFoot
|
||||
},
|
||||
// 是否显示头部
|
||||
showHead: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: () => defProps.card.showHead
|
||||
},
|
||||
// 是否显示尾部
|
||||
showFoot: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: () => defProps.card.showFoot
|
||||
},
|
||||
// 卡片外围阴影,字符串形式
|
||||
boxShadow: {
|
||||
type: String,
|
||||
default: 'none'
|
||||
default: () => defProps.card.boxShadow
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -136,13 +136,11 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
background-color: #ffffff;
|
||||
background-color: var(--up-card-bg-color, #ffffff);
|
||||
box-sizing: border-box;
|
||||
|
||||
&-full {
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
<template>
|
||||
<up-popup :show="popupShow" mode="bottom" :popup="false"
|
||||
:mask="true" :closeable="closeable" :safe-area-inset-bottom="true"
|
||||
:close-icon-color="closeIconColor" :z-index="uZIndex"
|
||||
:maskCloseAble="maskCloseAble" @close="close">
|
||||
<view class="up-p-t-30 up-p-l-20 up-m-b-10" v-if="headerDirection ==='column'">
|
||||
<up-steps v-if="popupShow" dot direction="column" v-model:current="tabsIndex">
|
||||
<view v-for="(item, index) in genTabsList" @click="toFatherIndex(index)">
|
||||
<up-steps-item
|
||||
:title="item.name"/>
|
||||
</view>
|
||||
</up-steps>
|
||||
</view>
|
||||
<view class="up-p-t-20 up-m-b-10" v-else>
|
||||
<up-tabs v-if="popupShow" :list="genTabsList"
|
||||
:scrollable="true" v-model:current="tabsIndex" @change="tabsChange" ref="tabs"></up-tabs>
|
||||
</view>
|
||||
<view class="area-box">
|
||||
<view class="u-flex" :class="{ 'change':isChange }"
|
||||
:style="{transform: optionsCols == 2 && isChange ? 'translateX(-33.3333333%)' : ''}">
|
||||
<template v-for="(levelData, levelIndex) in levelList" :key="levelIndex">
|
||||
<view v-if="optionsCols == 2 || levelIndex == tabsIndex" class="area-item"
|
||||
:style="{ width: optionsCols == 2 ? '33.33333%' : '750rpx'}">
|
||||
<view class="u-padding-10" :style="[levelPaneStyle, { height: '100%' }]">
|
||||
<scroll-view :scroll-y="true" style="height: 100%">
|
||||
<up-cell-group v-if="levelIndex === 0 || selectedValueIndexs[levelIndex - 1] !== undefined">
|
||||
<up-cell v-for="(item,index) in levelData"
|
||||
:title="item[labelKey]" :arrow="false"
|
||||
:index="index" :key="index"
|
||||
@click="levelChange(levelIndex, index)">
|
||||
<template v-slot:right-icon>
|
||||
<up-icon v-if="selectedValueIndexs[levelIndex] === index"
|
||||
size="17" name="checkbox-mark"></up-icon>
|
||||
</template>
|
||||
</up-cell>
|
||||
</up-cell-group>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 添加按钮区域 -->
|
||||
<view class="u-cascader-action up-flex up-flex-between">
|
||||
<view class="u-padding-20 up-flex-fill">
|
||||
<up-button @click="handleCancel" type="default">{{ t("up.common.cancel") }}</up-button>
|
||||
</view>
|
||||
<view class="u-padding-20 up-flex-fill">
|
||||
<up-button @click="handleConfirm" type="primary">{{ t("up.common.confirm") }}</up-button>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* u-cascader 通用无限级联选择器
|
||||
* @property {String Number} z-index 弹出时的z-index值(默认1075)
|
||||
* @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker(默认true)
|
||||
* @property {Array} data 级联数据
|
||||
* @property {Array} default-value 默认选中的值
|
||||
* @property {String} valueKey 指定选项的值为选项对象中的哪个属性值
|
||||
* @property {String} labelKey 指定选项标签为选项对象中的哪个属性值
|
||||
* @property {String} childrenKey 指定选项的子选项为选项对象中的哪个属性值
|
||||
* @property {Boolean} autoClose 是否在选择最后一级时自动关闭并触发confirm(默认false)
|
||||
* @property {Boolean} closeable 是否显示关闭图标(默认true)
|
||||
*/
|
||||
import { t } from '../../libs/i18n'
|
||||
export default {
|
||||
name: 'up-cascader',
|
||||
props: {
|
||||
// 通过双向绑定控制组件的弹出与收起
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 级联数据
|
||||
data: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// 默认选中的值
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// 指定选项的值为选项对象中的哪个属性值
|
||||
valueKey: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
// 指定选项标签为选项对象中的哪个属性值
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'label'
|
||||
},
|
||||
// 指定选项的子选项为选项对象中的哪个属性值
|
||||
childrenKey: {
|
||||
type: String,
|
||||
default: 'children'
|
||||
},
|
||||
// 是否允许通过点击遮罩关闭Picker
|
||||
maskCloseAble: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 弹出的z-index值
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 是否在选择最后一级时自动关闭并触发confirm
|
||||
autoClose: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 选中项目的展示方向direction垂直方向适合文字长度过长
|
||||
headerDirection: {
|
||||
type: String,
|
||||
default: 'row'
|
||||
},
|
||||
// 选项区域列数,支持1列和2列,默认为2列
|
||||
optionsCols: {
|
||||
type: [Number],
|
||||
default: 2
|
||||
},
|
||||
// 是否显示关闭图标
|
||||
closeable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 存储每一级的数据
|
||||
levelList: [],
|
||||
// 存储每一级选中的索引
|
||||
selectedValueIndexs: [],
|
||||
tabsIndex: 0,
|
||||
popupShow: false,
|
||||
// 新增confirmValues用于存储确认的值
|
||||
confirmValues: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
data: {
|
||||
handler() {
|
||||
this.initLevelList();
|
||||
this.setDefaultValue();
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
show() {
|
||||
this.popupShow = this.show;
|
||||
},
|
||||
modelValue: {
|
||||
handler() {
|
||||
// 初始化选中值
|
||||
this.setDefaultValue();
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isChange() {
|
||||
return this.tabsIndex > 1;
|
||||
},
|
||||
genTabsList() {
|
||||
let tabsList = [{
|
||||
name: "请选择"
|
||||
}];
|
||||
|
||||
// 根据选中的值动态生成tabs
|
||||
for (let i = 0; i < this.selectedValueIndexs.length; i++) {
|
||||
if (this.selectedValueIndexs[i] !== undefined && this.levelList[i]) {
|
||||
const selectedItem = this.levelList[i][this.selectedValueIndexs[i]];
|
||||
if (selectedItem) {
|
||||
tabsList[i] = {
|
||||
name: selectedItem[this.labelKey]
|
||||
};
|
||||
// 如果还有下一级,则添加"请选择"
|
||||
if (i === this.selectedValueIndexs.length - 1 &&
|
||||
selectedItem[this.childrenKey] &&
|
||||
selectedItem[this.childrenKey].length > 0) {
|
||||
tabsList.push({
|
||||
name: "请选择"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tabsList;
|
||||
},
|
||||
uZIndex() {
|
||||
// 如果用户有传递z-index值,优先使用
|
||||
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
|
||||
},
|
||||
closeIconColor() {
|
||||
return 'var(--up-main-color, #303133)';
|
||||
},
|
||||
levelPaneStyle() {
|
||||
return {
|
||||
backgroundColor: 'var(--up-bg-color, #f7f7f7)'
|
||||
};
|
||||
}
|
||||
},
|
||||
// 新增confirm事件
|
||||
emits: ['update:modelValue', 'update:show', 'change', 'confirm', 'cancel'],
|
||||
methods: {
|
||||
t,
|
||||
getSelectedValues() {
|
||||
const result = [];
|
||||
for (let i = 0; i < this.selectedValueIndexs.length; i++) {
|
||||
const selectedIndex = this.selectedValueIndexs[i];
|
||||
if (selectedIndex === undefined) continue;
|
||||
if (!this.levelList[i] || !this.levelList[i][selectedIndex]) continue;
|
||||
result.push(this.levelList[i][selectedIndex][this.valueKey]);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
initLevelList() {
|
||||
// 初始化第一级数据
|
||||
if (this.data && this.data.length > 0) {
|
||||
this.levelList = [this.data];
|
||||
this.selectedValueIndexs = [];
|
||||
}
|
||||
},
|
||||
setDefaultValue() {
|
||||
// 检查data是否为空
|
||||
if (!this.data || this.data.length == 0) return;
|
||||
// 检查modelValue是否为空
|
||||
if (!this.modelValue || this.modelValue.length == 0) {
|
||||
this.confirmValues = [];
|
||||
return;
|
||||
}
|
||||
// 根据默认值设置选中项
|
||||
// 根据modelValue获取indexs给selectedValueIndexs
|
||||
this.selectedValueIndexs = [];
|
||||
this.levelList = []; // 设置层级数据为空
|
||||
let currentLevelData = this.data;
|
||||
|
||||
for (let i = 0; i < this.modelValue.length; i++) {
|
||||
const value = this.modelValue[i];
|
||||
const index = currentLevelData.findIndex(item => item[this.valueKey] === value);
|
||||
this.levelList[i] = currentLevelData; // 设置每一层级的数据
|
||||
|
||||
if (index !== -1) {
|
||||
this.selectedValueIndexs.push(index);
|
||||
// 更新下一级的数据
|
||||
if (currentLevelData[index][this.childrenKey]) {
|
||||
currentLevelData = currentLevelData[index][this.childrenKey];
|
||||
} else {
|
||||
// 如果没有子级数据,则停止处理
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// 如果找不到匹配项,则停止处理
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 同步确认值,避免“仅回显未改动时确认返回空数组”
|
||||
this.confirmValues = this.getSelectedValues();
|
||||
},
|
||||
close() {
|
||||
this.$emit('cancel');
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
tabsChange(item) {
|
||||
},
|
||||
levelChange(levelIndex, index) {
|
||||
// 设置当前级的选中值
|
||||
this.$set(this.selectedValueIndexs, levelIndex, index);
|
||||
|
||||
// 清除后续级别的选中值
|
||||
this.selectedValueIndexs.splice(levelIndex + 1);
|
||||
this.tabsIndex = Math.min(this.tabsIndex, levelIndex);
|
||||
|
||||
// 清除后续级别的列表
|
||||
this.levelList.splice(levelIndex + 1);
|
||||
|
||||
// 获取当前选中项
|
||||
const currentItem = this.levelList[levelIndex][index];
|
||||
|
||||
// 如果有子级数据,则初始化下一级
|
||||
if (currentItem && currentItem[this.childrenKey] && currentItem[this.childrenKey].length > 0) {
|
||||
// 确保levelList数组足够长
|
||||
if (this.levelList.length <= levelIndex + 1) {
|
||||
this.levelList.push(currentItem[this.childrenKey]);
|
||||
} else {
|
||||
this.$set(this.levelList, levelIndex + 1, currentItem[this.childrenKey]);
|
||||
}
|
||||
// 切换到下一级tab
|
||||
this.tabsIndex = levelIndex + 1;
|
||||
} else {
|
||||
// 没有子级数据,说明是最后一级
|
||||
if (this.autoClose) {
|
||||
// 如果启用自动关闭,则触发change事件并关闭
|
||||
this.emitChange();
|
||||
this.handleConfirm();
|
||||
} else {
|
||||
// 否则只触发change事件,不关闭
|
||||
this.emitChange(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
// 修改emitChange方法,增加closePopup参数
|
||||
emitChange(closePopup = true) {
|
||||
// 构造选中结果
|
||||
const result = this.getSelectedValues();
|
||||
|
||||
// 更新confirmValues
|
||||
this.confirmValues = [...result];
|
||||
|
||||
// 触发change事件,返回value数组
|
||||
this.$emit('change', this.confirmValues);
|
||||
|
||||
// 根据参数决定是否关闭弹窗
|
||||
if (closePopup) {
|
||||
this.close();
|
||||
}
|
||||
},
|
||||
handleCancel() {
|
||||
this.close();
|
||||
},
|
||||
handleConfirm() {
|
||||
const values = this.confirmValues.length ? this.confirmValues : this.getSelectedValues();
|
||||
this.confirmValues = [...values];
|
||||
// 确认时触发confirm事件
|
||||
this.$emit('update:modelValue', values);
|
||||
this.$emit('confirm', values);
|
||||
this.close();
|
||||
},
|
||||
// 跳转父节点
|
||||
toFatherIndex(index){
|
||||
this.tabsIndex = index;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.area-box {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
height: 800rpx;
|
||||
|
||||
>view {
|
||||
width: 150%;
|
||||
transition: transform 0.3s ease-in-out 0s;
|
||||
transform: translateX(0);
|
||||
|
||||
&.change {
|
||||
// transform: translateX(-33.3333333%);
|
||||
}
|
||||
}
|
||||
|
||||
.area-item {
|
||||
// width: 750rpx;
|
||||
height: 800rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加按钮区域样式
|
||||
.u-cascader-action {
|
||||
border-top: 1px solid var(--up-border-color, #eee);
|
||||
}
|
||||
</style>
|
||||
@@ -1,22 +1,27 @@
|
||||
<template>
|
||||
<view class="u-cate-tab">
|
||||
<view class="u-cate-tab" :style="{ height: addUnit(height) }">
|
||||
<view class="u-cate-tab__wrap">
|
||||
<scroll-view class="u-cate-tab__view u-cate-tab__menu-scroll-view"
|
||||
scroll-y scroll-with-animation :scroll-top="scrollTop"
|
||||
:scroll-into-view="itemId">
|
||||
<view v-for="(item, index) in tabList" :key="index" class="u-cate-tab__item"
|
||||
:class="[current == index ? 'u-cate-tab__item-active' : '']"
|
||||
:class="[innerCurrent == index ? 'u-cate-tab__item-active' : '']"
|
||||
@tap.stop="swichMenu(index)">
|
||||
<slot name="tabItem" :item="item">
|
||||
</slot>
|
||||
<text v-if="!$slots['tabItem']" class="u-line-1">{{item[tabKeyName]}}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<scroll-view :scroll-top="scrollRightTop" scroll-with-animation
|
||||
<scroll-view :scroll-top="scrollRightTop" scroll-with-animation :scroll-into-view="scrollIntoView"
|
||||
scroll-y class="u-cate-tab__right-box" @scroll="rightScroll">
|
||||
<view class="u-cate-tab__right-top">
|
||||
<slot name="rightTop" :tabList="tabList">
|
||||
</slot>
|
||||
</view>
|
||||
<view class="u-cate-tab__page-view">
|
||||
<view class="u-cate-tab__page-item" :id="'item' + index"
|
||||
v-for="(item , index) in tabList" :key="index">
|
||||
<template :key="index" v-for="(item , index) in tabList">
|
||||
<view v-if="mode == 'follow' || ( mode == 'tab' && index == innerCurrent)"
|
||||
class="u-cate-tab__page-item" :id="'item' + index">
|
||||
<slot name="itemList" :item="item">
|
||||
</slot>
|
||||
<template v-if="!$slots['itemList']">
|
||||
@@ -35,15 +40,25 @@
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import { addUnit, sleep } from '../../libs/function/index';
|
||||
export default {
|
||||
name: 'up-cate-tab',
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'follow' // follo跟随联动, tab单一显示。
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
tabList: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
@@ -57,18 +72,43 @@
|
||||
itemKeyName: {
|
||||
type: String,
|
||||
default: 'name'
|
||||
},
|
||||
current: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
tabList() {
|
||||
this.getMenuItemTop()
|
||||
tabList: {
|
||||
deep: true,
|
||||
handler(newVal, oldVal) {
|
||||
// this.observer();
|
||||
sleep(30).then(() => {
|
||||
this.getMenuItemTop();
|
||||
this.leftMenuStatus(this.innerCurrent);
|
||||
})
|
||||
}
|
||||
},
|
||||
current(nval) {
|
||||
this.innerCurrent = nval;
|
||||
this.leftMenuStatus(this.innerCurrent);
|
||||
sleep(30).then(() => {
|
||||
this.swichMenu(this.innerCurrent)
|
||||
})
|
||||
},
|
||||
height() {
|
||||
// console.log('height change');
|
||||
this.getMenuItemTop();
|
||||
this.leftMenuStatus(this.innerCurrent);
|
||||
}
|
||||
},
|
||||
emits: ['update:current'],
|
||||
data() {
|
||||
return {
|
||||
scrollTop: 0, //tab标题的滚动条位置
|
||||
scrollIntoView: '', // 滚动至哪个元素
|
||||
oldScrollTop: 0,
|
||||
current: 0, // 预设当前项的值
|
||||
innerCurrent: 0, // 预设当前项的值
|
||||
menuHeight: 0, // 左边菜单的高度
|
||||
menuItemHeight: 0, // 左边菜单item的高度
|
||||
itemId: '', // 栏目右边scroll-view用于滚动的id
|
||||
@@ -79,26 +119,38 @@
|
||||
timer: null, // 定时器
|
||||
}
|
||||
},
|
||||
onMounted() {
|
||||
mounted() {
|
||||
// this.observer();
|
||||
this.innerCurrent = this.current;
|
||||
this.leftMenuStatus(this.innerCurrent);
|
||||
this.getMenuItemTop()
|
||||
// 设置默认index
|
||||
sleep(50).then(() => {
|
||||
this.swichMenu(this.innerCurrent)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
addUnit,
|
||||
// 点击左边的栏目切换
|
||||
async swichMenu(index) {
|
||||
if (this.mode == 'follow') {
|
||||
if(this.arr.length == 0) {
|
||||
await this.getMenuItemTop();
|
||||
}
|
||||
if (index == this.current) return;
|
||||
this.scrollRightTop = this.oldScrollTop;
|
||||
if (this.scrollIntoView != 'item' + index) {
|
||||
this.scrollIntoView = 'item' + index;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == this.innerCurrent) return;
|
||||
this.$nextTick(function(){
|
||||
this.scrollRightTop = this.arr[index];
|
||||
this.current = index;
|
||||
this.leftMenuStatus(index);
|
||||
this.innerCurrent = index;
|
||||
this.$emit('update:current', index);
|
||||
})
|
||||
},
|
||||
// 获取一个目标元素的高度
|
||||
getElRect(elClass, dataVal) {
|
||||
new Promise((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
query.select('.' + elClass).fields({
|
||||
size: true
|
||||
@@ -117,54 +169,73 @@
|
||||
},
|
||||
// 观测元素相交状态
|
||||
async observer() {
|
||||
await this.$nextTick();
|
||||
// 清除之前的观察器
|
||||
if (this._observerList) {
|
||||
this._observerList.forEach(observer => {
|
||||
observer.disconnect();
|
||||
});
|
||||
}
|
||||
this._observerList = [];
|
||||
|
||||
this.tabList.map((val, index) => {
|
||||
let observer = uni.createIntersectionObserver(this);
|
||||
// 检测右边scroll-view的id为itemxx的元素与u-cate-tab__right-box的相交状态
|
||||
// 如果跟.u-cate-tab__right-box底部相交,就动态设置左边栏目的活动状态
|
||||
this._observerList.push(observer);
|
||||
// 检测相交状态
|
||||
observer.relativeTo('.u-cate-tab__right-box', {
|
||||
top: 0
|
||||
}).observe('#item' + index, res => {
|
||||
top: 10
|
||||
}).observe('#item' + index, (res) => {
|
||||
if (res.intersectionRatio > 0) {
|
||||
let id = res.id.substring(4);
|
||||
this.leftMenuStatus(id);
|
||||
console.log('res', res);
|
||||
// 修复:确保正确获取索引
|
||||
let id = res.id ? res.id.substring(4) : index;
|
||||
this.leftMenuStatus(parseInt(id));
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
// 设置左边菜单的滚动状态
|
||||
async leftMenuStatus(index) {
|
||||
this.current = index;
|
||||
this.innerCurrent = index;
|
||||
this.$emit('update:current', index);
|
||||
// 如果为0,意味着尚未初始化
|
||||
if (this.menuHeight == 0 || this.menuItemHeight == 0) {
|
||||
await this.getElRect('u-cate-tab__menu-scroll-view', 'menuHeight');
|
||||
await this.getElRect('u-cate-tab__item', 'menuItemHeight');
|
||||
}
|
||||
// console.log(this.menuHeight, this.menuItemHeight)
|
||||
// 将菜单活动item垂直居中
|
||||
this.scrollTop = index * this.menuItemHeight + this.menuItemHeight / 2 - this.menuHeight / 2;
|
||||
},
|
||||
// 获取右边菜单每个item到顶部的距离
|
||||
getMenuItemTop() {
|
||||
new Promise(resolve => {
|
||||
async getMenuItemTop() {
|
||||
// await this.$nextTick();
|
||||
// console.log('getMenuItemTop')
|
||||
return new Promise(resolve => {
|
||||
let selectorQuery = uni.createSelectorQuery().in(this);
|
||||
selectorQuery.selectAll('.u-cate-tab__page-item').boundingClientRect((rects) => {
|
||||
// 如果节点尚未生成,rects值为[](因为用selectAll,所以返回的是数组),循环调用执行
|
||||
if(!rects.length) {
|
||||
setTimeout(() => {
|
||||
this.getMenuItemTop();
|
||||
}, 10);
|
||||
}, 100);
|
||||
return ;
|
||||
}
|
||||
// console.log(rects)
|
||||
this.rects = rects;
|
||||
this.arr = [];
|
||||
rects.forEach((rect) => {
|
||||
// 这里减去rects[0].top,是因为第一项顶部可能不是贴到导航栏(比如有个搜索框的情况)
|
||||
this.arr.push(rect.top - rects[0].top);
|
||||
resolve();
|
||||
})
|
||||
// console.log(this.arr)
|
||||
resolve();
|
||||
}).exec()
|
||||
})
|
||||
},
|
||||
// 右边菜单滚动
|
||||
async rightScroll(e) {
|
||||
if (this.mode !== 'follow') return;
|
||||
this.oldScrollTop = e.detail.scrollTop;
|
||||
// console.log(e.detail.scrollTop)
|
||||
// console.log(JSON.stringify(this.arr))
|
||||
@@ -195,7 +266,7 @@
|
||||
return ;
|
||||
}
|
||||
}
|
||||
}, 10)
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,11 +281,12 @@
|
||||
.u-cate-tab__wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-search-inner {
|
||||
background-color: rgb(234, 234, 234);
|
||||
background-color: var(--up-bg-color, rgb(234, 234, 234));
|
||||
border-radius: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -234,23 +306,23 @@
|
||||
|
||||
.u-cate-tab__item {
|
||||
height: 110rpx;
|
||||
background: #f6f6f6;
|
||||
background: var(--up-bg-color, #f6f6f6);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 26rpx;
|
||||
color: #444;
|
||||
color: var(--up-content-color, #444);
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.u-cate-tab__item-active {
|
||||
position: relative;
|
||||
color: #000;
|
||||
color: var(--up-main-color, #000);
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
background: #fff;
|
||||
background: var(--up-card-bg-color, #fff);
|
||||
}
|
||||
|
||||
.u-cate-tab__item-active::before {
|
||||
@@ -268,7 +340,7 @@
|
||||
|
||||
.u-cate-tab__right-box {
|
||||
flex: 1;
|
||||
background-color: rgb(250, 250, 250);
|
||||
background-color: var(--up-page-bg-color, var(--up-bg-color, rgb(250, 250, 250)));
|
||||
}
|
||||
|
||||
.u-cate-tab__page-view {
|
||||
@@ -277,7 +349,8 @@
|
||||
|
||||
.u-cate-tab__page-item {
|
||||
margin-bottom: 30rpx;
|
||||
background-color: #fff;
|
||||
background-color: var(--up-card-bg-color, #fff);
|
||||
border: 1px solid var(--up-border-color, #e5e7eb);
|
||||
padding: 16rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
@@ -288,14 +361,14 @@
|
||||
|
||||
.item-title {
|
||||
font-size: 26rpx;
|
||||
color: $u-main-color;
|
||||
color: var(--up-main-color, $u-main-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.item-menu-name {
|
||||
font-weight: normal;
|
||||
font-size: 24rpx;
|
||||
color: $u-main-color;
|
||||
color: var(--up-main-color, $u-main-color);
|
||||
}
|
||||
|
||||
.item-container {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-20 16:54:16
|
||||
* @FilePath : /u-view2.0/uview-ui/libs/config/props/cellGroup.js
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/cellGroup.js
|
||||
*/
|
||||
export default {
|
||||
// cell-group组件的props
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import CellGroupDefaultProps from './cellGroup'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(CellGroupDefaultProps)
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 分组标题
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<view :style="[addStyle(customStyle)]" :class="[customClass]" class="u-cell-group">
|
||||
<view :style="[groupStyle, addStyle(customStyle)]" :class="[customClass]" class="u-cell-group">
|
||||
<view v-if="title" class="u-cell-group__title">
|
||||
<slot name="title">
|
||||
<text class="u-cell-group__title__text">{{ title }}</text>
|
||||
<text class="u-cell-group__title__text" :style="{ color: upThemeVar('--up-main-color', '#303133') }">{{ title }}</text>
|
||||
</slot>
|
||||
</view>
|
||||
<view class="u-cell-group__wrapper">
|
||||
@@ -32,6 +32,14 @@
|
||||
export default {
|
||||
name: 'u-cell-group',
|
||||
mixins: [mpMixin, mixin, props],
|
||||
computed: {
|
||||
groupStyle() {
|
||||
const fallbackBg = this.upThemeIsDark ? '#1c1c1e' : '#ffffff'
|
||||
return {
|
||||
backgroundColor: this.upThemeVar('--up-card-bg-color', fallbackBg)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addStyle
|
||||
}
|
||||
@@ -39,7 +47,6 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
$u-cell-group-title-padding: 16px 16px 8px !default;
|
||||
$u-cell-group-title-font-size: 15px !default;
|
||||
@@ -64,4 +71,3 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-23 20:53:09
|
||||
* @FilePath : /u-view2.0/uview-ui/libs/config/props/cell.js
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/cell.js
|
||||
*/
|
||||
export default {
|
||||
// cell组件的props
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import CellDefaultProps from './cell'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(CellDefaultProps)
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 标题
|
||||
|
||||
@@ -7,32 +7,34 @@
|
||||
<view class="u-cell__left-icon-wrap" v-if="$slots.icon || icon">
|
||||
<slot name="icon" v-if="$slots.icon">
|
||||
</slot>
|
||||
<u-icon v-else :name="icon"
|
||||
<up-icon v-else :name="icon"
|
||||
:custom-style="iconStyle"
|
||||
:size="size === 'large' ? 22 : 18"></u-icon>
|
||||
:size="size === 'large' ? 22 : 18"></up-icon>
|
||||
</view>
|
||||
<view class="u-cell__title">
|
||||
<!-- 将slot与默认内容用if/else分开主要是因为微信小程序不支持slot嵌套传递,这样才能解决collapse组件的slot不失效问题,label暂时未用到。 -->
|
||||
<slot name="title" v-if="$slots.title || !title">
|
||||
</slot>
|
||||
<text v-else class="u-cell__title-text" :style="[titleTextStyle]"
|
||||
<text v-else class="u-cell__title-text" :style="[cellTitleDynamicStyle, titleTextStyle]"
|
||||
:class="[required && 'u-cell--required', disabled && 'u-cell--disabled', size === 'large' && 'u-cell__title-text--large']">{{ title }}</text>
|
||||
<slot name="label">
|
||||
<text class="u-cell__label" v-if="label"
|
||||
:style="[cellLabelDynamicStyle]"
|
||||
:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__label--large']">{{ label }}</text>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
<slot name="value">
|
||||
<text class="u-cell__value"
|
||||
:style="[cellValueDynamicStyle]"
|
||||
:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__value--large']"
|
||||
v-if="!testEmpty(value)">{{ value }}</text>
|
||||
</slot>
|
||||
<view class="u-cell__right-icon-wrap" v-if="$slots['right-icon'] || isLink"
|
||||
:class="[`u-cell__right-icon-wrap--${arrowDirection}`]">
|
||||
<u-icon v-if="rightIcon && !$slots['right-icon']" :name="rightIcon"
|
||||
:custom-style="rightIconStyle" :color="disabled ? '#c8c9cc' : 'info'"
|
||||
:size="size === 'large' ? 18 : 16"></u-icon>
|
||||
<up-icon v-if="rightIcon && !$slots['right-icon']" :name="rightIcon"
|
||||
:custom-style="rightIconStyle" :color="disabled ? cellDisabledColor : 'info'"
|
||||
:size="size === 'large' ? 18 : 16"></up-icon>
|
||||
<slot v-else name="right-icon">
|
||||
</slot>
|
||||
</view>
|
||||
@@ -91,6 +93,24 @@
|
||||
computed: {
|
||||
titleTextStyle() {
|
||||
return addStyle(this.titleStyle)
|
||||
},
|
||||
cellDisabledColor() {
|
||||
return this.upThemeVar('--up-disabled-color', '#c8c9cc')
|
||||
},
|
||||
cellTitleDynamicStyle() {
|
||||
return {
|
||||
color: this.upThemeVar('--up-main-color', '#303133')
|
||||
}
|
||||
},
|
||||
cellLabelDynamicStyle() {
|
||||
return {
|
||||
color: this.upThemeVar('--up-tips-color', '#909399')
|
||||
}
|
||||
},
|
||||
cellValueDynamicStyle() {
|
||||
return {
|
||||
color: this.upThemeVar('--up-content-color', '#606266')
|
||||
}
|
||||
}
|
||||
},
|
||||
emits: ['click'],
|
||||
@@ -113,7 +133,6 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
$u-cell-padding: 13px 15px !default;
|
||||
$u-cell-font-size: 15px !default;
|
||||
@@ -128,8 +147,8 @@
|
||||
$u-cell-label-line-height: 18px !default;
|
||||
$u-cell-value-font-size: 14px !default;
|
||||
$u-cell-value-color: $u-content-color !default;
|
||||
$u-cell-clickable-color: $u-bg-color !default;
|
||||
$u-cell-disabled-color: #c8c9cc !default;
|
||||
$u-cell-clickable-color: var(--up-hover-bg-color, #f1f1f1) !default;
|
||||
$u-cell-disabled-color: $u-disabled-color !default;
|
||||
$u-cell-padding-top-large: 13px !default;
|
||||
$u-cell-padding-bottom-large: 13px !default;
|
||||
$u-cell-value-font-size-large: 15px !default;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-20 16:54:47
|
||||
* @FilePath : /u-view2.0/uview-ui/libs/config/props/checkboxGroup.js
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/checkboxGroup.js
|
||||
*/
|
||||
export default {
|
||||
// checkbox-group组件
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import CheckboxGroupDefaultProps from './checkboxGroup'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(CheckboxGroupDefaultProps)
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 标识符
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
/**
|
||||
* checkboxGroup 复选框组
|
||||
* @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
|
||||
* @tutorial https://ijry.github.io/uview-plus/components/checkbox.html
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/checkbox.html
|
||||
* @property {String} name 标识符
|
||||
* @property {Array} value 绑定的值
|
||||
* @property {String} shape 形状,circle-圆形,square-方形 (默认 'square' )
|
||||
@@ -109,14 +109,16 @@
|
||||
this.$emit("input", values);
|
||||
// #endif
|
||||
// 放在最后更新,否则change事件传出去的values不会更新
|
||||
this.$emit('change', values)
|
||||
this.$emit('change', values, {
|
||||
isChecked: childInstance.$data['isChecked'],
|
||||
name: childInstance.$props['name'] // 当前变动的checkbox的name值
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-checkbox-group {
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-23 21:06:59
|
||||
* @FilePath : /u-view2.0/uview-ui/libs/config/props/checkbox.js
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/checkbox.js
|
||||
*/
|
||||
export default {
|
||||
// checkbox组件
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import CheckboxDefaultProps from './checkbox'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(CheckboxDefaultProps)
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// checkbox的名称
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
:class="iconClasses"
|
||||
:style="[iconWrapStyle]"
|
||||
>
|
||||
<slot name="icon" :elIconSize="elIconSize" :elIconColor="elIconColor">
|
||||
<u-icon
|
||||
<slot name="icon" :elIconSize="elIconSize" :elIconColor="elIconColor" :checked="isChecked" :elDisabled="elDisabled">
|
||||
<up-icon
|
||||
class="u-checkbox__icon-wrap__icon"
|
||||
name="checkbox-mark"
|
||||
:size="elIconSize"
|
||||
@@ -111,16 +111,16 @@
|
||||
},
|
||||
// 组件选中激活时的颜色
|
||||
elActiveColor() {
|
||||
return this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : '#2979ff');
|
||||
return this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : this.upThemeVar('--up-primary', '#2979ff'));
|
||||
},
|
||||
// 组件选未中激活时的颜色
|
||||
elInactiveColor() {
|
||||
return this.inactiveColor ? this.inactiveColor : (this.parentData.inactiveColor ? this.parentData.inactiveColor :
|
||||
'#c8c9cc');
|
||||
this.upThemeVar('--up-border-color', '#c8c9cc'));
|
||||
},
|
||||
// label的颜色
|
||||
elLabelColor() {
|
||||
return this.labelColor ? this.labelColor : (this.parentData.labelColor ? this.parentData.labelColor : '#606266')
|
||||
return this.labelColor ? this.labelColor : (this.parentData.labelColor ? this.parentData.labelColor : this.upThemeVar('--up-content-color', '#606266'))
|
||||
},
|
||||
// 组件的形状
|
||||
elShape() {
|
||||
@@ -133,7 +133,7 @@
|
||||
},
|
||||
elIconColor() {
|
||||
const iconColor = this.iconColor ? this.iconColor : (this.parentData.iconColor ? this.parentData.iconColor :
|
||||
'#ffffff');
|
||||
this.upThemeVar('--up-card-bg-color', '#ffffff'));
|
||||
// 图标的颜色
|
||||
if (this.elDisabled) {
|
||||
// disabled状态下,已勾选的checkbox图标改为elInactiveColor
|
||||
@@ -149,8 +149,12 @@
|
||||
if (this.elDisabled) {
|
||||
classes.push('u-checkbox__icon-wrap--disabled')
|
||||
}
|
||||
if (this.isChecked && this.elDisabled) {
|
||||
if (this.isChecked) {
|
||||
if (this.elDisabled) {
|
||||
classes.push('u-checkbox__icon-wrap--disabled--checked')
|
||||
} else {
|
||||
classes.push('u-checkbox__icon-wrap--checked')
|
||||
}
|
||||
}
|
||||
// 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
|
||||
// #ifdef MP-ALIPAY || MP-TOUTIAO
|
||||
@@ -161,7 +165,7 @@
|
||||
iconWrapStyle() {
|
||||
// checkbox的整体样式
|
||||
const style = {}
|
||||
style.backgroundColor = this.isChecked && !this.elDisabled ? this.elActiveColor : '#ffffff'
|
||||
style.backgroundColor = this.isChecked && !this.elDisabled ? this.elActiveColor : this.upThemeVar('--up-card-bg-color', '#ffffff')
|
||||
style.borderColor = this.isChecked && !this.elDisabled ? this.elActiveColor : this.elInactiveColor
|
||||
style.width = addUnit(this.elSize)
|
||||
style.height = addUnit(this.elSize)
|
||||
@@ -177,7 +181,7 @@
|
||||
const style = {}
|
||||
if (!this.usedAlone) {
|
||||
if (this.parentData.borderBottom && this.parentData.placement === 'row') {
|
||||
error('检测到您将borderBottom设置为true,需要同时将u-checkbox-group的placement设置为column才有效')
|
||||
error('检测到您将borderBottom设置为true,需要同时将up-checkbox-group的placement设置为column才有效')
|
||||
}
|
||||
// 当父组件设置了显示下边框并且排列形式为纵向时,给内容和边框之间加上一定间隔
|
||||
if (this.parentData.borderBottom && this.parentData.placement === 'column') {
|
||||
@@ -197,13 +201,14 @@
|
||||
// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
|
||||
this.updateParentData()
|
||||
if (!this.parent) {
|
||||
error('u-checkbox必须搭配u-checkbox-group组件使用')
|
||||
error('up-checkbox必须搭配up-checkbox-group组件使用')
|
||||
}
|
||||
let value = '';
|
||||
// #ifdef VUE2
|
||||
const value = this.parentData.value
|
||||
value = this.parentData.value
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
const value = this.parentData.modelValue
|
||||
value = this.parentData.modelValue
|
||||
// #endif
|
||||
// 设置初始化时,是否默认选中的状态,父组件u-checkbox-group的value可能是array,所以额外判断
|
||||
if (this.checked) {
|
||||
@@ -248,7 +253,9 @@
|
||||
}
|
||||
},
|
||||
emitEvent() {
|
||||
this.$emit('change', this.isChecked)
|
||||
this.$emit('change', this.isChecked, {
|
||||
name: this.name
|
||||
})
|
||||
// 双向绑定
|
||||
if (this.usedAlone) {
|
||||
this.$emit('update:checked', this.isChecked)
|
||||
@@ -281,7 +288,6 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
$u-checkbox-icon-wrap-margin-right:6px !default;
|
||||
$u-checkbox-icon-wrap-font-size:6px !default;
|
||||
$u-checkbox-icon-wrap-border-width:1px !default;
|
||||
@@ -292,7 +298,7 @@
|
||||
$u-checkbox-icon-wrap-checked-color:#fff !default;
|
||||
$u-checkbox-icon-wrap-checked-background-color:red !default;
|
||||
$u-checkbox-icon-wrap-checked-border-color:#2979ff !default;
|
||||
$u-checkbox-icon-wrap-disabled-background-color:#ebedf0 !default;
|
||||
$u-checkbox-icon-wrap-disabled-background-color:var(--up-bg-color, #ebedf0) !default;
|
||||
$u-checkbox-icon-wrap-disabled-checked-color:#c8c9cc !default;
|
||||
$u-checkbox-label-margin-left:5px !default;
|
||||
$u-checkbox-label-margin-right:12px !default;
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<style scoped lang="scss">
|
||||
.up-choose {
|
||||
::v-deep .up-tag {
|
||||
font-weight: 600;
|
||||
}
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.up-choose-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.up-choose-nowrap {
|
||||
flex-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<scroll-view
|
||||
:scroll-x="wrap === false"
|
||||
:class="['up-choose', wrap ? 'up-choose-wrap' : 'up-choose-nowrap']">
|
||||
<template :key="item.id" v-for="(item,index) in options">
|
||||
<view :style="{width: itemWidth, display: 'inline-block'}">
|
||||
<slot :item="item" :index="index">
|
||||
<up-tag :type="index == currentIndex ? 'primary' : 'info'"
|
||||
size="large" :plain="index == currentIndex ? false : true"
|
||||
:class="currentIndex === index ? 'active': ''" :height="itemHeight"
|
||||
:style="{width: itemWidth, padding: itemPadding}"
|
||||
@click="change(index)">
|
||||
{{item[labelName]}}
|
||||
</up-tag>
|
||||
</slot>
|
||||
</view>
|
||||
</template>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'up-choose',
|
||||
props: {
|
||||
options:{
|
||||
type: Array,
|
||||
default: ()=>{
|
||||
return [];
|
||||
}
|
||||
},
|
||||
modelValue: {
|
||||
type: [Number,String,Array],
|
||||
default: false
|
||||
},
|
||||
type: {
|
||||
type: [String],
|
||||
default: 'radio'
|
||||
},
|
||||
itemWidth: {
|
||||
type: [String],
|
||||
default: 'auto'
|
||||
},
|
||||
itemHeight: {
|
||||
type: [String],
|
||||
default: '50px'
|
||||
},
|
||||
itemPadding: {
|
||||
type: [String],
|
||||
default: '8px'
|
||||
},
|
||||
labelName: {
|
||||
type: String,
|
||||
default: 'title'
|
||||
},
|
||||
valueName: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
customClick: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否换行
|
||||
wrap: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentIndex: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
this.currentIndex = newVal
|
||||
}
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'custom-click'],
|
||||
methods: {
|
||||
change(index){
|
||||
if (this.customClick) {
|
||||
this.$emit('custom-click', index);
|
||||
} else {
|
||||
this.currentIndex = index;
|
||||
this.$emit('update:modelValue', index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-20 16:55:02
|
||||
* @FilePath : /u-view2.0/uview-ui/libs/config/props/circleProgress.js
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/circleProgress.js
|
||||
*/
|
||||
export default {
|
||||
// circleProgress 组件
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import CircleProgressDefaultProps from './circleProgress'
|
||||
import { registerComponentProps } from '../../libs/config/props.js'
|
||||
|
||||
const defProps = registerComponentProps(CircleProgressDefaultProps)
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
percentage: {
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
/**
|
||||
* CircleProgress 圆形进度条 TODO: 待完善
|
||||
* @description 展示操作或任务的当前进度,比如上传文件,是一个圆形的进度环。
|
||||
* @tutorial https://ijry.github.io/uview-plus/components/circleProgress.html
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/circleProgress.html
|
||||
* @property {String | Number} percentage 圆环进度百分比值,为数值类型,0-100 (默认 30 )
|
||||
* @example
|
||||
*/
|
||||
@@ -112,7 +112,6 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-circle-progress {
|
||||
@include flex(row);
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<view class="u-city-locate">
|
||||
<up-index-list :indexList="indexList">
|
||||
<template #header>
|
||||
<view class="u-current-city-wrap">
|
||||
<view class="u-current-city-title">{{ t("up.cityLocate.locateCity") }}</view>
|
||||
<view class="u-current-city-item" @tap="location">
|
||||
<view class="u-location-city">{{locationCity}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<template :key="index" v-for="(item, index) in cityList">
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<up-index-anchor :text="indexList[index]"></up-index-anchor>
|
||||
<!-- #endif -->
|
||||
<up-index-item>
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<up-index-anchor :text="indexList[index]"></up-index-anchor>
|
||||
<!-- #endif -->
|
||||
<view class="hot-city-list" v-if="index == 0">
|
||||
<view class="" v-for="(item1, index1) in item" @tap="selectedCity(item1)">
|
||||
<view class="hot-city-item">{{ item1[nameKey] }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="item-list" v-for="(item1, index1) in item" :key="index1">
|
||||
<view class="list__item" @tap="selectedCity(item1)">
|
||||
<text class="list__item__city-name">{{item1[nameKey]}}</text>
|
||||
</view>
|
||||
<up-line></up-line>
|
||||
</view>
|
||||
</up-index-item>
|
||||
</template>
|
||||
<template #footer>
|
||||
<view class="u-safe-area-inset--bottom">
|
||||
<text class="list__footer"></text>
|
||||
</view>
|
||||
</template>
|
||||
</up-index-list>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { t } from '../../libs/i18n'
|
||||
export default{
|
||||
name: 'u-city-locate',
|
||||
props:{
|
||||
indexList: {
|
||||
type: Array,
|
||||
default: ['🔥']
|
||||
},
|
||||
cityList:{
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [
|
||||
[{
|
||||
name: '北京',
|
||||
value: 'beijing'
|
||||
},
|
||||
{
|
||||
name: '上海',
|
||||
value: 'shanghai'
|
||||
},
|
||||
{
|
||||
name: '广州',
|
||||
value: 'guangzhou'
|
||||
},
|
||||
{
|
||||
name: '深圳',
|
||||
value: 'shenzhen'
|
||||
},
|
||||
{
|
||||
name: '杭州',
|
||||
value: 'hangzhou'
|
||||
}]
|
||||
]
|
||||
}
|
||||
},
|
||||
locationType: {
|
||||
type: String,
|
||||
default: 'wgs84'
|
||||
},
|
||||
currentCity: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
nameKey: {
|
||||
type: String,
|
||||
default: 'name'
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
},
|
||||
watch:{
|
||||
currentCity(val) {
|
||||
this.locationCity = val;
|
||||
}
|
||||
},
|
||||
data(){
|
||||
return{
|
||||
locationCity: t("up.cityLocate.locating") + '....'
|
||||
}
|
||||
},
|
||||
emits: ['location-success', 'select-city'],
|
||||
methods:{
|
||||
t,
|
||||
// 获取城市
|
||||
selectedCity(city){
|
||||
this.locationCity = city[this.nameKey];
|
||||
this.$emit('select-city', {
|
||||
locationCity: this.locationCity
|
||||
});
|
||||
},
|
||||
// 定位操作
|
||||
location(){
|
||||
let That = this;
|
||||
uni.getLocation({
|
||||
type: this.locationType,
|
||||
geocode:true,
|
||||
success(res){
|
||||
console.log(res);
|
||||
That.locationCity = res.address && res.address.city;
|
||||
That.$emit('location-success', {
|
||||
...res,
|
||||
locationCity: That.locationCity
|
||||
});
|
||||
},
|
||||
fail(){
|
||||
That.locationCity = t("up.cityLocate.fail");
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
// 页面挂载后进行异步操作
|
||||
created(){
|
||||
},
|
||||
mounted(){
|
||||
this.location();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.list__item {
|
||||
padding: 8px 1px;
|
||||
}
|
||||
.u-current-city-title {
|
||||
color: var(--up-tips-color, #909399);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.u-location-city,
|
||||
.list__item__city-name {
|
||||
color: var(--up-main-color, #303133);
|
||||
}
|
||||
.u-current-city-item {
|
||||
height: 30px;
|
||||
}
|
||||
.hot-city-list {
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
padding: 12px 0;
|
||||
.hot-city-item {
|
||||
padding: 6px 12px;
|
||||
margin: 5px;
|
||||
color: var(--up-main-color, #303133);
|
||||
border: 1px solid var(--up-border-color, #ededed);
|
||||
background-color: var(--up-card-bg-color, #ffffff);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-20 16:55:58
|
||||
* @FilePath : /u-view2.0/uview-ui/libs/config/props/codeInput.js
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/codeInput.js
|
||||
*/
|
||||
export default {
|
||||
// codeInput 组件
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user