This commit is contained in:
2026-01-05 12:47:14 +08:00
commit 1fc846fae3
1614 changed files with 162035 additions and 0 deletions

View File

@@ -0,0 +1,139 @@
import type { ButtonLang, ButtonOnAddgroupappEvent, ButtonOnAgreeprivacyauthorizationEvent, ButtonOnChooseaddressEvent, ButtonOnChooseavatarEvent, ButtonOnChooseinvoicetitleEvent, ButtonOnErrorEvent, ButtonOnGetphonenumberEvent, ButtonOnLaunchappEvent, ButtonOnLoginEvent, ButtonOnOpensettingEvent, ButtonOnSubscribeEvent, ButtonOpenType } from '@uni-helper/uni-app-types'
import type { ExtractPropTypes, PropType } from 'vue'
import { CLICK_EVENT } from '../_constants'
import { commonProps, makeNumberProp, makeStringProp } from '../_utils'
import type { ButtonFormType, ButtonShape, ButtonSize, ButtonType } from './type'
export const buttonProps = {
...commonProps,
/**
* @description 指定按钮按下去的样式类
*/
hoverClass: makeStringProp('button-hover'),
/**
* @description 按住后多久出现点击态,单位毫秒
*/
hoverStartTime: makeNumberProp(20),
/**
* @description 手指松开后点击态保留时间,单位毫秒
*/
hoverStayTime: makeNumberProp(70),
/**
* @description 按钮颜色,支持传入 `linear-gradient` 渐变色
*/
customColor: String,
/**
* @description 形状,可选值为 `square` `round`
*/
shape: makeStringProp<ButtonShape>('round'),
/**
* @description 是否为朴素按钮
*/
plain: Boolean,
/**
* @description 按钮 `loading` 状态
*/
loading: Boolean,
/**
* @description 是否禁用按钮
*/
disabled: Boolean,
/**
* @description 按钮类型,可选值为 `primary` `info` `warning` `danger` `success` `default`
*/
type: makeStringProp<ButtonType>('default'),
/**
* @description 表单类型,可选值 `button` `submit` `reset`
*/
formType: makeStringProp<ButtonFormType>('button'),
/**
* @description 尺寸,可选值为 `large` `small` `mini` `normal`
*/
size: makeStringProp<ButtonSize>('normal'),
/**
* @description 是否为块级元素
*/
block: Boolean,
/**
* @description 小程序开放能力
*/
openType: String as PropType<ButtonOpenType>,
/**
* @description 指定返回用户信息的语言zh_CN 简体中文zh_TW 繁体中文en 英文
*/
lang: makeStringProp<ButtonLang>('en'),
/**
* @description 会话来源openType="contact"时有效
*/
sessionFrom: String,
/**
* @description 会话内消息卡片标题openType="contact"时有效
*/
sendMessageTitle: String,
/**
* @description 会话内消息卡片点击跳转小程序路径openType="contact"时有效
*/
sendMessagePath: String,
/**
* @description 会话内消息卡片图片openType="contact"时有效
*/
sendMessageImg: String,
/**
* @description 是否显示会话内消息卡片,设置此参数为 true用户进入客服会话会在右下角显示"可能要发送的小程序"提示用户点击后可以快速发送小程序消息openType="contact"时有效
*/
showMessageCard: Boolean,
/**
* @description 打开群资料卡时传递的群号openType="openGroupProfile"时有效
*/
groupId: String,
/**
* @description 打开频道页面时,传递的频道号 openType="openGuildProfile"时有效
*/
guildId: makeStringProp(''),
/**
* @description 打开公众号资料卡时,传递的号码 openType="openPublicProfile"时有效
*/
publicId: String,
/**
* @description 客服的抖音号,openType="im"时有效
*/
dataImId: String,
/**
* @description IM卡片类型,openType="im"时有效
*/
dataImType: String,
/**
* @description 商品的id仅支持泛知识课程库和生活服务商品库中的商品,openType="im"时有效
*/
dataGoodsId: String,
/**
* @description 订单的id仅支持交易2.0订单, openType="im"时有效
*/
dataOrderId: String,
/**
* @description 商品类型“1”代表生活服务“2”代表泛知识。openType="im"时有效
*/
dataBizLine: String,
} as const
export type ButtonProps = ExtractPropTypes<typeof buttonProps>
export const buttonEmits = {
[CLICK_EVENT]: (evt: MouseEvent) => evt instanceof Object,
getphonenumber: (evt: ButtonOnGetphonenumberEvent) => evt instanceof Object,
getuserinfo: (evt: any) => evt instanceof Object,
error: (evt: ButtonOnErrorEvent) => evt instanceof Object,
opensetting: (evt: ButtonOnOpensettingEvent) => evt instanceof Object,
launchapp: (evt: ButtonOnLaunchappEvent) => evt instanceof Object,
contact: (evt: any) => evt instanceof Object,
chooseavatar: (evt: ButtonOnChooseavatarEvent) => evt instanceof Object,
agreeprivacyauthorization: (evt: ButtonOnAgreeprivacyauthorizationEvent) => evt instanceof Object,
addgroupapp: (evt: ButtonOnAddgroupappEvent) => evt instanceof Object,
chooseaddress: (evt: ButtonOnChooseaddressEvent) => evt instanceof Object,
chooseinvoicetitle: (evt: ButtonOnChooseinvoicetitleEvent) => evt instanceof Object,
subscribe: (evt: ButtonOnSubscribeEvent) => evt instanceof Object,
login: (evt: ButtonOnLoginEvent) => evt instanceof Object,
im: (evt: any) => evt instanceof Object,
}
export type ButtonEmits = typeof buttonEmits

View File

@@ -0,0 +1,121 @@
<script lang="ts" setup>
import type { CSSProperties } from 'vue'
import { computed, defineComponent } from 'vue'
import { CLICK_EVENT, PREFIX } from '../_constants'
import { getMainClass, getMainStyle } from '../_utils'
import Icon from '../icon/icon.vue'
import { buttonEmits, buttonProps } from './button'
const props = defineProps(buttonProps)
const emit = defineEmits(buttonEmits)
const classes = computed(() => {
return getMainClass(props, componentName, {
[`${componentName}--${props.type}`]: !!props.type,
[`${componentName}--${props.size}`]: !!props.size,
[`${componentName}--${props.shape}`]: !!props.shape,
[`${componentName}--plain`]: props.plain,
[`${componentName}--block`]: props.block,
[`${componentName}--disabled`]: props.disabled,
[`${componentName}--loading`]: props.loading,
[`${componentName}--hovercls`]: props.hoverClass !== 'button-hover',
})
})
const styles = computed(() => {
const value: CSSProperties = {}
if (props.customColor) {
if (props.plain) {
value.color = props.customColor
value.background = '#fff'
if (!props.customColor.includes('gradient'))
value.borderColor = props.customColor
}
else {
value.color = '#fff'
value.background = props.customColor
}
}
return getMainStyle(props, value)
})
function handleClick(event: any) {
if (props.disabled || props.loading)
return
emit(CLICK_EVENT, event)
}
</script>
<script lang="ts">
const componentName = `${PREFIX}-button`
export default defineComponent({
name: componentName,
options: {
virtualHost: true,
addGlobalClass: true,
// #ifndef H5
styleIsolation: 'shared',
// #endif
},
})
</script>
<template>
<button
:class="classes"
:style="styles"
:form-type="props.formType === 'button' ? undefined : props.formType"
:open-type="props.disabled || props.loading ? undefined : props.openType"
:hover-class="props.hoverClass"
:hover-start-time="props.hoverStartTime"
:hover-stay-time="props.hoverStayTime"
hover-stop-propagation
:lang="props.lang"
:session-from="props.sessionFrom"
:send-message-title="props.sendMessageTitle"
:send-message-path="props.sendMessagePath"
:send-message-img="props.sendMessageImg"
:show-message-card="props.showMessageCard"
:group-id="props.groupId"
:guild-id="props.guildId"
:public-id="props.publicId"
:data-im-id="props.dataImId"
:data-im-type="props.dataImType"
:data-goods-id="props.dataGoodsId"
:data-order-id="props.dataOrderId"
:data-biz-line="props.dataBizLine"
@click="handleClick"
@getphonenumber="emit('getphonenumber', $event)"
@getuserinfo="emit('getuserinfo', $event)"
@error="emit('error', $event)"
@opensetting="emit('opensetting', $event)"
@addgroupapp="emit('addgroupapp', $event)"
@chooseaddress="emit('chooseaddress', $event)"
@chooseavatar="emit('chooseavatar', $event)"
@chooseinvoicetitle="emit('chooseinvoicetitle', $event)"
@launchapp="emit('launchapp', $event)"
@login="emit('login', $event)"
@subscribe="emit('subscribe', $event)"
@contact="emit('contact', $event)"
@agreeprivacyauthorization="emit('agreeprivacyauthorization', $event)"
@im="emit('im', $event)"
>
<view class="nut-button__wrap">
<Icon v-if="loading" name="loading" class="nut-icon-loading" />
<slot v-if="$slots.icon && !loading" name="icon" />
<view v-if="$slots.default" :class="{ 'nut-button__text': $slots.icon || loading }">
<slot />
</view>
</view>
</button>
</template>
<style lang="scss">
@import './index';
</style>

View File

@@ -0,0 +1,292 @@
.nut-theme-dark {
.nut-button {
&--default {
color: $dark-color3;
background: $dark-background2;
border: $button-border-width solid $dark-background2;
}
&--plain {
background: $dark-background2;
}
&:not(.nut-button--hovercls) {
.nut-button--plain:not([disabled]):active {
background: $dark-background2;
}
.nut-button--default:not([disabled]):active {
color: $dark-color3;
background: $dark-background2;
border: $button-border-width solid $dark-background2;
}
}
}
}
.nut-button {
position: relative;
box-sizing: border-box;
display: inline-block;
flex-shrink: 0;
width: auto;
height: $button-default-height;
padding: 0;
margin: 0;
font-size: $button-default-font-size;
line-height: $button-default-line-height;
text-align: center;
vertical-align: bottom;
appearance: none;
touch-action: manipulation;
cursor: pointer;
user-select: none;
transition: opacity 0.2s;
-webkit-tap-highlight-color: rgb(0 0 0 / 0%);
-webkit-tap-highlight-color: transparent;
.nut-button__text {
margin-left: 5px;
}
&::before {
position: absolute;
top: 50%;
left: 50%;
width: 100%;
height: 100%;
content: "";
background-color: $black;
border: inherit;
border-color: $black;
border-radius: inherit;
opacity: 0;
transform: translate(-50%, -50%);
}
&::after {
display: none;
}
&:not(.nut-button--hovercls) {
&:active::before {
opacity: 0.1;
}
}
&__wrap {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
&--loading,
&--disabled {
&::before {
display: none;
}
}
&--default {
color: $button-default-color;
background: $button-default-bg-color;
background-origin: border-box;
border: $button-border-width solid $button-default-border-color;
}
&--primary {
color: $button-primary-color;
background: $button-primary-background-color;
background-origin: border-box;
border: $button-border-width solid transparent;
}
&--info {
color: $button-info-color;
background: $button-info-background-color;
background-origin: border-box;
border: $button-border-width solid transparent;
}
&--success {
color: $button-success-color;
background: $button-success-background-color;
background-origin: border-box;
border: $button-border-width solid transparent;
}
&--danger {
color: $button-danger-color;
background: $button-danger-background-color;
background-origin: border-box;
border: $button-border-width solid transparent;
}
&--warning {
color: $button-warning-color;
background: $button-warning-background-color;
background-origin: border-box;
border: $button-border-width solid transparent;
}
&--plain {
background: $button-plain-background-color;
background-origin: border-box;
&.nut-button--primary {
color: $button-primary-border-color;
border-color: $button-primary-border-color;
}
&.nut-button--info {
color: $button-info-border-color;
border-color: $button-info-border-color;
}
&.nut-button--success {
color: $button-success-border-color;
border-color: $button-success-border-color;
}
&.nut-button--danger {
color: $button-danger-border-color;
border-color: $button-danger-border-color;
}
&.nut-button--warning {
color: $button-warning-border-color;
border-color: $button-warning-border-color;
}
&:not(.nut-button--hovercls) {
&.nut-button--primary:not([disabled]):active {
color: $button-primary-border-color;
border-color: $button-primary-border-color;
}
&.nut-button--info:not([disabled]):active {
color: $button-info-border-color;
border-color: $button-info-border-color;
}
&.nut-button--success:not([disabled]):active {
color: $button-success-border-color;
border-color: $button-success-border-color;
}
&.nut-button--danger:not([disabled]):active {
color: $button-danger-border-color;
border-color: $button-danger-border-color;
}
&.nut-button--warning:not([disabled]):active {
color: $button-warning-border-color;
border-color: $button-warning-border-color;
}
}
}
&--large {
width: 100%;
height: $button-large-height;
font-size: $button-large-font-size;
line-height: $button-large-line-height;
}
&--normal {
padding: $button-default-padding;
font-size: $button-default-font-size;
}
&--small {
height: $button-small-height;
padding: $button-small-padding;
font-size: $button-small-font-size;
line-height: $button-small-line-height;
&.nut-button--round {
border-radius: $button-small-round-border-radius;
}
}
&--mini {
height: $button-mini-height;
padding: $button-mini-padding;
font-size: $button-mini-font-size;
line-height: $button-mini-line-height;
}
&--block {
display: block;
width: 100%;
}
&--disabled {
cursor: not-allowed;
opacity: $button-disabled-opacity;
}
&--loading {
cursor: default;
opacity: 0.9;
}
&--round {
border-radius: $button-border-radius;
}
&--square {
border-radius: 0;
}
&:not(.nut-button--hovercls) {
.nut-button--default:not([disabled]):active {
color: $button-default-color;
background: $button-default-bg-color;
background-origin: border-box;
border: $button-border-width solid $button-default-border-color;
}
.nut-button--primary:not([disabled]):active {
color: $button-primary-color;
background: $button-primary-background-color;
background-origin: border-box;
border: $button-border-width solid transparent;
}
.nut-button--info:not([disabled]):active {
color: $button-info-color;
background: $button-info-background-color;
background-origin: border-box;
border: $button-border-width solid transparent;
}
.nut-button--success:not([disabled]):active {
color: $button-success-color;
background: $button-success-background-color;
background-origin: border-box;
border: $button-border-width solid transparent;
}
.nut-button--danger:not([disabled]):active {
color: $button-danger-color;
background: $button-danger-background-color;
background-origin: border-box;
border: $button-border-width solid transparent;
}
.nut-button--warning:not([disabled]):active {
color: $button-warning-color;
background: $button-warning-background-color;
background-origin: border-box;
border: $button-border-width solid transparent;
}
.nut-button--plain:not([disabled]):active {
background: $button-plain-background-color;
background-origin: border-box;
}
}
}

View File

@@ -0,0 +1,2 @@
export * from './button'
export * from './type'

View File

@@ -0,0 +1,8 @@
export const buttonType = ['default', 'primary', 'info', 'success', 'warning', 'danger'] as const
export type ButtonType = (typeof buttonType)[number]
export const buttonSize = ['large', 'normal', 'small', 'mini'] as const
export type ButtonSize = (typeof buttonSize)[number]
export const buttonShape = ['square', 'round'] as const
export type ButtonShape = (typeof buttonShape)[number]
export const buttonFormType = ['button', 'submit', 'reset'] as const
export type ButtonFormType = (typeof buttonFormType)[number]