goods-category.uvue 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  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-[100px] 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-[50px] 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. :size="12"
  82. :ellipsis="true"
  83. :pt="{ className: 'text-center mt-2' }"
  84. >{{ goods.name }}</cl-text
  85. >
  86. </view>
  87. </cl-col>
  88. </cl-row>
  89. </view>
  90. </view>
  91. </view>
  92. </scroll-view>
  93. </view>
  94. </view>
  95. </view>
  96. </cl-page>
  97. </template>
  98. <script setup lang="ts">
  99. import { isDark, parseClass } from "@/.cool";
  100. import { getCurrentInstance, ref } from "vue";
  101. const { proxy } = getCurrentInstance()!;
  102. // 商品类型
  103. type Goods = {
  104. name: string;
  105. price: number;
  106. image: string;
  107. };
  108. // 分类类型
  109. type Category = {
  110. label: string;
  111. top?: number;
  112. list: Goods[];
  113. };
  114. // 商品分类示例数据
  115. const list = ref<Category[]>([
  116. {
  117. label: "推荐",
  118. list: [
  119. {
  120. name: "iPhone 15 Pro",
  121. price: 8999,
  122. image: "/static/goods/iphone15pro.png"
  123. },
  124. {
  125. name: "华为 Mate 60 Pro",
  126. price: 6999,
  127. image: "/static/goods/mate60pro.png"
  128. },
  129. {
  130. name: "小米 14 Ultra",
  131. price: 5999,
  132. image: "/static/goods/xiaomi14ultra.png"
  133. }
  134. ]
  135. },
  136. {
  137. label: "Apple",
  138. list: [
  139. {
  140. name: "iPhone 15 Pro",
  141. price: 8999,
  142. image: "/static/goods/iphone15pro.png"
  143. },
  144. {
  145. name: "iPhone 14",
  146. price: 5999,
  147. image: "/static/goods/iphone14.png"
  148. }
  149. ]
  150. },
  151. {
  152. label: "华为",
  153. list: [
  154. {
  155. name: "华为 Mate 60 Pro",
  156. price: 6999,
  157. image: "/static/goods/mate60pro.png"
  158. },
  159. {
  160. name: "华为 P60",
  161. price: 4999,
  162. image: "/static/goods/p60.png"
  163. }
  164. ]
  165. },
  166. {
  167. label: "小米",
  168. list: [
  169. {
  170. name: "小米 14 Ultra",
  171. price: 5999,
  172. image: "/static/goods/xiaomi14ultra.png"
  173. },
  174. {
  175. name: "小米 13",
  176. price: 3999,
  177. image: "/static/goods/xiaomi13.png"
  178. }
  179. ]
  180. },
  181. {
  182. label: "三星",
  183. list: [
  184. {
  185. name: "三星 Galaxy S24",
  186. price: 7999,
  187. image: "/static/goods/galaxys24.png"
  188. },
  189. {
  190. name: "三星 Galaxy Z Flip5",
  191. price: 8999,
  192. image: "/static/goods/galaxyzflip5.png"
  193. }
  194. ]
  195. },
  196. {
  197. label: "OPPO",
  198. list: [
  199. {
  200. name: "OPPO Find X7",
  201. price: 4999,
  202. image: "/static/goods/findx7.png"
  203. }
  204. ]
  205. },
  206. {
  207. label: "VIVO",
  208. list: [
  209. {
  210. name: "VIVO X100 Pro",
  211. price: 5999,
  212. image: "/static/goods/x100pro.png"
  213. }
  214. ]
  215. },
  216. {
  217. label: "荣耀",
  218. list: [
  219. {
  220. name: "荣耀 Magic6",
  221. price: 4599,
  222. image: "/static/goods/magic6.png"
  223. }
  224. ]
  225. },
  226. {
  227. label: "一加",
  228. list: [
  229. {
  230. name: "一加 12",
  231. price: 4299,
  232. image: "/static/goods/oneplus12.png"
  233. }
  234. ]
  235. },
  236. {
  237. label: "红米",
  238. list: [
  239. {
  240. name: "红米 K70 Pro",
  241. price: 3299,
  242. image: "/static/goods/k70pro.png"
  243. }
  244. ]
  245. },
  246. {
  247. label: "魅族",
  248. list: [
  249. {
  250. name: "魅族 21",
  251. price: 3999,
  252. image: "/static/goods/meizu21.png"
  253. }
  254. ]
  255. },
  256. {
  257. label: "坚果",
  258. list: [
  259. {
  260. name: "坚果 R2",
  261. price: 2999,
  262. image: "/static/goods/nutR2.png"
  263. }
  264. ]
  265. },
  266. {
  267. label: "其他",
  268. list: [
  269. {
  270. name: "诺基亚 X30",
  271. price: 2599,
  272. image: "/static/goods/nokiax30.png"
  273. }
  274. ]
  275. }
  276. ]);
  277. // 滚动到指定分类
  278. const scrollIntoView = ref("");
  279. // 滚动锁定
  280. const scrollLock = ref(false);
  281. // 当前选中的分类
  282. const categoryActive = ref(0);
  283. // 分类切换
  284. function onCategoryChange(index: number) {
  285. categoryActive.value = index;
  286. scrollIntoView.value = `category-item-${index}`;
  287. scrollLock.value = true;
  288. setTimeout(() => {
  289. scrollLock.value = false;
  290. }, 350);
  291. }
  292. // 滚动时,更新当前选中的分类
  293. function onScroll(e: UniScrollEvent) {
  294. if (scrollLock.value) return;
  295. const top = e.detail.scrollTop;
  296. list.value.forEach((e, i) => {
  297. if (top >= e.top!) {
  298. categoryActive.value = i;
  299. }
  300. });
  301. }
  302. // 初始化
  303. function init() {
  304. uni.createSelectorQuery()
  305. .in(proxy!.$root)
  306. .selectAll(".category-item")
  307. .boundingClientRect((res) => {
  308. (res as NodeInfo[]).forEach((e, i, arr) => {
  309. list.value[i].top = (e.top ?? 0) - (arr[0].top ?? 0);
  310. });
  311. })
  312. .exec();
  313. }
  314. onReady(() => {
  315. init();
  316. });
  317. </script>