index.vue 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501
  1. <!--
  2. * @Author: jmy
  3. * @Date: 2026-01-06 12:02:41
  4. * @LastEditors: Please set LastEditors
  5. * @LastEditTime: 2026-01-26
  6. * @Description: 首页
  7. -->
  8. <template>
  9. <view class="home-page">
  10. <image src="https://cdn.his.cdwjyyh.com/images/yuexuan_home_top_bg.png" class="home-bg"></image>
  11. <uni-nav-bar fixed :statusBar="true" left-icon="back" backgroundColor="" :border="false" rightWidth="0"
  12. leftWidth="0">
  13. <template v-slot:default>
  14. <view class="nav-header">
  15. <view class="nav-title">
  16. <view class="main-title">芳华悦选</view>
  17. <view class="sub-title">让家人放心吃健康安全的食品</view>
  18. </view>
  19. <view class="nav-cart">
  20. <image class="cart-icon" src="https://cdn.his.cdwjyyh.com/images/shopping_car_icon.png"></image>
  21. <view class="cart-badge">{{ cartCount }}</view>
  22. </view>
  23. </view>
  24. </template>
  25. </uni-nav-bar>
  26. <!-- 搜索栏 -->
  27. <HomeSearch @onSearch="onSearch" @onSearchClick="onSearchClick" />
  28. <view class="home-content">
  29. <ScsScrollView ref="fresher" refresherBackground="transparent" :refresherEnabled="false"
  30. @onfresher="onfresher" @loadMore="loadMore" @handleScroll="(flag) => isShowToTop = flag">
  31. <template v-slot:list>
  32. <!-- tab栏 -->
  33. <view class="nav-bar">
  34. <view class="nav-container">
  35. <ScsScrollNavbar :tabsData="tabsData" :tabCurrentIndex="tabCurrentIndex" nameKey="name"
  36. key="tabCurrentIndex" @tabChange="tabChange" />
  37. <image class="gradient-bg" src="https://cdn.his.cdwjyyh.com/images/white_gradient_bg.png">
  38. </image>
  39. <view class="nav-actions">
  40. <image class="action-icon product-icon" @click="navTo('/pages/shopping/index')"
  41. src="https://cdn.his.cdwjyyh.com/images/product_section_icon.png"></image>
  42. <image class="action-icon filter-icon"
  43. src="https://cdn.his.cdwjyyh.com/images/home_filter_icon.png"
  44. @click="toggleChannel"></image>
  45. </view>
  46. </view>
  47. <HomeMenu :autoplay="false" :swiperList="menusData" />
  48. </view>
  49. <!-- 商品栏 -->
  50. <HomeProduct :recommendList="recommendList" />
  51. <!-- tab栏 -->
  52. <view class="product-tabs">
  53. <ScsScrollNavbar :tabsData="tabsProduct" activeColor="#02B176" textColor="#333333"
  54. :isUseActImg="true" :isScroll="false" :isUseLine="false" :calcLeft="28"
  55. :tabCurrentIndex="tabsProductIndex" nameKey="name" key="tabsProductIndex"
  56. @tabChange="tabProductChange" />
  57. </view>
  58. <!-- 商品列表 -->
  59. <HomeGoodsItem :productList="currentCategoryProducts" @addToCart="addToCart" @goToBuy="goToBuy" />
  60. <!-- 销量榜和瀑布流商品容器 -->
  61. <view class="sales-waterfall">
  62. </view>
  63. <view class="px-12 mt-14">
  64. <HomeGoodsItem :productList="currentCategoryProducts" @addToCart="addToCart"
  65. @goToBuy="goToBuy" />
  66. </view>
  67. <!-- 更多列表 -->
  68. <!-- 实时销量榜 -->
  69. <!-- 瀑布流商品 -->
  70. <view class="goods-list">
  71. <view class="sales-ranking">
  72. <image class="bg"
  73. src="https://fs-1319721001.cos.ap-chongqing.myqcloud.com/images/sales_ranking_bg.png"
  74. mode="aspectFill"></image>
  75. <view class="sales-ranking-content">
  76. <view class="sales-ranking-title">
  77. <text>实时销量榜</text>
  78. <image
  79. src="https://fs-1319721001.cos.ap-chongqing.myqcloud.com/images/sales_ranking_arrow_right_icon.png">
  80. </image>
  81. </view>
  82. <view class="sales-ranking-total">
  83. 今日销量{{ formatSales(totalSales) }}件
  84. </view>
  85. <view class="sales-ranking-list" v-if="salesRankList">
  86. <!-- 显示实际数据 -->
  87. <view class="sales-ranking-item" v-for="(itm, index) in salesRankList"
  88. :key="index" @click="goToBuy(itm)">
  89. <view class="sales-ranking-item-img">
  90. <image
  91. :src="itm.image || 'https://fs-1319721001.cos.ap-chongqing.myqcloud.com/images/img.png'">
  92. </image>
  93. <view class="sales-ranking-item-rank" :class="'rank-' + (index + 1)">
  94. {{ index + 1 }}</view>
  95. </view>
  96. <view class="sales-ranking-item-info">
  97. <view class="sales-ranking-item-name">{{ itm.productName || '商品名称' }}
  98. </view>
  99. <view class="sales-ranking-item-price">
  100. <view>
  101. <text>¥</text>
  102. <text>{{ itm.price || 0 }}</text>
  103. </view>
  104. <image @click.stop="addToCart(itm)" class="sales-ranking-item-cart"
  105. src="https://fs-1319721001.cos.ap-chongqing.myqcloud.com/images/add_car_icon.png">
  106. </image>
  107. </view>
  108. </view>
  109. </view>
  110. </view>
  111. </view>
  112. </view>
  113. <view class="item" v-for="(item,index) in productList" :key="index" @click="goToBuy(item)">
  114. <view class="img-box">
  115. <image :src="item.firstImage" mode="aspectFit"></image>
  116. </view>
  117. <view class="info-box">
  118. <view class="title ellipsis2">{{item.title}}</view>
  119. <view class="intro ellipsis">{{item.intro}}</view>
  120. <view class="sale">已售 {{item.sales}} | 惊艳度{{item.amazingDegree}}%</view>
  121. <view class="lable-group">
  122. <view class="lable-item" v-if="item.otPrice > item.price">
  123. {{Math.round((1 - item.price / item.otPrice) * 10 * 10) / 10}}折</view>
  124. </view>
  125. <view class="prce-num">
  126. <view class="price">
  127. <text class="unit">¥</text><text
  128. class="bold">{{parseInt(item.price)}}</text>{{item.price.includes('.') ? '.' + item.price.split('.')[1] : '.00'}}
  129. </view>
  130. <view class="cart-img" @click.stop="addToCart(item)">
  131. <image class="w40 h40"
  132. src="https://fs-1319721001.cos.ap-chongqing.myqcloud.com/images/add_car.png">
  133. </image>
  134. </view>
  135. </view>
  136. <view class="card">
  137. <view class="card-item">
  138. <image class="img"
  139. src="https://fs-1319721001.cos.ap-chongqing.myqcloud.com/images/ranking_icon.png">
  140. </image>
  141. <text class="ranking">健康新品热销榜</text>
  142. <text class="bold">TOP.2</text>
  143. </view>
  144. <image class="go"
  145. src="https://fs-1319721001.cos.ap-chongqing.myqcloud.com/images/jb_arrow_right_icon.png">
  146. </image>
  147. </view>
  148. </view>
  149. </view>
  150. </view>
  151. <u-gap height="15"></u-gap>
  152. <!-- 频道 可拖拽 -->
  153. <channel ref="channelManager" :initial-my-channels="myChannels" :initial-all-channels="allChannels"
  154. :active-channel-id="activeChannelId" :show="showChannel" @channels-change="onChannelsChange"
  155. @order-change="onOrderChange" @channel-tap="onChannelTap" @close="showChannel = false" />
  156. <!-- 商品规格选择弹窗 -->
  157. <ProductSpecPopup :visible="specVisible" :product="product" :is-buy-mode="isBuyMode"
  158. @update:visible="specVisible = $event" @cart-updated="getCartCount" />
  159. </template>
  160. </ScsScrollView>
  161. </view>
  162. </view>
  163. </template>
  164. <script>
  165. import {
  166. getMyProductCate,
  167. channelAdd,
  168. channelDelete,
  169. } from "@/api/index.js"
  170. import HomeMenu from './components/home-menu.vue'
  171. import HomeProduct from './components/home-product.vue'
  172. import HomeSearch from './components/home-search.vue'
  173. import HomeGoodsItem from './components/home-goods-item.vue'
  174. import HomeGoodsItemTest from './components/home-goods-item-test.vue'
  175. import ScsHelangWaterfall from '@/components/public/scs-helang-waterfall.vue'
  176. import channel from '@/components/channel.vue'
  177. import PxPopupBottom from '@/components/px-popup-bottom/px-popup-bottom.vue'
  178. import ProductSpecPopup from '@/components/product-spec-popup/product-spec-popup.vue'
  179. import {
  180. // getMenu,
  181. productWaterfall, // 商品搜索瀑布流
  182. recommendProduct, // 获取首页推荐商品
  183. addCart, // 添加购物车
  184. getCartCount, // 获取商品购物车数量
  185. getCarts, // 获取购物车列表
  186. getProductValues, //获取商品规格列表
  187. } from '@/api/index'
  188. import {
  189. productSalesRanking //获取实时销量榜
  190. } from '@/api/product.js'
  191. export default {
  192. components: {
  193. HomeMenu,
  194. HomeProduct,
  195. HomeSearch,
  196. HomeGoodsItem,
  197. HomeGoodsItemTest,
  198. channel,
  199. ScsHelangWaterfall,
  200. PxPopupBottom,
  201. ProductSpecPopup,
  202. },
  203. data() {
  204. return {
  205. // 频道相关数据
  206. showChannel: false, // 默认不显示
  207. channelManager: false,
  208. activeChannelId: 1,
  209. myChannels: [],
  210. allChannels: [],
  211. // UI状态数据
  212. statusBarHeight: 0,
  213. isShowToTop: false,
  214. // Tab数据
  215. tabsData: [],
  216. tabCurrentIndex: 0,
  217. tabsProduct: [{
  218. name: '今日主推'
  219. },
  220. {
  221. name: '健康新品'
  222. },
  223. {
  224. name: '营养保健'
  225. }
  226. ],
  227. tabsProductIndex: 0,
  228. // 菜单数据
  229. menusData: [],
  230. // 商品列表数据
  231. goodList: [1, 2],
  232. // 购物车数据
  233. cartCount: 0,
  234. cartList: [],
  235. // 加载状态
  236. loading: false,
  237. refreshing: false,
  238. // 分页
  239. page: 1,
  240. pageSize: 10,
  241. // 是否有更多数据
  242. hasMore: true,
  243. // 商品数据
  244. recommendList: [], //推荐商品
  245. productList: [], //瀑布流商品
  246. salesRankList: [], //实时销量榜数据
  247. totalSales: 0, //今日总销量
  248. // 分类商品数据
  249. categoryProducts: {
  250. '今日主推': [],
  251. '健康新品': [],
  252. '营养保健': []
  253. },
  254. // 当前选中分类的商品列表
  255. currentCategoryProducts: [],
  256. // 瀑布流数据
  257. waterfallList: [],
  258. // 弹窗相关数据
  259. specVisible: false,
  260. productValueSelect: {
  261. image: '',
  262. price: 0
  263. },
  264. product: {
  265. image: ''
  266. },
  267. specNum: 1,
  268. isSubmitting: false,
  269. attrs: [],
  270. isBuyMode: false, // 标记是否为购买模式
  271. productSpecs: [], // 商品规格列表
  272. selectedSpec: null // 选中的规格
  273. }
  274. },
  275. onLoad() {
  276. this.getProductWaterfall(); // 商品搜索瀑布流
  277. this.getRecommendProduct(); // 获取首页推荐商品
  278. this.getCartCount(); // 获取商品购物车数量
  279. this.getCarts(); //获取购物车列表
  280. this.getMyProductCate(); // 获取我的频道
  281. this.getSalesRanking(); // 获取实时销量榜
  282. // 页面加载时初始化
  283. },
  284. onShow() {
  285. // this.getMenuData();
  286. },
  287. methods: {
  288. toDetail(product){
  289. console.log("商品详情", product);
  290. uni.navigateTo({
  291. url: '/pages_shopping/shopping/productDetails?productId=' + product.productId
  292. });
  293. },
  294. // 获取我的频道
  295. getMyProductCate() {
  296. getMyProductCate().then(
  297. res => {
  298. if (res.code == 200) {
  299. console.log("获取我的频道", res.data);
  300. if (res.data && res.data.length > 0) {
  301. const channels = res.data.map(item => ({
  302. id: item.cateId,
  303. name: item.cateName,
  304. pic: item.pic,
  305. isSelect: item.isSelect
  306. }));
  307. // 更新频道数据
  308. this.myChannels = channels.filter(item => item.isSelect === 1);
  309. this.allChannels = channels;
  310. // 更新菜单数据
  311. const menuItems = this.myChannels.map(channel => ({
  312. icon: channel.pic || '',
  313. menuName: channel.name || ''
  314. }));
  315. this.menusData = this.splitArrayIntoSubarrays(menuItems, 5);
  316. console.log('更新菜单数据:', this.menusData);
  317. // 更新 tabsData 数组,用于 scs-scroll-navbar 组件显示频道名称
  318. this.tabsData = this.myChannels;
  319. }
  320. }
  321. },
  322. rej => {
  323. console.error("获取我的频道失败", rej);
  324. }
  325. );
  326. },
  327. // 获取实时销量榜
  328. getSalesRanking() {
  329. productSalesRanking({
  330. cateId: 320
  331. }).then(
  332. res => {
  333. if (res.code == 200) {
  334. if (res.data && res.data.list) {
  335. this.salesRankList = res.data.list.slice(0, 3); // 只取前3个
  336. console.log("实时销量榜数据", this.salesRankList);
  337. this. totalSales =res.data.total;
  338. }
  339. }
  340. },
  341. rej => {
  342. console.error("获取实时销量榜失败", rej);
  343. }
  344. );
  345. },
  346. // 格式化销量数据
  347. formatSales(sales) {
  348. if (sales >= 10000) {
  349. return (sales / 10000).toFixed(1) + '万';
  350. }
  351. return sales;
  352. },
  353. navTo(url) {
  354. uni.navigateTo({
  355. url: url
  356. });
  357. },
  358. getProductValues() {
  359. getProductValues(data).then(
  360. res => {
  361. if (res.code == 200) {
  362. console.log("获取我的频道", res);
  363. } else {
  364. }
  365. },
  366. rej => {
  367. }
  368. );
  369. },
  370. // 分类tab切换
  371. tabChange(index) {
  372. this.tabCurrentIndex = index;
  373. // 这里可以添加分类切换后的数据加载逻辑
  374. },
  375. // 商品tab切换
  376. tabProductChange(index) {
  377. this.tabsProductIndex = index;
  378. // 更新当前分类商品
  379. const currentCategory = this.tabsProduct[index].name;
  380. this.currentCategoryProducts = this.categoryProducts[currentCategory] || [];
  381. console.log("切换到分类", currentCategory, "商品数量", this.currentCategoryProducts.length);
  382. },
  383. // 搜索
  384. onSearch(keyword) {
  385. console.log('搜索关键词:', keyword);
  386. // 跳转到商品搜索页面
  387. uni.navigateTo({
  388. url: `/pages/home/productSearch?keyword=${encodeURIComponent(keyword)}`
  389. });
  390. },
  391. // 点击搜索框
  392. onSearchClick() {
  393. console.log('点击搜索框');
  394. // 跳转到商品搜索页面
  395. uni.navigateTo({
  396. url: `/pages/home/productSearch`
  397. });
  398. },
  399. // 切换频道显示/隐藏
  400. toggleChannel() {
  401. this.showChannel = !this.showChannel;
  402. },
  403. // 商品搜索瀑布流
  404. getProductWaterfall(isLoadMore = false) {
  405. console.log("开始获取瀑布流商品数据");
  406. if (this.loading && !isLoadMore) return;
  407. this.loading = true;
  408. let data = {
  409. page: isLoadMore ? this.page + 1 : 1,
  410. pageSize: this.pageSize
  411. };
  412. console.log("调用productWaterfall API,参数:", data);
  413. productWaterfall(data).then(
  414. res => {
  415. console.log("productWaterfall API返回结果:", res);
  416. // 检查返回数据结构
  417. if (!res) {
  418. console.error("API返回结果为空");
  419. uni.showToast({
  420. title: '服务器返回数据异常',
  421. icon: 'none'
  422. });
  423. this.loading = false;
  424. this.refreshing = false;
  425. return;
  426. }
  427. if (res.code == 200) {
  428. // 确保 res.data 是数组
  429. const productData = Array.isArray(res.data) ? res.data : [];
  430. if (isLoadMore) {
  431. // 加载更多
  432. if (productData.length > 0) {
  433. this.productList = [...this.productList, ...productData];
  434. console.log("加载更多商品后,productList长度:", this.productList.length);
  435. this.page += 1;
  436. this.hasMore = productData.length === this.pageSize;
  437. } else {
  438. this.hasMore = false;
  439. }
  440. } else {
  441. // 首次加载或刷新
  442. this.productList = productData;
  443. console.log("加载瀑布流商品列表:", productData);
  444. this.page = 1;
  445. this.hasMore = productData.length === this.pageSize;
  446. }
  447. } else {
  448. console.log("获取商品列表失败:", res.msg);
  449. uni.showToast({
  450. title: res.msg || '获取商品列表失败',
  451. icon: 'none'
  452. });
  453. }
  454. this.loading = false;
  455. this.refreshing = false;
  456. },
  457. rej => {
  458. console.error("productWaterfall API调用失败:", rej);
  459. uni.showToast({
  460. title: '网络错误,请检查网络连接后重试',
  461. icon: 'none'
  462. });
  463. this.loading = false;
  464. this.refreshing = false;
  465. }
  466. );
  467. },
  468. // 获取首页推荐商品
  469. getRecommendProduct() {
  470. recommendProduct().then(
  471. res => {
  472. if (res.code == 200) {
  473. this.recommendList = res.data;
  474. console.log("获取首页推荐商品", res.data);
  475. // 提取recommendType为recommend的栏目作为tabsProduct数据
  476. const recommendTabs = res.data.filter(item => item.recommendType === 'recommend');
  477. this.tabsProduct = recommendTabs.map(item => ({
  478. name: item.recommendName
  479. }));
  480. console.log("更新后的tabsProduct", this.tabsProduct);
  481. // 确保tabsProductIndex不超出范围
  482. if (this.tabsProductIndex >= this.tabsProduct.length) {
  483. this.tabsProductIndex = 0;
  484. }
  485. // 解析分类商品数据
  486. this.categoryProducts = {};
  487. // 初始化分类商品对象
  488. this.tabsProduct.forEach(tab => {
  489. this.categoryProducts[tab.name] = [];
  490. });
  491. // 遍历推荐商品数据
  492. res.data.forEach(item => {
  493. if (this.categoryProducts.hasOwnProperty(item.recommendName) && item
  494. .productList) {
  495. this.categoryProducts[item.recommendName] = item.productList;
  496. }
  497. });
  498. // 初始化当前分类商品
  499. if (this.tabsProduct.length > 0) {
  500. const currentCategory = this.tabsProduct[this.tabsProductIndex].name;
  501. this.currentCategoryProducts = this.categoryProducts[currentCategory] || [];
  502. }
  503. console.log("分类商品数据", this.categoryProducts);
  504. console.log("当前分类商品", this.currentCategoryProducts);
  505. // 使用推荐商品数据作为销量榜数据(实际项目中应该使用专门的销量榜接口)
  506. // 按照销量排序
  507. const allProducts = [];
  508. Object.values(this.categoryProducts).forEach(products => {
  509. allProducts.push(...products);
  510. });
  511. } else {
  512. uni.showToast({
  513. title: res.msg || '获取推荐商品失败',
  514. icon: 'none'
  515. });
  516. }
  517. },
  518. rej => {
  519. uni.showToast({
  520. title: '网络错误,请重试',
  521. icon: 'none'
  522. });
  523. }
  524. );
  525. },
  526. // 获取商品购物车数量
  527. getCartCount() {
  528. getCartCount().then(
  529. res => {
  530. if (res.code == 200) {
  531. console.log("获取商品购物车数量", res.data);
  532. // 保存购物车数量到全局或本地状态
  533. this.cartCount = res.data;
  534. }
  535. },
  536. rej => {
  537. console.error("获取商品购物车数量失败", rej);
  538. }
  539. );
  540. },
  541. // 获取购物车列表
  542. getCarts() {
  543. getCarts().then(
  544. res => {
  545. if (res.code == 200) {
  546. console.log("获取购物车列表", res.data);
  547. // 保存购物车列表到全局或本地状态
  548. this.cartList = res.data;
  549. }
  550. },
  551. rej => {
  552. console.error("获取购物车列表失败", rej);
  553. }
  554. );
  555. },
  556. // 获取商品规格列表
  557. getProductValues(productId) {
  558. // 防护检查:如果 productId 是 undefined 或 null,直接返回
  559. if (!productId) {
  560. console.error('获取商品规格列表失败:商品ID为空');
  561. return;
  562. }
  563. getProductValues(productId).then(
  564. res => {
  565. if (res.code == 200) {
  566. console.log("获取商品规格列表", res.data);
  567. // 保存商品规格列表
  568. this.productSpecs = res.data || [];
  569. // 默认选择第一个规格
  570. if (res.data && res.data.length > 0) {
  571. this.selectedSpec = res.data[0];
  572. this.productValueSelect = {
  573. image: res.data[0].image || this.product.image || this.product.firstImage ||
  574. '',
  575. price: res.data[0].price || 0
  576. };
  577. }
  578. }
  579. },
  580. rej => {
  581. console.error("获取商品规格列表失败", rej);
  582. }
  583. );
  584. },
  585. // // 获取分类数据
  586. // async getMenuData() {
  587. // try {
  588. // const {
  589. // code,
  590. // data,
  591. // msg
  592. // } = await getMenu();
  593. // if (code == 200) {
  594. // // 确保数据格式正确,适配HomeMenu组件需要的icon和menuName字段
  595. // const formattedData = data.map(item => ({
  596. // // 适配接口返回的字段名,如果接口返回的字段名不同,可以在这里进行映射
  597. // icon: item.icon || '',
  598. // menuName: item.name || item.menuName || ''
  599. // }));
  600. // // 将数据每5个一组分割
  601. // if (this.$scsUtils && this.$scsUtils.splitArrayIntoSubarrays) {
  602. // this.menusData = this.$scsUtils.splitArrayIntoSubarrays(formattedData, 5) || [];
  603. // } else {
  604. // // 如果$scsUtils不存在,使用自定义的分割方法
  605. // this.menusData = this.splitArrayIntoSubarrays(formattedData, 5);
  606. // }
  607. // } else {
  608. // uni.showToast({
  609. // title: msg,
  610. // icon: 'none'
  611. // });
  612. // }
  613. // } catch (error) {
  614. // console.error('获取菜单数据失败:', error);
  615. // uni.showToast({
  616. // title: '网络错误,请重试',
  617. // icon: 'none'
  618. // });
  619. // }
  620. // },
  621. // 自定义数组分割方法
  622. splitArrayIntoSubarrays(arr, size) {
  623. if (!Array.isArray(arr)) return [];
  624. const result = [];
  625. for (let i = 0; i < arr.length; i += size) {
  626. result.push(arr.slice(i, i + size));
  627. }
  628. return result;
  629. },
  630. // 下拉刷新
  631. onfresher() {
  632. if (this.refreshing || this.loading) return;
  633. this.refreshing = true;
  634. const self = this;
  635. self.$refs.fresher.isTrigger = true;
  636. // 刷新数据
  637. this.getProductWaterfall(false);
  638. },
  639. // 上拉加载更多
  640. loadMore() {
  641. if (this.loading || this.refreshing || !this.hasMore) return;
  642. // 加载更多数据
  643. this.getProductWaterfall(true);
  644. console.log("加载更多商品111", this.productList);
  645. },
  646. // 添加到购物车
  647. addToCart(product) {
  648. console.log('添加到购物车:', product);
  649. // 防护检查:如果 product 是 undefined 或 null,直接返回
  650. if (!product) {
  651. console.error('添加到购物车失败:商品信息为空');
  652. return;
  653. }
  654. // 弹出规格选择弹窗
  655. this.product = product;
  656. this.productValueSelect = {
  657. image: product.image || product.firstImage || '',
  658. price: product.price || 0
  659. };
  660. this.specVisible = true;
  661. this.isBuyMode = false; // 标记为添加购物车模式
  662. // 获取商品规格列表
  663. this.getProductValues(product.id || product.productId);
  664. },
  665. // 去购买
  666. goToBuy(product) {
  667. console.log('去购买:', product);
  668. // 防护检查:如果 product 是 undefined 或 null,直接返回
  669. if (!product) {
  670. console.error('去购买失败:商品信息为空');
  671. return;
  672. }
  673. // 弹出规格选择弹窗
  674. this.product = product;
  675. this.productValueSelect = {
  676. image: product.image || product.firstImage || '',
  677. price: product.price || 0
  678. };
  679. this.specVisible = true;
  680. this.isBuyMode = true; // 标记为购买模式
  681. // 获取商品规格列表
  682. this.getProductValues(product.id || product.productId);
  683. },
  684. // 频道相关方法
  685. onChannelsChange(channels) {
  686. console.log('频道变化:', channels);
  687. // 处理频道变化
  688. if (channels && channels.myChannels) {
  689. // 更新 myChannels 数据
  690. this.myChannels = channels.myChannels;
  691. // 更新 allChannels 数据
  692. if (channels.allChannels) {
  693. this.allChannels = channels.allChannels;
  694. }
  695. // 将选中的频道数据转换为 home-menu 组件需要的格式
  696. const menuItems = channels.myChannels.map(channel => ({
  697. icon: channel.pic || '',
  698. menuName: channel.name || ''
  699. }));
  700. // 将数据每5个一组分割,符合 home-menu 组件的要求
  701. this.menusData = this.splitArrayIntoSubarrays(menuItems, 5);
  702. console.log('更新菜单数据:', this.menusData);
  703. // 更新 tabsData 数组,用于 scs-scroll-navbar 组件显示频道名称
  704. this.tabsData = this.myChannels;
  705. console.log('更新 tabsData 数据:', this.tabsData);
  706. }
  707. },
  708. onOrderChange(orderInfo) {
  709. console.log('顺序变化:', orderInfo);
  710. // 处理顺序变化
  711. },
  712. onChannelTap(channel) {
  713. console.log('点击频道:', channel);
  714. this.activeChannelId = channel.id;
  715. // 根据频道切换内容
  716. },
  717. // 分割价格
  718. splitPrice(price) {
  719. const priceStr = parseFloat(price).toFixed(2).toString();
  720. return {
  721. integer: priceStr.split('.')[0],
  722. decimal: priceStr.split('.')[1]
  723. };
  724. },
  725. // 显示图片
  726. showImg(image) {
  727. console.log('显示图片:', image);
  728. },
  729. // 提交订单
  730. submit() {
  731. console.log('提交订单');
  732. this.isSubmitting = true;
  733. if (this.isBuyMode) {
  734. // 购买模式:跳转到订单确认页面
  735. console.log('立即购买:', this.product, this.specNum);
  736. this.isSubmitting = false;
  737. this.specVisible = false;
  738. // const carts
  739. // 跳转到订单确认页面,传递商品信息和数量
  740. console.log("要传的参数", this.product.productId, this.specNum, this.selectedSpec ? this.selectedSpec.id :
  741. '')
  742. uni.navigateTo({
  743. url: `/pages_shopping/shopping/confirmCreateOrder?productId=${this.product.productId}&buyNum=${this.specNum}&attrValueId=${this.selectedSpec ? this.selectedSpec.id : ''}&indexBuy=true`
  744. });
  745. } else {
  746. // 添加购物车模式:调用添加购物车接口
  747. addCart({
  748. productId: this.product.id || this.product.productId,
  749. cartNum: this.specNum,
  750. attrValueId: this.selectedSpec ? this.selectedSpec.id : '' // 使用选中的规格ID
  751. }).then(res => {
  752. if (res.code == 200) {
  753. uni.showToast({
  754. title: '添加购物车成功',
  755. icon: 'success'
  756. });
  757. // 重新获取购物车数量
  758. this.getCartCount();
  759. this.specVisible = false;
  760. } else {
  761. uni.showToast({
  762. title: res.msg || '添加购物车失败',
  763. icon: 'none'
  764. });
  765. }
  766. this.isSubmitting = false;
  767. }).catch(error => {
  768. console.error('添加购物车失败:', error);
  769. uni.showToast({
  770. title: '网络错误,请重试',
  771. icon: 'none'
  772. });
  773. this.isSubmitting = false;
  774. });
  775. }
  776. },
  777. // 减少数量
  778. lessNum() {
  779. if (this.specNum > 1) {
  780. this.specNum--;
  781. }
  782. },
  783. // 增加数量
  784. addNum() {
  785. this.specNum++;
  786. },
  787. // 选择规格
  788. choseSpec(index, subindex) {
  789. console.log('选择规格:', index, subindex);
  790. },
  791. // 选择商品规格
  792. selectSpec(spec) {
  793. console.log('选择商品规格:', spec);
  794. this.selectedSpec = spec;
  795. this.productValueSelect = {
  796. image: spec.image || this.product.image || this.product.firstImage || '',
  797. price: spec.price || 0,
  798. sales: spec.sales || 0
  799. };
  800. }
  801. }
  802. }
  803. </script>
  804. <style lang="scss" scoped>
  805. /* 实时销量榜样式 */
  806. .sales-ranking {
  807. width: 342rpx;
  808. height: 500rpx;
  809. margin-bottom: 11rpx;
  810. border-radius: 8rpx;
  811. overflow: hidden;
  812. margin-top: 14rpx;
  813. position: relative;
  814. display: flex;
  815. align-items: center;
  816. justify-content: center;
  817. .bg {
  818. position: absolute;
  819. width: 342rpx;
  820. height: 500rpx;
  821. }
  822. .sales-ranking-content {
  823. position: relative;
  824. width: 100%;
  825. height: 100%;
  826. z-index: 10;
  827. display: flex;
  828. flex-direction: column;
  829. padding: 0 20rpx;
  830. .sales-ranking-title {
  831. display: flex;
  832. align-items: center;
  833. margin-top: 18rpx;
  834. text {
  835. font-weight: 500;
  836. font-size: 32rpx;
  837. color: #333333;
  838. }
  839. image {
  840. width: 24rpx;
  841. height: 24rpx;
  842. margin-top: 4rpx;
  843. margin-left: 4rpx;
  844. }
  845. }
  846. .sales-ranking-total {
  847. margin-top: 6rpx;
  848. font-weight: 400;
  849. font-size: 22rpx;
  850. color: #D46C0D;
  851. }
  852. .sales-ranking-list {
  853. padding: 16rpx;
  854. border-radius: 16rpx;
  855. background-color: white;
  856. margin-top: 14rpx;
  857. display: flex;
  858. flex-direction: column;
  859. // gap: 8rpx;
  860. width: 100%;
  861. max-width: 334rpx;
  862. .sales-ranking-item {
  863. display: flex;
  864. gap: 12rpx;
  865. margin-bottom: 20rpx;
  866. align-items: center;
  867. &:last-child {
  868. margin-bottom: 0rpx;
  869. }
  870. .sales-ranking-item-img {
  871. width: 104rpx;
  872. height: 104rpx;
  873. border-radius: 8rpx;
  874. overflow: hidden;
  875. position: relative;
  876. image {
  877. width: 100%;
  878. height: 100%;
  879. }
  880. .sales-ranking-item-rank {
  881. width: 24rpx;
  882. height: 24rpx;
  883. border-radius: 8rpx;
  884. position: absolute;
  885. top: 0;
  886. left: 0;
  887. display: flex;
  888. align-items: center;
  889. justify-content: center;
  890. color: white;
  891. font-size: 20rpx;
  892. }
  893. }
  894. .sales-ranking-item-info {
  895. flex: 1;
  896. display: flex;
  897. flex-direction: column;
  898. justify-content: space-between;
  899. .sales-ranking-item-name {
  900. font-size: 20rpx;
  901. color: #333333;
  902. font-weight: 400;
  903. display: -webkit-box;
  904. -webkit-line-clamp: 2;
  905. -webkit-box-orient: vertical;
  906. overflow: hidden;
  907. }
  908. .sales-ranking-item-price {
  909. display: flex;
  910. align-items: center;
  911. justify-content: space-between;
  912. margin-top: 8rpx;
  913. view {
  914. color: #FA341E;
  915. text:first-child {
  916. font-size: 20rpx;
  917. font-weight: 600;
  918. }
  919. text:last-child {
  920. font-size: 36rpx;
  921. font-weight: 600;
  922. }
  923. }
  924. .sales-ranking-item-cart {
  925. width: 40rpx;
  926. height: 40rpx;
  927. }
  928. }
  929. }
  930. }
  931. }
  932. }
  933. }
  934. /* 瀑布流商品样式 */
  935. .goods-list {
  936. padding: 20rpx;
  937. display: flex;
  938. flex-wrap: wrap;
  939. justify-content: space-between;
  940. margin-bottom: 18rpx;
  941. .item {
  942. width: calc(50% - 9rpx);
  943. margin-bottom: 18rpx;
  944. background: #FFFFFF;
  945. border-radius: 16rpx;
  946. overflow: hidden;
  947. display: flex;
  948. flex-direction: column;
  949. .img-box {
  950. width: 100%;
  951. height: 342rpx;
  952. overflow: hidden;
  953. image {
  954. width: 100%;
  955. height: 100%;
  956. object-fit: cover;
  957. }
  958. }
  959. .info-box {
  960. box-sizing: border-box;
  961. padding: 20rpx 20rpx 30rpx;
  962. display: flex;
  963. flex-direction: column;
  964. justify-content: space-between;
  965. flex: 1;
  966. .title {
  967. font-size: 26rpx;
  968. font-family: PingFang SC;
  969. font-weight: 500;
  970. color: #111111;
  971. margin-bottom: 4rpx;
  972. }
  973. .intro {
  974. font-weight: 400;
  975. font-size: 22rpx;
  976. color: #D46C0D;
  977. margin-bottom: 8rpx;
  978. }
  979. .sale {
  980. font-size: 21rpx;
  981. color: #999999;
  982. }
  983. .lable-group {
  984. display: flex;
  985. margin-top: 8rpx;
  986. .lable-item {
  987. margin-right: 8rpx;
  988. height: 30rpx;
  989. padding: 0 8rpx;
  990. border-radius: 4rpx 4rpx 4rpx 4rpx;
  991. border: 1rpx solid #FFA599;
  992. font-size: 21rpx;
  993. color: #FF4B33;
  994. }
  995. }
  996. .prce-num {
  997. display: flex;
  998. align-items: center;
  999. justify-content: space-between;
  1000. margin-top: 8upx;
  1001. .price {
  1002. font-weight: 600;
  1003. font-size: 26rpx;
  1004. color: #FA341E;
  1005. font-size: 26rpx;
  1006. .unit {
  1007. font-size: 20upx;
  1008. }
  1009. .bold {
  1010. font-size: 36upx;
  1011. }
  1012. }
  1013. }
  1014. .card {
  1015. margin-top: 20rpx;
  1016. display: flex;
  1017. justify-content: space-between;
  1018. align-items: center;
  1019. background: #FFF4E8;
  1020. border-radius: 6rpx 6rpx 6rpx 6rpx;
  1021. width: 100%;
  1022. height: 40rpx;
  1023. padding: 8rpx 10rpx;
  1024. box-sizing: border-box;
  1025. .card-item {
  1026. display: flex;
  1027. align-items: center;
  1028. .img {
  1029. width: 24rpx;
  1030. height: 24rpx;
  1031. margin-right: 4rpx;
  1032. }
  1033. .ranking {
  1034. font-size: 20rpx;
  1035. color: #4D4D4D;
  1036. }
  1037. .bold {
  1038. font-family: Roboto Flex, Roboto Flex;
  1039. font-weight: 600;
  1040. font-size: 24rpx;
  1041. color: #F4A007;
  1042. margin-right: 6rpx;
  1043. transform: skewX(-8deg);
  1044. /* 负值向左倾斜,正值向右倾斜 */
  1045. display: inline-block;
  1046. margin-left: 4rpx;
  1047. /* transform需要inline-block或block */
  1048. }
  1049. }
  1050. .go {
  1051. width: 16rpx;
  1052. height: 16rpx;
  1053. }
  1054. }
  1055. }
  1056. }
  1057. }
  1058. /* 排名标记颜色 */
  1059. .rank-1 {
  1060. background: linear-gradient(45deg, #FB7E4C 0%, #ED4B17 100%);
  1061. }
  1062. .rank-2 {
  1063. background: linear-gradient(45deg, #FBCF3F 0%, #F9B71B 100%);
  1064. }
  1065. .rank-3 {
  1066. background: linear-gradient(45deg, #D5AB78 0%, #C39760 100%);
  1067. }
  1068. .home-page {
  1069. display: flex;
  1070. flex-direction: column;
  1071. width: 100%;
  1072. height: 100%;
  1073. background-color: #F5F7FA;
  1074. }
  1075. .home-bg {
  1076. position: absolute;
  1077. top: 0;
  1078. left: 0;
  1079. z-index: 1;
  1080. width: 100%;
  1081. height: 266rpx;
  1082. }
  1083. .nav-header {
  1084. display: flex;
  1085. align-items: center;
  1086. height: 88rpx;
  1087. .nav-title {
  1088. flex: 1;
  1089. .main-title {
  1090. font-weight: 500;
  1091. font-size: 36rpx;
  1092. color: #000000;
  1093. }
  1094. .sub-title {
  1095. font-weight: 400;
  1096. font-size: 24rpx;
  1097. color: #999999;
  1098. }
  1099. }
  1100. .nav-cart {
  1101. position: relative;
  1102. display: flex;
  1103. align-items: center;
  1104. justify-content: center;
  1105. width: 64rpx;
  1106. height: 64rpx;
  1107. background-color: #FFFFFF;
  1108. border-radius: 50%;
  1109. .cart-icon {
  1110. width: 40rpx;
  1111. height: 40rpx;
  1112. }
  1113. .cart-badge {
  1114. position: absolute;
  1115. left: -8rpx;
  1116. top: -8rpx;
  1117. min-width: 32rpx;
  1118. min-height: 32rpx;
  1119. flex-shrink: 0;
  1120. text-align: center;
  1121. padding: 6rpx;
  1122. font-size: 16rpx;
  1123. box-sizing: border-box;
  1124. color: #fff;
  1125. background-color: #F56C6C;
  1126. border-radius: 50%;
  1127. }
  1128. }
  1129. }
  1130. .home-content {
  1131. z-index: 10;
  1132. flex: 1;
  1133. overflow: auto;
  1134. }
  1135. .nav-bar {
  1136. padding: 0 12rpx;
  1137. margin-top: 11rpx;
  1138. border-radius: 8rpx;
  1139. .nav-container {
  1140. display: flex;
  1141. align-items: center;
  1142. position: relative;
  1143. .gradient-bg {
  1144. position: absolute;
  1145. bottom: 2.5rpx;
  1146. right: 42rpx;
  1147. z-index: 10;
  1148. width: 32rpx;
  1149. height: 32rpx;
  1150. }
  1151. .nav-actions {
  1152. display: flex;
  1153. align-items: center;
  1154. justify-content: center;
  1155. .action-icon {
  1156. width: 32rpx;
  1157. height: 32rpx;
  1158. &.product-icon {
  1159. margin-left: 5rpx;
  1160. margin-right: 12rpx;
  1161. }
  1162. &.filter-icon {
  1163. margin-right: 7rpx;
  1164. }
  1165. }
  1166. }
  1167. }
  1168. }
  1169. .product-tabs {
  1170. width: 100%;
  1171. margin-top: 10rpx;
  1172. border-radius: 8rpx 8rpx 0 0;
  1173. padding: 0 28rpx;
  1174. }
  1175. .sales-waterfall {
  1176. width: 100%;
  1177. margin-top: 20rpx;
  1178. padding: 0 12rpx;
  1179. }
  1180. .product-spec {
  1181. padding: 40rpx 30rpx;
  1182. .pro-info {
  1183. display: flex;
  1184. align-items: center;
  1185. .img-box {
  1186. width: 200rpx;
  1187. height: 200rpx;
  1188. background: #FFFFFF;
  1189. border-radius: 16rpx;
  1190. overflow: hidden;
  1191. margin-right: 30rpx;
  1192. image {
  1193. width: 100%;
  1194. height: 100%;
  1195. }
  1196. }
  1197. .product-name {
  1198. font-weight: 600;
  1199. font-size: 32rpx;
  1200. color: #333333;
  1201. }
  1202. .info-text {
  1203. height: 200rpx;
  1204. display: flex;
  1205. flex-direction: column;
  1206. .price {
  1207. margin-top: 16rpx;
  1208. font-weight: 600;
  1209. font-size: 26rpx;
  1210. color: #FA341E;
  1211. .title {
  1212. font-weight: 500;
  1213. font-size: 24rpx;
  1214. color: #FF5030;
  1215. margin-right: 8rpx;
  1216. }
  1217. .bold {
  1218. font-size: 48upx;
  1219. }
  1220. }
  1221. .desc-box {
  1222. margin-top: 16rpx;
  1223. display: flex;
  1224. flex-direction: column;
  1225. padding-bottom: 9rpx;
  1226. .text {
  1227. font-size: 26rpx;
  1228. font-family: PingFang SC;
  1229. font-weight: 500;
  1230. color: #999999;
  1231. margin-top: 27rpx;
  1232. line-height: 1;
  1233. &:first-child {
  1234. margin-top: 0;
  1235. }
  1236. }
  1237. }
  1238. }
  1239. }
  1240. .spec-box {
  1241. padding-top: 50rpx;
  1242. .title {
  1243. font-size: 34rpx;
  1244. font-family: PingFang SC;
  1245. font-weight: bold;
  1246. color: #111111;
  1247. line-height: 1;
  1248. }
  1249. .spec-list {
  1250. display: flex;
  1251. flex-wrap: wrap;
  1252. margin-top: 30rpx;
  1253. .item {
  1254. box-sizing: border-box;
  1255. padding: 12rpx 24rpx;
  1256. font-size: 28rpx;
  1257. color: #333333;
  1258. background: #F5F7FA;
  1259. border-radius: 28rpx;
  1260. margin-right: 24rpx;
  1261. margin-bottom: 30rpx;
  1262. &.active {
  1263. font-size: 24rpx;
  1264. color: #02B176;
  1265. background: #EBFAF6;
  1266. border: 2rpx solid #02B176;
  1267. }
  1268. }
  1269. }
  1270. }
  1271. .price-num {
  1272. display: flex;
  1273. align-items: center;
  1274. justify-content: space-between;
  1275. margin-top: 14rpx;
  1276. .label {
  1277. font-size: 34rpx;
  1278. font-family: PingFang SC;
  1279. font-weight: bold;
  1280. color: #111111;
  1281. }
  1282. .num-box {
  1283. display: flex;
  1284. align-items: center;
  1285. .img-box {
  1286. width: 60rpx;
  1287. height: 60rpx;
  1288. display: flex;
  1289. align-items: center;
  1290. justify-content: center;
  1291. image {
  1292. width: 25rpx;
  1293. height: 25rpx;
  1294. }
  1295. }
  1296. input {
  1297. width: 60rpx;
  1298. height: 60rpx;
  1299. line-height: 60rpx;
  1300. font-size: 28rpx;
  1301. font-family: PingFang SC;
  1302. font-weight: 500;
  1303. color: #111111;
  1304. background: #F5F7FA;
  1305. text-align: center;
  1306. }
  1307. }
  1308. }
  1309. .sub-btn {
  1310. width: 100%;
  1311. height: 88rpx;
  1312. line-height: 88rpx;
  1313. text-align: center;
  1314. font-size: 30rpx;
  1315. font-family: PingFang SC;
  1316. font-weight: bold;
  1317. color: #FFFFFF;
  1318. border-radius: 44rpx;
  1319. margin-top: 30rpx;
  1320. background: linear-gradient(136deg, #38D97D 0%, #02B176 100%);
  1321. }
  1322. }
  1323. ::v-deep .nav-bar {
  1324. background: linear-gradient(180deg, #FFFFFF 0%, #FFFFFF 49.52%, #F5F7FA 100%);
  1325. .scs-scroll-navbar {
  1326. width: calc(100vw - 140rpx) !important;
  1327. display: flex;
  1328. justify-content: space-between;
  1329. .nav-underline {
  1330. background: linear-gradient(90deg, rgba(56, 217, 125, 0.5) 0%, rgba(56, 217, 125, 0) 100%);
  1331. bottom: 28rpx;
  1332. }
  1333. }
  1334. }
  1335. ::v-deep .product-tabs {
  1336. background: linear-gradient(180deg, #FFFFFF 0%, #FFFFFF 49.52%, #F5F7FA 100%);
  1337. .scs-scroll-navbar {
  1338. .nav-item {
  1339. margin-right: 78rpx;
  1340. }
  1341. }
  1342. }
  1343. </style>