u-coupon.vue 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. <template>
  2. <view class="up-coupon" :class="[`up-coupon--${shape}`, `up-coupon--${type}`, `up-coupon--${size}`, {'up-coupon--disabled': disabled}]"
  3. :style="[couponStyle]" @click="handleClick">
  4. <view class="up-coupon__content">
  5. <!-- 左侧金额区域 -->
  6. <view class="up-coupon__amount">
  7. <slot name="unit" :unit="unit" :unitPosition="unitPosition" v-if="unitPosition === 'left'">
  8. <text class="up-coupon__amount-unit" v-if="unitPosition === 'left'">{{ unit }}</text>
  9. </slot>
  10. <slot name="amount" :amount="amount">
  11. <text class="up-coupon__amount-value">{{ amount }}</text>
  12. </slot>
  13. <slot name="unit" :unit="unit" :unitPosition="unitPosition" v-if="unitPosition === 'right'">
  14. <text class="up-coupon__amount-unit" v-if="unitPosition === 'right'">{{ unit }}</text>
  15. </slot>
  16. <slot name="limit" :limit="limit">
  17. <text class="up-coupon__amount-limit" v-if="limit">{{ limit }}</text>
  18. </slot>
  19. </view>
  20. <!-- 中间描述区域 -->
  21. <view class="up-coupon__info">
  22. <slot name="title" :title="title">
  23. <text class="up-coupon__info-title">{{ title }}</text>
  24. </slot>
  25. <slot name="desc" :desc="desc">
  26. <text class="up-coupon__info-desc" v-if="desc">{{ desc }}</text>
  27. </slot>
  28. <slot name="time" :time="time">
  29. <text class="up-coupon__info-time" v-if="time">{{ time }}</text>
  30. </slot>
  31. </view>
  32. <!-- 右侧操作区域 -->
  33. <view class="up-coupon__action u-padding-right-20">
  34. <slot name="action" :actionText="actionText" :circle="circle">
  35. <up-tag type="error" :bgColor="type ? 'transparent' : '#eb433d'"
  36. :borderColor="type ? '#eee' : '#eb433d'" borderRadius="6px"
  37. size="medium" class="up-coupon__action-text"
  38. :shape="circle ? 'circle': 'circle'">{{ actionText }}</up-tag>
  39. </slot>
  40. </view>
  41. </view>
  42. <!-- 红包绳子效果 -->
  43. <view v-if="shape === 'envelope'" class="up-coupon__rope"></view>
  44. <!-- 默认插槽,可用于添加额外内容 -->
  45. <slot></slot>
  46. </view>
  47. </template>
  48. <script>
  49. export default {
  50. name: 'up-coupon',
  51. props: {
  52. // 金额
  53. amount: {
  54. type: [String, Number],
  55. default: ''
  56. },
  57. // 金额单位
  58. unit: {
  59. type: String,
  60. default: '¥'
  61. },
  62. // 单位位置
  63. unitPosition: {
  64. type: String,
  65. default: 'left'
  66. },
  67. // 使用限制
  68. limit: {
  69. type: String,
  70. default: ''
  71. },
  72. // 标题
  73. title: {
  74. type: String,
  75. default: '优惠券'
  76. },
  77. // 描述
  78. desc: {
  79. type: String,
  80. default: ''
  81. },
  82. // 有效期
  83. time: {
  84. type: String,
  85. default: ''
  86. },
  87. // 操作按钮文字
  88. actionText: {
  89. type: String,
  90. default: '使用'
  91. },
  92. // 形状:coupon-优惠券, envelope-红包, card-卡片
  93. shape: {
  94. type: String,
  95. default: 'coupon'
  96. },
  97. // 尺寸:small, medium, large
  98. size: {
  99. type: String,
  100. default: 'medium'
  101. },
  102. // 是否圆形按钮
  103. circle: {
  104. type: Boolean,
  105. default: false
  106. },
  107. // 是否禁用
  108. disabled: {
  109. type: Boolean,
  110. default: false
  111. },
  112. // 背景颜色
  113. bgColor: {
  114. type: String,
  115. default: ''
  116. },
  117. // 文字颜色
  118. color: {
  119. type: String,
  120. default: ''
  121. },
  122. // 内置背景类型
  123. type: {
  124. type: String,
  125. default: ''
  126. },
  127. },
  128. computed: {
  129. couponStyle() {
  130. const style = {};
  131. if (this.bgColor) style.background = this.bgColor;
  132. if (this.color) style.color = this.color;
  133. return style;
  134. },
  135. dotCount() {
  136. // 根据尺寸计算锯齿数量
  137. const map = {
  138. small: 8,
  139. medium: 10,
  140. large: 12
  141. };
  142. return map[this.size] || 10;
  143. }
  144. },
  145. methods: {
  146. handleClick() {
  147. if (this.disabled) return;
  148. this.$emit('click');
  149. }
  150. }
  151. }
  152. </script>
  153. <style lang="scss" scoped>
  154. .up-coupon {
  155. position: relative;
  156. overflow: hidden;
  157. border-radius: 8rpx;
  158. background: #ffebf0;
  159. color: $u-main-color;
  160. &--coupon {
  161. border-radius: 16rpx;
  162. overflow: hidden;
  163. &::before {
  164. content: '';
  165. position: absolute;
  166. left: -24rpx;
  167. top: 50%;
  168. transform: translateY(-50%);
  169. width: 48rpx;
  170. height: 48rpx;
  171. background-color: #fff;
  172. border-radius: 50%;
  173. }
  174. &::after {
  175. content: '';
  176. position: absolute;
  177. right: -24rpx;
  178. top: 50%;
  179. transform: translateY(-50%);
  180. width: 48rpx;
  181. height: 48rpx;
  182. background-color: #fff;
  183. border-radius: 50%;
  184. }
  185. }
  186. &--envelope {
  187. border-radius: 16rpx;
  188. &::before {
  189. content: '';
  190. position: absolute;
  191. left: 0;
  192. top: 0;
  193. right: 0;
  194. height: 20rpx;
  195. background: repeating-linear-gradient(-45deg, #ffd000, #ffd000 10rpx, #ffa000 10rpx, #ffa000 20rpx);
  196. }
  197. }
  198. &--card {
  199. border-radius: 16rpx;
  200. }
  201. width: 100%;
  202. &--small {
  203. // width: 520rpx;
  204. height: 160rpx;
  205. }
  206. &--medium {
  207. // width: 600rpx;
  208. height: 180rpx;
  209. }
  210. &--large {
  211. // width: 700rpx;
  212. height: 220rpx;
  213. }
  214. &--disabled {
  215. opacity: 0.5;
  216. }
  217. &__content {
  218. display: flex;
  219. flex-direction: row;
  220. align-items: center;
  221. justify-content: space-between;
  222. height: 100%;
  223. padding: 0 30rpx;
  224. position: relative;
  225. z-index: 2;
  226. }
  227. &__amount {
  228. display: flex;
  229. flex-direction: column;
  230. align-items: flex-start;
  231. padding-left: 10rpx;
  232. padding-right: 30rpx;
  233. border-right: 1px dashed #ccc;
  234. &-unit {
  235. font-size: 24rpx;
  236. font-weight: normal;
  237. }
  238. &-value {
  239. font-size: 56rpx;
  240. font-weight: bold;
  241. color: red;
  242. line-height: 1;
  243. margin: 10rpx 0;
  244. }
  245. &-limit {
  246. font-size: 24rpx;
  247. opacity: 0.9;
  248. }
  249. }
  250. &__info {
  251. flex: 1;
  252. display: flex;
  253. flex-direction: column;
  254. align-items: flex-start;
  255. padding-left: 30rpx;
  256. &-title {
  257. font-size: 32rpx;
  258. font-weight: bold;
  259. margin-bottom: 10rpx;
  260. }
  261. &-desc {
  262. font-size: 24rpx;
  263. opacity: 0.9;
  264. margin-bottom: 10rpx;
  265. }
  266. &-time {
  267. font-size: 20rpx;
  268. opacity: 0.8;
  269. }
  270. }
  271. &__action {
  272. display: flex;
  273. flex-direction: row;
  274. align-items: center;
  275. justify-content: center;
  276. }
  277. &__dots {
  278. position: absolute;
  279. left: 0;
  280. top: 0;
  281. bottom: 0;
  282. width: 100%;
  283. display: flex;
  284. flex-direction: column;
  285. justify-content: space-between;
  286. padding: 30rpx 0;
  287. z-index: 1;
  288. }
  289. &__dot {
  290. width: 32rpx;
  291. height: 32rpx;
  292. background-color: #fff;
  293. border-radius: 50%;
  294. margin: 0 -16rpx;
  295. z-index: 3;
  296. }
  297. &__rope {
  298. position: absolute;
  299. top: -40rpx;
  300. left: 50%;
  301. transform: translateX(-50%);
  302. width: 80rpx;
  303. height: 80rpx;
  304. background: linear-gradient(to right, #ffd000, #ffa000);
  305. border-radius: 40rpx 40rpx 0 0;
  306. z-index: 1;
  307. &::before {
  308. content: '';
  309. position: absolute;
  310. top: 0;
  311. left: -20rpx;
  312. width: 20rpx;
  313. height: 40rpx;
  314. background: linear-gradient(to bottom, #ffd000, #ffa000);
  315. border-radius: 10rpx 0 0 10rpx;
  316. }
  317. &::after {
  318. content: '';
  319. position: absolute;
  320. top: 0;
  321. right: -20rpx;
  322. width: 20rpx;
  323. height: 40rpx;
  324. background: linear-gradient(to bottom, #ffd000, #ffa000);
  325. border-radius: 0 10rpx 10rpx 0;
  326. }
  327. }
  328. // 不同主题样式
  329. &--primary {
  330. background: linear-gradient(90deg, #43afff, #3b8cff);
  331. color: #fff;
  332. .up-coupon__amount {
  333. border-right: 1px dashed #eee;
  334. }
  335. .up-coupon__amount-value {
  336. color: #fff;
  337. }
  338. }
  339. &--success {
  340. background: linear-gradient(90deg, #67dda9, #19be6b);
  341. color: #fff !important;
  342. .up-coupon__amount {
  343. border-right: 1px dashed #eee;
  344. }
  345. .up-coupon__amount-value {
  346. color: #fff;
  347. }
  348. }
  349. &--warning {
  350. background: linear-gradient(90deg, #ff9739, #ff6a39);
  351. color: #fff;
  352. .up-coupon__amount {
  353. border-right: 1px dashed #eee;
  354. }
  355. .up-coupon__amount-value {
  356. color: #fff;
  357. }
  358. }
  359. &--error {
  360. background: linear-gradient(90deg, #ff7070, #ff4747);
  361. color: #fff;
  362. .up-coupon__amount {
  363. border-right: 1px dashed #eee;
  364. }
  365. .up-coupon__amount-value {
  366. color: #fff;
  367. }
  368. }
  369. }
  370. </style>