Refactor code structure and remove redundant changes
This commit is contained in:
148
pages/home/components/Leaderboard.vue
Normal file
148
pages/home/components/Leaderboard.vue
Normal file
@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<div>
|
||||
<div class="my-32px mb-20px text-18px text-[#333333] font-normal"><img src="@/assets/images/2.png" alt="" srcset="" /> 多多排行榜</div>
|
||||
<div class="flex">
|
||||
<div class="ma-auto box-border h-470px w-460px border border-[#EEEEEE] rounded-12px border-solid bg-[#FFFFFF] px-28px">
|
||||
<div class="title-bg ma-auto mb-40px mt-20px">一周图纸作者排行</div>
|
||||
<div v-for="(item, index) in topList" :key="item.ownUserId" class="mb-23px flex items-center">
|
||||
<div class="w-30px text-center"
|
||||
><img
|
||||
v-if="index === 0 || index === 1 || index === 2"
|
||||
:src="imagesUrl(index)"
|
||||
alt=""
|
||||
srcset=""
|
||||
:class="index === 0 ? 'w-20px h-22px' : 'w-28px h-29px'"
|
||||
/>
|
||||
<span v-else class="LiHei (Noncommercial) font-MF text-16px text-[#999999] font-normal">{{ index }}</span>
|
||||
</div>
|
||||
<div class="ml-20px w-120px flex items-center text-16px text-[#333333] font-normal">
|
||||
<img :src="item.avatar" alt="" srcset="" class="h-36px w-36px rd-50%" />
|
||||
<span class="ellipsis1 ml-10px">{{ item.nickname }}</span>
|
||||
</div>
|
||||
<div class="ml-20px flex text-14px text-[#666666] font-normal">
|
||||
<!-- <el-icon class="text-17px color-#a8abb2!"><Folder /></el-icon> -->
|
||||
<img src="@/assets/images/file.png" alt="" srcset="" class="h-18px" />
|
||||
<div class="ellipsis1 ml-10px">作品:{{ item.projectCount || 0 }}</div>
|
||||
</div>
|
||||
<div class="ml-20px flex text-14px text-[#666666] font-normal">
|
||||
<!-- <el-icon class="text-17px color-[#e4e7ed!]"><User /></el-icon> -->
|
||||
<img src="@/assets/images/user4.png" alt="" srcset="" class="h-17px" />
|
||||
<div class="ellipsis1 ml-10px">粉丝:{{ item.fansCount || 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 暂无数据 -->
|
||||
<el-empty v-if="!topList.length" description="暂无数据"></el-empty>
|
||||
</div>
|
||||
<div class="ma-auto ml-18px box-border h-470px w-460px border border-[#EEEEEE] rounded-12px border-solid bg-[#FFFFFF] px-28px">
|
||||
<div class="title-bg ma-auto mb-40px mt-20px">优质上传作者图纸</div>
|
||||
<div v-for="(item, index) in userTopList" :key="index" class="mb-23px flex items-center">
|
||||
<div class="w-30px text-center"
|
||||
><img
|
||||
v-if="index === 0 || index === 1 || index === 2"
|
||||
:src="imagesUrl(index)"
|
||||
alt=""
|
||||
srcset=""
|
||||
:class="index === 0 ? 'w-20px h-22px' : 'w-28px h-29px'"
|
||||
/>
|
||||
<span v-else class="font-MF LiHei (Noncommercial) text-16px text-[#999999] font-normal">{{ index }}</span>
|
||||
</div>
|
||||
<div class="ml-20px w-120px flex items-center text-16px text-[#333333] font-normal">
|
||||
<img :src="item.avatar" alt="" srcset="" class="h-36px w-36px rd-50%" />
|
||||
<span class="ellipsis1 ml-10px">{{ item.nickname }}</span>
|
||||
</div>
|
||||
<div class="ml-20px flex text-14px text-[#666666] font-normal">
|
||||
<img src="@/assets/images/file.png" alt="" srcset="" class="h-18px" />
|
||||
<div class="ellipsis1 ml-10px">作品:{{ item.projectCount || 0 }}</div>
|
||||
</div>
|
||||
<div class="ml-20px flex text-14px text-[#666666] font-normal">
|
||||
<img src="@/assets/images/user4.png" alt="" srcset="" class="h-17px" />
|
||||
<div class="ellipsis1 ml-10px">粉丝:{{ item.fansCount || 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 暂无数据 -->
|
||||
<el-empty v-if="!userTopList.length" description="暂无数据"></el-empty>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-63px">
|
||||
<div class="my-32px mb-20px text-18px text-[#333333] font-normal"><img src="@/assets/images/2.png" alt="" srcset="" /> 发布动态</div>
|
||||
<div class="box-border h-470px w-437px border border-[#EEEEEE] rounded-12px border-solid bg-[#FFFFFF] p-15px">
|
||||
<div
|
||||
v-for="item in newDrawList"
|
||||
:key="item.id"
|
||||
class="flex cursor-pointer items-center justify-between px-10px py-10px text-15px text-[#333333] font-normal hover:bg-[#f5f5f5]"
|
||||
@click="handleClick(item)"
|
||||
>
|
||||
<div class="ellipsis1 w-70%">{{ item.title }}</div>
|
||||
<div class="text-15px color-#999">{{ dayjs(item.createTime).format('MM-DD') }}</div>
|
||||
</div>
|
||||
<!-- 暂无数据 -->
|
||||
<el-empty v-if="!newDrawList.length" description="暂无数据"></el-empty>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
// import { Folder, User } from '@element-plus/icons-vue'
|
||||
import { newDraw, userTop } from '@/api/home/index'
|
||||
import { ProjectDrawPageRespVO, ProjectTrendingScoreUserInfoVO } from '@/api/home/type'
|
||||
import dayjs from 'dayjs'
|
||||
const Url = Object.values(
|
||||
import.meta.glob('@/assets/images/no*.png', {
|
||||
eager: true,
|
||||
query: 'url',
|
||||
})
|
||||
).map((module: any) => module.default)
|
||||
|
||||
const imagesUrl = computed(() => {
|
||||
return (index: number) => {
|
||||
return `${Url[index]}`
|
||||
}
|
||||
})
|
||||
|
||||
// 最新动态
|
||||
const newDrawList = ref<ProjectDrawPageRespVO[]>([])
|
||||
const handlenewDraw = async () => {
|
||||
const res = await newDraw({
|
||||
type: 1,
|
||||
limit: 11,
|
||||
})
|
||||
newDrawList.value = res.data
|
||||
}
|
||||
handlenewDraw()
|
||||
|
||||
const topList = ref<ProjectTrendingScoreUserInfoVO[]>([]) // 最新动态
|
||||
const userTopList = ref<ProjectTrendingScoreUserInfoVO[]>([]) // 最新动态
|
||||
const handleuserTop = () => {
|
||||
Promise.all([userTop({ type: 1 }), userTop({})]).then((res) => {
|
||||
topList.value = res[0].data
|
||||
userTopList.value = res[1].data
|
||||
})
|
||||
}
|
||||
handleuserTop()
|
||||
|
||||
const handleClick = (item: ProjectDrawPageRespVO) => {
|
||||
window.open(`/down-drawe-detail?id=${item.id}`, '_blank') // 修改为在新窗口打开
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.title-bg {
|
||||
width: 224px;
|
||||
height: 52px;
|
||||
background-image: url('@/assets/images/name_bg.png');
|
||||
background-size: 100% 100%;
|
||||
text-align: center;
|
||||
line-height: 48px;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
color: #a44120;
|
||||
}
|
||||
|
||||
.ellipsis1 {
|
||||
@include ellipsis(1);
|
||||
}
|
||||
</style>
|
||||
48
pages/home/components/LearningRecommendations.vue
Normal file
48
pages/home/components/LearningRecommendations.vue
Normal file
@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div v-if="bannerList?.length" class="mt-34px w-100% flex items-center justify-between">
|
||||
<el-image
|
||||
v-for="(item, index) in bannerList"
|
||||
:key="index"
|
||||
:src="item.content"
|
||||
alt=""
|
||||
srcset=""
|
||||
fit="cover"
|
||||
class="h-180px w-460px"
|
||||
:class="{
|
||||
'cursor-pointer': item.url,
|
||||
}"
|
||||
@click="item.url && handleClick(item.url)"
|
||||
>
|
||||
<template #placeholder>
|
||||
<div class="image-slot">Loading<span class="dot">...</span></div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
|
||||
import { getSettingPage } from '@/api/home/index'
|
||||
import { PageResultIndexSettingRespVO } from '@/api/home/type'
|
||||
|
||||
const pageReq = reactive({
|
||||
type: 2,
|
||||
status: 0,
|
||||
})
|
||||
|
||||
const bannerList = ref<PageResultIndexSettingRespVO[]>([])
|
||||
const getBanner = async () => {
|
||||
const res = await getSettingPage(pageReq)
|
||||
if (res.code === 0) {
|
||||
bannerList.value = res.data
|
||||
}
|
||||
}
|
||||
getBanner()
|
||||
|
||||
const handleClick = (url: string) => {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
200
pages/home/components/LoginForm.vue
Normal file
200
pages/home/components/LoginForm.vue
Normal file
@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<div class="login-container flex flex-col justify-between">
|
||||
<div class="ma-auto mt-25px w-100% flex flex-col items-center">
|
||||
<el-image
|
||||
:src="userStore.userInfoRes.avatar || 'https://tuxixi.oss-cn-chengdu.aliyuncs.com/avater.png'"
|
||||
alt=""
|
||||
srcset=""
|
||||
class="h-69px w-69px rd-50%"
|
||||
:class="{ 'cursor-pointer': isLogin }"
|
||||
fit="cover"
|
||||
@click="handleUserInfo"
|
||||
/>
|
||||
<div class="mt-10px text-16px text-[#333333] font-normal">
|
||||
<img v-if="userStore.userInfoRes.vipLevel === 1" src="@/assets/svg/vip.svg" alt="" class="relative top-12px" />
|
||||
<img v-if="userStore.userInfoRes.vipLevel === 2" src="@/assets/svg/svip.svg" alt="" class="relative top-12px" />
|
||||
Hi,{{ userStore.userInfoRes.nickname || '欢迎访问~' }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isLogin" class="mt-20px flex flex-col gap-20px px-20px text-14px text-[#333333] font-normal">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<img src="@/assets/images/cad_0 (1).png" alt="" srcset="" />
|
||||
<span class="title ml-4px" :title="`${userStaticInfo?.pointCount}`">我的积分: {{ userStaticInfo?.pointCount || 0 }}</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<img src="@/assets/images/cad_0 (2).png" alt="" srcset="" />
|
||||
<span class="title ml-4px" :title="`${userStaticInfo?.followCount}`">我的收藏: {{ userStaticInfo?.followCount || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<img src="@/assets/images/cad_0 (3).png" alt="" srcset="" />
|
||||
<span class="title ml-4px" :title="`${userStaticInfo?.projectCount}`">我的发布: {{ userStaticInfo?.projectCount || 0 }}</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<img src="@/assets/images/cad_0 (4).png" alt="" srcset="" />
|
||||
<span class="title ml-4px" :title="`${userStaticInfo?.downloadCount}`">我的下载: {{ userStaticInfo?.downloadCount || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!isLogin" class="mt-48px box-border flex justify-between px-18px">
|
||||
<div
|
||||
class="h-37px w-101px cursor-pointer border border-[#1A65FF] rounded-2px border-solid bg-[#1A65FF] text-center text-14px text-[#FFFFFF] font-normal line-height-37px"
|
||||
@click="handleLogin"
|
||||
>立即登录</div
|
||||
>
|
||||
<div
|
||||
class="h-37px w-101px cursor-pointer border border-[#DDDDDD] rounded-2px border-solid text-center text-14px text-[#333333] font-normal line-height-37px"
|
||||
@click="handleRegister"
|
||||
>免费注册</div
|
||||
>
|
||||
</div>
|
||||
<div v-else class="mt-30px box-border flex justify-between px-18px">
|
||||
<div
|
||||
class="h-37px w-101px cursor-pointer border border-[#1A65FF] rounded-2px border-solid bg-[#1A65FF] text-center text-14px text-[#FFFFFF] font-normal line-height-37px"
|
||||
@click="handleDrawe"
|
||||
>发布图纸</div
|
||||
>
|
||||
<div
|
||||
class="h-37px w-101px cursor-pointer border border-[#DDDDDD] rounded-2px border-solid text-center text-14px text-[#333333] font-normal line-height-37px"
|
||||
@click="handleLoginOut"
|
||||
>退出登录</div
|
||||
>
|
||||
</div>
|
||||
<div v-if="!isLogin" class="mt-30px flex justify-between px-20px">
|
||||
<img src="@/assets/images/qq-v2.png" alt="QQ登录" class="social-icon" @click="handleLoginQQ" />
|
||||
<img src="@/assets/images/weixin-v2.png" alt="微信登录" class="social-icon" @click="handleLoginWechat" />
|
||||
<img src="@/assets/images/email-v2.png" alt="邮箱登录" class="social-icon" @click="handleLoginEmail" />
|
||||
<img src="@/assets/images/phone-v2.png" alt="手机登录" class="social-icon" @click="handleLoginPhone" />
|
||||
</div>
|
||||
|
||||
<div class="sign-bonus mt-18px" @click="handleSign">
|
||||
<img src="@/assets/images/sign.png" alt="签到奖励" class="bonus-image" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { refreshToken as REFRESHTOKEN } from '@/utils/axios'
|
||||
import { getCurrentInstance, computed, watchEffect, ref } from 'vue'
|
||||
import { handleLoginQQ, handleLoginWechat } from '@/utils/login'
|
||||
import { UserStatisticsCountRespVO } from '@/api/personal-center/types'
|
||||
import { getUserStatistics } from '@/api/personal-center/index'
|
||||
import useUserStore from '@/store/user'
|
||||
const userStore = useUserStore()
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
// 是否登录
|
||||
const isLogin = computed(() => {
|
||||
return !!userStore.token
|
||||
})
|
||||
|
||||
// 获取用户统计信息
|
||||
const userStaticInfo = ref<UserStatisticsCountRespVO>()
|
||||
const fetchUserStatistics = async () => {
|
||||
try {
|
||||
const res = await getUserStatistics()
|
||||
userStaticInfo.value = res.data
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleLogin = () => {
|
||||
if (instance) {
|
||||
instance.appContext.config.globalProperties.$openLogin()
|
||||
}
|
||||
}
|
||||
|
||||
const handleRegister = () => {
|
||||
if (instance) {
|
||||
instance.appContext.config.globalProperties.$openRegister()
|
||||
}
|
||||
}
|
||||
|
||||
const handleLoginPhone = () => {
|
||||
if (instance) {
|
||||
instance.appContext.config.globalProperties.$openLogin('verify')
|
||||
}
|
||||
}
|
||||
|
||||
const handleLoginEmail = () => {
|
||||
if (instance) {
|
||||
instance.appContext.config.globalProperties.$openLoginEmail()
|
||||
}
|
||||
}
|
||||
|
||||
const handleUserInfo = () => {
|
||||
if (!isLogin.value) return
|
||||
window.open('/personal-detail', '_blank') // 修改为在新窗口打开
|
||||
}
|
||||
|
||||
// 发布图纸
|
||||
const handleDrawe = () => {
|
||||
window.open('/upnew/drawe', '_blank') // 修改为在新窗口打开
|
||||
}
|
||||
|
||||
// 推出登录
|
||||
const handleLoginOut = () => {
|
||||
REFRESHTOKEN.removeToken()
|
||||
userStore.setToken('')
|
||||
userStore.setUserId('')
|
||||
userStore.setRefreshToken('')
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
const handleSign = () => {
|
||||
window.open('/sign-page', '_blank') // 修改为在新窗口打开
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (isLogin.value) {
|
||||
fetchUserStatistics()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.login-container {
|
||||
width: 260px;
|
||||
background: white;
|
||||
/* border-radius: 8px; */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.login-button:hover {
|
||||
background-color: #40a9ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.social-login {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.social-icon {
|
||||
width: 35px;
|
||||
height: 36px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sign-bonus {
|
||||
background: #f5f5f5;
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bonus-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include ellipsis(1);
|
||||
}
|
||||
</style>
|
||||
52
pages/home/components/MainContent.vue
Normal file
52
pages/home/components/MainContent.vue
Normal file
@ -0,0 +1,52 @@
|
||||
`<template>
|
||||
<div class="main-content">
|
||||
<div class="flex">
|
||||
<div class="h-424px w-957px">
|
||||
<el-carousel height="424px" indicator-position="none">
|
||||
<el-carousel-item v-for="(item, index) in bannerList" :key="index">
|
||||
<el-image :src="item.content" class="w-100%" :class="{ 'cursor-pointer': item.url }" fit="cover" @click="handleClick(item.url)" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</div>
|
||||
<LoginForm />
|
||||
</div>
|
||||
<div class="box-border h-56px w-1219px flex items-center border border-[#EEEEEE] border-solid border-t-none bg-[#FFFFFF] pl-10px line-height-46px">
|
||||
<img src="@/assets/images/voice.png" alt="" srcset="" class="mr-10px h-15px w-16px" />
|
||||
<Vue3Marquee :duration="10" direction="normal" pause-on-hover>· 经典来袭,SolidWorks装配经典案例之气动发动机 </Vue3Marquee>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import LoginForm from './LoginForm.vue'
|
||||
|
||||
import { getSettingPage } from '@/api/home/index'
|
||||
import { PageResultIndexSettingRespVO } from '@/api/home/type'
|
||||
|
||||
const pageReq = reactive({
|
||||
type: 1,
|
||||
status: 0,
|
||||
})
|
||||
|
||||
const bannerList = ref<PageResultIndexSettingRespVO[]>([])
|
||||
const getBanner = async () => {
|
||||
const res = await getSettingPage(pageReq)
|
||||
if (res.code === 0) {
|
||||
bannerList.value = res.data
|
||||
}
|
||||
}
|
||||
getBanner()
|
||||
|
||||
const handleClick = (url: string) => {
|
||||
if (url) {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.main-content {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
221
pages/home/components/PopularDrawings.vue
Normal file
221
pages/home/components/PopularDrawings.vue
Normal file
@ -0,0 +1,221 @@
|
||||
<template>
|
||||
<div class="mt-34px w-100%">
|
||||
<div class="flex items-center justify-between">
|
||||
<KlTabBar v-model="query.type" :data="tabBar" :show-icon="true" />
|
||||
<div class="flex gap-15px">
|
||||
<span
|
||||
v-for="(item, index) in projectTypeList"
|
||||
:key="index"
|
||||
:class="{ 'color-#1A65FF! border-b-2px border-b-solid border-b-[#1A65FF] rounded-1px pb-3px': query.projectTypeDay === item.id }"
|
||||
class="cursor-pointer text-14px color-#333333"
|
||||
@mouseenter="handleHover(item)"
|
||||
@click="handleClickType(item)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content mt-10px flex">
|
||||
<!-- <div class="sider">
|
||||
<div class="box-border h-100% h-55px w-221px flex items-center rounded-lg bg-[#1A65FF] pl-24px text-white">
|
||||
<img src="@/assets/images/1.png" alt="" srcset="" />
|
||||
<span class="ml-12px text-16px">全部资源分类</span>
|
||||
</div>
|
||||
<div class="side-menu border border-[#EEEEEE] border-solid">
|
||||
<div
|
||||
v-for="(item, index) in projectTypeListChildren"
|
||||
:key="index"
|
||||
class="menu-item"
|
||||
:class="{ active: query.projectType === item.id }"
|
||||
@mouseenter="handleClick(item)"
|
||||
@click="
|
||||
handleClickType(
|
||||
projectTypeList?.find((c: any) => c.id === query.projectTypeDay),
|
||||
item
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<el-empty v-if="projectTypeList.length === 0" description="暂无数据"></el-empty>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="box-border w-100%">
|
||||
<div class="grid grid-cols-5 gap-17px">
|
||||
<div
|
||||
v-for="item in hotTopList"
|
||||
:key="item.id"
|
||||
class="cursor-pointer border border-[#EEEEEE] rounded-1px border-solid bg-[#FFFFFF]"
|
||||
@click="handleCardClick(item)"
|
||||
>
|
||||
<div>
|
||||
<div class="h-212px w-100%">
|
||||
<el-image :src="item.iconUrl" alt="" srcset="" fit="cover" class="block h-100% w-100%" />
|
||||
</div>
|
||||
<div class="ellipsis mx-20px my-11px text-14px color-#333333 font-normal">{{ item.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-if="hotTopList.length === 0" description="暂无数据"></el-empty>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="morefont-400 text-16px text-[#1A65FF] text-right cursor-pointer"> 查看更多 >> </div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import KlTabBar from '@/components/kl-tab-bar/index.vue'
|
||||
// import KlCardDetail from '@/components/kl-card-detail/index.vue'
|
||||
import { hotTop, hotTag } from '@/api/home/index'
|
||||
import { ProjectDrawPageRespVO, ProjectDictNodeVO } from '@/api/home/type'
|
||||
|
||||
/** 请求参数 */
|
||||
const query = reactive({
|
||||
type: 1,
|
||||
projectType: '',
|
||||
projectTypeDay: '', // 自定义的
|
||||
isDomestic: 1,
|
||||
limit: 6,
|
||||
})
|
||||
|
||||
const tabBar = ref([
|
||||
{
|
||||
label: '热门图纸',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '热门模型',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
label: '热门文本',
|
||||
value: 2,
|
||||
},
|
||||
])
|
||||
|
||||
// const hotTopListTitle = computed(() => {
|
||||
// if (projectTypeList.value.length > 0) {
|
||||
// const item = projectTypeList.value.find((item) => item.id === query.projectType)
|
||||
// return item?.name
|
||||
// }
|
||||
// return ''
|
||||
// })
|
||||
|
||||
/** 点击卡片 */
|
||||
const handleCardClick = (item: ProjectDrawPageRespVO) => {
|
||||
// 跳转到下载详情页 并且是单独开标签
|
||||
window.open(`/down-drawe-detail?id=${item.id}`, '_blank') // 修改为在新窗口打开
|
||||
}
|
||||
|
||||
const handleClickType = (primary?: ProjectDictNodeVO, secondary?: ProjectDictNodeVO) => {
|
||||
if (primary?.name !== '全部分类') return
|
||||
const normal = { id: '0', name: query.type === 1 ? '图纸库' : query.type === 2 ? '文本库' : '模型库', isChildren: false }
|
||||
const level = [primary, secondary].filter((item) => item).map((item) => ({ id: item?.id, name: item?.name, isChildren: item?.children ? false : true }))
|
||||
if (primary?.id === '0') {
|
||||
level[0].name = '图纸库'
|
||||
} else {
|
||||
level.unshift(normal)
|
||||
}
|
||||
if (query.type === 1) {
|
||||
window.open(`/drawe?level=${JSON.stringify(level)}`)
|
||||
} else if (query.type === 2) {
|
||||
window.open(`/text?level=${JSON.stringify(level)}`)
|
||||
} else if (query.type === 3) {
|
||||
window.open(`/model?level=${JSON.stringify(level)}`)
|
||||
}
|
||||
}
|
||||
|
||||
/** 热门数据 */
|
||||
const hotTopList = ref<ProjectDrawPageRespVO[]>([])
|
||||
const getHotTop = () => {
|
||||
hotTop({
|
||||
type: query.type,
|
||||
projectType: query.projectTypeDay,
|
||||
isDomestic: query.isDomestic,
|
||||
projectTypeTop: query.projectTypeDay,
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
hotTopList.value = res.data || []
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取分类下拉框 */
|
||||
const projectTypeList = ref<ProjectDictNodeVO[]>([])
|
||||
const projectTypeListChildren = ref<ProjectDictNodeVO[]>([])
|
||||
const getParent = () => {
|
||||
hotTag({
|
||||
type: query.type,
|
||||
limit: 6,
|
||||
size: 1000,
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
if (Array.isArray(res.data) && res.data.length > 0) {
|
||||
projectTypeList.value = [...res.data, { id: '0', name: '全部分类', children: [] }]
|
||||
projectTypeListChildren.value = res.data[0].children || []
|
||||
query.projectTypeDay = res.data[0].id || ''
|
||||
query.projectType = res.data[0]!.children?.[0]!.id || ''
|
||||
// 热门数据
|
||||
getHotTop()
|
||||
} else {
|
||||
hotTopList.value = []
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleHover = (item: ProjectDictNodeVO) => {
|
||||
query.projectTypeDay = item.id || ''
|
||||
if (item.name === '全部分类') return
|
||||
projectTypeListChildren.value = item.children || []
|
||||
query.projectType = item.children?.[0].id || ''
|
||||
// 热门数据
|
||||
getHotTop()
|
||||
}
|
||||
|
||||
/** 点击分类 */
|
||||
// const handleClick = (item: ProjectDictNodeVO) => {
|
||||
// query.projectType = item.id || ''
|
||||
// getHotTop()
|
||||
// }
|
||||
|
||||
watch(
|
||||
() => query.type,
|
||||
() => {
|
||||
getParent()
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.side-menu {
|
||||
width: 221px;
|
||||
height: 470px;
|
||||
background-color: #fff;
|
||||
// padding: 10px 0;
|
||||
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
padding: 10px 24px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
&.active {
|
||||
background-color: #f0f7ff;
|
||||
color: #1a65ff !important;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background-color: #f0f7ff;
|
||||
color: #1a65ff;
|
||||
}
|
||||
</style>
|
||||
79
pages/home/components/RecommendedColumns.vue
Normal file
79
pages/home/components/RecommendedColumns.vue
Normal file
@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div class="mt-34px w-100%">
|
||||
<KlTabBar v-model="query.type" :data="tabBar" :show-icon="true" />
|
||||
<div class="content mt-10px">
|
||||
<el-row :gutter="20">
|
||||
<el-col v-for="i in recommendTopList" :key="i.id" :span="6">
|
||||
<CardPicture :item-info="i" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<!-- 暂无数据 -->
|
||||
<el-empty v-if="recommendTopList.length === 0" description="暂无数据"></el-empty>
|
||||
<div v-if="recommendTopList.length > 0" class="cursor-pointer text-right text-16px text-[#1A65FF] font-normal" @click="handleClick"> 查看更多 >> </div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import KlTabBar from '@/components/kl-tab-bar/index.vue'
|
||||
import CardPicture from '@/components/kl-card-picture/index.vue'
|
||||
import { recommendTop } from '@/api/home/index'
|
||||
import { ProjectDrawPageRespVO } from '@/api/home/type'
|
||||
|
||||
const query = reactive({
|
||||
type: 1,
|
||||
projectType: 0,
|
||||
isDomestic: 1,
|
||||
limit: 8,
|
||||
})
|
||||
|
||||
const tabBar = ref([
|
||||
{
|
||||
label: '推荐图纸',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '推荐模型',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
label: '推荐文本',
|
||||
value: 2,
|
||||
},
|
||||
])
|
||||
|
||||
const recommendTopList = ref<ProjectDrawPageRespVO[]>([])
|
||||
const getRecommendTop = () => {
|
||||
// @ts-ignore
|
||||
recommendTop(query).then((res) => {
|
||||
if (Array.isArray(res.data) && res.data.length > 0) {
|
||||
recommendTopList.value = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
if (query.type === 1) {
|
||||
window.open('/drawe', '_blank')
|
||||
} else if (query.type === 2) {
|
||||
window.open('/text', '_blank')
|
||||
} else {
|
||||
window.open('/model', '_blank')
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => query.type,
|
||||
(val) => {
|
||||
if (val) {
|
||||
getRecommendTop()
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
262
pages/home/components/SideMenu.vue
Normal file
262
pages/home/components/SideMenu.vue
Normal file
@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<div ref="sideMenu" class="side-menu">
|
||||
<div
|
||||
v-for="(item, index) in menuItems"
|
||||
:key="index"
|
||||
:ref="(el) => setMenuItemRef(el, index)"
|
||||
class="menu-item"
|
||||
@mouseenter="showSubMenu(index)"
|
||||
@mouseleave="hideSubMenu"
|
||||
>
|
||||
<!-- @click="handleSubmenuClick(item)" -->
|
||||
{{ item.name }}
|
||||
<!-- 悬浮浮窗 -->
|
||||
<div v-if="activeIndex === index" class="submenu-panel" :style="{ top: submenuTop + 'px' }" @mouseenter="keepSubmenuVisible" @mouseleave="hideSubMenu">
|
||||
<div class="submenu-content">
|
||||
<!-- <div class="text-right">更多</div> -->
|
||||
<div class="submenu-group">
|
||||
<template v-for="group in item.children" :key="group.id">
|
||||
<div class="submenu-group-title" @click.stop="handleSubmenuClick(group)">{{ group.name }}</div>
|
||||
<div class="submenu-group-items">
|
||||
<div v-for="sub in group.children" :key="sub.id" class="submenu-item" @click.stop="handleSubmenuClick(group, sub)">
|
||||
{{ sub.name }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import type { ComponentPublicInstance } from 'vue'
|
||||
import { tab2 } from '@/api/home/index'
|
||||
import { ProjectDictNodeVO } from '@/api/home/type'
|
||||
|
||||
const activeIndex = ref(-1)
|
||||
const submenuTop = ref(0)
|
||||
// const submenuLeft = ref(0)
|
||||
const sideMenu = ref()
|
||||
const menuItemRefs = ref<HTMLElement[]>([])
|
||||
|
||||
const showSubMenu = (index: number) => {
|
||||
// if (menuItems.value.length === index + 1) {
|
||||
// return
|
||||
// }
|
||||
activeIndex.value = index
|
||||
const dom = menuItemRefs.value[index].getBoundingClientRect()
|
||||
console.log(dom)
|
||||
// submenuTop.value = window.scrollY
|
||||
}
|
||||
|
||||
const hideSubMenu = () => {
|
||||
activeIndex.value = -1
|
||||
}
|
||||
|
||||
const handleSubmenuClick = (primary?: ProjectDictNodeVO, secondary?: ProjectDictNodeVO, tertiary?: ProjectDictNodeVO) => {
|
||||
const normal = { id: '0', name: '图纸库', isChildren: false }
|
||||
const level = [primary, secondary, tertiary]
|
||||
.filter(Boolean)
|
||||
.map((item) => ({ id: item?.id, name: item?.name, isChildren: item?.children?.length ? false : true }))
|
||||
if (primary?.id === '0') {
|
||||
level[0].name = '图纸库'
|
||||
} else {
|
||||
level.unshift(normal)
|
||||
}
|
||||
|
||||
window.open(`/drawe?level=${JSON.stringify(level)}`, '_blank')
|
||||
}
|
||||
|
||||
const keepSubmenuVisible = () => {
|
||||
// activeIndex.value = activeIndex.value // 保持当前索引
|
||||
}
|
||||
|
||||
const setMenuItemRef = (el: Element | ComponentPublicInstance | null, index: number) => {
|
||||
if (el && 'offsetTop' in el) {
|
||||
menuItemRefs.value[index] = el as HTMLElement
|
||||
}
|
||||
}
|
||||
|
||||
const menuItems = ref<ProjectDictNodeVO[]>([])
|
||||
const getLabel = () => {
|
||||
tab2().then((res) => {
|
||||
const arr = []
|
||||
for (let i = 0; i < res.data.length; i += 2) {
|
||||
arr.push({
|
||||
children: res.data.slice(i, i + 2),
|
||||
name: getName(res.data.slice(i, i + 2)),
|
||||
})
|
||||
}
|
||||
menuItems.value = arr
|
||||
})
|
||||
}
|
||||
|
||||
const getName = (arr: any[]) => {
|
||||
if (arr.length === 1) {
|
||||
return arr[0].name
|
||||
} else {
|
||||
return arr[0].name + ' / ' + arr[1].name
|
||||
}
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
// 获取标签
|
||||
getLabel()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.side-menu {
|
||||
width: 221px;
|
||||
background-color: #fff;
|
||||
/* padding: 10px 0; */
|
||||
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
|
||||
/* overflow: visible; */
|
||||
box-sizing: border-box;
|
||||
position: relative; /* 添加定位上下文 */
|
||||
/* overflow-y: auto; */
|
||||
/* 隐藏滚动条 */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
z-index: 9;
|
||||
/* padding-top: 10px; */
|
||||
}
|
||||
|
||||
/* 隐藏 Webkit 浏览器的滚动条 */
|
||||
.side-menu::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 鼠标悬停时显示滚动条 */
|
||||
.side-menu:hover {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
}
|
||||
|
||||
/* .side-menu:hover::-webkit-scrollbar {
|
||||
display: block;
|
||||
width: 8px !important;
|
||||
}
|
||||
|
||||
.side-menu:hover::-webkit-scrollbar-thumb {
|
||||
background-color: #e6e8eb;
|
||||
border-radius: 6px !important;
|
||||
}
|
||||
|
||||
.side-menu:hover::-webkit-scrollbar-track {
|
||||
background-color: #fff;
|
||||
} */
|
||||
|
||||
.menu-item {
|
||||
/* position: relative; */
|
||||
text-align: center;
|
||||
padding: 4px 24px;
|
||||
cursor: pointer;
|
||||
/* transition: all 0.3s ease; */
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
border: 1px solid transparent;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background-color: #fff;
|
||||
color: #1890ff;
|
||||
border: 1px solid #1890ff;
|
||||
border-right: transparent !important;
|
||||
}
|
||||
|
||||
.submenu-panel {
|
||||
position: absolute;
|
||||
left: 220px;
|
||||
top: 0;
|
||||
width: 957px;
|
||||
background: #fff;
|
||||
/* box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); */
|
||||
border: 1px solid #1890ff;
|
||||
/* border-radius: 4px; */
|
||||
display: flex;
|
||||
padding: 20px;
|
||||
z-index: -1;
|
||||
box-sizing: border-box;
|
||||
min-height: 480px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.submenu-panel::before {
|
||||
/* content: '';
|
||||
position: absolute;
|
||||
left: -6px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 8px solid transparent;
|
||||
border-bottom: 8px solid transparent;
|
||||
border-right: 8px solid #fff;
|
||||
filter: drop-shadow(-2px 0 2px rgba(0, 0, 0, 0.1)); */
|
||||
}
|
||||
|
||||
.submenu-content {
|
||||
/* flex: 1; */
|
||||
/* display: grid; */
|
||||
/* grid-template-columns: repeat(3, 1fr); */
|
||||
/* gap: 15px; */
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.submenu-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
-webkit-column-break-inside: avoid; /* 兼容webkit内核 */
|
||||
/* border: 1px solid #e6e8eb; */
|
||||
.submenu-group-title {
|
||||
font-size: 15px;
|
||||
/* font-weight: 700; */
|
||||
padding: 10px 16px;
|
||||
color: #000;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
/* border-bottom: 1px solid #e6e8eb; */
|
||||
}
|
||||
.submenu-group-title:hover {
|
||||
color: #1890ff;
|
||||
/* border-bottom: 1px solid #e6e8eb; */
|
||||
}
|
||||
.submenu-group-items {
|
||||
display: flex !important;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
color: #666;
|
||||
padding: 0px 16px;
|
||||
gap: 10px;
|
||||
.submenu-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submenu-item {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: inline-block; /* 改为行内块元素 */
|
||||
/* padding: 8px 12px; */
|
||||
padding-right: 12px;
|
||||
/* padding-bottom: 8px; */
|
||||
}
|
||||
|
||||
.submenu-item:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
49
pages/home/index.vue
Normal file
49
pages/home/index.vue
Normal file
@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="ma-auto w-1440px">
|
||||
<!-- 搜索 -->
|
||||
<KlSearch></KlSearch>
|
||||
<!-- 菜单 -->
|
||||
<KlMenuV2 />
|
||||
<!-- banner -->
|
||||
<div class="banner-container">
|
||||
<SideMenu class="side-menu" />
|
||||
<MainContent class="main-content" />
|
||||
</div>
|
||||
<!-- 学习方法推荐 -->
|
||||
<LearningRecommendations></LearningRecommendations>
|
||||
<!-- 推荐栏目 -->
|
||||
<RecommendedColumns></RecommendedColumns>
|
||||
<!-- 热门图纸 -->
|
||||
<PopularDrawings></PopularDrawings>
|
||||
<!-- 排行榜 -->
|
||||
<Leaderboard></Leaderboard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import KlSearch from '@/layout/kl-search/index.vue'
|
||||
import KlMenuV2 from '@/layout/kl-menus-v2/index.vue'
|
||||
import SideMenu from './components/SideMenu.vue'
|
||||
import MainContent from './components/MainContent.vue'
|
||||
import RecommendedColumns from './components/RecommendedColumns.vue'
|
||||
import PopularDrawings from './components/PopularDrawings.vue'
|
||||
import Leaderboard from './components/Leaderboard.vue'
|
||||
import LearningRecommendations from './components/LearningRecommendations.vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.banner-container {
|
||||
display: flex;
|
||||
height: 480px;
|
||||
background-color: #f0f2f5;
|
||||
border: 1px solid #eeeeee;
|
||||
}
|
||||
|
||||
.side-menu {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user