index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. <template>
  2. <div class="w-full bg-[#f6f6f6]">
  3. <div class="bg-[#fff]">
  4. <div class="main-container flex h-[410px]">
  5. <div class="relative">
  6. <div class="absolute z-2 flex overflow-hidden" @mouseleave.stop="handleOut($event)">
  7. <el-scrollbar height="376px" class="w-[200px] px-[20px] py-[17px]">
  8. <div>
  9. <template v-for="(item,index) in categoryList" :key="index">
  10. <div class="flex items-center mb-[24px] category-first cursor-pointer" :class="`index${index}`" @mouseenter.stop="subMenuClick($event)">
  11. <el-image class="w-[40px] h-[40px] rounded-full" :src="img(item.image)" fit="cover">
  12. <template #error>
  13. <img class="w-[40px] h-[40px] rounded-full" src="@/assets/images/nav/default_category.png" />
  14. </template>
  15. </el-image>
  16. <div class="ml-[10px]">
  17. <div class="text-[14px] truncate text-[#333] w-[109px] hover:text-[var(--el-color-primary)]" @click.stop="toGoodsList(item.category_id)">{{item.category_name}}</div>
  18. <div class="flex flex-wrap mt-[8px]" v-if="item.child_list && item.child_list.length">
  19. <div class="text-[12px] text-[#999] max-w-[49px] truncate mr-[10px] hover:text-[var(--el-color-primary)]" @click.stop="toGoodsList(item.child_list[0].category_id)">{{ item.child_list[0].category_name }}</div>
  20. <div class="text-[12px] text-[#999] max-w-[49px] truncate hover:text-[var(--el-color-primary)]" @click.stop="toGoodsList(item.child_list[1].category_id)">{{ item.child_list[1] ? item.child_list[1].category_name:'' }}</div>
  21. </div>
  22. </div>
  23. </div>
  24. </template>
  25. </div>
  26. </el-scrollbar>
  27. <div class="box-border w-[1000px] bg-[#fafafa] pt-[20px] text-[#333] rounded-r-[12px] " v-if="rightShow">
  28. <el-scrollbar height="390px">
  29. <template v-for="(subItem,subIndex) in categoryList[tabActive]?.child_list" :key="subIndex">
  30. <div>
  31. <div class="px-[20px] text-[14px] text-[#333] !leading-[20px] cursor-pointer text-left mb-[30px]" :class="{'mb-[10px]': !subItem.child_list }" @click.stop="toGoodsList(subItem.category_id)">{{subItem.category_name}}</div>
  32. <div class="flex-1 flex flex-wrap">
  33. <template v-for="(grandItem,grandIndex) in subItem.child_list" :key="grandIndex">
  34. <div class="flex items-center cursor-pointer px-[20px] mb-[30px]" @click.stop="toGoodsList(grandItem.category_id)">
  35. <div class="h-[60px] flex-shink-0 mr-[10px]">
  36. <el-image class="w-[60px] h-[60px] " :src="img(grandItem.image)" fit="cover">
  37. <template #error>
  38. <div class="image-slot">
  39. <img class="w-[60px] h-[60px]" src="@/assets/images/nav/default_category.png" />
  40. </div>
  41. </template>
  42. </el-image>
  43. </div>
  44. <div class="text-[14px] text-[#999] text-left w-[140px] h-[60px] leading-[60px] truncate">{{grandItem.category_name}}</div>
  45. </div>
  46. </template>
  47. </div>
  48. </div>
  49. </template>
  50. </el-scrollbar>
  51. </div>
  52. </div>
  53. </div>
  54. <div class="w-[740px] h-[380px] ml-[220px] mt-[15px] home-carousel">
  55. <el-carousel :interval="3000" height="380px" arrow="never" class="rounded-[12px]">
  56. <el-carousel-item v-for="(item,index) in advInfo.adv_list">
  57. <NuxtLink :to="item.adv_url && item.adv_url.url ? item.adv_url.url : '' ">
  58. <div class="h-full index-carousel">
  59. <img :src="img(item.adv_image)" alt="" class="w-full h-full">
  60. </div>
  61. </NuxtLink>
  62. </el-carousel-item>
  63. </el-carousel>
  64. </div>
  65. <div class="ml-[30px] mt-[15px] w-[220px]">
  66. <div class="flex items-center">
  67. <div class="w-[50px] h-[50px] flex-shrink-0" >
  68. <img v-if="(info && !info.headimg) || !info" class="w-[50px] h-[50px] rounded-full" src="@/assets/images/default_headimg.png" alt="">
  69. <img v-else :src="img(info.headimg)" class="w-[50px] h-[50px] rounded-full" alt="">
  70. </div>
  71. <div class="ml-[10px]">
  72. <div class="text-[14px] text-[#999] leading-[18px] mb-[10px]">Hi~你好</div>
  73. <div v-if="info" class="text-[16px] text-primary">{{ info.nickname }}</div>
  74. <div v-else class="text-[16px] text-primary cursor-pointer" @click="toLogin">{{ t('login') }}/{{ t('register') }}</div>
  75. </div>
  76. </div>
  77. <div class="h-[36px] rounded-[100px] bg-primary text-[#fff] flex-center text-[12px] my-[30px] cursor-pointer" @click="handleDetail('/member/merchant_settled')">申请入驻</div>
  78. <div class="border-b-[1px] border-dashed border-[#eee] pb-[8px] mb-[20px]" v-if="articleHotList.length">
  79. <div class="flex items-center mb-[18px] cursor-pointer" v-for="(item,index) in articleHotList" :key="index" @click="router.push(`/article/detail?id=${item.id}`)">
  80. <span class="w-[34px] h-[18px] bg-[#e8f1ff] text-[#116DFE] text-[12px] flex-center rounded-[3px] rounded-br-[10px] mr-[10px] flex-shrink-0">资讯</span>
  81. <span class="truncate text-[14px] text-[#666]">{{ item.title }}</span>
  82. </div>
  83. </div>
  84. <div class="flex-center">
  85. <div class="flex-1 flex flex-col items-center cursor-pointer" @click="handleDetail('/member/collect/goods')">
  86. <span class="iconfont icon-shoucang2 text-primary !text-[18px]"></span>
  87. <div class="text-[12px] mt-[6px]">我的收藏</div>
  88. </div>
  89. <div class="flex-1 flex flex-col items-center cursor-pointer" @click="handleDetail('/member/collect/shop')">
  90. <span class="iconfont icon-Vector-25 text-primary !text-[18px]"></span>
  91. <div class="text-[12px] mt-[6px]">关注店铺</div>
  92. </div>
  93. <div class="flex-1 flex flex-col items-center cursor-pointer" @click="handleDetail('/member/browse')">
  94. <span class="iconfont icon-zujiV6xx text-primary !text-[20px]"></span>
  95. <div class="text-[12px] mt-[6px]">我的足迹</div>
  96. </div>
  97. </div>
  98. </div>
  99. </div>
  100. </div>
  101. <div class="main-container">
  102. <div class="mt-[40px]">
  103. <div class="mt-[30px] mb-[10px]" v-if="shopList.config && shopList.config.is_show_shop">
  104. <div class="flex justify-between items-center mb-[20px]">
  105. <div class="leading-[40px] h-[40px] text-[24px] text-[#333]"> 甄选好店</div>
  106. </div>
  107. <div class="flex box-border">
  108. <div class="w-[288px] h-[422px]">
  109. <el-image :src="img(shopList.config.shop_ad_image)" fit="cover" class="w-[288px] h-[422px] rounded-tl-[16px] rounded-br-[16px]"/>
  110. </div>
  111. <div class="flex flex-wrap flex-1 w-[912px]">
  112. <template v-for="(item,index) in shopList.list" :key="index">
  113. <div class="w-[288px] h-[203px] bg-[#fff] rounded-[16px] py-[15px] pl-[15px] mb-[15px] ml-[16px] cursor-pointer" @click="router.push('/shop/detail?site_id='+item.site_id)">
  114. <div class="flex items-center mb-[15px] pr-[20px]">
  115. <div class="flex-center">
  116. <el-image class="w-[36px] h-[36px] rounded-[50%] mr-[10px]" :src="img(item.front_end_logo ? item.front_end_logo : '')" fit="cover">
  117. <template #error>
  118. <img src="@/assets/images/shop_default.png" alt="">
  119. </template>
  120. </el-image>
  121. <div class="flex-center">
  122. <div class="text-[14px] text-[#333] mr-[6px] max-w-[160px] font-500 truncate">{{ item.site_name }}</div>
  123. <div class="bg-primary rounded-[2px] text-[12px] text-[#fff] py-[2px] px-[4px]" v-if="item.shop && item.shop.is_self">自营</div>
  124. </div>
  125. </div>
  126. </div>
  127. <div class="flex">
  128. <div class="w-[122px] h-[122px] relative rounded-[12px] overflow-hidden mr-[14px]" v-for="(subItem,subIndex) in item.goods_list" :key="subIndex" @click.stop="toDetail(subItem.goods_id)">
  129. <el-image class="w-[122px] h-[122px]" :src="img(subItem.goods_cover_thumb_small)" fit="cover">
  130. <template #error>
  131. <img src="@/assets/images/goods_default.png" class="w-[122px] h-[122px]">
  132. </template>
  133. </el-image>
  134. <div class="px-[8px] py-[4px] price-font text-[12px] font-500 text-[#fff] absolute bottom-0 right-0 price-style">
  135. <span class="oppoSans-M">¥</span>
  136. <span class="text-16px">{{ subItem.goodsSku.price }}</span>
  137. </div>
  138. </div>
  139. </div>
  140. </div>
  141. </template>
  142. </div>
  143. </div>
  144. </div>
  145. <template v-for="(item,index) in floorList" :key="index">
  146. <div class="mt-[30px] mb-[10px]">
  147. <div class="flex justify-between items-center mb-[20px]" v-if="item.config.title || item.config.sub_title || item.config.url">
  148. <div class="flex items-center">
  149. <div class="leading-[40px] h-[40px]">
  150. <span class="text-[24px] text-[#333]">{{item.config.title}}</span>
  151. <span class="text-[#999] text-[12px] ml-[10px]">{{item.config.sub_title}}</span>
  152. </div>
  153. </div>
  154. <div class="text-[14px] text-[#999] cursor-pointer oppoSans-R" v-if="item.config && item.config.url && item.config.url.url" @click="toLink(item.config.url.url)">
  155. <span>更多</span>
  156. <span class="iconfont icon-youV6xx ml-[4px]"></span>
  157. </div>
  158. </div>
  159. <div class="flex box-border">
  160. <div class="w-[224px] h-[628px] mr-[20px]" v-if="item.config.adv_img1">
  161. <el-image :src="img(item.config.adv_img1)" fit="cover" class="w-full max-w-[628px] rounded-tl-[16px] rounded-br-[16px]"/>
  162. </div>
  163. <div class="flex flex-wrap flex-1">
  164. <template v-for="(subItem,subIndex) in item.goods_list" :key="subIndex">
  165. <div class="w-[224px] h-[304px] mb-[20px] bg-[#fff] py-[11px] cursor-pointer rounded-[var(--rounded-big)]" :class="{'mr-[20px]': (item.config.adv_img1 ? (subIndex + 1) % 4 : (subIndex + 1) % 5 ) }" @click="toDetail(subItem.goods_id)" v-if="item.config.adv_img1 ? (subIndex < 8) : (subIndex < 10)">
  166. <div class="w-full h-[200px] mb-[10px] flex items-center justify-center" >
  167. <el-image class="rounded-[var(--rounded-mid)]" style="width: 200px; height: 200px" :src="img(subItem.goods_cover)" fit="cover">
  168. <template #error>
  169. <img src="@/assets/images/goods_default.png" class="w-[200px] h-[200px]">
  170. </template>
  171. </el-image>
  172. </div>
  173. <div class="mx-[10px]">
  174. <div class="mb-[10px] h-[42px] text-[14px] multi-hidden text-[#333] leading-[21px]">{{ subItem.goods_name }}</div>
  175. <div class="flex items-center justify-between flex-wrap">
  176. <div class="text-[var(--el-price)] flex items-baseline">
  177. <span class="text-[12px] price-font">¥</span>
  178. <span class="text-[20px] price-font">{{parseFloat(goodsPrice(subItem)).toFixed(2)}}</span>
  179. <img v-if="priceType(subItem) == 'member_price'" class="h-[14px] ml-[3px] w-[24px]" src="@/assets/images/addon/VIP.png" />
  180. <img v-if="priceType(subItem) == 'discount_price'" class="h-[12px] ml-[3px] w-[36px]" src="@/assets/images/addon/discount.png" />
  181. </div>
  182. <div class="text-[12px] leading-[16px] text-[#999]">已售{{subItem.sale_num}}{{ subItem.unit || '件' }}</div>
  183. </div>
  184. </div>
  185. </div>
  186. </template>
  187. </div>
  188. </div>
  189. <div class="w-[1200px] h-[120px] mt-[10px]" v-if="item.config.adv_img2">
  190. <el-image :src="img(item.config.adv_img2)" fit="cover" class="w-full max-h-[120px]"/>
  191. </div>
  192. </div>
  193. </template>
  194. </div>
  195. </div>
  196. </div>
  197. </template>
  198. <script lang="ts" setup>
  199. import { ref } from 'vue'
  200. import { getToken} from '@/utils/common'
  201. import { useRouter, useRoute } from 'vue-router'
  202. import { getAdvInfo, getFloor, getCategoryTree, getWebShopList } from '@/app/api/index'
  203. import { getArticleHot } from '@/app/api/article'
  204. import useMemberStore from '@/stores/member'
  205. import useConfigStore from '@/stores/config'
  206. const router = useRouter()
  207. const memberStore = useMemberStore()
  208. const configStore = useConfigStore()
  209. const info = computed(() => memberStore.info)
  210. // 一级菜单样式控制
  211. const tabActive = ref(0)
  212. const categoryList = ref<any>([])
  213. const getCategoryTreeFn = () => {
  214. getCategoryTree().then((res: any) => {
  215. categoryList.value = res.data
  216. })
  217. }
  218. getCategoryTreeFn()
  219. // 控制右侧展示
  220. const rightShow = ref(false)
  221. const subMenuClick = (event: any) =>{
  222. if(event.target.className.indexOf('index') !== -1){
  223. let data = event.target.className.split('index')
  224. tabActive.value = Number(data[1])
  225. if(categoryList.value[tabActive.value].child_list&& categoryList.value[tabActive.value].child_list.length){
  226. rightShow.value = true
  227. }else{
  228. rightShow.value = false
  229. }
  230. }
  231. }
  232. const handleOut = (event: any)=>{
  233. rightShow.value = false
  234. }
  235. // 去详情
  236. const toGoodsList = (id: any) =>{
  237. rightShow.value = false
  238. router.push({path:'/goods/list',query:{goods_mall_category:id}})
  239. }
  240. // 未登录
  241. const toLogin = () =>{
  242. if(!getToken() && !configStore.login.is_username && !configStore.login.is_mobile && !configStore.login.is_bind_mobile){
  243. ElMessage.error('商家未开启普通账号登录注册')
  244. return false
  245. }
  246. memberStore.logOpen()
  247. }
  248. //获取热门资讯信息
  249. const articleHotList = ref([])
  250. const articleHotLoading = ref(true)
  251. const getArticleHotFn = () =>{
  252. getArticleHot({limit:4}).then((res: any) =>{
  253. articleHotList.value = res.data
  254. articleHotLoading.value = false
  255. }).catch(() =>{
  256. articleHotLoading.value = false
  257. })
  258. }
  259. getArticleHotFn()
  260. // 申请入驻,收藏,足迹,店铺
  261. const handleDetail = (data: any) =>{
  262. if(!getToken()){
  263. if(!configStore.login.is_username && !configStore.login.is_mobile && !configStore.login.is_bind_mobile){
  264. ElMessage.error('商家未开启普通账号登录注册')
  265. return false
  266. }else{
  267. memberStore.logOpen()
  268. return false
  269. }
  270. }
  271. router.push(data)
  272. }
  273. // 广告位
  274. const advInfo = ref({})
  275. const getAdvInfoFn = () =>{
  276. getAdvInfo({ap_key:'ADV_INDEX'}).then(res =>{
  277. advInfo.value = res.data
  278. })
  279. }
  280. getAdvInfoFn()
  281. // 首页楼层
  282. const floorList = ref([])
  283. const getFloorFn = () =>{
  284. getFloor().then(res =>{
  285. floorList.value = res.data
  286. })
  287. }
  288. getFloorFn()
  289. // 甄选店铺
  290. const shopList = ref<any>({})
  291. const getWebShopListFn = () =>{
  292. getWebShopList().then((res: any) =>{
  293. shopList.value = res.data
  294. })
  295. }
  296. getWebShopListFn()
  297. // 查看更多
  298. const toLink = (url:string) =>{
  299. // 外部链接
  300. if (url.indexOf('https') != -1 || url.indexOf('http') != -1) {
  301. window.open(url)
  302. }else {
  303. router.push(url)
  304. }
  305. }
  306. // 商品详情
  307. const toDetail = (goods_id:number) =>{
  308. router.push(`/goods/detail?id=${goods_id}`)
  309. }
  310. // 价格类型
  311. let priceType = (data:any) =>{
  312. let type = "";
  313. if(data.is_discount && data.goodsSku.sale_price != data.goodsSku.price){
  314. type = 'discount_price'// 折扣
  315. }else if(data.member_discount && getToken() && data.goodsSku.member_price != data.goodsSku.price){
  316. type = 'member_price' // 会员价
  317. }else{
  318. type = ""
  319. }
  320. return type;
  321. }
  322. // 商品价格
  323. let goodsPrice = (data:any) =>{
  324. let price = "0.00";
  325. if(data.is_discount && data.goodsSku.sale_price != data.goodsSku.price){
  326. price = data.goodsSku.sale_price?data.goodsSku.sale_price:data.goodsSku.price // 折扣价
  327. }else if(data.member_discount && getToken() && data.goodsSku.member_price != data.goodsSku.price){
  328. price = data.goodsSku.member_price?data.goodsSku.member_price:data.goodsSku.price // 会员价
  329. }else{
  330. price = data.goodsSku.price
  331. }
  332. return price;
  333. }
  334. </script>
  335. <style lang="scss" scoped>
  336. .category-first:last-child{
  337. margin-bottom: 0px!important;
  338. }
  339. .brick-item{
  340. transition: all .2s linear;
  341. &:hover{
  342. box-shadow: 0 15px 30px rgba(0,0,0,.1);
  343. transform: translate3d(0,-2px,0)
  344. }
  345. }
  346. .price-style{
  347. background: rgba(0, 0, 0, .5);
  348. border-radius: 12px 0;
  349. }
  350. /* 多行超出隐藏 */
  351. .multi-hidden {
  352. word-break: break-all;
  353. text-overflow: ellipsis;
  354. overflow: hidden;
  355. display: -webkit-box;
  356. -webkit-line-clamp: 2;
  357. -webkit-box-orient: vertical;
  358. }
  359. .floor-bg{
  360. /*background-image: url(@/assets/images/index/floor-bg.png);*/
  361. /*background-position: center center;*/
  362. /*background-size: 100% 100%;*/
  363. /*background-repeat: no-repeat;*/
  364. }
  365. </style>