创建两个组件

home-categroy.vue 分类菜单 home-banner.vue 轮播图

  1. <template>
  2. <div class="home-category">
  3. <ul class="menu">
  4. <li v-for="item in list" :key="item.id">
  5. <RouterLink to="/">{{item.name}}</RouterLink>
  6. <!--
  7. v-for 和 v-if 不能一起用
  8. -->
  9. <template v-if="item.children.length > 0">
  10. <RouterLink v-for="i in item.children" :key="i.id" to="/">{{i.name}}</RouterLink>
  11. </template>
  12. <!-- 弹层layer位置 -->
  13. </li>
  14. </ul>
  15. </div>
  16. </template>
  17. <script>
  18. // 数据:使用的vuex category中的数据 只不过需要把chilren中的前俩项数据筛选出来
  19. // 基于某些现有的响应式数据经过一定的计算得到新的数据 -> 计算属性
  20. import { computed } from 'vue'
  21. import { useStore } from 'vuex'
  22. export default {
  23. name: 'HomeCategory',
  24. setup () {
  25. const store = useStore()
  26. const list = computed(() => {
  27. // 基于vuex中的数据 截取children中的前俩项
  28. // 基于一个数组 针对于数据的每一项都做处理 然后返回一个全新的数组 map
  29. return store.state.category.list.map((item) => {
  30. return {
  31. id: item.id,
  32. name: item.name,
  33. children: item.children.slice(0, 2),
  34. goods: item.goods
  35. }
  36. })
  37. })
  38. return {
  39. list
  40. }
  41. }
  42. }
  43. </script>
  44. <style scoped lang='less'>
  45. .home-category {
  46. width: 250px;
  47. height: 500px;
  48. background: rgba(0, 0, 0, 0.8);
  49. position: relative;
  50. z-index: 99;
  51. .menu {
  52. li {
  53. padding-left: 40px;
  54. height: 55px;
  55. line-height: 55px;
  56. &:hover {
  57. background: @xtxColor;
  58. }
  59. a {
  60. margin-right: 4px;
  61. color: #fff;
  62. &:first-child {
  63. font-size: 16px;
  64. }
  65. }
  66. .layer {
  67. width: 990px;
  68. height: 500px;
  69. background: rgba(255, 255, 255, 1);
  70. position: absolute;
  71. left: 250px;
  72. top: 0;
  73. display: none;
  74. padding: 0 15px;
  75. h4 {
  76. font-size: 20px;
  77. font-weight: normal;
  78. line-height: 80px;
  79. small {
  80. font-size: 16px;
  81. color: #666;
  82. }
  83. }
  84. ul {
  85. display: flex;
  86. flex-wrap: wrap;
  87. li {
  88. width: 310px;
  89. height: 120px;
  90. margin-right: 15px;
  91. margin-bottom: 15px;
  92. border: 1px solid #eee;
  93. border-radius: 4px;
  94. background: #fff;
  95. &:nth-child(3n) {
  96. margin-right: 0;
  97. }
  98. a {
  99. display: flex;
  100. width: 100%;
  101. height: 100%;
  102. align-items: center;
  103. padding: 10px;
  104. &:hover {
  105. background: #e3f9f4;
  106. }
  107. img {
  108. width: 95px;
  109. height: 95px;
  110. }
  111. .info {
  112. padding-left: 10px;
  113. line-height: 24px;
  114. overflow: hidden;
  115. .name {
  116. font-size: 16px;
  117. color: #666;
  118. }
  119. .desc {
  120. color: #999;
  121. }
  122. .price {
  123. font-size: 22px;
  124. color: @priceColor;
  125. i {
  126. font-size: 16px;
  127. }
  128. }
  129. }
  130. }
  131. }
  132. }
  133. }
  134. &:hover {
  135. .layer {
  136. display: block;
  137. }
  138. }
  139. }
  140. }
  141. }
  142. </style>
  1. <template>
  2. <div class="home-banner">
  3. banner
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. name: 'HomeBanner'
  9. }
  10. </script>
  11. <style scoped lang='less'>
  12. .home-banner {
  13. width: 1240px;
  14. height: 500px;
  15. position: absolute;
  16. left: 0;
  17. top: 0;
  18. z-index: 98;
  19. }
  20. </style>

导入首页

  1. <template>
  2. <div class="page-home">
  3. <div class="home-entry">
  4. <div class="container">
  5. <!-- 左侧分类 -->
  6. <HomeCategory />
  7. <!-- banner轮播图 -->
  8. <HomeBanner/>
  9. </div>
  10. </div>
  11. </div>
  12. </template>
  13. <script>
  14. import HomeBanner from './components/home-banner.vue'
  15. import HomeCategory from './components/home-categroy.vue'
  16. export default {
  17. name: 'PageHome',
  18. components: {
  19. HomeBanner,
  20. HomeCategory
  21. }
  22. }
  23. </script>
  24. <style>
  25. </style>

分类

分类弹层组件

  1. <<!-- 弹层layer位置 -->
  2. <div class="layer">
  3. <h4>分类推荐 <small>根据您的购买或浏览记录推荐</small></h4>
  4. <ul>
  5. <li v-for="i in item.goods" :key="i.id">
  6. <RouterLink to="/">
  7. <img :src="i.picture" alt="" />
  8. <div class="info">
  9. <p class="name ellipsis-2">
  10. {{ i.name }}
  11. </p>
  12. <p class="desc ellipsis">{{ i.desc }}</p>
  13. <p class="price"><i>¥</i>{{ i.price }}</p>
  14. </div>
  15. </RouterLink>
  16. </li>
  17. </ul>
  18. </div>

loading效果

骨架屏组件

  1. <template>
  2. <!-- 决定组件的宽高 -->
  3. <div class="xtx-skeleton shan" :style="{ width: width + 'px', height: height + 'px' }">
  4. <!-- 决定组件的背景色 -->
  5. <div class="block" :style="{ backgroundColor: bg}"></div>
  6. </div>
  7. </template>
  8. <script>
  9. export default {
  10. name: 'XtxSkeleton',
  11. props: {
  12. // 宽度定制
  13. width: {
  14. type: Number,
  15. default: 100
  16. },
  17. // 高度定制
  18. height: {
  19. type: Number,
  20. default: 60
  21. },
  22. // 背景颜色定制
  23. bg: {
  24. type: String,
  25. default: '#ccc'
  26. }
  27. }
  28. }
  29. </script>
  30. <style scoped lang="less">
  31. .xtx-skeleton {
  32. display: inline-block;
  33. position: relative;
  34. overflow: hidden;
  35. vertical-align: middle;
  36. .block {
  37. width: 100%;
  38. height: 100%;
  39. border-radius: 2px;
  40. }
  41. }
  42. .shan {
  43. &::after {
  44. content: "";
  45. position: absolute;
  46. animation: shan 1.5s ease 0s infinite;
  47. top: 0;
  48. width: 50%;
  49. height: 100%;
  50. background: linear-gradient(
  51. to left,
  52. rgba(255, 255, 255, 0) 0,
  53. rgba(255, 255, 255, 0.3) 50%,
  54. rgba(255, 255, 255, 0) 100%
  55. );
  56. transform: skewX(-45deg);
  57. }
  58. }
  59. @keyframes shan {
  60. 0% {
  61. left: -100%;
  62. }
  63. 100% {
  64. left: 120%;
  65. }
  66. }
  67. </style>

封装插件

  1. import Skeleton from './Skeleton/index.vue'
  2. export default {
  3. install (app) {
  4. app.component('Skeleton', Skeleton)
  5. }
  6. }
  1. // 插件
  2. import ComponentsPlugin from '@/components/index'
  3. const app = createApp(App)
  4. app.use(ComponentsPlugin).use(store).use(router).mount('#app')

轮播图

  1. <template>
  2. <div class="box" style="height: 500px">
  3. <div class="xtx-slider" @mouseenter="clearTimer" @mouseleave="startTimer">
  4. <!-- 图片列表 -->
  5. <ul class="slider-body" >
  6. <!--
  7. fade: 当fade类名存在 当前图片就显示 不存在就不显示
  8. -->
  9. <li
  10. class="slider-item"
  11. v-for="(item, i) in sliders"
  12. :key="i"
  13. :class="{ fade: curIndex === i }"
  14. >
  15. <img :src="item.imgUrl" alt="" />
  16. </li>
  17. </ul>
  18. <!-- 圆圈切换按钮 -->
  19. <div class="slider-indicator">
  20. <span
  21. v-for="(item, index) in sliders"
  22. :key="index"
  23. @click="curIndex = index"
  24. :class="{ active: curIndex === index }"
  25. ></span>
  26. </div>
  27. </div>
  28. </div>
  29. </template>
  30. <script>
  31. /**
  32. 目标:点击圆圈按钮 实现对应图片的切换
  33. 思路:
  34. 1. 图片和圆圈按钮数量是一样的 下标值是对应的
  35. 2. 记录一下当前点击的是哪一项
  36. 3. 需要根据记录下来的下标值 去配合:class 控制fade这个类名是否应该显示
  37. */
  38. /**
  39. 目标:图片的自动轮播功能
  40. 思路:哪个数据变化决定了图片切换? 从之前手动修改curIndex的值 变成一个自动修改 每隔几秒修改一下
  41. 计时器 setInterval
  42. */
  43. /**
  44. 目标:鼠标移入暂停播放 鼠标移除再次开启
  45. 思路:暂停 - 清除定时器 定时器id 开启 - 再执行一次定时器
  46. */
  47. import { onMounted, onUnmounted, ref } from 'vue'
  48. export default {
  49. name: 'XtxSlider',
  50. props: {
  51. // 数据列表
  52. sliders: {
  53. type: Array,
  54. default: () => {
  55. return []
  56. }
  57. },
  58. autoPlay: {
  59. type: Boolean,
  60. default: true
  61. }
  62. },
  63. setup (props) {
  64. const curIndex = ref(0)
  65. // 声明一个存放定时器的数据
  66. const timer = ref(null)
  67. function clearTimer () {
  68. clearInterval(timer.value)
  69. }
  70. function startTimer () {
  71. // 开启定时器 每隔2s中修改一下curIndex的值
  72. initLoop()
  73. }
  74. function initLoop () {
  75. if (!props.autoPlay) {
  76. return false
  77. }
  78. // 开启定时器 每隔2s中修改一下curIndex的值
  79. timer.value = window.setInterval(() => {
  80. // 最大能到多大
  81. // 图片总数为4 length - 1为3 只要我发现你大于3了
  82. // 我就会重新赋值为 0 ,永远不能到达4 最大只能等于3
  83. curIndex.value++
  84. if (curIndex.value > props.sliders.length - 1) {
  85. curIndex.value = 0
  86. }
  87. }, 2000)
  88. }
  89. onMounted(() => {
  90. initLoop()
  91. })
  92. onUnmounted(() => {
  93. // 清理工作
  94. clearInterval(timer.value)
  95. })
  96. return {
  97. curIndex,
  98. clearTimer,
  99. startTimer
  100. }
  101. }
  102. }
  103. </script>
  104. <style scoped lang='less'>
  105. .xtx-slider {
  106. width: 100%;
  107. height: 100%;
  108. min-width: 300px;
  109. min-height: 150px;
  110. position: relative;
  111. .slider {
  112. &-body {
  113. width: 100%;
  114. height: 100%;
  115. }
  116. &-item {
  117. width: 100%;
  118. height: 100%;
  119. position: absolute;
  120. left: 0;
  121. top: 0;
  122. opacity: 0;
  123. transition: opacity 0.5s linear;
  124. &.fade {
  125. opacity: 1;
  126. z-index: 1;
  127. }
  128. img {
  129. width: 100%;
  130. height: 100%;
  131. }
  132. }
  133. &-indicator {
  134. position: absolute;
  135. left: 0;
  136. bottom: 20px;
  137. z-index: 2;
  138. width: 100%;
  139. text-align: center;
  140. span {
  141. display: inline-block;
  142. width: 12px;
  143. height: 12px;
  144. background: rgba(0, 0, 0, 0.2);
  145. border-radius: 50%;
  146. cursor: pointer;
  147. ~ span {
  148. margin-left: 12px;
  149. }
  150. &.active {
  151. background: #fff;
  152. }
  153. }
  154. }
  155. &-btn {
  156. width: 44px;
  157. height: 44px;
  158. background: rgba(0, 0, 0, 0.2);
  159. color: #fff;
  160. border-radius: 50%;
  161. position: absolute;
  162. top: 228px;
  163. z-index: 2;
  164. text-align: center;
  165. line-height: 44px;
  166. opacity: 0;
  167. transition: all 0.5s;
  168. &.prev {
  169. left: 20px;
  170. }
  171. &.next {
  172. right: 20px;
  173. }
  174. }
  175. }
  176. &:hover {
  177. .slider-btn {
  178. opacity: 1;
  179. }
  180. }
  181. }
  182. </style>

注册后使用

数据懒加载

computed
当依赖项不变的时候会有缓存
不支持异步的操作
不能直接改变值
改值get set

watch
不支持缓存
支持异步操作
属性 deep 深度监听
使用场景