goods-category.uvue 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. <template>
  2. <cl-page>
  3. <view class="flex flex-col h-full">
  4. <view class="flex flex-row p-3">
  5. <cl-input
  6. :pt="{
  7. className: parseClass(['flex-1 !border-2 !rounded-xl'])
  8. }"
  9. prefix-icon="search-line"
  10. placeholder="iPhone 16 Pro Max"
  11. ></cl-input>
  12. </view>
  13. <view class="flex flex-row flex-1">
  14. <!-- 左侧分类列表 -->
  15. <view class="h-full w-[200rpx] bg-white dark:bg-surface-800 mr-2 rounded-tr-xl">
  16. <scroll-view direction="vertical" :show-scrollbar="false" class="h-full">
  17. <view
  18. class="h-[100rpx] p-2"
  19. v-for="(item, index) in list"
  20. :key="item.label"
  21. @click="onCategoryChange(index)"
  22. >
  23. <view
  24. class="flex flex-row items-center justify-center h-full rounded-lg"
  25. :class="[
  26. categoryActive == index
  27. ? isDark
  28. ? 'bg-primary-500'
  29. : 'bg-primary-50'
  30. : ''
  31. ]"
  32. >
  33. <cl-text
  34. :pt="{
  35. className: parseClass([
  36. [
  37. categoryActive == index,
  38. isDark ? '!text-white' : '!text-primary-500',
  39. isDark ? '!text-surface-300' : '!text-surface-500'
  40. ]
  41. ])
  42. }"
  43. >{{ item.label }}</cl-text
  44. >
  45. </view>
  46. </view>
  47. </scroll-view>
  48. </view>
  49. <!-- 右侧商品列表 -->
  50. <view class="flex-1">
  51. <scroll-view
  52. direction="vertical"
  53. :scroll-into-view="scrollIntoView"
  54. :scroll-with-animation="!scrollLock"
  55. class="h-full"
  56. @scroll="onScroll"
  57. >
  58. <view class="pr-2">
  59. <view
  60. class="category-item flex rounded-xl bg-white dark:bg-surface-800 mb-2 pb-3"
  61. v-for="(item, index) in list"
  62. :key="item.label"
  63. :id="`category-item-${index}`"
  64. >
  65. <cl-text
  66. :pt="{
  67. className: 'p-3'
  68. }"
  69. >{{ item.label }}</cl-text
  70. >
  71. <view class="px-1">
  72. <cl-row :gutter="10">
  73. <cl-col
  74. v-for="goods in item.list"
  75. :key="goods.name"
  76. :span="8"
  77. >
  78. <view class="flex items-center flex-col justify-center">
  79. <cl-image :src="goods.image"></cl-image>
  80. <cl-text
  81. :ellipsis="true"
  82. :pt="{ className: '!text-xs text-center mt-2' }"
  83. >{{ goods.name }}</cl-text
  84. >
  85. </view>
  86. </cl-col>
  87. </cl-row>
  88. </view>
  89. </view>
  90. </view>
  91. </scroll-view>
  92. </view>
  93. </view>
  94. </view>
  95. </cl-page>
  96. </template>
  97. <script setup lang="ts">
  98. import { isDark, parseClass } from "@/cool";
  99. import { getCurrentInstance, ref } from "vue";
  100. const { proxy } = getCurrentInstance()!;
  101. // 商品类型
  102. type Goods = {
  103. name: string;
  104. price: number;
  105. image: string;
  106. };
  107. // 分类类型
  108. type Category = {
  109. label: string;
  110. top?: number;
  111. list: Goods[];
  112. };
  113. // 商品分类示例数据
  114. const list = ref<Category[]>([
  115. {
  116. label: "推荐",
  117. list: [
  118. {
  119. name: "iPhone 15 Pro",
  120. price: 8999,
  121. image: "/static/goods/iphone15pro.png"
  122. },
  123. {
  124. name: "华为 Mate 60 Pro",
  125. price: 6999,
  126. image: "/static/goods/mate60pro.png"
  127. },
  128. {
  129. name: "小米 14 Ultra",
  130. price: 5999,
  131. image: "/static/goods/xiaomi14ultra.png"
  132. }
  133. ]
  134. },
  135. {
  136. label: "Apple",
  137. list: [
  138. {
  139. name: "iPhone 15 Pro",
  140. price: 8999,
  141. image: "/static/goods/iphone15pro.png"
  142. },
  143. {
  144. name: "iPhone 14",
  145. price: 5999,
  146. image: "/static/goods/iphone14.png"
  147. }
  148. ]
  149. },
  150. {
  151. label: "华为",
  152. list: [
  153. {
  154. name: "华为 Mate 60 Pro",
  155. price: 6999,
  156. image: "/static/goods/mate60pro.png"
  157. },
  158. {
  159. name: "华为 P60",
  160. price: 4999,
  161. image: "/static/goods/p60.png"
  162. }
  163. ]
  164. },
  165. {
  166. label: "小米",
  167. list: [
  168. {
  169. name: "小米 14 Ultra",
  170. price: 5999,
  171. image: "/static/goods/xiaomi14ultra.png"
  172. },
  173. {
  174. name: "小米 13",
  175. price: 3999,
  176. image: "/static/goods/xiaomi13.png"
  177. }
  178. ]
  179. },
  180. {
  181. label: "三星",
  182. list: [
  183. {
  184. name: "三星 Galaxy S24",
  185. price: 7999,
  186. image: "/static/goods/galaxys24.png"
  187. },
  188. {
  189. name: "三星 Galaxy Z Flip5",
  190. price: 8999,
  191. image: "/static/goods/galaxyzflip5.png"
  192. }
  193. ]
  194. },
  195. {
  196. label: "OPPO",
  197. list: [
  198. {
  199. name: "OPPO Find X7",
  200. price: 4999,
  201. image: "/static/goods/findx7.png"
  202. }
  203. ]
  204. },
  205. {
  206. label: "VIVO",
  207. list: [
  208. {
  209. name: "VIVO X100 Pro",
  210. price: 5999,
  211. image: "/static/goods/x100pro.png"
  212. }
  213. ]
  214. },
  215. {
  216. label: "荣耀",
  217. list: [
  218. {
  219. name: "荣耀 Magic6",
  220. price: 4599,
  221. image: "/static/goods/magic6.png"
  222. }
  223. ]
  224. },
  225. {
  226. label: "一加",
  227. list: [
  228. {
  229. name: "一加 12",
  230. price: 4299,
  231. image: "/static/goods/oneplus12.png"
  232. }
  233. ]
  234. },
  235. {
  236. label: "红米",
  237. list: [
  238. {
  239. name: "红米 K70 Pro",
  240. price: 3299,
  241. image: "/static/goods/k70pro.png"
  242. }
  243. ]
  244. },
  245. {
  246. label: "魅族",
  247. list: [
  248. {
  249. name: "魅族 21",
  250. price: 3999,
  251. image: "/static/goods/meizu21.png"
  252. }
  253. ]
  254. },
  255. {
  256. label: "坚果",
  257. list: [
  258. {
  259. name: "坚果 R2",
  260. price: 2999,
  261. image: "/static/goods/nutR2.png"
  262. }
  263. ]
  264. },
  265. {
  266. label: "其他",
  267. list: [
  268. {
  269. name: "诺基亚 X30",
  270. price: 2599,
  271. image: "/static/goods/nokiax30.png"
  272. }
  273. ]
  274. }
  275. ]);
  276. // 滚动到指定分类
  277. const scrollIntoView = ref("");
  278. // 滚动锁定
  279. const scrollLock = ref(false);
  280. // 当前选中的分类
  281. const categoryActive = ref(0);
  282. // 分类切换
  283. function onCategoryChange(index: number) {
  284. categoryActive.value = index;
  285. scrollIntoView.value = `category-item-${index}`;
  286. scrollLock.value = true;
  287. setTimeout(() => {
  288. scrollLock.value = false;
  289. }, 50);
  290. }
  291. // 滚动时,更新当前选中的分类
  292. function onScroll(e: UniScrollEvent) {
  293. if (scrollLock.value) return;
  294. const top = e.detail.scrollTop;
  295. list.value.forEach((e, i) => {
  296. if (top >= e.top!) {
  297. categoryActive.value = i;
  298. }
  299. });
  300. }
  301. // 初始化
  302. function init() {
  303. uni.createSelectorQuery()
  304. .in(proxy!.$root)
  305. .selectAll(".category-item")
  306. .boundingClientRect((res) => {
  307. (res as NodeInfo[]).forEach((e, i, arr) => {
  308. list.value[i].top = (e.top ?? 0) - (arr[0].top ?? 0);
  309. });
  310. })
  311. .exec();
  312. }
  313. onReady(() => {
  314. init();
  315. });
  316. </script>