Files
WAI_Project_UNIX/uni_modules/cool-ui/components/cl-button/cl-button.uvue
2025-08-22 16:44:22 +08:00

674 lines
11 KiB
Plaintext

<template>
<view
class="cl-button"
:class="[
`cl-button--${size}`,
`cl-button--${type} `,
{
'cl-button--loading': loading,
'cl-button--disabled': disabled,
'cl-button--text': text,
'cl-button--border': border,
'cl-button--rounded': rounded,
'cl-button--icon': isIcon,
'cl-button--hover': isHover,
'is-dark': isDark
},
isHover ? hoverClass : '',
pt.className
]"
:key="cache.key"
:style="buttonStyle"
@tap.stop="onTap"
>
<button
class="cl-button__clicker"
:disabled="isDisabled"
:hover-class="hoverClass"
:hover-stop-propagation="hoverStopPropagation"
:hover-start-time="hoverStartTime"
:hover-stay-time="hoverStayTime"
:form-type="formType"
:open-type="openType"
:lang="lang"
:session-from="sessionFrom"
:send-message-title="sendMessageTitle"
:send-message-path="sendMessagePath"
:send-message-img="sendMessageImg"
:show-message-card="showMessageCard"
:app-parameter="appParameter"
:group-id="groupId"
:guild-id="guildId"
:public-id="publicId"
:phone-number-no-quota-toast="phoneNumberNoQuotaToast"
:createliveactivity="createliveactivity"
@getuserinfo="onGetUserInfo"
@contact="onContact"
@getphonenumber="onGetPhoneNumber"
@error="onError"
@opensetting="onOpenSetting"
@launchapp="onLaunchApp"
@chooseavatar="onChooseAvatar"
@chooseaddress="onChooseAddress"
@chooseinvoicetitle="onChooseInvoiceTitle"
@addgroupapp="onAddGroupApp"
@subscribe="onSubscribe"
@login="onLogin"
@getrealtimephonenumber="onGetRealtimePhoneNumber"
@agreeprivacyauthorization="onAgreePrivacyAuthorization"
@touchstart="onTouchStart"
@touchend="onTouchEnd"
@touchcancel="onTouchCancel"
></button>
<cl-loading
:color="loadingIcon.color"
:size="loadingIcon.size"
:pt="{
className: parseClass(['mr-[10rpx]', pt.loading?.className])
}"
v-if="loading && !disabled"
></cl-loading>
<cl-icon
:name="icon"
:color="leftIcon.color"
:size="leftIcon.size"
:pt="{
className: parseClass([
{
'mr-[8rpx]': !isIcon
},
pt.icon?.className
])
}"
v-if="icon"
></cl-icon>
<template v-if="!isIcon">
<cl-text
:color="textColor"
:pt="{
className: parseClass([
'cl-button__label',
{
'!text-sm': size == 'small'
},
pt.label?.className
])
}"
>
<slot></slot>
</cl-text>
<slot name="content"></slot>
</template>
</view>
</template>
<script setup lang="ts">
import { computed, ref, useSlots, type PropType } from "vue";
import { get, isDark, parseClass, parsePt, useCache } from "@/cool";
import type { ClIconProps } from "../cl-icon/props";
import type { ClButtonType, PassThroughProps, Size } from "../../types";
import type { ClLoadingProps } from "../cl-loading/props";
defineOptions({
name: "cl-button"
});
// 组件属性定义
const props = defineProps({
// 样式穿透
pt: {
type: Object,
default: () => ({})
},
// 按钮类型
type: {
type: String as PropType<ClButtonType>,
default: "primary"
},
// 字体、图标颜色
color: {
type: String,
default: ""
},
// 图标
icon: {
type: String,
default: ""
},
// 文本按钮
text: {
type: Boolean,
default: false
},
// 圆角按钮
rounded: {
type: Boolean,
default: false
},
// 边框按钮
border: {
type: Boolean,
default: false
},
// 加载状态
loading: {
type: Boolean,
default: false
},
// 禁用状态
disabled: {
type: Boolean,
default: false
},
// 按钮尺寸
size: {
type: String as PropType<Size>,
default: "normal"
},
// 按钮点击态样式类
hoverClass: {
type: String,
default: ""
},
// 是否阻止点击态冒泡
hoverStopPropagation: {
type: Boolean,
default: false
},
// 按住后多久出现点击态
hoverStartTime: {
type: Number,
default: 20
},
// 手指松开后点击态保留时间
hoverStayTime: {
type: Number,
default: 70
},
// 表单提交类型
formType: {
type: String as PropType<"submit" | "reset">,
default: ""
},
// 开放能力类型
openType: {
type: String as PropType<
| "agreePrivacyAuthorization"
| "feedback"
| "share"
| "getUserInfo"
| "contact"
| "getPhoneNumber"
| "launchApp"
| "openSetting"
| "chooseAvatar"
| "getAuthorize"
| "lifestyle"
| "contactShare"
| "openGroupProfile"
| "openGuildProfile"
| "openPublicProfile"
| "shareMessageToFriend"
| "addFriend"
| "addColorSign"
| "addGroupApp"
| "addToFavorites"
| "chooseAddress"
| "chooseInvoiceTitle"
| "login"
| "subscribe"
| "favorite"
| "watchLater"
| "openProfile"
| "liveActivity"
| "getRealtimePhoneNumber"
>,
default: ""
},
// 语言
lang: {
type: String as PropType<"en" | "zh_CN" | "zh_TW">,
default: "zh_CN"
},
// 会话来源
sessionFrom: {
type: String,
default: ""
},
// 会话标题
sendMessageTitle: {
type: String,
default: ""
},
// 会话路径
sendMessagePath: {
type: String,
default: ""
},
// 会话图片
sendMessageImg: {
type: String,
default: ""
},
// 显示会话卡片
showMessageCard: {
type: Boolean,
default: false
},
// 打开 APP 时,向 APP 传递的参数
appParameter: {
type: String,
default: ""
},
// 群ID
groupId: {
type: String,
default: ""
},
// 公会ID
guildId: {
type: String,
default: ""
},
// 公众号ID
publicId: {
type: String,
default: ""
},
// 手机号获取失败时是否弹出错误提示
phoneNumberNoQuotaToast: {
type: Boolean,
default: false
},
// 是否创建直播活动
createliveactivity: {
type: Boolean,
default: false
}
});
// 事件定义
const emit = defineEmits([
"click",
"tap",
"getuserinfo",
"contact",
"getphonenumber",
"error",
"opensetting",
"launchapp",
"chooseavatar",
"chooseaddress",
"chooseinvoicetitle",
"addgroupapp",
"subscribe",
"login",
"getrealtimephonenumber",
"agreeprivacyauthorization"
]);
const slots = useSlots();
const { cache } = useCache(() => [
props.type,
props.text,
props.disabled,
props.loading,
props.color
]);
// 样式穿透类型
type PassThrough = {
className?: string;
label?: PassThroughProps;
icon?: ClIconProps;
loading?: ClLoadingProps;
};
// 样式穿透计算
const pt = computed(() => parsePt<PassThrough>(props.pt));
// 是否是图标按钮
const isIcon = computed(() => get(slots, "default") == null && get(slots, "content") == null);
// 文本颜色
const textColor = computed(() => {
if (props.color != "") {
return props.color;
}
let color = "light";
if (props.text) {
color = props.type;
if (props.disabled) {
color = "disabled";
}
}
if (props.type == "light") {
if (!isDark.value) {
color = "dark";
}
}
return color;
});
// 图标信息
const leftIcon = computed<ClIconProps>(() => {
let color = textColor.value;
let size: number | string;
switch (props.size) {
case "small":
size = 26;
break;
default:
size = 32;
break;
}
const ptIcon = pt.value.icon;
if (ptIcon != null) {
color = ptIcon.color ?? color;
size = ptIcon.size ?? size;
}
return {
size,
color
};
});
// 加载图标信息
const loadingIcon = computed<ClLoadingProps>(() => {
let color = textColor.value;
let size: number | string;
switch (props.size) {
case "small":
size = 22;
break;
default:
size = 24;
break;
}
const ptIcon = pt.value.loading;
if (ptIcon != null) {
color = ptIcon.color ?? color;
size = ptIcon.size ?? size;
}
return {
size,
color
};
});
// 按钮样式
const buttonStyle = computed(() => {
const style = {};
if (props.color != "") {
style["border-color"] = props.color;
}
return style;
});
// 是否禁用状态
const isDisabled = computed(() => props.disabled || props.loading);
// 点击事件处理
function onTap(e: UniPointerEvent) {
if (isDisabled.value) return;
emit("click", e);
emit("tap", e);
}
// 获取用户信息事件处理
function onGetUserInfo(e: UniEvent) {
emit("getuserinfo", e);
}
// 客服消息事件处理
function onContact(e: UniEvent) {
emit("contact", e);
}
// 获取手机号事件处理
function onGetPhoneNumber(e: UniEvent) {
emit("getphonenumber", e);
}
// 错误事件处理
function onError(e: UniEvent) {
emit("error", e);
}
// 打开设置事件处理
function onOpenSetting(e: UniEvent) {
emit("opensetting", e);
}
// 打开APP事件处理
function onLaunchApp(e: UniEvent) {
emit("launchapp", e);
}
// 选择头像事件处理
function onChooseAvatar(e: UniEvent) {
emit("chooseavatar", e);
}
// 选择收货地址事件处理
function onChooseAddress(e: UniEvent) {
emit("chooseaddress", e);
}
// 选择发票抬头事件处理
function onChooseInvoiceTitle(e: UniEvent) {
emit("chooseinvoicetitle", e);
}
// 添加群应用事件处理
function onAddGroupApp(e: UniEvent) {
emit("addgroupapp", e);
}
// 订阅消息事件处理
function onSubscribe(e: UniEvent) {
emit("subscribe", e);
}
// 登录事件处理
function onLogin(e: UniEvent) {
emit("login", e);
}
// 获取实时手机号事件处理
function onGetRealtimePhoneNumber(e: UniEvent) {
emit("getrealtimephonenumber", e);
}
// 同意隐私授权事件处理
function onAgreePrivacyAuthorization(e: UniEvent) {
emit("agreeprivacyauthorization", e);
}
// 点击态状态
const isHover = ref(false);
// 触摸开始事件处理
function onTouchStart() {
if (!isDisabled.value) {
isHover.value = true;
}
}
// 触摸结束事件处理
function onTouchEnd() {
isHover.value = false;
}
// 触摸取消事件处理
function onTouchCancel() {
isHover.value = false;
}
</script>
<style lang="scss" scoped>
@mixin button-type($color) {
@apply bg-#{$color}-500;
&.cl-button--hover {
@apply bg-#{$color}-600;
}
&.cl-button--text {
background-color: transparent;
&.cl-button--hover {
@apply bg-transparent opacity-50;
}
}
&.cl-button--border {
@apply border-#{$color}-500;
}
}
.cl-button {
@apply flex flex-row items-center justify-center relative box-border;
@apply border border-transparent border-solid;
overflow: visible;
transition-duration: 0.3s;
transition-property: background-color, border-color, opacity;
&__clicker {
@apply absolute p-0 m-0;
@apply w-full h-full;
@apply opacity-0;
@apply z-10;
}
&--small {
padding: 6rpx 14rpx;
border-radius: 12rpx;
&.cl-button--icon {
padding: 10rpx;
}
}
&--normal {
padding: 10rpx 28rpx;
border-radius: 16rpx;
&.cl-button--icon {
padding: 14rpx;
}
}
&--large {
padding: 14rpx 32rpx;
border-radius: 20rpx;
&.cl-button--icon {
padding: 18rpx;
}
}
&--rounded {
@apply rounded-full;
}
&--primary {
@include button-type("primary");
}
&--warn {
@include button-type("yellow");
}
&--error {
@include button-type("red");
}
&--info {
@include button-type("surface");
}
&--success {
@include button-type("green");
}
&--light {
@apply border-surface-700;
&.cl-button--hover {
@apply bg-surface-100;
}
&.is-dark {
&.cl-button--hover {
@apply bg-surface-700;
}
}
}
&--dark {
@apply bg-surface-700;
&.cl-button--hover {
@apply bg-surface-800;
}
}
&--disabled {
@apply bg-surface-300;
&.cl-button--border {
@apply border-surface-300;
}
}
&--loading {
opacity: 0.6;
}
&.is-dark {
&.cl-button--disabled {
@apply bg-surface-400;
&.cl-button--border {
@apply border-surface-500;
}
}
&.cl-button--text {
@apply bg-transparent;
}
&.cl-button--light {
@apply border-surface-500;
}
}
}
.cl-button {
& + .cl-button {
@apply ml-2;
}
}
</style>