feat:重构按钮和icon组件
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# ui 目录用于放UI类组件
|
||||
@@ -0,0 +1,141 @@
|
||||
# Toy Button 组件
|
||||
|
||||
基于涂鸦丫设计规范的按钮组件,当前视觉参考 `stitch-ui/letterTracing/code.html` 中底部“分享 / 保存到相册”按钮,内部不依赖 Vant。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 多种按钮类型:primary、secondary、green、white
|
||||
- ✅ 丰富的状态:正常、禁用、加载中
|
||||
- ✅ 扁平化模式支持
|
||||
- ✅ 图标支持
|
||||
- ✅ 自定义尺寸
|
||||
- ✅ 参考设计稿的缩放点击动效
|
||||
- ✅ 与 doodle-ui 样式完全一致
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基础用法
|
||||
|
||||
```xml
|
||||
<!-- 主要按钮 -->
|
||||
<toy-button type="primary" bind:click="onClick">主要按钮</toy-button>
|
||||
|
||||
<!-- 次要按钮 -->
|
||||
<toy-button type="secondary" bind:click="onClick">次要按钮</toy-button>
|
||||
|
||||
<!-- 绿色按钮 -->
|
||||
<toy-button type="green" bind:click="onClick">绿色按钮</toy-button>
|
||||
|
||||
<!-- 白色按钮 -->
|
||||
<toy-button type="white" bind:click="onClick">白色按钮</toy-button>
|
||||
```
|
||||
|
||||
### 按钮状态
|
||||
|
||||
```xml
|
||||
<!-- 禁用状态 -->
|
||||
<toy-button type="primary" disabled>禁用按钮</toy-button>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<toy-button type="primary" loading>加载中</toy-button>
|
||||
|
||||
<!-- 扁平化模式 -->
|
||||
<toy-button type="primary" flat>扁平按钮</toy-button>
|
||||
```
|
||||
|
||||
### 带图标按钮
|
||||
|
||||
```xml
|
||||
<!-- 带图标 -->
|
||||
<toy-button type="primary" icon="add">添加</toy-button>
|
||||
|
||||
<!-- 自定义图标颜色 -->
|
||||
<toy-button type="white" icon="edit" icon-color="#FFD719">编辑</toy-button>
|
||||
```
|
||||
|
||||
### 自定义尺寸
|
||||
|
||||
```xml
|
||||
<!-- 自定义宽度 -->
|
||||
<toy-button type="primary" width="300rpx">自定义宽度</toy-button>
|
||||
|
||||
<!-- 自定义高度 -->
|
||||
<toy-button type="primary" height="100rpx">自定义高度</toy-button>
|
||||
```
|
||||
|
||||
## 属性说明
|
||||
|
||||
| 属性 | 类型 | 默认值 | 说明 |
|
||||
| --------------- | ------- | ---------- | ------------------------------------------ |
|
||||
| type | String | 'default' | 按钮类型:primary、secondary、green、white |
|
||||
| disabled | Boolean | false | 是否禁用 |
|
||||
| loading | Boolean | false | 是否显示加载状态 |
|
||||
| flat | Boolean | false | 是否扁平化模式 |
|
||||
| plain | Boolean | false | 是否朴素模式(仅white类型有效) |
|
||||
| width | String | '' | 按钮宽度 |
|
||||
| height | String | '' | 按钮高度 |
|
||||
| icon | String | '' | 图标名称 |
|
||||
| iconColor | String | '#ffffff' | 图标颜色 |
|
||||
| iconSize | String | '44rpx' | 图标大小 |
|
||||
| iconClassPrefix | String | 'toy-icon' | 图标类名前缀 |
|
||||
|
||||
## 事件说明
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
| ------ | -------------- | -------- |
|
||||
| click | 点击按钮时触发 | event |
|
||||
|
||||
## 样式说明
|
||||
|
||||
### 按钮类型
|
||||
|
||||
- **primary**: 黄色背景 (#FFD700),深色文字 (#6c5a00)
|
||||
- **secondary**: 绿色背景 (#9CD343),白色文字
|
||||
- **green**: 绿色背景 (#9CD343),白色文字(与secondary相同)
|
||||
- **white**: 米白背景,深色文字,浅边框
|
||||
|
||||
### 点击动效
|
||||
|
||||
- 所有按钮都支持 `scale(0.95)` 的点击缩放效果
|
||||
- `flat` 模式下更接近底部操作栏按钮视觉
|
||||
- 动效持续时间约为 180ms
|
||||
|
||||
### 禁用状态
|
||||
|
||||
- 背景色变为浅灰色 (#F5F5F5)
|
||||
- 文字颜色变为浅灰色 (#CCCCCC)
|
||||
- 移除阴影效果
|
||||
|
||||
## 设计规范
|
||||
|
||||
本组件完全遵循 doodle-ui 的设计规范:
|
||||
|
||||
- 圆角:24rpx (对应 doodle-ui 的 12px)
|
||||
- 字体大小:48rpx (对应 doodle-ui 的 24px)
|
||||
- 字体粗细:600
|
||||
- 内边距:0 48rpx
|
||||
- 最小宽度:240rpx
|
||||
- 过渡动画:0.15s ease-in-out
|
||||
- 点击动效:抖动缩放动画
|
||||
|
||||
## 测试页面
|
||||
|
||||
可以通过访问 `pages/buttonTest/buttonTest` 页面查看所有按钮类型的效果。
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v2.1.0
|
||||
|
||||
- 修复圆角大小,与 doodle-ui 保持一致 (24rpx)
|
||||
- 修复字体大小,与 doodle-ui 保持一致 (48rpx)
|
||||
- 修复点击抖动效果,移除 wxs 中的 setTimeout
|
||||
- 优化按钮动画,使用纯 CSS 实现抖动效果
|
||||
- 改进所有按钮类型的点击反馈
|
||||
|
||||
### v2.0.0
|
||||
|
||||
- 完全重构样式,与 doodle-ui 保持一致
|
||||
- 添加 secondary 类型支持
|
||||
- 优化点击动效
|
||||
- 改进扁平化模式
|
||||
- 统一设计规范
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"toy-icon": "/toy/icon/icon"
|
||||
},
|
||||
"styleIsolation": "apply-shared",
|
||||
"addGlobalClass": true
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
@button-default-height: 92rpx;
|
||||
@button-border-radius: 32rpx;
|
||||
@button-default-font-size: 30rpx;
|
||||
@button-default-color: #fff;
|
||||
@button-transition: 0.18s ease;
|
||||
@button-shadow-primary: 0 8rpx 20rpx rgba(255, 215, 0, 0.22);
|
||||
@button-shadow-green: 0 8rpx 20rpx rgba(156, 211, 67, 0.22);
|
||||
@button-shadow-white: 0 6rpx 16rpx rgba(50, 46, 37, 0.08);
|
||||
@button-shadow-pressed: 0 4rpx 10rpx rgba(50, 46, 37, 0.1);
|
||||
@button-disabled-bg: #ece6da;
|
||||
@button-disabled-color: #b8b2a6;
|
||||
|
||||
wx-button:not([size=mini]) {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.toy-button {
|
||||
--button-bg: #f0e7d6;
|
||||
--button-color: @button-default-color;
|
||||
--button-border-color: transparent;
|
||||
--button-shadow: none;
|
||||
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 180rpx;
|
||||
height: @button-default-height;
|
||||
line-height: 1;
|
||||
padding: 0 28rpx;
|
||||
border-radius: @button-border-radius;
|
||||
border: 2rpx solid var(--button-border-color);
|
||||
background: var(--button-bg);
|
||||
color: var(--button-color);
|
||||
box-shadow: var(--button-shadow);
|
||||
font-size: @button-default-font-size;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5rpx;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform @button-transition,
|
||||
box-shadow @button-transition,
|
||||
background-color @button-transition,
|
||||
border-color @button-transition,
|
||||
color @button-transition,
|
||||
opacity @button-transition;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&--default {
|
||||
--button-bg: #f0e7d6;
|
||||
--button-color: #322e25;
|
||||
--button-border-color: #e5dcc9;
|
||||
--button-shadow: @button-shadow-white;
|
||||
}
|
||||
|
||||
&--large {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&--small {
|
||||
min-width: 136rpx;
|
||||
height: 72rpx;
|
||||
padding: 0 20rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
&--mini {
|
||||
min-width: 96rpx;
|
||||
height: 56rpx;
|
||||
padding: 0 16rpx;
|
||||
border-radius: 18rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
--button-bg: #ffd700;
|
||||
--button-color: #6c5a00;
|
||||
--button-shadow: @button-shadow-primary;
|
||||
}
|
||||
|
||||
&--green,
|
||||
&--secondary {
|
||||
--button-bg: #9cd343;
|
||||
--button-color: #ffffff;
|
||||
--button-shadow: @button-shadow-green;
|
||||
}
|
||||
|
||||
&--white {
|
||||
--button-bg: rgba(255, 255, 255, 0.96);
|
||||
--button-color: #322e25;
|
||||
--button-border-color: #ebe3d2;
|
||||
--button-shadow: @button-shadow-white;
|
||||
}
|
||||
|
||||
&.plain.toy-button--white {
|
||||
--button-bg: transparent;
|
||||
--button-color: #ffffff;
|
||||
--button-border-color: rgba(255, 255, 255, 0.92);
|
||||
--button-shadow: none;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
--button-bg: @button-disabled-bg !important;
|
||||
--button-color: @button-disabled-color !important;
|
||||
--button-border-color: transparent !important;
|
||||
--button-shadow: none !important;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.loading {
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
&.active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: @button-shadow-pressed;
|
||||
}
|
||||
|
||||
&--flat {
|
||||
min-width: 0;
|
||||
padding: 0 24rpx;
|
||||
border-width: 0;
|
||||
border-radius: 32rpx;
|
||||
|
||||
&.toy-button--green,
|
||||
&.toy-button--secondary {
|
||||
--button-bg: #9cd343;
|
||||
--button-color: #ffffff;
|
||||
--button-shadow: 0 4rpx 12rpx rgba(50, 46, 37, 0.08);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&.toy-button--primary {
|
||||
--button-bg: #ffd700;
|
||||
--button-color: #6c5a00;
|
||||
--button-shadow: 0 8rpx 18rpx rgba(108, 90, 0, 0.12);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
&.toy-button--white {
|
||||
--button-bg: rgba(255, 255, 255, 0.96);
|
||||
--button-color: #322e25;
|
||||
--button-shadow: 0 4rpx 12rpx rgba(50, 46, 37, 0.08);
|
||||
}
|
||||
|
||||
&.toy-button--white.plain {
|
||||
--button-bg: transparent;
|
||||
--button-color: #ffffff;
|
||||
--button-shadow: none;
|
||||
border-width: 2rpx;
|
||||
}
|
||||
|
||||
&.active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
--button-bg: #e6e0d4 !important;
|
||||
--button-color: #b8b2a6 !important;
|
||||
--button-shadow: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-class,
|
||||
&__icon {
|
||||
margin-right: 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__spinner {
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
border-width: 4rpx;
|
||||
border-style: solid;
|
||||
animation: toy-button-spin 0.8s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes toy-button-spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
Component({
|
||||
options: {
|
||||
styleIsolation: 'apply-shared',
|
||||
addGlobalClass: true,
|
||||
},
|
||||
properties: {
|
||||
type: {
|
||||
type: String,
|
||||
value: 'default',
|
||||
},
|
||||
openType: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
plain: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
value: 'normal',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
flat: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
style: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
loadingType: {
|
||||
type: String,
|
||||
value: 'circular',
|
||||
},
|
||||
loadingSize: {
|
||||
type: String,
|
||||
value: '20px',
|
||||
},
|
||||
loadingColor: {
|
||||
type: String,
|
||||
value: '#ffffff',
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
iconClassPrefix: {
|
||||
type: String,
|
||||
value: 'toy-icon',
|
||||
},
|
||||
iconSize: {
|
||||
type: String,
|
||||
value: '44rpx',
|
||||
},
|
||||
iconColor: {
|
||||
type: String,
|
||||
value: '#ffffff',
|
||||
},
|
||||
sessionFrom: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
sendMessageTitle: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
sendMessagePath: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
sendMessageImg: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
showMessageCard: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
appParameter: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
ariaLabel: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
},
|
||||
data: {
|
||||
pressed: false,
|
||||
rootClassName: '',
|
||||
rootStyle: '',
|
||||
resolvedIconColor: '#ffffff',
|
||||
resolvedLoadingColor: '#ffffff',
|
||||
resolvedIconSize: '44rpx',
|
||||
resolvedLoadingSize: '20px',
|
||||
},
|
||||
observers: {
|
||||
'type,plain,size,disabled,flat,loading,style,width,height,iconColor,iconSize,loadingColor,loadingSize,pressed':
|
||||
function () {
|
||||
this.syncViewState();
|
||||
},
|
||||
},
|
||||
lifetimes: {
|
||||
attached() {
|
||||
this.syncViewState();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
syncViewState() {
|
||||
const {
|
||||
type,
|
||||
plain,
|
||||
size,
|
||||
disabled,
|
||||
flat,
|
||||
loading,
|
||||
style,
|
||||
width,
|
||||
height,
|
||||
iconColor,
|
||||
iconSize,
|
||||
loadingColor,
|
||||
loadingSize,
|
||||
pressed,
|
||||
} = this.data;
|
||||
|
||||
const classNames = ['toy-button', `toy-button--${type}`, `toy-button--${size}`];
|
||||
if (flat) {
|
||||
classNames.push('toy-button--flat');
|
||||
}
|
||||
if (plain) {
|
||||
classNames.push('plain');
|
||||
}
|
||||
if (disabled) {
|
||||
classNames.push('disabled');
|
||||
}
|
||||
if (loading) {
|
||||
classNames.push('loading');
|
||||
}
|
||||
if (pressed && !disabled && !loading) {
|
||||
classNames.push('active');
|
||||
}
|
||||
|
||||
const styleSegments = [style];
|
||||
const normalizedWidth = this.normalizeUnit(width);
|
||||
const normalizedHeight = this.normalizeUnit(height);
|
||||
if (normalizedWidth) {
|
||||
styleSegments.push(`width:${normalizedWidth}`);
|
||||
}
|
||||
if (normalizedHeight) {
|
||||
styleSegments.push(`height:${normalizedHeight}`);
|
||||
}
|
||||
|
||||
const resolvedIconColor = this.getResolvedIconColor(
|
||||
iconColor,
|
||||
type,
|
||||
plain,
|
||||
disabled,
|
||||
);
|
||||
const resolvedLoadingColor = loadingColor || resolvedIconColor;
|
||||
|
||||
this.setData({
|
||||
rootClassName: classNames.join(' '),
|
||||
rootStyle: styleSegments.filter(Boolean).join(';'),
|
||||
resolvedIconColor,
|
||||
resolvedLoadingColor,
|
||||
resolvedIconSize: this.normalizeUnit(iconSize) || '44rpx',
|
||||
resolvedLoadingSize: this.normalizeUnit(loadingSize) || '20px',
|
||||
});
|
||||
},
|
||||
normalizeUnit(value: string) {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
return /^\d+(\.\d+)?$/.test(value) ? `${value}rpx` : value;
|
||||
},
|
||||
getResolvedIconColor(
|
||||
iconColor: string,
|
||||
type: string,
|
||||
plain: boolean,
|
||||
disabled: boolean,
|
||||
) {
|
||||
if (disabled) {
|
||||
return '#b8b2a6';
|
||||
}
|
||||
if (type === 'primary') {
|
||||
return '#6c5a00';
|
||||
}
|
||||
if (type === 'white' && !plain) {
|
||||
return '#322e25';
|
||||
}
|
||||
return iconColor || '#ffffff';
|
||||
},
|
||||
onTouchStart() {
|
||||
if (this.data.disabled || this.data.loading) {
|
||||
return;
|
||||
}
|
||||
this.setData({ pressed: true });
|
||||
},
|
||||
onTouchEnd() {
|
||||
if (!this.data.pressed) {
|
||||
return;
|
||||
}
|
||||
this.setData({ pressed: false });
|
||||
},
|
||||
onTouchCancel() {
|
||||
if (!this.data.pressed) {
|
||||
return;
|
||||
}
|
||||
this.setData({ pressed: false });
|
||||
},
|
||||
onClick(e: WechatMiniprogram.TouchEvent): void {
|
||||
this.triggerEvent('click', e);
|
||||
},
|
||||
onGetUserInfo(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('getuserinfo', e.detail);
|
||||
},
|
||||
onContact(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('contact', e.detail);
|
||||
},
|
||||
onGetPhoneNumber(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('getphonenumber', e.detail);
|
||||
},
|
||||
onGetRealTimePhoneNumber(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('getrealtimephonenumber', e.detail);
|
||||
},
|
||||
onAgreePrivacyAuthorization(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('agreeprivacyauthorization', e.detail);
|
||||
},
|
||||
onError(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('error', e.detail);
|
||||
},
|
||||
onLaunchApp(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('launchapp', e.detail);
|
||||
},
|
||||
onOpenSetting(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('opensetting', e.detail);
|
||||
},
|
||||
onChooseAvatar(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('chooseavatar', e.detail);
|
||||
},
|
||||
},
|
||||
externalClasses: ['custom-class'],
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
<button
|
||||
data-id="test-toy-button-id"
|
||||
class="{{rootClassName}} custom-class"
|
||||
style="{{rootStyle}}"
|
||||
session-from="{{ sessionFrom }}"
|
||||
open-type="{{ disabled || loading ? '' : openType }}"
|
||||
send-message-title="{{ sendMessageTitle }}"
|
||||
send-message-path="{{ sendMessagePath }}"
|
||||
send-message-img="{{ sendMessageImg }}"
|
||||
show-message-card="{{ showMessageCard }}"
|
||||
app-parameter="{{ appParameter }}"
|
||||
aria-label="{{ ariaLabel }}"
|
||||
bindtouchstart="onTouchStart"
|
||||
bindtouchend="onTouchEnd"
|
||||
bindtouchcancel="onTouchCancel"
|
||||
bind:tap="{{ disabled || loading ? '' : 'onClick'}}"
|
||||
bindgetuserinfo="onGetUserInfo"
|
||||
bindcontact="onContact"
|
||||
bindgetphonenumber="onGetPhoneNumber"
|
||||
bindgetrealtimephonenumber="onGetRealTimePhoneNumber"
|
||||
bindagreeprivacyauthorization="onAgreePrivacyAuthorization"
|
||||
binderror="onError"
|
||||
bindlaunchapp="onLaunchApp"
|
||||
bindopensetting="onOpenSetting"
|
||||
bindchooseavatar="onChooseAvatar">
|
||||
<block wx:if="{{ loading }}">
|
||||
<view
|
||||
class="loading-class toy-button__spinner toy-button__spinner--{{loadingType}}"
|
||||
style="width:{{resolvedLoadingSize}};height:{{resolvedLoadingSize}};border-color:transparent;border-top-color:{{resolvedLoadingColor}};border-right-color:{{resolvedLoadingColor}};"></view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<toy-icon
|
||||
wx:if="{{ icon }}"
|
||||
name="{{ icon }}"
|
||||
size="{{ resolvedIconSize }}"
|
||||
color="{{ resolvedIconColor }}"
|
||||
class-prefix="{{ iconClassPrefix }}"
|
||||
custom-class="toy-button__icon" />
|
||||
</block>
|
||||
<slot></slot>
|
||||
</button>
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"toy-icon": "/toy/icon/icon"
|
||||
},
|
||||
"styleIsolation": "apply-shared",
|
||||
"addGlobalClass": true
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
@import '../../style/theme.less';
|
||||
|
||||
/* ── design tokens(严格对齐 stitch-ui/buttonUi/code.html 色谱) ── */
|
||||
|
||||
/* primary 色系 */
|
||||
@btn-primary-solid: #ffd709; /* primary-container / primary-fixed */
|
||||
@btn-primary-gradient-from: #ffd709; /* primary-fixed */
|
||||
@btn-primary-gradient-to: #efc900; /* primary-fixed-dim */
|
||||
@btn-primary-text: #5b4b00; /* on-primary-container */
|
||||
@btn-primary-pressed-bg: #5e4e00; /* primary-dim */
|
||||
@btn-primary-pressed-text: #fff2cd; /* on-primary */
|
||||
@btn-primary-ring: rgba(108, 90, 0, 0.10);
|
||||
|
||||
/* secondary 色系 */
|
||||
@btn-secondary-bg: #9cd343; /* tertiary-container / tertiary-fixed */
|
||||
@btn-secondary-text-on-tint: #5a8c1a;
|
||||
|
||||
/* error 色系 */
|
||||
@btn-error-bg: #f95630; /* error-container */
|
||||
@btn-error-text: #520c00; /* on-error-container */
|
||||
|
||||
/* surface 参考 */
|
||||
@btn-surface-variant: #e5dcc9;
|
||||
@btn-surface-container-high: #eae2d0;
|
||||
@btn-surface-container-highest: #e5dcc9;
|
||||
@btn-outline-variant-20: rgba(179, 172, 159, 0.20);
|
||||
|
||||
/* 阴影 */
|
||||
@btn-shadow-primary: 0 8rpx 24rpx rgba(108, 90, 0, 0.15);
|
||||
@btn-shadow-primary-lg: 0 8rpx 24rpx rgba(255, 215, 0, 0.3);
|
||||
@btn-shadow-secondary: 0 8rpx 24rpx rgba(156, 211, 67, 0.18);
|
||||
@btn-shadow-error: 0 8rpx 24rpx rgba(249, 86, 48, 0.18);
|
||||
@btn-shadow-default: @shadow;
|
||||
@btn-shadow-pressed: none;
|
||||
|
||||
/* 圆角 */
|
||||
@btn-radius: @radius-lg;
|
||||
@btn-radius-sm: 20rpx;
|
||||
@btn-radius-mini: 18rpx;
|
||||
|
||||
/* 过渡 */
|
||||
@btn-transition: 0.18s ease;
|
||||
|
||||
/* 禁用 */
|
||||
@btn-disabled-bg: @bg-gray;
|
||||
@btn-disabled-color: @text-disable;
|
||||
|
||||
/* ── reset native button chrome ── */
|
||||
wx-button:not([size=mini]) {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* ── base ── */
|
||||
.toy-btn {
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10rpx;
|
||||
min-width: 180rpx;
|
||||
height: 92rpx;
|
||||
padding: 0 32rpx;
|
||||
border-radius: @btn-radius;
|
||||
border: none;
|
||||
background: @bg-gray;
|
||||
color: @text-title;
|
||||
box-shadow: @btn-shadow-default;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5rpx;
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
transition:
|
||||
transform @btn-transition,
|
||||
box-shadow @btn-transition,
|
||||
background @btn-transition,
|
||||
color @btn-transition,
|
||||
opacity @btn-transition;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* ═══════ type variants ═══════ */
|
||||
|
||||
/* 默认:surface-container 底 + on-surface 字 */
|
||||
&--default {
|
||||
background: @bg-gray;
|
||||
color: @text-title;
|
||||
box-shadow: @btn-shadow-default;
|
||||
}
|
||||
|
||||
/*
|
||||
* 主色:中号/小号/mini 用纯色 primary-container (#ffd709)
|
||||
* 大号单独覆盖为渐变(见 &--large 内)
|
||||
*/
|
||||
&--primary {
|
||||
background: @btn-primary-solid;
|
||||
color: @btn-primary-text;
|
||||
box-shadow: @btn-shadow-primary;
|
||||
}
|
||||
|
||||
/* 次级(语义名;视觉上目前为绿色) */
|
||||
&--secondary {
|
||||
background: @btn-secondary-bg;
|
||||
color: @text-white;
|
||||
box-shadow: @btn-shadow-secondary;
|
||||
}
|
||||
|
||||
/* @deprecated 向后兼容,等同 secondary */
|
||||
&--green {
|
||||
background: @btn-secondary-bg;
|
||||
color: @text-white;
|
||||
box-shadow: @btn-shadow-secondary;
|
||||
}
|
||||
|
||||
/* 危险操作:error-container 底 + on-error-container 深字 */
|
||||
&--error {
|
||||
background: @btn-error-bg;
|
||||
color: @btn-error-text;
|
||||
box-shadow: @btn-shadow-error;
|
||||
}
|
||||
|
||||
/* 白色 / 浅底 */
|
||||
&--white {
|
||||
background: @bg-white;
|
||||
color: @text-title;
|
||||
box-shadow: @btn-shadow-default;
|
||||
}
|
||||
|
||||
/* ═══════ size variants ═══════ */
|
||||
|
||||
/* 大号 / 全宽:primary 此时启用渐变 + 更强阴影 */
|
||||
&--large {
|
||||
width: 100%;
|
||||
height: 100rpx;
|
||||
padding: 0 40rpx;
|
||||
font-size: 34rpx;
|
||||
|
||||
&.toy-btn--primary {
|
||||
background: linear-gradient(135deg, @btn-primary-gradient-from, @btn-primary-gradient-to);
|
||||
box-shadow: @btn-shadow-primary-lg;
|
||||
}
|
||||
}
|
||||
|
||||
&--normal {
|
||||
min-width: 180rpx;
|
||||
}
|
||||
|
||||
&--small {
|
||||
min-width: 136rpx;
|
||||
height: 72rpx;
|
||||
padding: 0 24rpx;
|
||||
border-radius: @btn-radius-sm;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
&--mini {
|
||||
min-width: 96rpx;
|
||||
height: 56rpx;
|
||||
padding: 0 16rpx;
|
||||
border-radius: @btn-radius-mini;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
/* ═══════ variant: outlined (描边) ═══════ */
|
||||
&--outlined {
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
border: 4rpx solid @btn-surface-variant;
|
||||
|
||||
&.toy-btn--primary {
|
||||
border-color: @btn-primary-solid;
|
||||
color: @btn-primary-text;
|
||||
}
|
||||
|
||||
&.toy-btn--secondary,
|
||||
&.toy-btn--green {
|
||||
border-color: @btn-secondary-bg;
|
||||
color: @btn-secondary-text-on-tint;
|
||||
}
|
||||
|
||||
&.toy-btn--error {
|
||||
border-color: @btn-error-bg;
|
||||
color: #b02500;
|
||||
}
|
||||
|
||||
&.toy-btn--default {
|
||||
border-color: @btn-surface-variant;
|
||||
color: @text-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════ variant: ghost (幽灵) ═══════ */
|
||||
&--ghost {
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
&.toy-btn--primary {
|
||||
color: #6c5a00;
|
||||
}
|
||||
|
||||
&.toy-btn--secondary,
|
||||
&.toy-btn--green {
|
||||
color: @btn-secondary-text-on-tint;
|
||||
}
|
||||
|
||||
&.toy-btn--error {
|
||||
color: #b02500;
|
||||
}
|
||||
|
||||
&.toy-btn--default {
|
||||
color: @text-secondary;
|
||||
}
|
||||
|
||||
&.toy-btn--active {
|
||||
background: @btn-surface-container-high !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════ icon-only(纯图标按钮) ═══════ */
|
||||
&--icon-only {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
gap: 0;
|
||||
|
||||
&.toy-btn--large {
|
||||
width: 128rpx;
|
||||
height: 128rpx;
|
||||
border-radius: @btn-radius;
|
||||
}
|
||||
|
||||
&.toy-btn--normal {
|
||||
width: 112rpx;
|
||||
height: 112rpx;
|
||||
border-radius: @btn-radius;
|
||||
}
|
||||
|
||||
&.toy-btn--small {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: @btn-radius-sm;
|
||||
}
|
||||
|
||||
&.toy-btn--mini {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: @btn-radius-mini;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════ block ═══════ */
|
||||
&--block {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ═══════ states ═══════ */
|
||||
|
||||
/* 通用按下态:缩放 0.95 */
|
||||
&--active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: @btn-shadow-pressed;
|
||||
}
|
||||
|
||||
/* 图标按钮按下态:缩放 0.90 */
|
||||
&--icon-only&--active {
|
||||
transform: scale(0.90);
|
||||
}
|
||||
|
||||
/* primary 按下态:切到 primary-dim 底色 + on-primary 字 + ring */
|
||||
&--primary&--active {
|
||||
background: @btn-primary-pressed-bg !important;
|
||||
color: @btn-primary-pressed-text;
|
||||
box-shadow: 0 0 0 8rpx @btn-primary-ring;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
background: @btn-disabled-bg !important;
|
||||
color: @btn-disabled-color !important;
|
||||
border-color: transparent !important;
|
||||
box-shadow: none !important;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&--loading {
|
||||
opacity: 0.88;
|
||||
}
|
||||
|
||||
/* ═══════ inner elements ═══════ */
|
||||
&__icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__label {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__spinner {
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
border: 4rpx solid transparent;
|
||||
border-style: solid;
|
||||
animation: toy-btn-spin 0.8s linear infinite;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes toy-btn-spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
Component({
|
||||
options: {
|
||||
styleIsolation: 'apply-shared',
|
||||
addGlobalClass: true,
|
||||
},
|
||||
properties: {
|
||||
type: {
|
||||
type: String,
|
||||
value: 'default',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
value: 'normal',
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
value: 'filled',
|
||||
},
|
||||
block: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
iconOnly: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
iconClassPrefix: {
|
||||
type: String,
|
||||
value: 'toy-icon',
|
||||
},
|
||||
iconSize: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
iconColor: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
openType: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
style: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
loadingSize: {
|
||||
type: String,
|
||||
value: '20px',
|
||||
},
|
||||
sessionFrom: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
sendMessageTitle: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
sendMessagePath: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
sendMessageImg: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
showMessageCard: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
appParameter: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
ariaLabel: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
},
|
||||
externalClasses: ['custom-class'],
|
||||
data: {
|
||||
pressed: false,
|
||||
rootClassName: '',
|
||||
rootStyle: '',
|
||||
resolvedIconColor: '',
|
||||
resolvedIconSize: '44rpx',
|
||||
resolvedLoadingColor: '#ffffff',
|
||||
},
|
||||
observers: {
|
||||
'type,size,variant,block,disabled,loading,iconOnly,style,width,height,iconColor,iconSize,pressed':
|
||||
function () {
|
||||
this.syncViewState();
|
||||
},
|
||||
},
|
||||
lifetimes: {
|
||||
attached() {
|
||||
this.syncViewState();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
syncViewState() {
|
||||
const {
|
||||
type,
|
||||
size,
|
||||
variant,
|
||||
block,
|
||||
disabled,
|
||||
loading,
|
||||
iconOnly,
|
||||
style,
|
||||
width,
|
||||
height,
|
||||
iconColor,
|
||||
iconSize,
|
||||
pressed,
|
||||
} = this.data;
|
||||
|
||||
const visualType = type === 'green' ? 'secondary' : type;
|
||||
|
||||
const cls = ['toy-btn'];
|
||||
cls.push(`toy-btn--${visualType}`);
|
||||
cls.push(`toy-btn--${size}`);
|
||||
|
||||
if (variant !== 'filled') {
|
||||
cls.push(`toy-btn--${variant}`);
|
||||
}
|
||||
if (block) {
|
||||
cls.push('toy-btn--block');
|
||||
}
|
||||
if (iconOnly) {
|
||||
cls.push('toy-btn--icon-only');
|
||||
}
|
||||
if (disabled) {
|
||||
cls.push('toy-btn--disabled');
|
||||
}
|
||||
if (loading) {
|
||||
cls.push('toy-btn--loading');
|
||||
}
|
||||
if (pressed && !disabled && !loading) {
|
||||
cls.push('toy-btn--active');
|
||||
}
|
||||
|
||||
const styleParts = [style];
|
||||
const w = this.normalizeUnit(width);
|
||||
const h = this.normalizeUnit(height);
|
||||
if (w) styleParts.push(`width:${w}`);
|
||||
if (h) styleParts.push(`height:${h}`);
|
||||
|
||||
const resolvedIconColor = this.resolveIconColor(
|
||||
iconColor,
|
||||
visualType,
|
||||
variant,
|
||||
disabled,
|
||||
);
|
||||
const resolvedIconSize =
|
||||
this.normalizeUnit(iconSize) ||
|
||||
this.getDefaultIconSize(size, iconOnly);
|
||||
|
||||
this.setData({
|
||||
rootClassName: cls.join(' '),
|
||||
rootStyle: styleParts.filter(Boolean).join(';'),
|
||||
resolvedIconColor,
|
||||
resolvedIconSize,
|
||||
resolvedLoadingColor: resolvedIconColor || '#ffffff',
|
||||
});
|
||||
},
|
||||
|
||||
normalizeUnit(value: string) {
|
||||
if (!value) return '';
|
||||
return /^\d+(\.\d+)?$/.test(value) ? `${value}rpx` : value;
|
||||
},
|
||||
|
||||
getDefaultIconSize(size: string, iconOnly: boolean) {
|
||||
if (iconOnly) {
|
||||
const map: Record<string, string> = {
|
||||
large: '56rpx',
|
||||
normal: '48rpx',
|
||||
small: '44rpx',
|
||||
mini: '40rpx',
|
||||
};
|
||||
return map[size] || '48rpx';
|
||||
}
|
||||
const map: Record<string, string> = {
|
||||
large: '44rpx',
|
||||
normal: '40rpx',
|
||||
small: '36rpx',
|
||||
mini: '32rpx',
|
||||
};
|
||||
return map[size] || '40rpx';
|
||||
},
|
||||
|
||||
resolveIconColor(
|
||||
iconColor: string,
|
||||
type: string,
|
||||
variant: string,
|
||||
disabled: boolean,
|
||||
) {
|
||||
if (disabled) return '#b8b2a6';
|
||||
if (iconColor) return iconColor;
|
||||
|
||||
if (variant === 'outlined' || variant === 'ghost') {
|
||||
const outlinedMap: Record<string, string> = {
|
||||
primary: '#6c5a00',
|
||||
secondary: '#5a8c1a',
|
||||
error: '#b02500',
|
||||
default: '#605b50',
|
||||
};
|
||||
return outlinedMap[type] || '#605b50';
|
||||
}
|
||||
|
||||
const filledMap: Record<string, string> = {
|
||||
primary: '#5b4b00',
|
||||
error: '#520c00',
|
||||
default: '#322e25',
|
||||
white: '#322e25',
|
||||
};
|
||||
return filledMap[type] || '#ffffff';
|
||||
},
|
||||
|
||||
onTouchStart() {
|
||||
if (this.data.disabled || this.data.loading) return;
|
||||
this.setData({ pressed: true });
|
||||
},
|
||||
onTouchEnd() {
|
||||
if (!this.data.pressed) return;
|
||||
this.setData({ pressed: false });
|
||||
},
|
||||
onTouchCancel() {
|
||||
if (!this.data.pressed) return;
|
||||
this.setData({ pressed: false });
|
||||
},
|
||||
onClick(e: WechatMiniprogram.TouchEvent) {
|
||||
if (this.data.disabled || this.data.loading) return;
|
||||
this.triggerEvent('click', e);
|
||||
},
|
||||
onGetUserInfo(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('getuserinfo', e.detail);
|
||||
},
|
||||
onContact(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('contact', e.detail);
|
||||
},
|
||||
onGetPhoneNumber(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('getphonenumber', e.detail);
|
||||
},
|
||||
onGetRealTimePhoneNumber(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('getrealtimephonenumber', e.detail);
|
||||
},
|
||||
onAgreePrivacyAuthorization(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('agreeprivacyauthorization', e.detail);
|
||||
},
|
||||
onError(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('error', e.detail);
|
||||
},
|
||||
onLaunchApp(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('launchapp', e.detail);
|
||||
},
|
||||
onOpenSetting(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('opensetting', e.detail);
|
||||
},
|
||||
onChooseAvatar(e: WechatMiniprogram.CustomEvent) {
|
||||
this.triggerEvent('chooseavatar', e.detail);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
<button
|
||||
class="{{rootClassName}} custom-class"
|
||||
style="{{rootStyle}}"
|
||||
session-from="{{ sessionFrom }}"
|
||||
open-type="{{ disabled || loading ? '' : openType }}"
|
||||
send-message-title="{{ sendMessageTitle }}"
|
||||
send-message-path="{{ sendMessagePath }}"
|
||||
send-message-img="{{ sendMessageImg }}"
|
||||
show-message-card="{{ showMessageCard }}"
|
||||
app-parameter="{{ appParameter }}"
|
||||
aria-label="{{ ariaLabel }}"
|
||||
bindtouchstart="onTouchStart"
|
||||
bindtouchend="onTouchEnd"
|
||||
bindtouchcancel="onTouchCancel"
|
||||
bind:tap="onClick"
|
||||
bindgetuserinfo="onGetUserInfo"
|
||||
bindcontact="onContact"
|
||||
bindgetphonenumber="onGetPhoneNumber"
|
||||
bindgetrealtimephonenumber="onGetRealTimePhoneNumber"
|
||||
bindagreeprivacyauthorization="onAgreePrivacyAuthorization"
|
||||
binderror="onError"
|
||||
bindlaunchapp="onLaunchApp"
|
||||
bindopensetting="onOpenSetting"
|
||||
bindchooseavatar="onChooseAvatar">
|
||||
|
||||
<view wx:if="{{ loading }}" class="toy-btn__spinner"
|
||||
style="width:{{loadingSize}};height:{{loadingSize}};border-top-color:{{resolvedLoadingColor}};border-right-color:{{resolvedLoadingColor}};"></view>
|
||||
|
||||
<toy-icon
|
||||
wx:elif="{{ icon }}"
|
||||
name="{{ icon }}"
|
||||
size="{{ resolvedIconSize }}"
|
||||
color="{{ resolvedIconColor }}"
|
||||
class-prefix="{{ iconClassPrefix }}"
|
||||
custom-class="toy-btn__icon" />
|
||||
|
||||
<view wx:if="{{ !iconOnly }}" class="toy-btn__label">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</button>
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"usingComponents": {},
|
||||
"component": true,
|
||||
"styleIsolation": "apply-shared",
|
||||
"addGlobalClass": true
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
@import '../../style/theme.less';
|
||||
@import '../../assets/fonts/iconfont.less';
|
||||
|
||||
.toy-icon-root {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
vertical-align: middle;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
line-height: 1;
|
||||
box-sizing: border-box;
|
||||
|
||||
&--image {
|
||||
width: @font-size-icon;
|
||||
height: @font-size-icon;
|
||||
}
|
||||
|
||||
&__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 内层 text 显示 iconfont 字形 */
|
||||
> text {
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
Component({
|
||||
options: {
|
||||
styleIsolation: 'apply-shared',
|
||||
addGlobalClass: true,
|
||||
},
|
||||
properties: {
|
||||
name: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
classPrefix: {
|
||||
type: String,
|
||||
value: 'toy-icon',
|
||||
},
|
||||
style: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
},
|
||||
externalClasses: ['custom-class', 'ext-class'],
|
||||
data: {
|
||||
isImage: false,
|
||||
rootClassName: '',
|
||||
rootStyle: '',
|
||||
fontGlyphClass: '',
|
||||
},
|
||||
observers: {
|
||||
'name,color,size,classPrefix,style': function () {
|
||||
this.syncViewState();
|
||||
},
|
||||
},
|
||||
lifetimes: {
|
||||
attached() {
|
||||
this.syncViewState();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
syncViewState() {
|
||||
const { name, color, size, classPrefix, style } = this.data;
|
||||
const isImage = this.isImageSource(name);
|
||||
const normalizedSize = this.normalizeUnit(size);
|
||||
const classNames = ['toy-icon-root'];
|
||||
let fontGlyphClass = '';
|
||||
|
||||
if (isImage) {
|
||||
classNames.push('toy-icon-root--image');
|
||||
} else {
|
||||
const parts: string[] = [];
|
||||
if (classPrefix) {
|
||||
parts.push(classPrefix);
|
||||
}
|
||||
if (name) {
|
||||
parts.push(`${classPrefix}-${name}`);
|
||||
}
|
||||
fontGlyphClass = parts.join(' ');
|
||||
}
|
||||
|
||||
const styleSegments = [style];
|
||||
if (normalizedSize) {
|
||||
styleSegments.push(`font-size:${normalizedSize}`);
|
||||
if (isImage) {
|
||||
styleSegments.push(`width:${normalizedSize}`);
|
||||
styleSegments.push(`height:${normalizedSize}`);
|
||||
}
|
||||
}
|
||||
if (color && !isImage) {
|
||||
styleSegments.push(`color:${color}`);
|
||||
}
|
||||
|
||||
this.setData({
|
||||
isImage,
|
||||
rootClassName: classNames.join(' '),
|
||||
rootStyle: styleSegments.filter(Boolean).join(';'),
|
||||
fontGlyphClass,
|
||||
});
|
||||
},
|
||||
isImageSource(name: string) {
|
||||
return /^(https?:\/\/|data:|\/|\.{1,2}\/)/.test(name);
|
||||
},
|
||||
normalizeUnit(value: string) {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
return /^\d+(\.\d+)?$/.test(value) ? `${value}rpx` : value;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
<view
|
||||
wx:if="{{isImage}}"
|
||||
class="{{rootClassName}} custom-class ext-class"
|
||||
style="{{rootStyle}}">
|
||||
<image src="{{name}}" mode="aspectFit" class="toy-icon-root__image" />
|
||||
</view>
|
||||
<!-- 字体图标:内层 text 承载 iconfont(::before),外层 view 负责布局与 externalClass -->
|
||||
<view wx:else class="{{rootClassName}} custom-class ext-class" style="{{rootStyle}}">
|
||||
<text class="{{fontGlyphClass}}"></text>
|
||||
</view>
|
||||
@@ -0,0 +1,60 @@
|
||||
/* eslint-disable */
|
||||
|
||||
// key加引号会报错 如:{ "a": 1, "b": 0}, 仅支持: { a: 1, b: 0}
|
||||
function classNames() {
|
||||
var classes = [],
|
||||
hasOwn = {};
|
||||
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
var arg = arguments[i];
|
||||
if (!arg) continue;
|
||||
|
||||
var argType = typeof arg;
|
||||
|
||||
if (argType === 'string' || argType === 'number') {
|
||||
classes.push(arg);
|
||||
} else if ('Array' === arg.constructor && arg.length) {
|
||||
var inner = classNames.apply(null, arg);
|
||||
if (inner) {
|
||||
classes.push(inner);
|
||||
}
|
||||
} else if (argType === 'object') {
|
||||
var objStr = JSON.stringify(arg);
|
||||
var dotReg = getRegExp('"', 'g');
|
||||
objStr = objStr.replace('{', '').replace('}', '').split(',');
|
||||
|
||||
for (var i = 0; i < objStr.length; i++) {
|
||||
var key = objStr[i].split(':')[0],
|
||||
_key = key.replace(dotReg, '');
|
||||
|
||||
if (!hasOwn[_key] && arg[_key]) {
|
||||
classes.push(_key);
|
||||
hasOwn[_key] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return classes.join(' ');
|
||||
}
|
||||
|
||||
function isUrl(url) {
|
||||
var expression =
|
||||
'https?://(www.)?[-a-zA-Z0-9@:%._+~#=]{2,256}.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)',
|
||||
regex = getRegExp(expression, 'g');
|
||||
|
||||
return url.match(regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁止事件冒泡
|
||||
*/
|
||||
function stopPropagation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
classNames: classNames,
|
||||
isUrl: isUrl,
|
||||
stopPropagation: stopPropagation,
|
||||
};
|
||||
Reference in New Issue
Block a user