index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. <template>
  2. <Theme>
  3. <view class="wrap" :style="{ '--tabbarHeight': tabbarHeight + 'px' }">
  4. <view class="cont">
  5. <view class="cont_bg" id="tob">
  6. <Navbar bgColor="transparent" fixed>
  7. <template #left>
  8. <view class="nav_left">
  9. <image
  10. src="@/static/logo.png"
  11. class="nav_logo"
  12. mode="widthFix"
  13. ></image>
  14. </view>
  15. </template>
  16. <template #right>
  17. <view class="nav_right">
  18. <view class="icon-badge-wrap">
  19. <image
  20. src="/static/home/kefu.png"
  21. class="mobile_system"
  22. @click="toGo('/pages/setting/system')"
  23. >
  24. </image>
  25. <up-badge
  26. type="error"
  27. max="99"
  28. :value="unreadCount"
  29. class="tips"
  30. v-if="unreadCount > 0"
  31. ></up-badge>
  32. </view>
  33. <image
  34. src="/static/language.png"
  35. class="mobile_language"
  36. @click="toGo('/pages/setting/language_currency')"
  37. ></image>
  38. <navMenu />
  39. </view>
  40. </template>
  41. </Navbar>
  42. <view class="bg_top">
  43. <view class="search">
  44. <Search
  45. v-model="searchValue"
  46. border="none"
  47. @click="searchClick"
  48. :placeholder="t('搜索商品')"
  49. >
  50. <template #prefix>
  51. <i class="icon-font icon-search"></i>
  52. </template>
  53. <template #suffix>
  54. <i class="icon-font icon-camera"></i>
  55. </template>
  56. </Search>
  57. </view>
  58. </view>
  59. </view>
  60. <scroll-view
  61. class="content-scroll"
  62. scroll-y
  63. refresher-enabled
  64. :refresher-triggered="refreshering"
  65. @refresherrefresh="onRefresherRefresh"
  66. @refresherrestore="onRefresherRestore"
  67. refresher-default-style="none"
  68. :style="{ height: contentHeight + 'px' }"
  69. >
  70. <view class="refresher" v-if="refreshering">
  71. <up-loadmore :status="'loading'" :loadingText="''" />
  72. </view>
  73. <view class="content">
  74. <!-- 轮播图区域 -->
  75. <view class="banner-section">
  76. <Swiper @click="swiperClick" class="swiper-box" />
  77. </view>
  78. <!-- 分类导航 -->
  79. <view class="category-section">
  80. <view class="category-title">
  81. <trans _t="商品分类" />
  82. </view>
  83. <view class="category-grid">
  84. <view
  85. class="category-item"
  86. v-for="(item, index) in categoryList"
  87. :key="index"
  88. @click="toCategory(item)"
  89. >
  90. <image :src="item.icon" class="category-icon" />
  91. <text class="category-name">{{ t(item.name) }}</text>
  92. </view>
  93. <!-- 录音功能入口 -->
  94. <view class="category-item record-item" @click="toRecord">
  95. <view class="record-icon">
  96. <i class="icon-font icon-microphone"></i>
  97. </view>
  98. <text class="category-name">录音功能</text>
  99. </view>
  100. </view>
  101. </view>
  102. <!-- 热门商品 -->
  103. <view class="hot-products-section">
  104. <view class="section-title">
  105. <trans _t="热门商品" />
  106. <view class="more-btn" @click="toMoreProducts">
  107. <trans _t="更多" />
  108. <i class="icon-font icon-arrow-right"></i>
  109. </view>
  110. </view>
  111. <view class="products-grid">
  112. <view
  113. class="product-item"
  114. v-for="(item, index) in hotProducts"
  115. :key="index"
  116. @click="toProductDetail(item)"
  117. >
  118. <image :src="item.image" class="product-image" />
  119. <view class="product-info">
  120. <text class="product-name">{{ item.name }}</text>
  121. <view class="product-price">
  122. <text class="current-price"
  123. >{{ symbol.symbol }}{{ item.price }}</text
  124. >
  125. <text class="original-price" v-if="item.originalPrice"
  126. >{{ symbol.symbol }}{{ item.originalPrice }}</text
  127. >
  128. </view>
  129. <view class="product-tags">
  130. <text class="tag" v-for="tag in item.tags" :key="tag">{{
  131. tag
  132. }}</text>
  133. </view>
  134. </view>
  135. </view>
  136. </view>
  137. </view>
  138. </view>
  139. </scroll-view>
  140. </view>
  141. </view>
  142. <IosUpdateModal />
  143. <blindModel />
  144. <Tabbar page="index" @getTabbarHeight="getTabbarHeight" />
  145. </Theme>
  146. </template>
  147. <script setup>
  148. import Tabbar from "@/components/tabbar";
  149. import Navbar from "@/components/navbar";
  150. import Swiper from "@/components/swiper";
  151. import blindModel from "@/components/blindModel";
  152. import Search from "@/components/input";
  153. import navMenu from "@/components/nav_menu";
  154. import IosUpdateModal from "@/components/IosUpdateModal";
  155. import { computed, ref, watch, nextTick, onMounted } from "vue";
  156. import {
  157. onLoad,
  158. onShow,
  159. onUnload,
  160. onHide,
  161. onPageScroll,
  162. } from "@dcloudio/uni-app";
  163. import { storeToRefs } from "pinia";
  164. import {
  165. useShopStore,
  166. useSystemStore,
  167. useMessageStore,
  168. useTabbarStore,
  169. useUserStore,
  170. } from "@/store";
  171. import { t } from "@/locale";
  172. import { query, systemInfo } from "@/utils";
  173. const useShop = useShopStore();
  174. const useSystem = useSystemStore();
  175. const useTabbar = useTabbarStore();
  176. const useUser = useUserStore();
  177. const langList = computed(() => useSystem.getLangeuage);
  178. const lang = computed(() => useSystem.getLang);
  179. const token = computed(() => useUser.getToken);
  180. const symbol = computed(() => useSystem.getSymbol);
  181. const userType = computed(() => useUser.getUserType);
  182. const useMessage = useMessageStore();
  183. const { globalMap } = storeToRefs(useMessage);
  184. const unreadCount = ref(0);
  185. watch(
  186. () => globalMap.value.noticeChannel,
  187. (newVal, oldVal) => {
  188. if (newVal) {
  189. unreadCount.value = newVal.unreadCount;
  190. }
  191. },
  192. {
  193. deep: true,
  194. immediate: true,
  195. }
  196. );
  197. const searchValue = ref("");
  198. const refreshering = ref(false);
  199. const contentHeight = ref(0);
  200. const tabbarHeight = ref(0);
  201. // 轮播图数据
  202. const bannerList = ref([
  203. {
  204. id: 1,
  205. image: "/static/home/banner1.png",
  206. title: "新品上市",
  207. action: "goto_product",
  208. },
  209. {
  210. id: 2,
  211. image: "/static/home/banner2.png",
  212. title: "限时优惠",
  213. action: "goto_activity",
  214. },
  215. ]);
  216. // 分类数据
  217. const categoryList = ref([
  218. { id: 1, name: "服装", icon: "/static/shop/category_clothes.png" },
  219. { id: 2, name: "美妆", icon: "/static/shop/category_beauty.png" },
  220. { id: 3, name: "数码", icon: "/static/shop/category_digital.png" },
  221. { id: 4, name: "家居", icon: "/static/shop/category_home.png" },
  222. { id: 5, name: "食品", icon: "/static/shop/category_food.png" },
  223. { id: 6, name: "母婴", icon: "/static/shop/category_baby.png" },
  224. { id: 7, name: "运动", icon: "/static/shop/category_sports.png" },
  225. { id: 8, name: "更多", icon: "/static/shop/category_more.png" },
  226. ]);
  227. // 热门商品数据
  228. const hotProducts = ref([
  229. {
  230. id: 1,
  231. name: "时尚连衣裙",
  232. image: "/static/shop/product1.png",
  233. price: "299.00",
  234. originalPrice: "399.00",
  235. tags: ["热销", "包邮"],
  236. },
  237. {
  238. id: 2,
  239. name: "护肤精华",
  240. image: "/static/shop/product2.png",
  241. price: "199.00",
  242. originalPrice: "299.00",
  243. tags: ["新品", "限时"],
  244. },
  245. {
  246. id: 3,
  247. name: "无线耳机",
  248. image: "/static/shop/product3.png",
  249. price: "599.00",
  250. originalPrice: "799.00",
  251. tags: ["爆款", "包邮"],
  252. },
  253. {
  254. id: 4,
  255. name: "智能手表",
  256. image: "/static/shop/product4.png",
  257. price: "1299.00",
  258. originalPrice: "1599.00",
  259. tags: ["科技", "热销"],
  260. },
  261. ]);
  262. // 推荐商品数据
  263. const recommendProducts = ref([
  264. {
  265. id: 5,
  266. name: "经典牛仔裤",
  267. image: "/static/shop/product5.png",
  268. price: "199.00",
  269. sales: 1234,
  270. },
  271. {
  272. id: 6,
  273. name: "口红套装",
  274. image: "/static/shop/product6.png",
  275. price: "299.00",
  276. sales: 567,
  277. },
  278. {
  279. id: 7,
  280. name: "运动鞋",
  281. image: "/static/shop/product7.png",
  282. price: "399.00",
  283. sales: 890,
  284. },
  285. {
  286. id: 8,
  287. name: "保温杯",
  288. image: "/static/shop/product8.png",
  289. price: "89.00",
  290. sales: 2345,
  291. },
  292. ]);
  293. const searchClick = () => {
  294. toGo("/pagesBuyer/home/search");
  295. };
  296. const toGo = (url) => {
  297. if (!url) return;
  298. uni.navigateTo({ url });
  299. };
  300. const toCategory = (category) => {
  301. toGo(`/pagesBuyer/home/category?id=${category.id}&name=${category.name}`);
  302. };
  303. const toProductDetail = (product) => {
  304. toGo(`/pagesBuyer/home/product?id=${product.id}`);
  305. };
  306. const toMoreProducts = () => {
  307. toGo("/pagesBuyer/home/products");
  308. };
  309. const toRecord = () => {
  310. toGo("/pagesBuyer/home/record");
  311. };
  312. const swiperClick = (item) => {
  313. if (item.action == "goto_product") {
  314. toGo("/pagesBuyer/home/products");
  315. } else if (item.action == "goto_activity") {
  316. toGo("/pagesBuyer/home/activity");
  317. }
  318. };
  319. const getTabbarHeight = (height) => {
  320. tabbarHeight.value = height;
  321. };
  322. const loadPageData = async () => {
  323. // 加载页面数据
  324. await useSystem.setBoxes({}, { isLoading: true });
  325. };
  326. onShow(() => {
  327. loadPageData();
  328. });
  329. onMounted(() => {
  330. nextTick(async () => {
  331. const res = await query("#tob");
  332. const { windowHeight, statusBarHeight } = systemInfo();
  333. contentHeight.value = windowHeight - res.height - statusBarHeight;
  334. });
  335. });
  336. uni.hideTabBar();
  337. const onRefresherRefresh = async () => {
  338. try {
  339. refreshering.value = true;
  340. await loadPageData();
  341. } finally {
  342. refreshering.value = false;
  343. }
  344. };
  345. const onRefresherRestore = () => {
  346. refreshering.value = false;
  347. };
  348. </script>
  349. <style lang="less" scoped>
  350. .wrap {
  351. background: var(--bg);
  352. min-height: calc(100vh - var(--tabbarHeight));
  353. padding-bottom: var(--tabbarHeight);
  354. .nav_left {
  355. display: flex;
  356. align-items: center;
  357. .nav_logo {
  358. width: 284rpx;
  359. }
  360. }
  361. .nav_right {
  362. display: flex;
  363. align-items: center;
  364. .mobile_language {
  365. width: 48rpx;
  366. height: 48rpx;
  367. margin: 0 20rpx 0 24rpx;
  368. }
  369. .icon-badge-wrap {
  370. position: relative;
  371. display: inline-block;
  372. width: 48rpx;
  373. height: 48rpx;
  374. .mobile_system {
  375. width: 48rpx;
  376. height: 48rpx;
  377. }
  378. .tips {
  379. position: absolute;
  380. right: -10rpx;
  381. top: -8rpx;
  382. z-index: 2;
  383. }
  384. }
  385. }
  386. .cont {
  387. width: 100%;
  388. background-color: var(--light);
  389. &_bg {
  390. position: sticky;
  391. top: 0;
  392. z-index: 9999;
  393. background-color: var(--bg);
  394. .bg_top {
  395. padding: 0 30rpx;
  396. .search {
  397. padding: 26rpx 0;
  398. :deep(.u-input) {
  399. padding: 0 20px 0 16px !important;
  400. .u-input__content__field-wrapper__field {
  401. height: 47px;
  402. }
  403. }
  404. .icon-search {
  405. color: var(--text);
  406. font-size: 40rpx;
  407. }
  408. .icon-camera {
  409. color: var(--text-01);
  410. font-size: 70rpx;
  411. }
  412. }
  413. .debug-info {
  414. padding: 10rpx 0;
  415. text-align: center;
  416. text {
  417. font-size: 24rpx;
  418. color: var(--primary);
  419. background: rgba(255, 107, 107, 0.1);
  420. padding: 8rpx 16rpx;
  421. border-radius: 20rpx;
  422. }
  423. }
  424. }
  425. }
  426. .content-scroll {
  427. overflow: hidden;
  428. }
  429. .content {
  430. width: 100%;
  431. padding: 0 30rpx 30rpx;
  432. position: relative;
  433. box-sizing: border-box;
  434. .banner-section {
  435. margin-bottom: 32rpx;
  436. .swiper-box {
  437. width: 100%;
  438. border-radius: 20rpx;
  439. overflow: hidden;
  440. }
  441. }
  442. .category-section {
  443. margin-bottom: 32rpx;
  444. .category-title {
  445. font-size: 36rpx;
  446. font-weight: bold;
  447. color: var(--black);
  448. margin-bottom: 24rpx;
  449. }
  450. .category-grid {
  451. display: grid;
  452. grid-template-columns: repeat(4, 1fr);
  453. gap: 24rpx;
  454. .category-item {
  455. display: flex;
  456. flex-direction: column;
  457. align-items: center;
  458. padding: 24rpx 12rpx;
  459. background: var(--inputBg);
  460. border-radius: 20rpx;
  461. .category-icon {
  462. width: 60rpx;
  463. height: 60rpx;
  464. margin-bottom: 12rpx;
  465. }
  466. .category-name {
  467. font-size: 24rpx;
  468. color: var(--text);
  469. text-align: center;
  470. }
  471. &.record-item {
  472. background: linear-gradient(135deg, var(--primary), #ff6b6b);
  473. position: relative;
  474. overflow: hidden;
  475. &::before {
  476. content: "";
  477. position: absolute;
  478. top: -50%;
  479. left: -50%;
  480. width: 200%;
  481. height: 200%;
  482. background: radial-gradient(
  483. circle,
  484. rgba(255, 255, 255, 0.1) 0%,
  485. transparent 70%
  486. );
  487. animation: shimmer 3s infinite;
  488. }
  489. .record-icon {
  490. width: 60rpx;
  491. height: 60rpx;
  492. border-radius: 50%;
  493. background: rgba(255, 255, 255, 0.2);
  494. display: flex;
  495. align-items: center;
  496. justify-content: center;
  497. margin-bottom: 12rpx;
  498. position: relative;
  499. z-index: 1;
  500. .icon-microphone {
  501. font-size: 32rpx;
  502. color: var(--light);
  503. }
  504. }
  505. .category-name {
  506. color: var(--light);
  507. font-weight: bold;
  508. position: relative;
  509. z-index: 1;
  510. }
  511. }
  512. }
  513. }
  514. }
  515. .hot-products-section,
  516. .recommend-section {
  517. margin-bottom: 32rpx;
  518. .section-title {
  519. display: flex;
  520. justify-content: space-between;
  521. align-items: center;
  522. font-size: 36rpx;
  523. font-weight: bold;
  524. color: var(--black);
  525. margin-bottom: 24rpx;
  526. .more-btn {
  527. display: flex;
  528. align-items: center;
  529. font-size: 28rpx;
  530. color: var(--text-01);
  531. .icon-arrow-right {
  532. margin-left: 8rpx;
  533. font-size: 24rpx;
  534. }
  535. }
  536. }
  537. .products-grid {
  538. display: grid;
  539. grid-template-columns: repeat(2, 1fr);
  540. gap: 24rpx;
  541. .product-item {
  542. background: var(--inputBg);
  543. border-radius: 20rpx;
  544. overflow: hidden;
  545. .product-image {
  546. width: 100%;
  547. height: 300rpx;
  548. object-fit: cover;
  549. }
  550. .product-info {
  551. padding: 20rpx;
  552. .product-name {
  553. font-size: 28rpx;
  554. color: var(--black);
  555. display: -webkit-box;
  556. -webkit-line-clamp: 2;
  557. line-clamp: 2;
  558. -webkit-box-orient: vertical;
  559. overflow: hidden;
  560. margin-bottom: 12rpx;
  561. }
  562. .product-price {
  563. display: flex;
  564. align-items: center;
  565. margin-bottom: 12rpx;
  566. .current-price {
  567. font-size: 32rpx;
  568. font-weight: bold;
  569. color: var(--red);
  570. margin-right: 12rpx;
  571. }
  572. .original-price {
  573. font-size: 24rpx;
  574. color: var(--text-01);
  575. text-decoration: line-through;
  576. }
  577. }
  578. .product-tags {
  579. display: flex;
  580. gap: 8rpx;
  581. .tag {
  582. font-size: 20rpx;
  583. color: var(--primary);
  584. background: rgba(255, 107, 107, 0.1);
  585. padding: 4rpx 12rpx;
  586. border-radius: 12rpx;
  587. }
  588. }
  589. }
  590. }
  591. }
  592. .recommend-list {
  593. .recommend-item {
  594. display: flex;
  595. background: var(--inputBg);
  596. border-radius: 20rpx;
  597. padding: 20rpx;
  598. margin-bottom: 16rpx;
  599. .recommend-image {
  600. width: 160rpx;
  601. height: 160rpx;
  602. border-radius: 16rpx;
  603. margin-right: 20rpx;
  604. }
  605. .recommend-info {
  606. flex: 1;
  607. display: flex;
  608. flex-direction: column;
  609. justify-content: space-between;
  610. .recommend-name {
  611. font-size: 28rpx;
  612. color: var(--black);
  613. display: -webkit-box;
  614. -webkit-line-clamp: 2;
  615. line-clamp: 2;
  616. -webkit-box-orient: vertical;
  617. overflow: hidden;
  618. margin-bottom: 12rpx;
  619. }
  620. .recommend-price {
  621. display: flex;
  622. justify-content: space-between;
  623. align-items: center;
  624. .price {
  625. font-size: 32rpx;
  626. font-weight: bold;
  627. color: var(--red);
  628. }
  629. .sales {
  630. font-size: 24rpx;
  631. color: var(--text-01);
  632. }
  633. }
  634. }
  635. }
  636. }
  637. }
  638. }
  639. }
  640. }
  641. @keyframes shimmer {
  642. 0% {
  643. transform: rotate(0deg);
  644. }
  645. 100% {
  646. transform: rotate(360deg);
  647. }
  648. }
  649. </style>