Files
2026-09-24 16:25:22 +08:00

584 lines
18 KiB
Plaintext

<script lang="ts" setup>
import { colors, getDefaultColor, getDefaultColorObj, getTextColorObj, getThinColorObj, getOutlineColorObj } from "../../core/util/xCoreColorUtil.uts"
import { toFillMarginAr, checkIsCssUnit, getUnit } from "../../core/util/xCoreUtil.uts"
import { xConfig } from "../../config/xConfig.uts"
import { PropType, computed, watch, onMounted, getCurrentInstance, ref, type Ref } from 'vue'
/**
* @name 按钮 xButton
* @page /pages/index/button
* @category 常用组件
* @description 圆角,主题可通过配置统一设置或者动态全局设置,使设计风格统一并保持一致性。让你的风格独一无二。
* @constant 平台兼容
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
| --- | --- | --- | --- | --- | --- | --- | --- |
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
*/
defineOptions({ name: 'xButton' })
type SkinType = "default" | "secondary" | "text" | "outline" | "dashed" | "thin"
type SizeType = "mini" | "large" | "normal" | "small"
export type XButtonProps = {
/** 主题颜色,空取全局 */
color : string,
/** 暗黑主题颜色,空取全局 */
darkColor : string,
/** 自定背景色 */
bgColor : string,
/** 渐变[方向,颜色1,颜色2] */
linearGradient : string[],
/** 字号颜色 */
fontColor : string,
/** 暗黑字号颜色 */
fontDarkColor : string,
/** 字号大小 */
fontSize : string,
/** 圆角,空取全局 */
round : string,
/** 边线大小 */
border : number,
/** 投影[x,y,大小] */
shadow : number[],
/** 边线颜色 */
borderColor : string,
/** 样式主题"default" | "secondary" | "text" | "outline" | "dashed" | "thin" */
skin : SkinType,
/** 按钮图标 */
icon : string,
/** 是否是纯按钮图标 */
iconBtn : boolean,
/** 图标大小 */
iconSize : string,
/** 按钮大小"mini" | "large" | "normal" | "small" */
size : SizeType,
/** 跳转链接 */
url : string,
/** 跳转方式,同官方 */
navigateMode : string,
/** 是否禁用 */
disabled : boolean,
/** 加载状态 */
loading : boolean,
/** 高 */
height : string,
/** 宽 */
width : string,
/** 是否占据整行 */
block : boolean,
/** 是否作为x-form提交表单用于触发提交表单 */
formType : 'form' | '',
/** 行高 */
lineHeight : string,
/** 加粗 */
fontWeight : string,
/** 开放类型,同官方 */
openType : string,
lang : string,
sessionFrom : string,
sendMessageTitle : string,
sendMessagePath : string,
sendMessageImg : string,
appParameter : string,
showMessageCard : boolean,
phoneNumberNoQuotaToast : boolean
}
const props = withDefaults(defineProps<XButtonProps>(), {
color: '',
darkColor: '',
bgColor: '',
linearGradient: ():string[] => [] as string[],
fontColor: '',
fontDarkColor: '',
fontSize: '',
round: '',
border: 0.5,
shadow: ():number[] => [] as number[],
borderColor: '',
skin: 'default' as SkinType,
icon: '',
iconBtn: false,
iconSize: '',
size: 'normal' as SizeType,
url: '',
navigateMode: 'navigateTo',
disabled: false,
loading: false,
height: '',
width: '',
block: false,
formType: '' as 'form' | '',
lineHeight: '1.4',
fontWeight: 'normal',
openType: '',
lang: 'en',
sessionFrom: '',
sendMessageTitle: '',
sendMessagePath: '',
sendMessageImg: '',
appParameter: '',
showMessageCard: false,
phoneNumberNoQuotaToast: true
})
const emits = defineEmits([
'click',
'getuserinfo',
'contact',
'getphonenumber',
'getrealtimephonenumber',
'error',
'opensetting',
'launchapp',
'chooseavatar',
'chooseaddress',
'chooseinvoicetitle',
'addgroupapp',
'subscribe',
'login',
'agreeprivacyauthorization'
])
const instance = getCurrentInstance()
const proxy = instance?.proxy ?? null
const _set_border_color = ref(`1px solid rgba(0,0,0,0)`) as Ref<string>
const _set_background_color = ref(`rgba(0,0,0,0)`) as Ref<string>
const _set_background_img = ref(``) as Ref<string>
const _set_font_color = ref(`#ffffff`) as Ref<string>
const _isHover = ref(false) as Ref<boolean>
const boxShadow = ref('0 0px 0px rgba(0,0,0,0)') as Ref<string>
const _fontWeight = computed(() : string => props.fontWeight)
const _color = computed(() : string => {
let color = props.color
if (xConfig.dark == 'dark' && props.darkColor != '') {
color = props.darkColor
} else {
if (color == '') {
color = xConfig.color
}
}
return color
})
const _fontSize = computed(() : string => {
let fontSize = props.fontSize
if (fontSize == '') {
if (props.size == 'mini') fontSize = '12'
if (props.size == 'small') fontSize = '14'
if (props.size == 'normal') fontSize = '16'
if (props.size == 'large') fontSize = '18'
}
fontSize = checkIsCssUnit(fontSize, xConfig.unit)
if (xConfig.fontScale == 1) return fontSize
let sizeNumber = parseInt(fontSize)
if (isNaN(sizeNumber)) sizeNumber = 16
return (sizeNumber * xConfig.fontScale).toString() + getUnit(props.fontSize)
})
const _iconSize = computed(() : string => {
if (props.iconSize != '') {
let fontSize = checkIsCssUnit(props.iconSize, xConfig.unit)
if (xConfig.fontScale == 1) return fontSize
let sizeNumber = parseInt(fontSize)
if (isNaN(sizeNumber)) sizeNumber = 16
return (sizeNumber * xConfig.fontScale).toString() + getUnit(props.fontSize)
}
return _fontSize.value
})
const _disabled = computed(() : boolean => props.disabled)
const _icon = computed(() : string => props.icon)
const _loading = computed(() : boolean => props.loading)
const _radius = computed(() : string => {
let radius = props.round
if (radius == '') {
radius = xConfig.buttonRadius
if (props.size == 'mini') radius = '6'
if (props.size == 'small') radius = '8'
}
return checkIsCssUnit(radius, xConfig.unit)
})
const _border = computed(() : string => checkIsCssUnit(props.border.toString(), xConfig.unit))
const _iconBtn = computed(() : boolean => props.iconBtn)
const _height = computed(() : string => {
if (props.height != '') return checkIsCssUnit(props.height, xConfig.unit)
if (props.size == 'mini') return checkIsCssUnit('24', xConfig.unit)
if (props.size == 'small') return checkIsCssUnit('32', xConfig.unit)
if (props.size == 'normal') return checkIsCssUnit('46', xConfig.unit)
if (props.size == 'large') return checkIsCssUnit('56', xConfig.unit)
return checkIsCssUnit(props.height == '' ? '44' : props.height, xConfig.unit)
})
const _width = computed(() : string => {
if (_iconBtn.value) return _height.value
if (props.block) return '100%'
if (props.width != '') return checkIsCssUnit(props.width, xConfig.unit)
if (props.size == 'mini') return checkIsCssUnit('46', xConfig.unit)
if (props.size == 'small') return checkIsCssUnit('60', xConfig.unit)
if (props.size == 'normal') return checkIsCssUnit('98', xConfig.unit)
if (props.size == 'large') return checkIsCssUnit('128', xConfig.unit)
return checkIsCssUnit(props.width, xConfig.unit)
})
const _shadow = computed(() : number[] => {
if (props.shadow.length == 0) return [0, 0] as number[]
if (props.shadow.length == 1) return [props.shadow[0], props.shadow[0]] as number[]
return props.shadow
})
const _styleMap = computed(() : Map<string, any> => {
const styleMap = new Map<string, any>()
styleMap.set('width', _width.value)
styleMap.set('height', _height.value)
styleMap.set('border', _set_border_color.value)
styleMap.set('backgroundColor', _set_background_color.value)
if (_set_background_img.value != '') {
styleMap.set('backgroundImage', _set_background_img.value)
}
styleMap.set('borderRadius', _radius.value)
let opacity = '1'
if (_disabled.value || _loading.value) {
opacity = '0.5'
}
styleMap.set('opacity', opacity)
return styleMap
})
function findParent(parent : VueComponent | null) : VueComponent | null {
if (parent == null) return null;
// #ifdef WEB||APP-IOS||MP-WEIXIN
if (parent.$parent?.$options?.name?.indexOf('xForm') > -1) return parent.$parent;
// #endif
// #ifdef APP-HARMONY
if (parent.$parent?.$options?.name?.indexOf('xForm') > -1) return parent.$parent;
// #endif
// #ifdef APP-ANDROID
if (parent.$parent instanceof XFormComponentPublicInstance) return parent.$parent;
// #endif
let parents = findParent(parent.$parent)
// #ifdef WEB||APP-IOS||MP-WEIXIN
if (parents?.$options?.name?.indexOf('xForm') > -1) return parents;
// #endif
// #ifdef APP-HARMONY
if (parents?.$options?.name?.indexOf('xForm') > -1) return parents;
// #endif
// #ifdef APP-ANDROID
if (parents instanceof XFormComponentPublicInstance) return parents;
// #endif
return null;
}
function formSubmit() {
let pelement = findParent(proxy);
if (pelement == null) return;
let parent : XFormComponentPublicInstance = pelement as XFormComponentPublicInstance;
parent.submit();
}
function customStyles(hover : boolean) {
let dePrimarycolor = getDefaultColor(xConfig.color);
let color = getDefaultColor(_color.value);
let hoverColor = props.color == 'info' ? getDefaultColor(xConfig.color) : color
let colorInit : UTSJSONObject = getDefaultColorObj(color, hoverColor);
let borderStyle = "solid"
if (props.skin == 'text') {
colorInit = getTextColorObj(color, hoverColor, xConfig.dark == 'dark')
}
if (props.skin == 'thin') {
colorInit = getThinColorObj(color, hoverColor, xConfig.dark == 'dark')
}
if (props.skin == 'outline' || props.skin == 'dashed') {
colorInit = getOutlineColorObj(color, hoverColor, xConfig.dark == 'dark')
}
if (props.skin == 'dashed') {
borderStyle = 'dashed'
}
let defaultObj : UTSJSONObject = colorInit.getJSON("default")!
let defaultActive : UTSJSONObject = colorInit.getJSON("active")!
let borderWidth = checkIsCssUnit(props.border.toString(), 'rpx')
let dbordercolor = getDefaultColor(props.borderColor)
let background = getDefaultColor(props.bgColor)
let fontcolor = getDefaultColor(props.fontColor)
let realColor = fontcolor
let realBackground = background
let realBackImg = ''
let shadowX = _shadow.value[0].toString();
let shadowY = _shadow.value[1].toString();
if (shadowX != shadowX && _shadow.value[0] != 0) {
boxShadow.value = `0 ${shadowX}px ${shadowY}px ${_iconBtn.value ? 'rgba(0,0,0,0)' : defaultObj.getString("shadow")!}`
}
_set_border_color.value = `${borderWidth} ${borderStyle} ${dbordercolor == "" ? defaultObj.getString("borderColor")! : dbordercolor}`
realBackground = background == "" ? defaultObj.getString("background")! : background
if (props.color == 'info') {
realColor = dePrimarycolor;
} else {
realColor = fontcolor == "" ? defaultObj.getString("fontColor")! : fontcolor
}
if (hover) {
if (shadowX != shadowX && _shadow.value[0] != 0) {
boxShadow.value = `0 ${shadowX}px ${shadowY}px ${_iconBtn.value ? 'rgba(0,0,0,0)' : defaultActive.getString("shadow")!}`
}
_set_border_color.value = `${borderWidth} ${borderStyle} ${dbordercolor == "" ? defaultActive.getString("borderColor")! : dbordercolor}`
realBackground = background == "" ? defaultActive.getString("background")! : background
realColor = fontcolor == "" ? defaultActive.getString("fontColor")! : fontcolor
}
if (props.linearGradient.length > 0) {
let dirs = props.linearGradient[0]
if (props.linearGradient[0] == 'top') {
dirs = 'to top'
} else if (props.linearGradient[0] == 'bottom') {
dirs = 'to bottom'
} else if (props.linearGradient[0] == 'left') {
dirs = 'to left'
} else if (props.linearGradient[0] == 'right') {
dirs = 'to right'
}
realBackground = ``
realBackImg = `linear-gradient(${dirs},${props.linearGradient[1]},${props.linearGradient[2]})`
}
if (props.fontDarkColor != '' && xConfig.dark == 'dark') {
realColor = getDefaultColor(props.fontDarkColor)
}
_set_background_color.value = realBackground
_set_background_img.value = realBackImg
_set_font_color.value = realColor
}
function touchStart() {
if (_disabled.value || _loading.value) return;
customStyles(true);
_isHover.value = true;
}
function touchCacel() {
if (_disabled.value || _loading.value) return;
customStyles(false);
_isHover.value = false
}
function touchEnd() {
if (_disabled.value || _loading.value) return;
customStyles(false);
_isHover.value = false
}
function clickListen(e : UniPointerEvent) {
if (!_disabled.value && !_loading.value) {
emits('click', e)
if (props.formType == 'form') {
formSubmit();
}
}
if (_disabled.value == false && props.url != "" && _loading.value == false) {
if (props.navigateMode == 'navigateTo') {
uni.navigateTo({
url: props.url,
fail: (error) => {
console.error(error)
}
})
} else if (props.navigateMode == 'redirectTo') {
uni.redirectTo({
url: props.url,
fail: (error) => {
console.error(error)
}
})
} else if (props.navigateMode == 'switchTab') {
uni.switchTab({
url: props.url,
fail: (error) => {
console.error(error)
}
})
} else if (props.navigateMode == 'reLaunch') {
uni.reLaunch({
url: props.url,
fail: (error) => {
console.error(error)
}
})
} else if (props.navigateMode == 'navigateBack') {
uni.navigateBack({
fail: (error) => {
console.error(error)
}
})
}
}
}
// #ifdef MP-WEIXIN
function getuserinfo(event : UniEvent) { emits('getuserinfo', event) }
function contact(event : UniEvent) { emits('contact', event) }
function getphonenumber(event : UniEvent) { emits('getphonenumber', event) }
function getrealtimephonenumber(event : UniEvent) { emits('getrealtimephonenumber', event) }
function error(event : UniEvent) { emits('error', event) }
function opensetting(event : UniEvent) { emits('opensetting', event) }
function launchapp(event : UniEvent) { emits('launchapp', event) }
function chooseavatar(event : UniEvent) { emits('chooseavatar', event) }
function chooseaddress(event : UniEvent) { emits('chooseaddress', event) }
function chooseinvoicetitle(event : UniEvent) { emits('chooseinvoicetitle', event) }
function addgroupapp(event : UniEvent) { emits('addgroupapp', event) }
function subscribe(event : UniEvent) { emits('subscribe', event) }
function login(event : UniEvent) { emits('login', event) }
function agreeprivacyauthorization(event : UniEvent) { emits('agreeprivacyauthorization', event) }
// #endif
watch([
():any => _color.value,
():any => props.bgColor,
():any => props.borderColor,
():any => props.skin,
():any => props.fontColor,
],
() => {
customStyles(false)
})
onMounted(() => {
customStyles(false)
})
</script>
<template>
<view <!-- #ifdef APP||H5 -->
@touchcancel="touchCacel"
@touchend="touchEnd"
@touchstart="touchStart"
@click="clickListen"
<!-- #endif -->
<!-- #ifdef WEB -->
@mousedown="touchStart"
@mouseup="touchEnd"
@mouseleave="touchEnd"
<!-- #endif -->
:style="_styleMap" class="parentButton" :class="[_disabled||_loading?'noDrag':'']">
<view :class="['xButton',_loading?'load':'']">
<x-icon v-if="_icon!=''&&!_loading" :style="{marginRight:_iconBtn?'0px':' 3px'}" :font-size="_iconSize"
:color="_set_font_color" :name="_icon"></x-icon>
<x-icon v-if="_loading" :color="_set_font_color" :font-size="_iconSize" :spin="true" name="loader-fill"></x-icon>
<!-- #ifdef MP -->
<view v-if="!_iconBtn"
:style="{fontWeight:_fontWeight,'color':_set_font_color,fontSize:_fontSize,lineHeight:lineHeight}">
<!--
@slot 默认插槽
-->
<slot></slot>
</view>
<!-- #endif -->
<!-- #ifndef MP -->
<text v-if="!_iconBtn"
:style="{fontWeight:_fontWeight,'color':_set_font_color,fontSize:_fontSize,lineHeight:lineHeight}">
<!--
@slot 默认插槽
-->
<slot></slot>
</text>
<!-- #endif -->
</view>
<!-- #ifdef MP-WEIXIN -->
<button :disabled="_disabled||_loading" :open-type="openType" :lang="lang" :sessionFrom="sessionFrom"
:sendMessageTitle="sendMessageTitle" :sendMessagePath="sendMessagePath" :sendMessageImg="sendMessageImg"
:appParameter="appParameter" :showMessageCard="showMessageCard"
:phoneNumberNoQuotaToast="phoneNumberNoQuotaToast" @touchcancel="touchCacel" @touchend="touchEnd"
@touchstart="touchStart" @click="clickListen" @getuserinfo="getuserinfo" @contact="contact"
@getphonenumber="getphonenumber" @getrealtimephonenumber="getrealtimephonenumber" @error="error"
@opensetting="opensetting" @launchapp="launchapp" @chooseavatar="chooseavatar"
@chooseaddress="chooseaddress" @chooseinvoicetitle="chooseinvoicetitle" @addgroupapp="addgroupapp"
@subscribe="subscribe" @login="login" @agreeprivacyauthorization="agreeprivacyauthorization"
class="xButtonReal">
</button>
<!-- #endif -->
</view>
</template>
<style scoped lang="scss">
.parentButton {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
position: relative;
/* #ifdef WEB*/
box-sizing: border-box;
cursor: pointer;
/* #endif */
}
/* #ifdef WEB*/
.parentButton.noDrag {
cursor: no-drop;
}
.parentButton:hover {}
.parentButton:active {}
/* #endif */
/* #ifdef MP-WEIXIN */
.xButtonReal {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
box-sizing: border-box;
border: none;
background: transparent !important;
border-width: 0;
margin: 0;
padding: 0;
pointer-events: all;
&::after {
border: none;
border-width: 0;
}
}
/* #endif */
.xButton {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
.loadingMask {
position: absolute;
background-color: rgba(200, 200, 200, 0.6);
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
/* #ifndef APP */
box-sizing: border-box;
width: 100%;
height: 100%;
/* #endif */
/* #ifdef APP*/
box-sizing: border-box;
width: 100%;
height: 100%;
/* #endif */
}
</style>