ActivityCard.vue 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. <template>
  2. <uni-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
  3. <view class="active-box">
  4. <!-- 热门标签 -->
  5. <view v-if="config.hot" class="btn-tag-star">
  6. <uni-icons type="star-filled" size="16" color="#ffd700"></uni-icons>
  7. </view>
  8. <!-- 图片区域 -->
  9. <view class="img crm-cursor" @click="handleClick">
  10. <image :src="imageSrc" mode="widthFix" />
  11. <!-- <view v-if="config.imageTitle" class="imgTitle">{{ t(config.imageTitle) }}</view> -->
  12. </view>
  13. <!-- 内容区域 -->
  14. <view class="content">
  15. <view class="content-box">
  16. <view v-if="config.time" class="time">{{ config.time }}</view>
  17. <view class="title" @click="handleClick">{{ t(config.title) }}</view>
  18. <view v-if="config.description" class="des crm-one-font">{{ t(config.description) }}</view>
  19. <!-- 按钮区域 -->
  20. <view class="bottom">
  21. <template v-for="(btn, index) in config.buttons" :key="index">
  22. <span v-if="shouldShowButton(btn)" :class="['btn', getButtonType(btn)]"
  23. @click="handleButtonClick(btn)">
  24. {{ t(btn.text) }}
  25. <template v-if="btn.suffix && state[btn.suffix]">
  26. {{ state[btn.suffix] }}
  27. </template>
  28. <template v-if="btn.suffixText">
  29. {{ t(btn.suffixText) }}
  30. </template>
  31. </span>
  32. <span v-else-if="btn.elseType && btn.condition" :class="['btn', btn.elseType]">
  33. {{ t(btn.text) }}
  34. </span>
  35. </template>
  36. </view>
  37. </view>
  38. </view>
  39. </view>
  40. </uni-col>
  41. </template>
  42. <script setup lang="ts">
  43. import { computed } from 'vue'
  44. import { useI18n } from 'vue-i18n'
  45. const { t, locale } = useI18n()
  46. const props = defineProps<{
  47. config: any
  48. state?: Record<string, any>
  49. }>()
  50. const emit = defineEmits(['action'])
  51. // 图片源
  52. const imageSrc = computed(() => {
  53. if (typeof props.config.image === 'object') {
  54. const lang = locale.value || 'en'
  55. return props.config.image[lang] || props.config.image.default
  56. }
  57. return props.config.image
  58. })
  59. // 判断按钮是否显示
  60. const shouldShowButton = (btn: any): boolean => {
  61. if (!btn.condition) return true
  62. if (!props.state) return false
  63. try {
  64. const conditionStr = btn.condition
  65. const evalStr = conditionStr.replace(/([a-zA-Z_][a-zA-Z0-9_.]*)/g, (match) => {
  66. if (match.includes('.')) {
  67. const parts = match.split('.')
  68. let value: any = props.state
  69. for (const part of parts) {
  70. value = value?.[part]
  71. }
  72. return JSON.stringify(value)
  73. }
  74. return JSON.stringify(props.state[match])
  75. })
  76. const fn = new Function(`return ${evalStr}`)
  77. return fn()
  78. } catch (error) {
  79. console.error('按钮条件评估失败:', error, btn.condition)
  80. return false
  81. }
  82. }
  83. // 获取按钮类型
  84. const getButtonType = (btn: any): string => {
  85. if (btn.type === 'dynamic') {
  86. return shouldShowButton(btn) ? 'red' : 'gray'
  87. }
  88. return btn.type
  89. }
  90. // 处理点击事件
  91. const handleClick = () => {
  92. if (props.config.onClick) {
  93. emit('action', {
  94. type: props.config.onClick,
  95. params: props.config.onClickParams || []
  96. })
  97. }
  98. }
  99. // 处理按钮点击
  100. const handleButtonClick = (btn: any) => {
  101. if (btn.action && btn.action !== 'disabled') {
  102. emit('action', {
  103. type: btn.action,
  104. params: btn.params || []
  105. })
  106. }
  107. }
  108. </script>
  109. <style scoped lang="scss">
  110. @import "@/uni.scss";
  111. .active-box {
  112. width: 100%;
  113. position: relative;
  114. margin-bottom: px2rpx(20);
  115. padding: 0;
  116. // overflow: hidden;
  117. aspect-ratio: 3 / 4;
  118. .btn-tag-star {
  119. position: absolute;
  120. top: px2rpx(30);
  121. left: px2rpx(30);
  122. z-index: 2;
  123. background: linear-gradient(135deg, #ffd700 0%, #ffb347 100%);
  124. padding: px2rpx(12) px2rpx(28);
  125. border-radius: px2rpx(40);
  126. font-size: px2rpx(26);
  127. font-weight: bold;
  128. color: #0f1423;
  129. display: flex;
  130. align-items: center;
  131. gap: px2rpx(8);
  132. box-shadow: 0 px2rpx(4) px2rpx(12) rgba(255, 215, 0, 0.3);
  133. letter-spacing: px2rpx(1);
  134. text-transform: uppercase;
  135. .uni-icons {
  136. font-size: px2rpx(28) !important;
  137. }
  138. }
  139. .img {
  140. width: 100%;
  141. position: relative;
  142. overflow: hidden;
  143. image {
  144. width: 100%;
  145. object-fit: cover;
  146. transition: transform 0.6s ease;
  147. }
  148. }
  149. .content {
  150. width: 100%;
  151. padding: 0 px2rpx(14);
  152. box-sizing: border-box;
  153. margin-top: px2rpx(-20);
  154. .content-box {
  155. width: 100%;
  156. height: 100%;
  157. display: flex;
  158. flex-direction: column;
  159. padding: 0 px2rpx(14);
  160. box-sizing: border-box;
  161. background-color: var(--color-white);
  162. z-index: 1;
  163. position: relative;
  164. }
  165. .title {
  166. width: 100%;
  167. margin: px2rpx(20) 0;
  168. font-size: px2rpx(24);
  169. font-weight: 700;
  170. color: var(--color-error);
  171. line-height: 1.3;
  172. display: block;
  173. margin-bottom: px2rpx(12);
  174. overflow: hidden;
  175. text-overflow: ellipsis;
  176. white-space: nowrap;
  177. }
  178. .time {
  179. margin-top: px2rpx(20);
  180. width: fit-content;
  181. font-size: px2rpx(12);
  182. color: var(--color-error);
  183. background-color:
  184. color-mix(in oklab, var(--color-error) 10%, transparent);
  185. line-height: px2rpx(20);
  186. padding: px2rpx(5) px2rpx(7);
  187. border-radius: px2rpx(2);
  188. }
  189. .des {
  190. width: 100%;
  191. font-size: px2rpx(14);
  192. color: var(--color-slate-900);
  193. margin-bottom: px2rpx(40);
  194. display: -webkit-box;
  195. -webkit-line-clamp: 2;
  196. -webkit-box-orient: vertical;
  197. overflow: hidden;
  198. line-height: 1.2;
  199. }
  200. .bottom {
  201. display: flex;
  202. flex-wrap: wrap;
  203. align-items: center;
  204. gap: px2rpx(20);
  205. .btn {
  206. color: var(--color-white);
  207. padding: px2rpx(16);
  208. font-size: px2rpx(14);
  209. font-weight: 600;
  210. transition: all 0.3s ease;
  211. display: inline-flex;
  212. align-items: center;
  213. justify-content: center;
  214. white-space: nowrap;
  215. letter-spacing: px2rpx(1);
  216. text-transform: uppercase;
  217. &.red {
  218. width: 100%;
  219. background-color: var(--color-secondary);
  220. border: 1px solid #333333;
  221. }
  222. &.gray {
  223. padding: px2rpx(8) px2rpx(16);
  224. font-size: px2rpx(11);
  225. background-color: var(--color-navy-200);
  226. border: 1px solid var(--color-sky-100);
  227. }
  228. &.check {
  229. padding: px2rpx(8) px2rpx(16);
  230. font-size: px2rpx(11);
  231. background-color: var(--color-error);
  232. }
  233. }
  234. }
  235. }
  236. // 列表项样式(用于底部链接)
  237. .link-list {
  238. display: flex;
  239. gap: px2rpx(30);
  240. flex-wrap: wrap;
  241. margin-top: px2rpx(30);
  242. padding-top: px2rpx(30);
  243. border-top: px2rpx(1) solid rgba(255, 215, 0, 0.2);
  244. .link-item {
  245. color: #8e9aaf;
  246. font-size: px2rpx(26);
  247. transition: color 0.3s;
  248. &:active {
  249. color: #ffd700;
  250. }
  251. &::before {
  252. content: '•';
  253. color: #ffd700;
  254. margin-right: px2rpx(8);
  255. }
  256. }
  257. }
  258. }
  259. // 针对不同屏幕尺寸的响应式调整
  260. @media screen and (max-width: 767px) {
  261. .active-box {
  262. .title {
  263. width: px2rpx(200) !important;
  264. }
  265. }
  266. }
  267. // 添加悬停效果(仅H5)
  268. /* #ifdef H5 */
  269. .active-box:hover {
  270. // transform: translateY(-6rpx);
  271. // box-shadow: 0 p 60rpx rgba(0, 0, 0, 0.5);
  272. // transition: all 0.3s ease;
  273. .img image {
  274. transform: scale(1.05);
  275. }
  276. }
  277. /* #endif */
  278. </style>