cl-button.uvue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. <template>
  2. <view
  3. class="cl-button"
  4. :class="[
  5. `cl-button--${size}`,
  6. `cl-button--${type} `,
  7. {
  8. 'cl-button--loading': loading,
  9. 'cl-button--disabled': disabled,
  10. 'cl-button--text': text,
  11. 'cl-button--border': border,
  12. 'cl-button--rounded': rounded,
  13. 'cl-button--icon': isIcon,
  14. 'cl-button--hover': isHover,
  15. 'is-dark': isDark
  16. },
  17. isHover ? hoverClass : '',
  18. pt.className
  19. ]"
  20. :key="cache.key"
  21. :style="buttonStyle"
  22. @tap.stop="onTap"
  23. >
  24. <button
  25. class="cl-button__clicker"
  26. :disabled="isDisabled"
  27. :hover-class="hoverClass"
  28. :hover-stop-propagation="hoverStopPropagation"
  29. :hover-start-time="hoverStartTime"
  30. :hover-stay-time="hoverStayTime"
  31. :form-type="formType"
  32. :open-type="openType"
  33. :lang="lang"
  34. :session-from="sessionFrom"
  35. :send-message-title="sendMessageTitle"
  36. :send-message-path="sendMessagePath"
  37. :send-message-img="sendMessageImg"
  38. :show-message-card="showMessageCard"
  39. :app-parameter="appParameter"
  40. :group-id="groupId"
  41. :guild-id="guildId"
  42. :public-id="publicId"
  43. :phone-number-no-quota-toast="phoneNumberNoQuotaToast"
  44. :createliveactivity="createliveactivity"
  45. @getuserinfo="onGetUserInfo"
  46. @contact="onContact"
  47. @getphonenumber="onGetPhoneNumber"
  48. @error="onError"
  49. @opensetting="onOpenSetting"
  50. @launchapp="onLaunchApp"
  51. @chooseavatar="onChooseAvatar"
  52. @chooseaddress="onChooseAddress"
  53. @chooseinvoicetitle="onChooseInvoiceTitle"
  54. @addgroupapp="onAddGroupApp"
  55. @subscribe="onSubscribe"
  56. @login="onLogin"
  57. @getrealtimephonenumber="onGetRealtimePhoneNumber"
  58. @agreeprivacyauthorization="onAgreePrivacyAuthorization"
  59. @touchstart="onTouchStart"
  60. @touchend="onTouchEnd"
  61. @touchcancel="onTouchCancel"
  62. ></button>
  63. <cl-loading
  64. :color="loadingIcon.color"
  65. :size="loadingIcon.size"
  66. :pt="{
  67. className: parseClass(['mr-[10rpx]', pt.loading?.className])
  68. }"
  69. v-if="loading && !disabled"
  70. ></cl-loading>
  71. <cl-icon
  72. :name="icon"
  73. :color="leftIcon.color"
  74. :size="leftIcon.size"
  75. :pt="{
  76. className: parseClass([
  77. {
  78. 'mr-[8rpx]': !isIcon
  79. },
  80. pt.icon?.className
  81. ])
  82. }"
  83. v-if="icon"
  84. ></cl-icon>
  85. <template v-if="!isIcon">
  86. <cl-text
  87. :color="textColor"
  88. :pt="{
  89. className: parseClass([
  90. 'cl-button__label',
  91. {
  92. '!text-sm': size == 'small'
  93. },
  94. pt.label?.className
  95. ])
  96. }"
  97. >
  98. <slot></slot>
  99. </cl-text>
  100. <slot name="content"></slot>
  101. </template>
  102. </view>
  103. </template>
  104. <script setup lang="ts">
  105. import { computed, ref, useSlots, type PropType } from "vue";
  106. import { get, isDark, parseClass, parsePt, useCache } from "@/cool";
  107. import type { ClIconProps } from "../cl-icon/props";
  108. import type { ClButtonType, PassThroughProps, Size } from "../../types";
  109. import type { ClLoadingProps } from "../cl-loading/props";
  110. defineOptions({
  111. name: "cl-button"
  112. });
  113. // 组件属性定义
  114. const props = defineProps({
  115. // 样式穿透
  116. pt: {
  117. type: Object,
  118. default: () => ({})
  119. },
  120. // 按钮类型
  121. type: {
  122. type: String as PropType<ClButtonType>,
  123. default: "primary"
  124. },
  125. // 字体、图标颜色
  126. color: {
  127. type: String,
  128. default: ""
  129. },
  130. // 图标
  131. icon: {
  132. type: String,
  133. default: ""
  134. },
  135. // 文本按钮
  136. text: {
  137. type: Boolean,
  138. default: false
  139. },
  140. // 圆角按钮
  141. rounded: {
  142. type: Boolean,
  143. default: false
  144. },
  145. // 边框按钮
  146. border: {
  147. type: Boolean,
  148. default: false
  149. },
  150. // 加载状态
  151. loading: {
  152. type: Boolean,
  153. default: false
  154. },
  155. // 禁用状态
  156. disabled: {
  157. type: Boolean,
  158. default: false
  159. },
  160. // 按钮尺寸
  161. size: {
  162. type: String as PropType<Size>,
  163. default: "normal"
  164. },
  165. // 按钮点击态样式类
  166. hoverClass: {
  167. type: String,
  168. default: ""
  169. },
  170. // 是否阻止点击态冒泡
  171. hoverStopPropagation: {
  172. type: Boolean,
  173. default: false
  174. },
  175. // 按住后多久出现点击态
  176. hoverStartTime: {
  177. type: Number,
  178. default: 20
  179. },
  180. // 手指松开后点击态保留时间
  181. hoverStayTime: {
  182. type: Number,
  183. default: 70
  184. },
  185. // 表单提交类型
  186. formType: {
  187. type: String as PropType<"submit" | "reset">,
  188. default: ""
  189. },
  190. // 开放能力类型
  191. openType: {
  192. type: String as PropType<
  193. | "agreePrivacyAuthorization"
  194. | "feedback"
  195. | "share"
  196. | "getUserInfo"
  197. | "contact"
  198. | "getPhoneNumber"
  199. | "launchApp"
  200. | "openSetting"
  201. | "chooseAvatar"
  202. | "getAuthorize"
  203. | "lifestyle"
  204. | "contactShare"
  205. | "openGroupProfile"
  206. | "openGuildProfile"
  207. | "openPublicProfile"
  208. | "shareMessageToFriend"
  209. | "addFriend"
  210. | "addColorSign"
  211. | "addGroupApp"
  212. | "addToFavorites"
  213. | "chooseAddress"
  214. | "chooseInvoiceTitle"
  215. | "login"
  216. | "subscribe"
  217. | "favorite"
  218. | "watchLater"
  219. | "openProfile"
  220. | "liveActivity"
  221. | "getRealtimePhoneNumber"
  222. >,
  223. default: ""
  224. },
  225. // 语言
  226. lang: {
  227. type: String as PropType<"en" | "zh_CN" | "zh_TW">,
  228. default: "zh_CN"
  229. },
  230. // 会话来源
  231. sessionFrom: {
  232. type: String,
  233. default: ""
  234. },
  235. // 会话标题
  236. sendMessageTitle: {
  237. type: String,
  238. default: ""
  239. },
  240. // 会话路径
  241. sendMessagePath: {
  242. type: String,
  243. default: ""
  244. },
  245. // 会话图片
  246. sendMessageImg: {
  247. type: String,
  248. default: ""
  249. },
  250. // 显示会话卡片
  251. showMessageCard: {
  252. type: Boolean,
  253. default: false
  254. },
  255. // 打开 APP 时,向 APP 传递的参数
  256. appParameter: {
  257. type: String,
  258. default: ""
  259. },
  260. // 群ID
  261. groupId: {
  262. type: String,
  263. default: ""
  264. },
  265. // 公会ID
  266. guildId: {
  267. type: String,
  268. default: ""
  269. },
  270. // 公众号ID
  271. publicId: {
  272. type: String,
  273. default: ""
  274. },
  275. // 手机号获取失败时是否弹出错误提示
  276. phoneNumberNoQuotaToast: {
  277. type: Boolean,
  278. default: false
  279. },
  280. // 是否创建直播活动
  281. createliveactivity: {
  282. type: Boolean,
  283. default: false
  284. }
  285. });
  286. // 事件定义
  287. const emit = defineEmits([
  288. "click",
  289. "tap",
  290. "getuserinfo",
  291. "contact",
  292. "getphonenumber",
  293. "error",
  294. "opensetting",
  295. "launchapp",
  296. "chooseavatar",
  297. "chooseaddress",
  298. "chooseinvoicetitle",
  299. "addgroupapp",
  300. "subscribe",
  301. "login",
  302. "getrealtimephonenumber",
  303. "agreeprivacyauthorization"
  304. ]);
  305. const slots = useSlots();
  306. const { cache } = useCache(() => [
  307. props.type,
  308. props.text,
  309. props.disabled,
  310. props.loading,
  311. props.color
  312. ]);
  313. // 样式穿透类型
  314. type PassThrough = {
  315. className?: string;
  316. label?: PassThroughProps;
  317. icon?: ClIconProps;
  318. loading?: ClLoadingProps;
  319. };
  320. // 样式穿透计算
  321. const pt = computed(() => parsePt<PassThrough>(props.pt));
  322. // 是否是图标按钮
  323. const isIcon = computed(() => get(slots, "default") == null && get(slots, "content") == null);
  324. // 文本颜色
  325. const textColor = computed(() => {
  326. if (props.color != "") {
  327. return props.color;
  328. }
  329. let color = "light";
  330. if (props.text) {
  331. color = props.type;
  332. if (props.disabled) {
  333. color = "disabled";
  334. }
  335. }
  336. if (props.type == "light") {
  337. if (!isDark.value) {
  338. color = "dark";
  339. }
  340. }
  341. return color;
  342. });
  343. // 图标信息
  344. const leftIcon = computed<ClIconProps>(() => {
  345. let color = textColor.value;
  346. let size: number | string;
  347. switch (props.size) {
  348. case "small":
  349. size = 26;
  350. break;
  351. default:
  352. size = 32;
  353. break;
  354. }
  355. const ptIcon = pt.value.icon;
  356. if (ptIcon != null) {
  357. color = ptIcon.color ?? color;
  358. size = ptIcon.size ?? size;
  359. }
  360. return {
  361. size,
  362. color
  363. };
  364. });
  365. // 加载图标信息
  366. const loadingIcon = computed<ClLoadingProps>(() => {
  367. let color = textColor.value;
  368. let size: number | string;
  369. switch (props.size) {
  370. case "small":
  371. size = 22;
  372. break;
  373. default:
  374. size = 24;
  375. break;
  376. }
  377. const ptIcon = pt.value.loading;
  378. if (ptIcon != null) {
  379. color = ptIcon.color ?? color;
  380. size = ptIcon.size ?? size;
  381. }
  382. return {
  383. size,
  384. color
  385. };
  386. });
  387. // 按钮样式
  388. const buttonStyle = computed(() => {
  389. const style = {};
  390. if (props.color != "") {
  391. style["border-color"] = props.color;
  392. }
  393. return style;
  394. });
  395. // 是否禁用状态
  396. const isDisabled = computed(() => props.disabled || props.loading);
  397. // 点击事件处理
  398. function onTap(e: UniPointerEvent) {
  399. if (isDisabled.value) return;
  400. emit("click", e);
  401. emit("tap", e);
  402. }
  403. // 获取用户信息事件处理
  404. function onGetUserInfo(e: UniEvent) {
  405. emit("getuserinfo", e);
  406. }
  407. // 客服消息事件处理
  408. function onContact(e: UniEvent) {
  409. emit("contact", e);
  410. }
  411. // 获取手机号事件处理
  412. function onGetPhoneNumber(e: UniEvent) {
  413. emit("getphonenumber", e);
  414. }
  415. // 错误事件处理
  416. function onError(e: UniEvent) {
  417. emit("error", e);
  418. }
  419. // 打开设置事件处理
  420. function onOpenSetting(e: UniEvent) {
  421. emit("opensetting", e);
  422. }
  423. // 打开APP事件处理
  424. function onLaunchApp(e: UniEvent) {
  425. emit("launchapp", e);
  426. }
  427. // 选择头像事件处理
  428. function onChooseAvatar(e: UniEvent) {
  429. emit("chooseavatar", e);
  430. }
  431. // 选择收货地址事件处理
  432. function onChooseAddress(e: UniEvent) {
  433. emit("chooseaddress", e);
  434. }
  435. // 选择发票抬头事件处理
  436. function onChooseInvoiceTitle(e: UniEvent) {
  437. emit("chooseinvoicetitle", e);
  438. }
  439. // 添加群应用事件处理
  440. function onAddGroupApp(e: UniEvent) {
  441. emit("addgroupapp", e);
  442. }
  443. // 订阅消息事件处理
  444. function onSubscribe(e: UniEvent) {
  445. emit("subscribe", e);
  446. }
  447. // 登录事件处理
  448. function onLogin(e: UniEvent) {
  449. emit("login", e);
  450. }
  451. // 获取实时手机号事件处理
  452. function onGetRealtimePhoneNumber(e: UniEvent) {
  453. emit("getrealtimephonenumber", e);
  454. }
  455. // 同意隐私授权事件处理
  456. function onAgreePrivacyAuthorization(e: UniEvent) {
  457. emit("agreeprivacyauthorization", e);
  458. }
  459. // 点击态状态
  460. const isHover = ref(false);
  461. // 触摸开始事件处理
  462. function onTouchStart() {
  463. if (!isDisabled.value) {
  464. isHover.value = true;
  465. }
  466. }
  467. // 触摸结束事件处理
  468. function onTouchEnd() {
  469. isHover.value = false;
  470. }
  471. // 触摸取消事件处理
  472. function onTouchCancel() {
  473. isHover.value = false;
  474. }
  475. </script>
  476. <style lang="scss" scoped>
  477. @mixin button-type($color) {
  478. @apply bg-#{$color}-500;
  479. &.cl-button--hover {
  480. @apply bg-#{$color}-600;
  481. }
  482. &.cl-button--text {
  483. background-color: transparent;
  484. &.cl-button--hover {
  485. @apply bg-transparent opacity-50;
  486. }
  487. }
  488. &.cl-button--border {
  489. @apply border-#{$color}-500;
  490. }
  491. }
  492. .cl-button {
  493. @apply flex flex-row items-center justify-center relative;
  494. @apply border border-transparent border-solid;
  495. overflow: visible;
  496. transition-duration: 0.3s;
  497. transition-property: background-color, border-color, opacity;
  498. &__clicker {
  499. @apply absolute p-0 m-0;
  500. @apply w-full h-full;
  501. @apply opacity-0;
  502. @apply z-10;
  503. }
  504. &--small {
  505. padding: 6rpx 14rpx;
  506. border-radius: 12rpx;
  507. &.cl-button--icon {
  508. padding: 10rpx;
  509. }
  510. }
  511. &--normal {
  512. padding: 10rpx 28rpx;
  513. border-radius: 16rpx;
  514. &.cl-button--icon {
  515. padding: 14rpx;
  516. }
  517. }
  518. &--large {
  519. padding: 14rpx 32rpx;
  520. border-radius: 20rpx;
  521. &.cl-button--icon {
  522. padding: 18rpx;
  523. }
  524. }
  525. &--rounded {
  526. @apply rounded-full;
  527. }
  528. &--primary {
  529. @include button-type("primary");
  530. }
  531. &--warn {
  532. @include button-type("yellow");
  533. }
  534. &--error {
  535. @include button-type("red");
  536. }
  537. &--info {
  538. @include button-type("surface");
  539. }
  540. &--success {
  541. @include button-type("green");
  542. }
  543. &--light {
  544. @apply border-surface-700;
  545. &.cl-button--hover {
  546. @apply bg-surface-100;
  547. }
  548. &.is-dark {
  549. &.cl-button--hover {
  550. @apply bg-surface-700;
  551. }
  552. }
  553. }
  554. &--dark {
  555. @apply bg-surface-700;
  556. &.cl-button--hover {
  557. @apply bg-surface-800;
  558. }
  559. }
  560. &--disabled {
  561. @apply bg-surface-300;
  562. &.cl-button--border {
  563. @apply border-surface-300;
  564. }
  565. }
  566. &--loading {
  567. opacity: 0.6;
  568. }
  569. &.is-dark {
  570. &.cl-button--disabled {
  571. @apply bg-surface-400;
  572. &.cl-button--border {
  573. @apply border-surface-500;
  574. }
  575. }
  576. &.cl-button--text {
  577. @apply bg-transparent;
  578. }
  579. &.cl-button--light {
  580. @apply border-surface-500;
  581. }
  582. }
  583. }
  584. .cl-button {
  585. & + .cl-button {
  586. @apply ml-2;
  587. }
  588. }
  589. </style>