Browse Source

feat(study-tour): 设计游学页面组件和样式优化

EvilDragon 7 months ago
parent
commit
cb26c21248

+ 4 - 0
pages.config.ts

@@ -41,4 +41,8 @@ export default defineUniPages({
       },
     ],
   },
+  condition: {
+    current: -1,
+    list: [{ name: '设计游学', path: 'pages/index/study-tour/index' }],
+  },
 })

+ 189 - 0
src/components/avatar-group-casual/avatar-group-casual.vue

@@ -0,0 +1,189 @@
+<template>
+  <view class="relative" :style="[boxBaseSyle()]">
+    <image
+      v-for="(avatar, index) in list"
+      :key="avatar.id"
+      :src="avatar.url"
+      class="absolute border border-solid border-white vertical-bottom br-50_"
+      :style="[itemBaseSyle(), itemSyle(index, avatar)]"
+    />
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed, watch, nextTick, ref } from 'vue'
+
+type UserSwiperProps = {
+  /** 轮播间隔时间 */
+  interval?: number
+  urls?: any[]
+  width?: number
+  height?: number
+  /** 重叠的部分 */
+  overlap?: number
+  prop?: {
+    /** urls数组中,图片url对应属性的key */
+    url?: string
+  }
+  /** 头像组展示数量 */
+  showNumber?: number
+}
+const props = withDefaults(defineProps<UserSwiperProps>(), {
+  interval: 3000,
+  urls: () => [],
+  width: 50,
+  height: 50,
+  overlap: 20,
+  prop: () => ({
+    url: 'url',
+  }),
+  showNumber: 3,
+})
+
+const list = ref<any[]>([])
+const intervalTimer = ref()
+
+const curLastIndex = ref(props?.showNumber)
+
+const marginLeft = computed(() => props?.width - props?.overlap)
+
+const stop = () => {
+  clearInterval(intervalTimer.value)
+}
+
+const start = () => {
+  intervalTimer.value = setInterval(() => {
+    forwardMove()
+  }, props?.interval) // 可以根据需要调整间隔时间
+}
+
+watch(
+  () => props?.urls,
+  (newVal) => {
+    // TODO 新数据到来,一个一个的替换,
+    list.value = newVal.slice(0, props?.showNumber).map((item, index) => {
+      let url = item && item[props?.prop.url || '']
+      if (typeof item === 'string') {
+        url = item
+      }
+
+      return { url, order: index + 1, id: `avatar-${index}` }
+    })
+    if (newVal.length > props?.showNumber) {
+      stop()
+      start()
+    } else {
+      stop()
+    }
+  },
+  {
+    immediate: true,
+  },
+)
+
+const itemSyle = (index: number, item: any) => {
+  // 正常状态的展示
+  const style = {
+    'z-index': item.order,
+    transition: '0.25s',
+    transform: ` translateX(${(item.order - 1) * marginLeft.value}rpx) scale(1)`,
+  }
+  const total = list.value.length
+
+  // 缩小中
+  if (item.order === 0) {
+    style.transform = ` translateX(${item.order * marginLeft.value}rpx) scale(0)`
+  }
+
+  // 位移中 移动到末尾
+  if (item.order === -1) {
+    style.transform = ` translateX(${(total - 1) * marginLeft.value}rpx) scale(0)`
+    style['z-index'] = total
+  }
+
+  // 进行缩放和位移状态的改变
+  // nextState(item);
+
+  if (item.order === total) {
+    style['z-index'] = total
+  }
+  return style
+}
+
+const itemBaseSyle = () => {
+  return { width: props?.width + 'rpx', height: props?.height + 'rpx' }
+}
+
+const boxBaseSyle = () => {
+  const len = list.value.length
+  return {
+    width: len * props?.width - (len - 1) * props?.overlap + 'rpx',
+    height: props?.height + 'rpx',
+  }
+}
+
+// const nextState = (item: any) => {
+//   if (item.order == 0) {
+//     const timer = setTimeout(() => {
+//       // 300后缩放结束 开始移动到末尾
+//       item.order = -1;
+//       clearTimeout(timer);
+//     }, 300);
+//   } else if (item.order == -1) {
+//     const timer1 = setTimeout(() => {
+//       // 300后移动到末尾结束 开始正常展示
+//       item.order = list.value.length;
+//       clearTimeout(timer1);
+//     }, 300);
+//   }
+// };
+const forwardMove = async () => {
+  list.value.forEach((item, index) => {
+    if (item.order === 1) {
+      const timer = setTimeout(() => {
+        item.order = 0
+        list.value.shift()
+        clearTimeout(timer)
+      }, 250)
+    }
+    item.order--
+  })
+
+  await nextTick()
+  const netItem = props?.urls?.[curLastIndex.value]
+  let url = netItem && netItem[props?.prop.url || '']
+  if (typeof netItem === 'string') {
+    url = netItem
+  }
+
+  const tempItme = { url, order: -1, id: `avatar-${curLastIndex.value}` }
+  list.value.push(tempItme)
+  const timer1 = setTimeout(() => {
+    // 300后移动到末尾结束 开始正常展示
+    list.value[list.value.length - 1].order = list.value.length
+    clearTimeout(timer1)
+  }, 300)
+  if (curLastIndex.value === props?.urls?.length - 1) {
+    curLastIndex.value = 0
+  } else {
+    curLastIndex.value++
+  }
+}
+</script>
+
+<style scoped>
+.avatar-slider {
+  width: 100%;
+  overflow: hidden;
+}
+
+.relative {
+  position: relative;
+}
+.absolute {
+  position: absolute;
+}
+.br-50_ {
+  border-radius: 50%;
+}
+</style>

+ 12 - 1
src/components/card.vue

@@ -1,5 +1,16 @@
+<script lang="ts" setup>
+defineProps({
+  customClass: {
+    type: String,
+    default: () => '',
+  },
+})
+</script>
 <template>
-  <view class="rounded-2xl bg-white shadow-[0_16rpx_20rpx_-10rpx_rgba(0,0,0,0.05)] p-3.5">
+  <view
+    class="rounded-2xl bg-white shadow-[0_16rpx_20rpx_-10rpx_rgba(0,0,0,0.05)] p-3.5 overflow-hidden"
+    :class="customClass"
+  >
     <slot></slot>
   </view>
 </template>

+ 5 - 1
src/components/moment-item.vue

@@ -13,7 +13,11 @@ const props = defineProps({
         avatar: 'https://img.yzcdn.cn/vant/cat.jpeg',
       },
       createdAt: new Date(),
-      images: ['', '', ''],
+      images: [
+        'https://via.placeholder.com/104x104',
+        'https://via.placeholder.com/104x104',
+        'https://via.placeholder.com/104x104',
+      ],
       content: '',
       tags: [],
       shares: 0,

+ 22 - 0
src/components/section-heading.vue

@@ -0,0 +1,22 @@
+<script lang="ts" setup>
+defineProps({
+  customClass: {
+    type: String,
+    default: () => '',
+  },
+  title: {
+    type: String,
+    default: () => '',
+  },
+})
+</script>
+<template>
+  <view class="flex justify-between items-center" :class="[customClass]">
+    <div class="text-black text-xl font-normal font-['PingFang SC'] leading-[10.18px]">
+      {{ title }}
+    </div>
+    <div class="text-right text-black/30 text-sm font-normal font-['PingFang SC'] leading-tight">
+      查看全部
+    </div>
+  </view>
+</template>

+ 30 - 5
src/components/tilted-button.vue

@@ -1,20 +1,45 @@
-<script lang="ts" setup></script>
+<script lang="ts" setup>
+defineProps({
+  customClass: {
+    type: String,
+    default: () => '',
+  },
+  color: {
+    type: String as PropType<'black' | 'white'>,
+    default: () => 'black',
+  },
+  size: {
+    type: String as PropType<'default' | 'small' | 'large'>,
+    default: () => 'default',
+  },
+})
+</script>
 <template>
   <view
-    class="aaa inline-block text-white relative box-border text-3.5 font-400 line-height-5 px-4 py-1"
+    class="base inline-block text-white relative box-border text-3.5 font-400 line-height-5 px-4 py-1"
+    :class="[size, color, customClass]"
   >
-    <view class="flex items-center justify-center">
+    <view class="h-full ml-2 flex items-center justify-center">
       <slot></slot>
     </view>
   </view>
 </template>
 <style lang="scss">
-.aaa {
+.base {
   border-style: solid;
-  border-image-source: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MiIgaGVpZ2h0PSIzNSIgdmlld0JveD0iMCAwIDkyIDM1IiBmaWxsPSJub25lIj4KICA8cGF0aCBkPSJNOS45MzAxNSA1LjI3NjI0QzExLjY3MTMgMi4wMjc1NCAxNS4wNTgzIDAgMTguNzQ0MSAwSDgyQzg3LjUyMjkgMCA5MiA0LjQ3NzE1IDkyIDEwVjI1QzkyIDMwLjUyMjggODcuNTIyOSAzNSA4MiAzNUg5LjAzNDUxQzIuMjMyMiAzNSAtMi4xMTEzMSAyNy43NDQyIDEuMTAxOTQgMjEuNzQ4Nkw5LjkzMDE1IDUuMjc2MjRaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjg1Ii8+Cjwvc3ZnPg==');
   border-image-slice: 10 10 10 25 fill;
   border-image-width: 20rpx 20rpx 20rpx 40rpx;
   border-image-outset: 0 0 0 0;
   border-image-repeat: stretch stretch;
 }
+.black {
+  border-image-source: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MiIgaGVpZ2h0PSIzNSIgdmlld0JveD0iMCAwIDkyIDM1IiBmaWxsPSJub25lIj4KICA8cGF0aCBkPSJNOS45MzAxNSA1LjI3NjI0QzExLjY3MTMgMi4wMjc1NCAxNS4wNTgzIDAgMTguNzQ0MSAwSDgyQzg3LjUyMjkgMCA5MiA0LjQ3NzE1IDkyIDEwVjI1QzkyIDMwLjUyMjggODcuNTIyOSAzNSA4MiAzNUg5LjAzNDUxQzIuMjMyMiAzNSAtMi4xMTEzMSAyNy43NDQyIDEuMTAxOTQgMjEuNzQ4Nkw5LjkzMDE1IDUuMjc2MjRaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjg1Ii8+Cjwvc3ZnPg==');
+}
+.white {
+  border-image-source: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTE1IiBoZWlnaHQ9IjQ0IiB2aWV3Qm94PSIwIDAgMTE1IDQ0IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBpZD0iUmVjdGFuZ2xlIDEzNTAiIGQ9Ik0xNS4xNjQ0IDUuMzcyMzhDMTYuODg4MSAyLjA3MDMyIDIwLjMwNDMgMCAyNC4wMjkyIDBIMTA1QzExMC41MjMgMCAxMTUgNC40NzcxNSAxMTUgMTBWMzRDMTE1IDM5LjUyMjggMTEwLjUyMyA0NCAxMDUgNDRIOS44NTA2NUMzLjA5MTM5IDQ0IC0xLjI1NTYyIDM2LjgyNzEgMS44NzIzMSAzMC44MzUxTDE1LjE2NDQgNS4zNzIzOFoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=');
+  @apply text-black text-base font-normal font-['PingFang SC'] leading-tight;
+}
+.large {
+  @apply h-11;
+}
 </style>

+ 17 - 0
src/package.json

@@ -0,0 +1,17 @@
+{
+  "id": "avatar-group-casual",
+  "name": "头像组无限轮播",
+  "displayName": "头像组无限轮播",
+  "version": "1.0.0",
+  "description": "用户组头像的轮播循环",
+  "keywords": [
+    "vue3头像组",
+    "轮播图"
+  ],
+  "dcloudext": {
+    "category": [
+      "前端组件",
+      "通用组件"
+    ]
+  }
+}

+ 11 - 1
src/pages.json

@@ -32,6 +32,15 @@
       }
     ]
   },
+  "condition": {
+    "current": -1,
+    "list": [
+      {
+        "name": "设计游学",
+        "path": "pages/index/study-tour/index"
+      }
+    ]
+  },
   "pages": [
     {
       "path": "pages/index/index",
@@ -102,7 +111,8 @@
       "path": "pages/index/study-tour/index",
       "type": "page",
       "style": {
-        "navigationBarTitleText": "设计游学"
+        "navigationBarTitleText": "设计游学",
+        "navigationBarBackgroundColor": "#fff"
       }
     }
   ],

+ 87 - 15
src/pages/index/study-tour/index.vue

@@ -1,25 +1,97 @@
 <route lang="yaml">
 style:
   navigationBarTitleText: 设计游学
+  navigationBarBackgroundColor: '#fff'
 </route>
 <script lang="ts" setup>
+import AvatarGroupCasual from '@/components/avatar-group-casual/avatar-group-casual.vue'
 import Card from '@/components/card.vue'
+import MomentItem from '@/components/moment-item.vue'
+import SectionHeading from '@/components/section-heading.vue'
+import TiltedButton from '@/components/tilted-button.vue'
 </script>
 <template>
-  <view>
-    <card class="my-6">1</card>
-    <card class="my-6"></card>
-    <!-- <card class="my-6"></card>
-    <card class="my-6"></card>
-    <card class="my-6"></card>
-    <card class="my-6"></card>
-    <card class="my-6"></card>
-    <card class="my-6"></card>
-    <card class="my-6"></card>
-    <card class="my-6"></card>
-    <card class="my-6"></card>
-    <card class="my-6"></card>
-    <card class="my-6"></card>
-    <card class="my-6"></card> -->
+  <view class="px-3.5">
+    <view class="my-6">
+      <card class=""></card>
+    </view>
+    <card custom-class="my-6 p-0!">
+      <view class="relative">
+        <wd-img
+          custom-class="vertical-bottom"
+          width="100%"
+          height="275"
+          src="https://via.placeholder.com/347x464"
+        ></wd-img>
+        <div
+          class="w-[63px] h-[29px] bg-black/60 rounded-[20px] backdrop-blur-[15px] absolute top-5 right-3.5 flex items-center justify-center"
+        >
+          <div class="text-white text-sm font-normal font-['PingFang SC'] leading-relaxed">
+            报名中
+          </div>
+        </div>
+        <view class="absolute left-3.5 bottom-2.5 flex items-center">
+          <avatar-group-casual
+            :show-number="3"
+            :urls="[
+              'https://via.placeholder.com/20x20',
+              'https://via.placeholder.com/20x20',
+              'https://via.placeholder.com/20x20',
+            ]"
+          ></avatar-group-casual>
+          <div
+            class="ml-1 text-white/60 text-sm font-normal font-['PingFang SC'] leading-[10.18px]"
+          >
+            40人已报名
+          </div>
+        </view>
+      </view>
+      <div class="bg-[#27130d]/50 rounded-bl-2xl rounded-br-2xl backdrop-blur-[20px] p-3.5">
+        <div class="w-[293px] text-white text-xl font-normal font-['PingFang SC'] leading-relaxed">
+          日本研学·东京艺术大学设计游学
+        </div>
+        <view class="flex items-center">
+          <div class="text-white/60 text-sm font-normal font-['PingFang SC'] leading-[34px]">
+            游学时间:
+          </div>
+          <div class="text-white/60 text-base font-normal font-['PingFang SC'] leading-[34px]">
+            07.15 08.10
+          </div>
+        </view>
+        <div
+          class="text-justify text-white/60 text-sm font-normal font-['PingFang SC'] leading-relaxed"
+        >
+          等级限制:黄金会员以上
+        </div>
+        <view class="flex items-center justify-between">
+          <view class="flex items-end">
+            <div class="text-white text-3xl font-bold font-['D-DIN Exp'] leading-normal">16000</div>
+            <div class="ml-1 text-white/60 text-sm font-normal font-['PingFang SC'] leading-[34px]">
+              积分
+            </div>
+          </view>
+          <tilted-button custom-class="" size="large" color="white">立即报名</tilted-button>
+        </view>
+      </div>
+    </card>
+    <card custom-class="my-6">
+      <div class="my-7.5 text-black text-xl font-normal font-['PingFang SC'] leading-[10.18px]">
+        筑巢荟-设计游学
+      </div>
+      <div
+        class="w-[319px] h-[264px] text-justify text-black/40 text-base font-normal font-['PingFang SC'] leading-relaxed"
+      >
+        我们为您精心打造了一个独特且极具价值的游学项目。这个项目的核心旨在全方位提升您作为设计师的能力。
+        在这里,您将拥有无比优质的游学资源。我们与全球知名的设计学府、顶尖设计工作室以及具有代表性的经典建筑和室内空间建立了紧密合作。您将有机会深入这些卓越的场所,亲身体验最前沿的设计理念和实践。
+        参与专业的研讨会和工作坊,与同行们分享见解、碰撞思维火花,进一步深化对设计的理解。
+      </div>
+    </card>
+    <section-heading custom-class="my-6" title="同学荟"></section-heading>
+    <view class="my-6">
+      <img class="w-[136px] h-[171px] rounded-2xl" src="https://via.placeholder.com/136x171" />
+      <div class="w-[347px] h-[145px] bg-white rounded-2xl shadow"></div>
+    </view>
+    <section-heading custom-class="my-6" title="设计圈"></section-heading>
+    <moment-item></moment-item>
   </view>
 </template>

+ 8 - 0
src/readme.md

@@ -0,0 +1,8 @@
+# avatar-group-casual
+
+参考 <https://ext.dcloud.net.cn/plugin?id=14322> 进行了修改
+
+## 额外的属性
+
+头像组展示数量
+showNumber: number

+ 3 - 0
src/static/svgs/rectangle-1350.svg

@@ -0,0 +1,3 @@
+<svg width="115" height="44" viewBox="0 0 115 44" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path id="Rectangle 1350" d="M15.1644 5.37238C16.8881 2.07032 20.3043 0 24.0292 0H105C110.523 0 115 4.47715 115 10V34C115 39.5228 110.523 44 105 44H9.85065C3.09139 44 -1.25562 36.8271 1.87231 30.8351L15.1644 5.37238Z" fill="white"/>
+</svg>

+ 3 - 0
src/style/index.scss

@@ -19,6 +19,8 @@ page {
   // --wot-button-primary-bg-color: green;
   min-height: 100%;
 }
+/* ifdef(MP-WEIXIN) */
+// 微信小程序填充最小高度
 /* stylelint-disable-next-line selector-type-no-unknown */
 layout-default-uni,
 .wot-theme-light {
@@ -26,3 +28,4 @@ layout-default-uni,
   flex-direction: column;
   flex-grow: 1;
 }
+/* endif */