bindbox-mini/components/ClayButton.vue
2026-02-05 16:00:40 +08:00

272 lines
5.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view
class="clay-button-wrapper"
:class="[
`clay-btn-${size}`,
{ 'clay-btn-block': block },
{ 'clay-btn-disabled': disabled }
]"
>
<view
class="clay-button"
:class="[
`clay-btn-${variant}`,
{ 'clay-btn-outline': outline },
{ 'is-loading': loading }
]"
:style="customStyle"
@tap="handleTap"
>
<!-- 加载动画 -->
<view v-if="loading" class="clay-loading">
<view class="loading-spinner"></view>
</view>
<!-- 图标 -->
<view v-if="icon && !loading" class="clay-icon">
<text>{{ icon }}</text>
</view>
<!-- 按钮文字 -->
<text class="clay-text">{{ text }}</text>
</view>
</view>
</template>
<script>
export default {
name: 'ClayButton',
props: {
// 按钮文字
text: {
type: String,
default: '按钮'
},
// 尺寸sm, md, lg
size: {
type: String,
default: 'md'
},
// 变体primary, secondary, success, warning, error
variant: {
type: String,
default: 'primary'
},
// 是否轮廓样式
outline: {
type: Boolean,
default: false
},
// 是否块级按钮
block: {
type: Boolean,
default: false
},
// 是否禁用
disabled: {
type: Boolean,
default: false
},
// 是否加载中
loading: {
type: Boolean,
default: false
},
// 图标emoji
icon: {
type: String,
default: ''
},
// 自定义样式
customStyle: {
type: Object,
default: () => ({})
}
},
methods: {
handleTap(e) {
if (this.disabled || this.loading) return
this.$emit('tap', e)
}
}
}
</script>
<style lang="scss" scoped>
/* ============================================
Claymorphism 按钮组件
使用示例:
<ClayButton text="确认" variant="primary" size="lg" @tap="handleConfirm" />
============================================ */
.clay-button-wrapper {
display: inline-flex;
&.clay-btn-block {
display: flex;
width: 100%;
}
&.clay-btn-disabled {
opacity: 0.5;
pointer-events: none;
}
}
.clay-button {
border-radius: 50rpx;
font-weight: 700;
position: relative;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
border: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8rpx;
/* Claymorphism 双阴影 - 创造凸起感 */
box-shadow:
8rpx 8rpx 16rpx rgba(0, 0, 0, 0.08),
-8rpx -8rpx 16rpx rgba(255, 255, 255, 0.8),
inset 2rpx 2rpx 4rpx rgba(255, 255, 255, 0.5),
inset -2rpx -2rpx 4rpx rgba(0, 0, 0, 0.05);
&.clay-btn-sm {
padding: 12rpx 32rpx;
font-size: 24rpx;
border-radius: 40rpx;
box-shadow:
6rpx 6rpx 12rpx rgba(0, 0, 0, 0.06),
-6rpx -6rpx 12rpx rgba(255, 255, 255, 0.8),
inset 2rpx 2rpx 4rpx rgba(255, 255, 255, 0.5),
inset -2rpx -2rpx 4rpx rgba(0, 0, 0, 0.05);
}
&.clay-btn-md {
padding: 20rpx 48rpx;
font-size: 28rpx;
border-radius: 50rpx;
}
&.clay-btn-lg {
padding: 28rpx 64rpx;
font-size: 32rpx;
border-radius: 60rpx;
box-shadow:
10rpx 10rpx 20rpx rgba(0, 0, 0, 0.1),
-10rpx -10rpx 20rpx rgba(255, 255, 255, 0.7),
inset 3rpx 3rpx 6rpx rgba(255, 255, 255, 0.5),
inset -3rpx -3rpx 6rpx rgba(0, 0, 0, 0.06);
}
/* 按钮变体 */
&.clay-btn-primary {
background: linear-gradient(145deg, #FF9500, #FF6B00);
color: #fff;
box-shadow:
10rpx 10rpx 20rpx rgba(255, 107, 0, 0.15),
-10rpx -10rpx 20rpx rgba(255, 255, 255, 0.7),
inset 3rpx 3rpx 6rpx rgba(255, 255, 255, 0.4),
inset -3rpx -3rpx 6rpx rgba(0, 0, 0, 0.1);
}
&.clay-btn-secondary {
background: linear-gradient(145deg, #ffffff, #f0f0f0);
color: #1D1D1F;
}
&.clay-btn-success {
background: linear-gradient(145deg, #34C759, #30D158);
color: #fff;
box-shadow:
10rpx 10rpx 20rpx rgba(52, 199, 89, 0.15),
-10rpx -10rpx 20rpx rgba(255, 255, 255, 0.7),
inset 3rpx 3rpx 6rpx rgba(255, 255, 255, 0.4),
inset -3rpx -3rpx 6rpx rgba(0, 0, 0, 0.1);
}
&.clay-btn-warning {
background: linear-gradient(145deg, #FF9F0A, #FF9500);
color: #fff;
box-shadow:
10rpx 10rpx 20rpx rgba(255, 159, 10, 0.15),
-10rpx -10rpx 20rpx rgba(255, 255, 255, 0.7),
inset 3rpx 3rpx 6rpx rgba(255, 255, 255, 0.4),
inset -3rpx -3rpx 6rpx rgba(0, 0, 0, 0.1);
}
&.clay-btn-error {
background: linear-gradient(145deg, #FF3B30, #FF453A);
color: #fff;
box-shadow:
10rpx 10rpx 20rpx rgba(255, 59, 48, 0.15),
-10rpx -10rpx 20rpx rgba(255, 255, 255, 0.7),
inset 3rpx 3rpx 6rpx rgba(255, 255, 255, 0.4),
inset -3rpx -3rpx 6rpx rgba(0, 0, 0, 0.1);
}
/* 轮廓样式 */
&.clay-btn-outline {
background: transparent;
border: 3rpx solid currentColor;
&.clay-btn-primary {
color: #FF6B00;
background: rgba(255, 107, 0, 0.05);
}
&.clay-btn-secondary {
color: #86868B;
background: rgba(134, 134, 139, 0.05);
}
}
/* 按下效果 */
&:active {
transform: scale(0.96);
box-shadow:
4rpx 4rpx 8rpx rgba(0, 0, 0, 0.1),
-4rpx -4rpx 8rpx rgba(255, 255, 255, 0.5),
inset 4rpx 4rpx 8rpx rgba(0, 0, 0, 0.08),
inset -4rpx -4rpx 8rpx rgba(255, 255, 255, 0.3);
}
/* 加载状态 */
&.is-loading {
pointer-events: none;
}
}
/* 加载动画 */
.clay-loading {
display: flex;
align-items: center;
justify-content: center;
}
.loading-spinner {
width: 32rpx;
height: 32rpx;
border: 4rpx solid rgba(255, 255, 255, 0.3);
border-top-color: #fff;
border-radius: 50%;
animation: clay-spin 0.8s linear infinite;
}
@keyframes clay-spin {
to { transform: rotate(360deg); }
}
/* 图标 */
.clay-icon {
font-size: 32rpx;
line-height: 1;
}
/* 文字 */
.clay-text {
line-height: 1;
white-space: nowrap;
}
</style>