修改UI风格为粘土拟态风格

This commit is contained in:
tsui110 2026-02-05 16:00:40 +08:00
parent 90110f5bce
commit 2a98cde85f
9 changed files with 1412 additions and 58 deletions

271
components/ClayButton.vue Normal file
View File

@ -0,0 +1,271 @@
<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>

151
components/ClayCard.vue Normal file
View File

@ -0,0 +1,151 @@
<template>
<view
class="clay-card"
:class="[
`clay-card-${size}`,
{ 'clay-card-primary': variant === 'primary' },
{ 'clay-card-gold': variant === 'gold' },
{ 'clay-card-inset': inset },
customClass
]"
:style="customStyle"
@tap="handleTap"
>
<slot></slot>
</view>
</template>
<script>
export default {
name: 'ClayCard',
props: {
// sm, md, lg
size: {
type: String,
default: 'md'
},
// default, primary, gold
variant: {
type: String,
default: 'default'
},
//
inset: {
type: Boolean,
default: false
},
//
customClass: {
type: String,
default: ''
},
//
customStyle: {
type: Object,
default: () => ({})
}
},
methods: {
handleTap(e) {
this.$emit('tap', e)
}
}
}
</script>
<style lang="scss" scoped>
/* ============================================
Claymorphism 卡片组件
使用示例
<ClayCard size="lg" variant="primary">内容</ClayCard>
============================================ */
.clay-card {
background: linear-gradient(145deg, #ffffff, #f0f0f0);
border-radius: 24rpx;
position: relative;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
/* 外部双阴影 - 创造凸起效果 */
box-shadow:
8rpx 8rpx 16rpx rgba(0, 0, 0, 0.06),
-8rpx -8rpx 16rpx rgba(255, 255, 255, 0.8),
inset 2rpx 2rpx 4rpx rgba(255, 255, 255, 0.9),
inset -2rpx -2rpx 4rpx rgba(0, 0, 0, 0.03);
&.clay-card-sm {
border-radius: 16rpx;
box-shadow:
6rpx 6rpx 12rpx rgba(0, 0, 0, 0.04),
-6rpx -6rpx 12rpx rgba(255, 255, 255, 0.8),
inset 2rpx 2rpx 4rpx rgba(255, 255, 255, 0.9),
inset -2rpx -2rpx 4rpx rgba(0, 0, 0, 0.03);
}
&.clay-card-md {
border-radius: 24rpx;
}
&.clay-card-lg {
border-radius: 32rpx;
box-shadow:
12rpx 12rpx 24rpx rgba(0, 0, 0, 0.08),
-12rpx -12rpx 24rpx rgba(255, 255, 255, 0.7),
inset 4rpx 4rpx 8rpx rgba(255, 255, 255, 0.85),
inset -4rpx -4rpx 8rpx rgba(0, 0, 0, 0.04);
}
&:active {
transform: scale(0.98);
box-shadow:
4rpx 4rpx 8rpx rgba(0, 0, 0, 0.06),
-4rpx -4rpx 8rpx rgba(255, 255, 255, 0.6),
inset 4rpx 4rpx 8rpx rgba(0, 0, 0, 0.05),
inset -4rpx -4rpx 8rpx rgba(255, 255, 255, 0.4);
}
}
/* 彩色粘土卡片 */
.clay-card-primary {
background: linear-gradient(145deg, #FF9500, #FF6B00);
color: #fff;
box-shadow:
10rpx 10rpx 20rpx rgba(255, 107, 0, 0.2),
-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);
&:active {
box-shadow:
5rpx 5rpx 10rpx rgba(255, 107, 0, 0.25),
-5rpx -5rpx 10rpx rgba(255, 255, 255, 0.5),
inset 5rpx 5rpx 10rpx rgba(0, 0, 0, 0.15),
inset -5rpx -5rpx 10rpx rgba(255, 255, 255, 0.2);
}
}
.clay-card-gold {
background: linear-gradient(145deg, #FFD60A, #FF9F0A);
box-shadow:
10rpx 10rpx 20rpx rgba(255, 159, 10, 0.2),
-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);
&:active {
box-shadow:
5rpx 5rpx 10rpx rgba(255, 159, 10, 0.25),
-5rpx -5rpx 10rpx rgba(255, 255, 255, 0.5),
inset 5rpx 5rpx 10rpx rgba(0, 0, 0, 0.15),
inset -5rpx -5rpx 10rpx rgba(255, 255, 255, 0.2);
}
}
/* 凹陷粘土卡片 (Inset) */
.clay-card-inset {
background: linear-gradient(145deg, #e8e8e8, #f8f8f8);
box-shadow:
inset 6rpx 6rpx 12rpx rgba(0, 0, 0, 0.08),
inset -6rpx -6rpx 12rpx rgba(255, 255, 255, 0.9);
}
</style>

282
components/ClayInput.vue Normal file
View File

@ -0,0 +1,282 @@
<template>
<view
class="clay-input-wrapper"
:class="[
`clay-input-${size}`,
{ 'clay-input-focused': isFocused },
{ 'clay-input-error': error },
{ 'clay-input-disabled': disabled },
customClass
]"
>
<!-- 前缀图标 -->
<view v-if="prefixIcon" class="clay-prefix-icon">
<text>{{ prefixIcon }}</text>
</view>
<!-- 输入框 -->
<input
class="clay-input-field"
:type="type"
:value="modelValue"
:placeholder="placeholder"
:disabled="disabled"
:maxlength="maxlength"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
@confirm="handleConfirm"
/>
<!-- 后缀图标/按钮 -->
<view v-if="suffixIcon || clearable" class="clay-suffix-icon" @tap="handleSuffixTap">
<text v-if="showClearButton" class="clear-button">×</text>
<text v-else-if="suffixIcon">{{ suffixIcon }}</text>
</view>
<!-- 错误提示 -->
<view v-if="error && errorText" class="clay-error-text">
<text>{{ errorText }}</text>
</view>
</view>
</template>
<script>
export default {
name: 'ClayInput',
props: {
// v-model
modelValue: {
type: [String, Number],
default: ''
},
//
type: {
type: String,
default: 'text'
},
//
placeholder: {
type: String,
default: '请输入'
},
// sm, md, lg
size: {
type: String,
default: 'md'
},
//
disabled: {
type: Boolean,
default: false
},
//
error: {
type: Boolean,
default: false
},
//
errorText: {
type: String,
default: ''
},
//
maxlength: {
type: [String, Number],
default: 140
},
//
clearable: {
type: Boolean,
default: false
},
// emoji
prefixIcon: {
type: String,
default: ''
},
// emoji
suffixIcon: {
type: String,
default: ''
},
//
customClass: {
type: String,
default: ''
}
},
data() {
return {
isFocused: false
}
},
computed: {
showClearButton() {
return this.clearable && this.modelValue && !this.disabled
}
},
methods: {
handleInput(e) {
this.$emit('update:modelValue', e.detail.value)
this.$emit('input', e.detail.value)
},
handleFocus(e) {
this.isFocused = true
this.$emit('focus', e)
},
handleBlur(e) {
this.isFocused = false
this.$emit('blur', e)
},
handleConfirm(e) {
this.$emit('confirm', e)
},
handleSuffixTap() {
if (this.showClearButton) {
this.$emit('update:modelValue', '')
this.$emit('clear')
} else if (this.suffixIcon) {
this.$emit('suffix-tap')
}
}
}
}
</script>
<style lang="scss" scoped>
/* ============================================
Claymorphism 输入框组件
使用示例
<ClayInput v-model="value" placeholder="请输入" prefixIcon="🔍" />
============================================ */
.clay-input-wrapper {
position: relative;
display: flex;
align-items: center;
background: linear-gradient(145deg, #ffffff, #f5f5f5);
border-radius: 24rpx;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
/* Claymorphism 双阴影 */
box-shadow:
inset 4rpx 4rpx 8rpx rgba(0, 0, 0, 0.06),
inset -4rpx -4rpx 8rpx rgba(255, 255, 255, 0.9),
4rpx 4rpx 8rpx rgba(0, 0, 0, 0.04),
-4rpx -4rpx 8rpx rgba(255, 255, 255, 0.7);
&.clay-input-sm {
border-radius: 20rpx;
padding: 16rpx 24rpx;
.clay-input-field {
font-size: 24rpx;
}
}
&.clay-input-md {
border-radius: 24rpx;
padding: 24rpx 28rpx;
.clay-input-field {
font-size: 28rpx;
}
}
&.clay-input-lg {
border-radius: 28rpx;
padding: 28rpx 32rpx;
.clay-input-field {
font-size: 32rpx;
}
}
/* 聚焦状态 */
&.clay-input-focused {
box-shadow:
inset 6rpx 6rpx 12rpx rgba(255, 107, 0, 0.08),
inset -6rpx -6rpx 12rpx rgba(255, 255, 255, 0.85),
6rpx 6rpx 12rpx rgba(255, 107, 0, 0.1),
-6rpx -6rpx 12rpx rgba(255, 255, 255, 0.6);
}
/* 错误状态 */
&.clay-input-error {
box-shadow:
inset 4rpx 4rpx 8rpx rgba(255, 59, 48, 0.1),
inset -4rpx -4rpx 8rpx rgba(255, 255, 255, 0.9),
4rpx 4rpx 8rpx rgba(255, 59, 48, 0.1),
-4rpx -4rpx 8rpx rgba(255, 255, 255, 0.7);
}
/* 禁用状态 */
&.clay-input-disabled {
opacity: 0.5;
background: linear-gradient(145deg, #f0f0f0, #e8e8e8);
}
}
.clay-input-field {
flex: 1;
border: none;
background: transparent;
color: #1D1D1F;
outline: none;
line-height: 1.5;
&::placeholder {
color: #C7C7CC;
}
}
/* 前缀图标 */
.clay-prefix-icon {
margin-right: 16rpx;
font-size: 32rpx;
opacity: 0.6;
}
/* 后缀图标 */
.clay-suffix-icon {
margin-left: 16rpx;
font-size: 32rpx;
opacity: 0.6;
cursor: pointer;
transition: all 0.2s;
&:active {
opacity: 1;
transform: scale(1.1);
}
.clear-button {
display: inline-block;
width: 40rpx;
height: 40rpx;
line-height: 36rpx;
text-align: center;
font-size: 40rpx;
color: #86868B;
background: linear-gradient(145deg, #e8e8e8, #f8f8f8);
border-radius: 50%;
/* Claymorphism 凹陷效果 */
box-shadow:
inset 2rpx 2rpx 4rpx rgba(0, 0, 0, 0.1),
inset -2rpx -2rpx 4rpx rgba(255, 255, 255, 0.9);
}
}
/* 错误提示 */
.clay-error-text {
position: absolute;
bottom: -40rpx;
left: 0;
font-size: 22rpx;
color: #FF3B30;
white-space: nowrap;
}
</style>

View File

@ -1,23 +1,31 @@
<template>
<!-- #ifndef MP-TOUTIAO -->
<view class="app-tab-bar">
<view class="tab-bar-item" @tap="switchTab('pages/index/index')">
<image class="tab-icon" :src="selected === 0 ? '/static/tab/home_active.png' : '/static/tab/home.png'" mode="aspectFit"></image>
<view class="clay-tab-bar">
<view class="tab-bar-item" @tap="switchTab('pages/index/index')" :class="{ active: selected === 0 }">
<view class="tab-icon-wrapper" :class="{ 'icon-active': selected === 0 }">
<image class="tab-icon" :src="selected === 0 ? '/static/tab/home_active.png' : '/static/tab/home.png'" mode="aspectFit"></image>
</view>
<text class="tab-text" :class="{ active: selected === 0 }">首页</text>
</view>
<view class="tab-bar-item" @tap="switchTab('pages/shop/index')">
<image class="tab-icon" :src="selected === 1 ? '/static/tab/shop_active.png' : '/static/tab/shop.png'" mode="aspectFit"></image>
<view class="tab-bar-item" @tap="switchTab('pages/shop/index')" :class="{ active: selected === 1 }">
<view class="tab-icon-wrapper" :class="{ 'icon-active': selected === 1 }">
<image class="tab-icon" :src="selected === 1 ? '/static/tab/shop_active.png' : '/static/tab/shop.png'" mode="aspectFit"></image>
</view>
<text class="tab-text" :class="{ active: selected === 1 }">商城</text>
</view>
<view class="tab-bar-item" @tap="switchTab('pages/cabinet/index')">
<image class="tab-icon" :src="selected === 2 ? '/static/tab/box_active.png' : '/static/tab/box.png'" mode="aspectFit"></image>
<view class="tab-bar-item" @tap="switchTab('pages/cabinet/index')" :class="{ active: selected === 2 }">
<view class="tab-icon-wrapper" :class="{ 'icon-active': selected === 2 }">
<image class="tab-icon" :src="selected === 2 ? '/static/tab/box_active.png' : '/static/tab/box.png'" mode="aspectFit"></image>
</view>
<text class="tab-text" :class="{ active: selected === 2 }">盒柜</text>
</view>
<view class="tab-bar-item" @tap="switchTab('pages/mine/index')">
<image class="tab-icon" :src="selected === 3 ? '/static/tab/profile_active.png' : '/static/tab/profile.png'" mode="aspectFit"></image>
<view class="tab-bar-item" @tap="switchTab('pages/mine/index')" :class="{ active: selected === 3 }">
<view class="tab-icon-wrapper" :class="{ 'icon-active': selected === 3 }">
<image class="tab-icon" :src="selected === 3 ? '/static/tab/profile_active.png' : '/static/tab/profile.png'" mode="aspectFit"></image>
</view>
<text class="tab-text" :class="{ active: selected === 3 }">我的</text>
</view>
</view>
@ -63,19 +71,30 @@ export default {
<style lang="scss" scoped>
/* #ifndef MP-TOUTIAO */
.app-tab-bar {
/* ============================================
Claymorphism 底部导航栏
粘土风格 - 柔和浮感 & 双阴影效果
============================================ */
.clay-tab-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 100rpx;
background: #FFFFFF;
border-top: 1rpx solid #E5E5E5;
background: linear-gradient(145deg, #ffffff, #f5f5f5);
border-top: 1px solid rgba(255, 255, 255, 0.6);
display: flex;
justify-content: space-around;
align-items: center;
padding-bottom: env(safe-area-inset-bottom);
padding: 12rpx 0 calc(12rpx + env(safe-area-inset-bottom));
z-index: 999;
/* Claymorphism 双阴影 - 创造浮起效果 */
box-shadow:
0 -8rpx 16rpx rgba(0, 0, 0, 0.04),
0 8rpx 16rpx rgba(255, 255, 255, 0.8),
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.9),
inset 0 -2rpx 4rpx rgba(0, 0, 0, 0.02);
}
.tab-bar-item {
@ -84,22 +103,68 @@ export default {
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 8rpx 0;
position: relative;
transition: all 0.3s ease;
&.active {
.tab-icon-wrapper {
/* 选中状态 - Claymorphism 凸起效果 */
background: linear-gradient(145deg, #FF9500, #FF6B00);
box-shadow:
6rpx 6rpx 12rpx rgba(255, 107, 0, 0.2),
-6rpx -6rpx 12rpx rgba(255, 255, 255, 0.7),
inset 2rpx 2rpx 4rpx rgba(255, 255, 255, 0.4),
inset -2rpx -2rpx 4rpx rgba(0, 0, 0, 0.1);
transform: translateY(-4rpx);
}
.tab-text {
color: #FF6B00;
font-weight: 700;
}
}
&:active {
transform: scale(0.95);
}
}
/* 图标包装器 - Claymorphism 圆形徽章 */
.tab-icon-wrapper {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 6rpx;
background: linear-gradient(145deg, #f8f8f8, #e8e8e8);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
/* 默认状态 - 凹陷效果 */
box-shadow:
inset 3rpx 3rpx 6rpx rgba(0, 0, 0, 0.08),
inset -3rpx -3rpx 6rpx rgba(255, 255, 255, 0.9);
}
.tab-icon {
width: 48rpx;
height: 48rpx;
margin-bottom: 4rpx;
width: 40rpx;
height: 40rpx;
transition: all 0.3s ease;
}
.tab-text {
font-size: 22rpx;
color: #7A7E83;
&.active {
color: #007AFF;
}
color: #86868B;
transition: all 0.3s ease;
font-weight: 500;
}
/* 选中状态的图标颜色调整 */
.tab-bar-item.active .tab-icon {
filter: brightness(0) invert(1);
}
/* #endif */
</style>

View File

@ -0,0 +1,69 @@
/**
* Claymorphism 组件库导出
*
* 使用方式 pages.json 中配置 easycom
* {
* "easycom": {
* "autoscan": true,
* "custom": {
* "^Clay(A.*)": "@/components/Clay$1.vue"
* }
* }
* }
*
* 或手动导入
* import ClayCard from '@/components/ClayCard.vue'
* import ClayButton from '@/components/ClayButton.vue'
* import ClayInput from '@/components/ClayInput.vue'
*/
export { default as ClayCard } from './ClayCard.vue'
export { default as ClayButton } from './ClayButton.vue'
export { default as ClayInput } from './ClayInput.vue'
/* ============================================
Claymorphism 设计系统说明
🎨 核心特点
1. 双阴影效果外部 + 内部创造立体浮感
2. 柔和的渐变背景
3. 圆润的边角设计
4. 有机感的按压动画
📦 组件列表
- ClayCard: 粘土风格卡片
- ClayButton: 粘土风格按钮
- ClayInput: 粘土风格输入框
🎯 使用示例
<!-- 卡片 -->
<ClayCard size="lg" variant="primary" @tap="handleTap">
<text>卡片内容</text>
</ClayCard>
<!-- 按钮 -->
<ClayButton
text="确认"
variant="primary"
size="lg"
:loading="isLoading"
@tap="handleConfirm"
/>
<!-- 输入框 -->
<ClayInput
v-model="inputValue"
placeholder="请输入内容"
prefixIcon="🔍"
:clearable="true"
@confirm="handleSearch"
/>
🎨 样式变量uni.scss
- $clay-shadow-sm/md/lg/xl: 阴影层级
- $clay-bg-light: #FAFAFA
- $clay-bg-white: #FFFFFF
- $clay-border: rgba(255, 255, 255, 0.8)
============================================ */

260
docs/CLAYMORPHISM.md Normal file
View File

@ -0,0 +1,260 @@
# Claymorphism UI 优化文档
## 🎨 设计系统概述
### 什么是 Claymorphism粘土拟态
Claymorphism 是一种结合了 **Neumorphism新拟态****3D 粘土质感** 的设计风格,具有以下特点:
- ✨ **双阴影效果**:外部阴影 + 内部阴影创造立体浮感
- 🎨 **柔和渐变**:使用 145deg 渐变创造有机感
- 🔄 **圆润边角**:大圆角设计传递柔和友好感
- 💫 **有机动画**:平滑的过渡和按压反馈
- 🌈 **高对比度**:保持可访问性的同时提供视觉吸引力
---
## 📦 组件库
### 核心组件
| 组件 | 文件路径 | 功能 |
|------|---------|------|
| **ClayCard** | `/components/ClayCard.vue` | 粘土风格卡片 |
| **ClayButton** | `/components/ClayButton.vue` | 粘土风格按钮 |
| **ClayInput** | `/components/ClayInput.vue` | 粘土风格输入框 |
### 使用示例
```vue
<template>
<view>
<!-- 卡片组件 -->
<ClayCard size="lg" variant="primary" @tap="handleTap">
<text>这是一张粘土卡片</text>
</ClayCard>
<!-- 按钮组件 -->
<ClayButton
text="确认操作"
variant="primary"
size="lg"
:loading="isLoading"
@tap="handleConfirm"
/>
<!-- 输入框组件 -->
<ClayInput
v-model="searchText"
placeholder="搜索内容..."
prefixIcon="🔍"
:clearable="true"
@confirm="handleSearch"
/>
</view>
</template>
<script>
import { ClayCard, ClayButton, ClayInput } from '@/components/clay-components.js'
export default {
components: { ClayCard, ClayButton, ClayInput },
data() {
return {
searchText: '',
isLoading: false
}
},
methods: {
handleTap() { console.log('卡片被点击') },
async handleConfirm() {
this.isLoading = true
// 执行操作...
},
handleSearch(val) { console.log('搜索:', val) }
}
}
</script>
```
---
## 🎯 组件 Props
### ClayCard
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `size` | String | `'md'` | 尺寸:`sm`, `md`, `lg` |
| `variant` | String | `'default'` | 变体:`default`, `primary`, `gold` |
| `inset` | Boolean | `false` | 是否凹陷效果 |
| `customClass` | String | `''` | 自定义类名 |
| `customStyle` | Object | `{}` | 自定义样式 |
### ClayButton
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `text` | String | `'按钮'` | 按钮文字 |
| `size` | String | `'md'` | 尺寸:`sm`, `md`, `lg` |
| `variant` | String | `'primary'` | 变体:`primary`, `secondary`, `success`, `warning`, `error` |
| `outline` | Boolean | `false` | 是否轮廓样式 |
| `block` | Boolean | `false` | 是否块级按钮 |
| `disabled` | Boolean | `false` | 是否禁用 |
| `loading` | Boolean | `false` | 是否加载中 |
| `icon` | String | `''` | 图标emoji |
| `customStyle` | Object | `{}` | 自定义样式 |
### ClayInput
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `modelValue` | String/Number | `''` | v-model 绑定值 |
| `type` | String | `'text'` | 输入框类型 |
| `placeholder` | String | `'请输入'` | 占位符 |
| `size` | String | `'md'` | 尺寸:`sm`, `md`, `lg` |
| `disabled` | Boolean | `false` | 是否禁用 |
| `error` | Boolean | `false` | 是否错误状态 |
| `errorText` | String | `''` | 错误提示 |
| `clearable` | Boolean | `false` | 是否可清空 |
| `prefixIcon` | String | `''` | 前缀图标emoji |
| `suffixIcon` | String | `''` | 后缀图标emoji |
---
## 🎨 样式变量
`uni.scss` 中定义的核心变量:
```scss
/* Claymorphism 阴影层级 */
$clay-shadow-sm: (...)
$clay-shadow-md: (...)
$clay-shadow-lg: (...)
$clay-shadow-xl: (...)
/* 颜色变量 */
$clay-bg-light: #FAFAFA;
$clay-bg-white: #FFFFFF;
$clay-bg-soft: rgba(255, 255, 255, 0.85);
$clay-border: rgba(255, 255, 255, 0.8);
```
---
## 🔧 已优化的页面
### ✅ 首页 (`pages/index/index.vue`)
- Banner 轮播卡片
- 通知栏
- 玩法分类卡片
- 活动列表项
### ✅ 底部导航栏 (`components/app-tab-bar.vue`)
- 导航栏背景
- 图标容器(圆形徽章)
- 选中状态动画
### ✅ 个人中心 (`pages/mine/index.vue`)
- 用户信息卡片
- 头像凹陷效果
- 统计数据卡片
- 功能图标容器
---
## 🚀 快速开始
### 1. 配置 easycom推荐
`pages.json` 中添加:
```json
{
"easycom": {
"autoscan": true,
"custom": {
"^Clay(A.*)": "@/components/Clay$1.vue"
}
}
}
```
### 2. 直接使用组件
```vue
<template>
<ClayCard size="lg">内容</ClayCard>
<ClayButton text="按钮" variant="primary" />
<ClayInput v-model="value" placeholder="输入..." />
</template>
```
### 3. 使用全局样式类
```vue
<template>
<!-- 粘土卡片 -->
<view class="clay-card">内容</view>
<!-- 粘土按钮 -->
<view class="clay-btn clay-btn-primary clay-btn-md">按钮</view>
<!-- 凹陷效果 -->
<view class="clay-card-inset">凹陷卡片</view>
</template>
```
---
## 🎨 设计原则
### ✅ 推荐做法
1. **保持一致性**:在整个应用中统一使用 Claymorphism 组件
2. **适度使用**:不要过度使用,保持界面呼吸感
3. **注意对比度**:确保文字与背景有足够的对比度
4. **动画平滑**:使用 `cubic-bezier(0.4, 0, 0.2, 1)` 缓动函数
5. **圆角统一**:卡片 24-32rpx按钮 50-60rpx圆角
### ❌ 避免做法
1. **不要混合多种拟态风格**
2. **不要使用过多的彩色渐变**
3. **不要忽略深色模式适配**
4. **不要在小元素上使用粘土效果**
---
## 📱 兼容性
| 平台 | 支持情况 |
|------|----------|
| 微信小程序 | ✅ 完全支持 |
| 抖音小程序 | ✅ 完全支持 |
| H5 | ✅ 完全支持 |
| App | ✅ 完全支持 |
---
## 🔮 后续计划
- [ ] 添加更多组件ClaySwitch、ClaySlider、ClayModal
- [ ] 深色模式适配
- [ ] 动画效果增强
- [ ] 性能优化
---
## 📝 更新日志
### v1.0.0 (2025-02-05)
- ✅ 初始化 Claymorphism 设计系统
- ✅ 创建核心组件ClayCard、ClayButton、ClayInput
- ✅ 优化首页、个人中心、底部导航栏
- ✅ 添加全局样式变量和 Mixins
---
**设计团队**: Z Code AI Studio
**最后更新**: 2025-02-05

View File

@ -448,7 +448,7 @@ export default {
z-index: 1;
}
/* Banner Container (Modern Floating) */
/* Banner Container - Claymorphism Style */
.banner-container {
padding: $spacing-sm $spacing-lg $spacing-xl;
position: relative;
@ -462,17 +462,27 @@ export default {
.banner-card {
height: 100%;
margin: 0 4rpx;
border-radius: 32rpx;
border-radius: 40rpx;
overflow: hidden;
position: relative;
transform: scale(0.96);
transition: all 0.5s $ease-out;
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.08);
/* Claymorphism 双阴影效果 */
box-shadow:
12rpx 12rpx 24rpx rgba(0, 0, 0, 0.08),
-12rpx -12rpx 24rpx rgba(255, 255, 255, 0.7),
inset 4rpx 4rpx 8rpx rgba(255, 255, 255, 0.5),
inset -4rpx -4rpx 8rpx rgba(0, 0, 0, 0.05);
}
.banner-card.active {
transform: scale(1);
box-shadow: $shadow-float;
box-shadow:
16rpx 16rpx 32rpx rgba(255, 107, 0, 0.12),
-16rpx -16rpx 32rpx rgba(255, 255, 255, 0.6),
inset 6rpx 6rpx 12rpx rgba(255, 255, 255, 0.4),
inset -6rpx -6rpx 12rpx rgba(0, 0, 0, 0.06);
}
.banner-image {
@ -541,17 +551,23 @@ export default {
background: $brand-primary;
}
/* Notice Bar V2 (Minimalist) */
/* Notice Bar - Claymorphism Style */
.notice-bar-v2 {
margin: 0 $spacing-lg $spacing-xl;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10rpx);
border-radius: 32rpx;
background: linear-gradient(145deg, #ffffff, #f5f5f5);
border-radius: 40rpx;
padding: 24rpx 32rpx;
display: flex;
align-items: center;
gap: 20rpx;
box-shadow: $shadow-sm;
/* Claymorphism 双阴影 */
box-shadow:
8rpx 8rpx 16rpx rgba(0, 0, 0, 0.06),
-8rpx -8rpx 16rpx rgba(255, 255, 255, 0.8),
inset 2rpx 2rpx 4rpx rgba(255, 255, 255, 0.9),
inset -2rpx -2rpx 4rpx rgba(0, 0, 0, 0.03);
border: 1px solid rgba(255, 255, 255, 0.6);
}
@ -616,19 +632,31 @@ export default {
height: 190rpx;
}
/* 玩法卡片 - Claymorphism Style */
.game-card-large {
flex: 1;
border-radius: $radius-lg;
border-radius: $radius-xl;
position: relative;
overflow: hidden;
padding: 22rpx;
box-shadow: $shadow-card;
transition: transform 0.2s;
/* Claymorphism 阴影 */
box-shadow:
12rpx 12rpx 24rpx rgba(0, 0, 0, 0.1),
-12rpx -12rpx 24rpx rgba(255, 255, 255, 0.6),
inset 4rpx 4rpx 8rpx rgba(255, 255, 255, 0.3),
inset -4rpx -4rpx 8rpx rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.game-card-large:active {
transform: scale(0.98);
transform: scale(0.96);
box-shadow:
6rpx 6rpx 12rpx rgba(0, 0, 0, 0.12),
-6rpx -6rpx 12rpx rgba(255, 255, 255, 0.4),
inset 6rpx 6rpx 12rpx rgba(0, 0, 0, 0.15),
inset -6rpx -6rpx 12rpx rgba(255, 255, 255, 0.2);
}
/* 下排 */
@ -640,20 +668,31 @@ export default {
.game-card-small {
flex: 1;
border-radius: $radius-md;
border-radius: $radius-lg;
position: relative;
overflow: hidden;
padding: 16rpx;
display: flex;
flex-direction: column;
justify-content: center;
box-shadow: $shadow-sm;
background: white;
transition: all 0.2s;
/* Claymorphism 阴影 */
box-shadow:
8rpx 8rpx 16rpx rgba(0, 0, 0, 0.08),
-8rpx -8rpx 16rpx rgba(255, 255, 255, 0.7),
inset 2rpx 2rpx 4rpx rgba(255, 255, 255, 0.5),
inset -2rpx -2rpx 4rpx rgba(0, 0, 0, 0.05);
background: linear-gradient(145deg, #ffffff, #f8f8f8);
border: 1px solid rgba(255, 255, 255, 0.6);
}
.game-card-small:active {
transform: scale(0.96);
box-shadow: none;
transform: scale(0.94);
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);
}
/* 内容样式 - 大卡片 */
@ -748,7 +787,7 @@ export default {
.card-more .card-title-small { color: $text-sub; }
/* 推荐活动列表 */
/* 推荐活动列表 - Claymorphism Style */
.activity-section {
padding: 0 $spacing-lg;
animation: fadeInUp 0.6s ease-out 0.3s backwards;
@ -763,20 +802,29 @@ export default {
}
.activity-item {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10rpx);
background: linear-gradient(145deg, #ffffff, #f8f8f8);
border-radius: $radius-xl;
overflow: hidden;
box-shadow: $shadow-sm;
display: flex;
flex-direction: column;
transition: all 0.3s ease;
/* Claymorphism 阴影 */
box-shadow:
10rpx 10rpx 20rpx rgba(0, 0, 0, 0.06),
-10rpx -10rpx 20rpx rgba(255, 255, 255, 0.7),
inset 3rpx 3rpx 6rpx rgba(255, 255, 255, 0.8),
inset -3rpx -3rpx 6rpx rgba(0, 0, 0, 0.03);
border: 1px solid rgba(255, 255, 255, 0.6);
}
.activity-item:active {
transform: translateY(4rpx);
box-shadow: none;
transform: translateY(4rpx) scale(0.98);
box-shadow:
5rpx 5rpx 10rpx rgba(0, 0, 0, 0.08),
-5rpx -5rpx 10rpx rgba(255, 255, 255, 0.5),
inset 5rpx 5rpx 10rpx rgba(0, 0, 0, 0.05),
inset -5rpx -5rpx 10rpx rgba(255, 255, 255, 0.3);
}
.activity-thumb-box {

View File

@ -1533,12 +1533,16 @@ export default {
50% { transform: translate(30rpx, 50rpx); }
}
/* 通用毛玻璃卡片 */
/* 通用毛玻璃卡片 - Claymorphism */
.glass-card {
background: $bg-glass;
background: linear-gradient(145deg, #ffffff, #f8f8f8);
backdrop-filter: blur(20rpx);
border: 1px solid rgba(255, 255, 255, 0.6);
box-shadow: $shadow-card;
box-shadow:
10rpx 10rpx 20rpx rgba(0, 0, 0, 0.06),
-10rpx -10rpx 20rpx rgba(255, 255, 255, 0.7),
inset 3rpx 3rpx 6rpx rgba(255, 255, 255, 0.8),
inset -3rpx -3rpx 6rpx rgba(0, 0, 0, 0.03);
border-radius: $radius-lg;
position: relative;
z-index: 1;
@ -1553,9 +1557,14 @@ export default {
.user-info-card {
padding: $spacing-xl;
background: linear-gradient(135deg, rgba(255,255,255,0.95), rgba(255,255,255,0.8));
box-shadow: $shadow-float;
border: 1px solid rgba(255,255,255,0.8);
background: linear-gradient(145deg, #ffffff, #f8f8f8);
box-shadow:
12rpx 12rpx 24rpx rgba(0, 0, 0, 0.06),
-12rpx -12rpx 24rpx rgba(255, 255, 255, 0.7),
inset 4rpx 4rpx 8rpx rgba(255, 255, 255, 0.85),
inset -4rpx -4rpx 8rpx rgba(0, 0, 0, 0.03);
border: 1px solid rgba(255, 255, 255, 0.6);
border-radius: $radius-xl;
}
.user-main {
@ -1569,7 +1578,11 @@ export default {
background: $bg-card;
border-radius: 50%;
border: 4rpx solid $bg-card;
box-shadow: $shadow-sm;
/* Claymorphism 凹陷效果 */
box-shadow:
inset 4rpx 4rpx 8rpx rgba(0, 0, 0, 0.1),
inset -4rpx -4rpx 8rpx rgba(255, 255, 255, 0.9);
margin-right: 24rpx;
}
@ -1588,13 +1601,19 @@ export default {
right: 0;
width: 40rpx;
height: 40rpx;
background: linear-gradient(135deg, $brand-primary, $brand-secondary);
background: linear-gradient(145deg, #FF9500, #FF6B00);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 3rpx solid $bg-card;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
/* Claymorphism 凸起效果 */
box-shadow:
4rpx 4rpx 8rpx rgba(255, 107, 0, 0.2),
-4rpx -4rpx 8rpx rgba(255, 255, 255, 0.7),
inset 2rpx 2rpx 4rpx rgba(255, 255, 255, 0.4),
inset -2rpx -2rpx 4rpx rgba(0, 0, 0, 0.1);
.edit-icon {
font-size: 20rpx;
@ -1750,10 +1769,18 @@ export default {
transform: rotate(-15deg);
}
/* 常用功能 & 订单 */
/* 常用功能 & 订单 - Claymorphism */
.section-card {
margin: 0 $spacing-lg $spacing-lg;
padding: 30rpx;
background: linear-gradient(145deg, #ffffff, #f8f8f8);
box-shadow:
10rpx 10rpx 20rpx rgba(0, 0, 0, 0.05),
-10rpx -10rpx 20rpx rgba(255, 255, 255, 0.7),
inset 3rpx 3rpx 6rpx rgba(255, 255, 255, 0.8),
inset -3rpx -3rpx 6rpx rgba(0, 0, 0, 0.03);
border: 1px solid rgba(255, 255, 255, 0.6);
border-radius: $radius-lg;
}
.section-header {
display: flex; justify-content: space-between; align-items: center;
@ -1774,14 +1801,22 @@ export default {
}
.icon-wrapper, .menu-icon-box {
width: 80rpx; height: 80rpx;
background: $bg-secondary;
background: linear-gradient(145deg, #f8f8f8, #e8e8e8);
border-radius: 24rpx;
display: flex; align-items: center; justify-content: center;
margin-bottom: 12rpx;
transition: all 0.3s;
/* Claymorphism 凹陷效果 */
box-shadow:
inset 3rpx 3rpx 6rpx rgba(0, 0, 0, 0.06),
inset -3rpx -3rpx 6rpx rgba(255, 255, 255, 0.9);
}
.grid-item:active .icon-wrapper, .menu-item:active .menu-icon-box {
background: $uni-bg-color-hover;
background: linear-gradient(145deg, #e8e8e8, #d8d8d8);
box-shadow:
inset 4rpx 4rpx 8rpx rgba(0, 0, 0, 0.1),
inset -4rpx -4rpx 8rpx rgba(255, 255, 255, 0.7);
}
.grid-icon-img, .menu-icon-img { width: 44rpx; height: 44rpx; }
.grid-label, .menu-label { font-size: $font-sm; color: $text-main; }

173
uni.scss
View File

@ -185,6 +185,44 @@ $uni-font-size-paragraph: 15px;
-webkit-box-orient: vertical;
}
/* ============================================
💎 Claymorphism 设计系统 (2025)
粘土拟态风格 - 柔和圆润立体浮感
============================================ */
/* 🎨 Claymorphism 核心阴影 */
$clay-shadow-sm: (
6rpx 6rpx 12rpx rgba(0, 0, 0, 0.04),
-6rpx -6rpx 12rpx rgba(255, 255, 255, 0.8),
inset 2rpx 2rpx 4rpx rgba(255, 255, 255, 0.9),
inset -2rpx -2rpx 4rpx rgba(0, 0, 0, 0.03)
);
$clay-shadow-md: (
12rpx 12rpx 24rpx rgba(0, 0, 0, 0.06),
-12rpx -12rpx 24rpx rgba(255, 255, 255, 0.7),
inset 4rpx 4rpx 8rpx rgba(255, 255, 255, 0.85),
inset -4rpx -4rpx 8rpx rgba(0, 0, 0, 0.04)
);
$clay-shadow-lg: (
20rpx 20rpx 40rpx rgba(0, 0, 0, 0.08),
-20rpx -20rpx 40rpx rgba(255, 255, 255, 0.6),
inset 6rpx 6rpx 12rpx rgba(255, 255, 255, 0.8),
inset -6rpx -6rpx 12rpx rgba(0, 0, 0, 0.05)
);
$clay-shadow-xl: (
28rpx 28rpx 56rpx rgba(0, 0, 0, 0.1),
-28rpx -28rpx 56rpx rgba(255, 255, 255, 0.5),
inset 8rpx 8rpx 16rpx rgba(255, 255, 255, 0.75),
inset -8rpx -8rpx 16rpx rgba(0, 0, 0, 0.06)
);
/* Claymorphism 颜色变量 */
$clay-bg-light: #FAFAFA;
$clay-bg-white: #FFFFFF;
$clay-bg-soft: rgba(255, 255, 255, 0.85);
$clay-border: rgba(255, 255, 255, 0.8);
$clay-border-dark: rgba(0, 0, 0, 0.04);
/* ============================================
💎 核心公共 UI (Premium UI 6.0)
============================================ */
@ -240,6 +278,141 @@ $uni-font-size-paragraph: 15px;
z-index: 1;
}
/* ============================================
🟤 Claymorphism 卡片组件
============================================ */
/* 基础粘土卡片 */
.clay-card {
background: linear-gradient(145deg, #ffffff, #f0f0f0);
border-radius: $radius-lg;
position: relative;
transition: all $transition-normal $ease-out;
/* 外部双阴影 - 创造凸起效果 */
box-shadow:
8rpx 8rpx 16rpx rgba(0, 0, 0, 0.06),
-8rpx -8rpx 16rpx rgba(255, 255, 255, 0.8),
inset 2rpx 2rpx 4rpx rgba(255, 255, 255, 0.9),
inset -2rpx -2rpx 4rpx rgba(0, 0, 0, 0.03);
&.clay-card-sm {
border-radius: $radius-md;
box-shadow:
6rpx 6rpx 12rpx rgba(0, 0, 0, 0.04),
-6rpx -6rpx 12rpx rgba(255, 255, 255, 0.8),
inset 2rpx 2rpx 4rpx rgba(255, 255, 255, 0.9),
inset -2rpx -2rpx 4rpx rgba(0, 0, 0, 0.03);
}
&.clay-card-lg {
border-radius: $radius-xl;
box-shadow:
12rpx 12rpx 24rpx rgba(0, 0, 0, 0.08),
-12rpx -12rpx 24rpx rgba(255, 255, 255, 0.7),
inset 4rpx 4rpx 8rpx rgba(255, 255, 255, 0.85),
inset -4rpx -4rpx 8rpx rgba(0, 0, 0, 0.04);
}
&:active {
transform: scale(0.98);
box-shadow:
4rpx 4rpx 8rpx rgba(0, 0, 0, 0.06),
-4rpx -4rpx 8rpx rgba(255, 255, 255, 0.6),
inset 4rpx 4rpx 8rpx rgba(0, 0, 0, 0.05),
inset -4rpx -4rpx 8rpx rgba(255, 255, 255, 0.4);
}
}
/* 彩色粘土卡片 */
.clay-card-primary {
background: linear-gradient(145deg, $brand-primary-light, $brand-primary);
color: #fff;
box-shadow:
10rpx 10rpx 20rpx rgba(255, 107, 0, 0.2),
-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-card-gold {
background: linear-gradient(145deg, #FFD60A, #FF9F0A);
box-shadow:
10rpx 10rpx 20rpx rgba(255, 159, 10, 0.2),
-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);
}
}
/* 凹陷粘土卡片 (Inset) */
.clay-card-inset {
background: linear-gradient(145deg, #e8e8e8, #f8f8f8);
box-shadow:
inset 6rpx 6rpx 12rpx rgba(0, 0, 0, 0.08),
inset -6rpx -6rpx 12rpx rgba(255, 255, 255, 0.9);
}
/* ============================================
🔘 Claymorphism 按钮组件
============================================ */
.clay-btn {
border-radius: $radius-round;
font-weight: 700;
position: relative;
transition: all $transition-normal $ease-out;
border: none;
/* 外部双阴影 - 创造凸起感 */
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-primary {
background: linear-gradient(145deg, $brand-primary-light, $brand-primary);
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: $text-main;
}
&.clay-btn-sm {
padding: 12rpx 32rpx;
font-size: $font-sm;
border-radius: 40rpx;
}
&.clay-btn-md {
padding: 20rpx 48rpx;
font-size: $font-md;
border-radius: 50rpx;
}
&.clay-btn-lg {
padding: 28rpx 64rpx;
font-size: $font-lg;
border-radius: 60rpx;
}
&: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);
}
}
/* 3. 通用功能按钮 */
.btn-primary {
background: $gradient-brand;