cl-button.uvue 12 KB

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