完成游戏资讯小组件的开发

This commit is contained in:
expdsn 2024-10-17 16:21:25 +08:00
parent ebb4bc8421
commit 91f8b1a229
28 changed files with 567 additions and 38 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
public/icons/gt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
public/icons/mita.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
public/icons/today_eat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -71,11 +71,10 @@ export default defineComponent(() => {
return () => ( return () => (
<div class={clsx('w-full h-full relative flex', isGame.value && 'bg-[#2c2e3e]')}> <div class={clsx('w-full h-full relative flex', isGame.value && 'bg-[#2c2e3e]')}>
<ThemeProvider dark={isGame.value}> <ThemeProvider dark={isGame.value}>
{isGame.value && <AdderPageBack />}
<div <div
class={ class={
'w-[200px] h-full relative z-10 pt-[100px] ' + 'w-[220px] h-full relative z-10 pt-[100px] ' +
(isGame.value ? 'pl-6 pr-2' : 'bg-white/60 backdrop-blur px-4') (isGame.value ? 'pl-8 pr-2 rounded-xl' : 'bg-white/60 l backdrop-blur px-4')
} }
> >
<ItemButton <ItemButton

View File

@ -58,7 +58,7 @@ export default defineComponent(() => {
const sortBy = ref<'hotNum' | 'time'>('hotNum') const sortBy = ref<'hotNum' | 'time'>('hotNum')
onMounted(() => { onMounted(() => {
request<BackgroundType[]>('GET', '/api/backgroundTypes').then((res) => { request<BackgroundType[]>('GET', '/api/app/backgroundTypes').then((res) => {
console.log(res) console.log(res)
typeList.value = res typeList.value = res
selectType.value = res[0].id selectType.value = res[0].id
@ -69,7 +69,7 @@ export default defineComponent(() => {
(e) => { (e) => {
if (!e[0]) return if (!e[0]) return
wallpaperList.value = [] wallpaperList.value = []
request<WallpaperItem[]>('GET', `/api/backgrounds?typeId=${e[0]}&sort=${sortBy.value}`).then( request<WallpaperItem[]>('GET', `/api/app/backgrounds?typeId=${e[0]}&sort=${sortBy.value}`).then(
(res) => { (res) => {
console.log(res) console.log(res)
wallpaperList.value = res wallpaperList.value = res

View File

@ -0,0 +1,9 @@
import { defineComponent } from 'vue'
export default defineComponent(() => {
return () => (
<div class="w-full h-full bg-[#ecfbff] flex flex-col">
large
</div>
)
})

View File

@ -0,0 +1,10 @@
import { defineComponent } from 'vue'
export default defineComponent(() => {
return () => (
<div class="w-full h-full bg-[#ecfbff] flex flex-col">
middle
</div>
)
})

View File

@ -0,0 +1,12 @@
import { defineComponent } from 'vue'
export default defineComponent(() => {
return () => (
<div
class="w-full h-full bg-red-50 flex "
style={{
background: 'linear-gradient(180deg,#dcefff 0%,#e7ecff 100%)'
}}
></div>
)
})

View File

@ -0,0 +1,14 @@
import { defineComponent } from 'vue'
export default defineComponent(() => {
return () => (
<div
class="w-full h-full items-center pl-3 gap-x-2 flex py-3 text-white"
style={{
background: 'linear-gradient(135deg,#5996ff 0%,#4862ff 100%)'
}}
>
</div>
)
})

30
src/widgets/demo/index.ts Normal file
View File

@ -0,0 +1,30 @@
import asyncLoader from '@/utils/asyncLoader'
import type { Widget } from '..'
export default {
name: 'gameNews',
label: '游戏资讯',
description: '游戏资讯',
icon: '/icons/game_news_icon.png',
modal: asyncLoader(() => import('./Modal')),
list: [
{
w: 2,
h: 1,
label: '小',
component: asyncLoader(() => import('./Small'))
},
{
w: 2,
h: 2,
label: '中',
component: asyncLoader(() => import('./Middle'))
},
{
w: 4,
h: 2,
label: '大',
component: asyncLoader(() => import('./Large'))
}
]
} as Widget

View File

@ -0,0 +1,28 @@
import { defineComponent } from 'vue'
import useGameNews from './useGameNews'
export default defineComponent(() => {
const store = useGameNews()
return () => (
<div
class="w-full h-full grid grid-cols-1 grid-rows-3 p-2 gap-y-1"
style={{
background: 'rgba(23,33,45,.6)'
}}
>
{store.list
.filter((_, index) => index < 3)
.map((item) => (
<div class={'w-full rounded-xl bg-[#17212d] flex gap-x-2 p-1'}>
<img class={'h-full w-[45px] rounded-lg object-cover'} src={item.cover}></img>
<div class={'flex flex-col overflow-hidden justify-between py-[2px]'}>
<span class={'text-white text-nowrap text-[14px] text-ellipsis overflow-hidden'}>
{item.title}
</span>
<span class={'text-white text-nowrap text-[12px] opacity-60 text-ellipsis overflow-hidden'}>{item.content}</span>
</div>
</div>
))}
</div>
)
})

View File

@ -0,0 +1,28 @@
import { defineComponent } from 'vue'
import useGameNews from './useGameNews'
export default defineComponent(() => {
const store = useGameNews()
return () => (
<div
class="w-full h-full grid grid-cols-1 grid-rows-3 p-2 gap-y-1"
style={{
background: 'rgba(23,33,45,.6)'
}}
>
{store.list
.filter((_, index) => index < 3)
.map((item) => (
<div class={'w-full rounded-xl bg-[#17212d] flex gap-x-2 p-1'}>
<img class={'h-full w-[45px] rounded-lg object-cover'} src={item.cover}></img>
<div class={'flex flex-col overflow-hidden justify-between py-[2px]'}>
<span class={'text-white text-nowrap text-[14px] text-ellipsis overflow-hidden'}>
{item.title}
</span>
<span class={'text-white text-nowrap text-[12px] opacity-60 text-ellipsis overflow-hidden'}>{item.content}</span>
</div>
</div>
))}
</div>
)
})

View File

@ -0,0 +1,88 @@
import { defineComponent, ref, watch, type VNodeRef } from 'vue'
import useGameNews from './useGameNews'
import { RiTimeLine } from 'oh-vue-icons/icons'
import { addIcons, OhVueIcon } from 'oh-vue-icons'
import dayjs from 'dayjs'
addIcons(RiTimeLine)
export default defineComponent(() => {
const store = useGameNews()
const containerRef = ref<VNodeRef | null>(null as VNodeRef | null)
const handleScroll = () => {
const container = containerRef.value
console.log(container)
if (container.scrollTop + container.clientHeight >= container.scrollHeight - 50) {
console.log('到底了')
store.getNews()
}
}
return () => (
<div
class="w-full h-full flex flex-col p-5 bg-[#17212d]"
style={{
background: 'rgba(23,33,45,.6)'
}}
>
<div class={'flex w-full justify-center pb-5'}>
<img class={'h-[20px]'} src="/icons/游戏资讯@2x.png"></img>
</div>
<div class={'h-0 w-full flex-1'}>
<div class={'w-full h-full overflow-y-scroll'} onWheel={handleScroll} ref={containerRef}>
<div class={'grid grid-cols-2 gap-4 '}>
{store.list.map((item, index) => {
return (
<div
class={
'flex cursor-pointer items-center h-[110px] rounded-lg relative overflow-hidden'
}
onClick={()=> {
window.open(item.share_url)
}}
key={index}
>
<img class={'h-full w-[40%] rounded-xl object-cover'} src={item.cover}></img>
<div
class={
' absolute right-0 top-0 w-[70%] h-full rounded-xl flex flex-col justify-between py-4 text-white pl-2 overflow-hidden'
}
style={{
backdropFilter: 'blur(16px)',
backgroundColor: '#17212d',
background: 'rgba(0, 0, 0, .2)'
}}
>
<span class={'text-nowrap text-ellipsis overflow-hidden text-[14px]'}>
{item.title}
</span>
<span
class={' text-ellipsis overflow-hidden text-[12px] opacity-60 line-clamp-2'}
>
{item.content}
</span>
<div class={'text-[12px] items-center flex opacity-40 gap-x-1'}>
<OhVueIcon
name={RiTimeLine.name}
class={'text-white'}
scale={0.7}
></OhVueIcon>
<span>{dayjs(item.create_time * 1000).format('YYYY-MM-DD')}</span>
</div>
</div>
</div>
)
})}
</div>
{store.loading && (
<div class={'w-full font-bold flex justify-center text-white py-2'}>...</div>
)}
{store.noMoreData && (
<div class={'w-full font-bold flex justify-center text-white py-2'}></div>
)}
</div>
</div>
</div>
)
})

View File

@ -0,0 +1,28 @@
import { defineComponent } from 'vue'
import useGameNews from './useGameNews'
export default defineComponent(() => {
const store = useGameNews()
return () => (
<div
class="w-full h-full flex items-center px-3"
style={{
background: `url('/bg/gamenews__back.png')`,
backgroundSize: 'cover'
}}
>
<img class={'w-[48px] h-[48px]'} src={'/icons/gamenews__icon.png'}></img>
<div class={'flex-1 flex justify-center'}>
<div class="flex-col flex">
<span class={'text-[16px] text-white'}></span>
<div class={'flex items-center text-[#fffc] text-[12px] '}>
<div>
<img src="/icons/gt.png"></img>
</div>
</div>
</div>
</div>
</div>
)
})

View File

@ -0,0 +1,30 @@
import asyncLoader from '@/utils/asyncLoader'
import type { Widget } from '..'
export default {
name: 'gameNews',
label: '游戏资讯',
description: '游戏资讯',
icon: '/icons/game_news_icon.png',
modal: asyncLoader(() => import('./Modal')),
list: [
{
w: 2,
h: 1,
label: '小',
component: asyncLoader(() => import('./Small'))
},
{
w: 2,
h: 2,
label: '中',
component: asyncLoader(() => import('./Middle'))
},
{
w: 4,
h: 2,
label: '大',
component: asyncLoader(() => import('./Large'))
}
]
} as Widget

View File

@ -0,0 +1,58 @@
import request from "@/utils/request";
import { defineStore } from "pinia";
import { ref, type VNodeRef } from "vue";
export interface GameNews {
id: number;
title: string;
content: string;
cover: string;
image: string;
create_time: number;
share_url: string;
}
export default defineStore("gameNews", () => {
const list = ref<GameNews[]>([])
const page = ref(1)
const loading = ref(false)
const noMoreData = ref(false)
const containerRef = ref<VNodeRef | null>(null)
const getList = async () => {
const res = await request<{
data: GameNews[];
}>('GET', `/api/gameDes?page=${page.value}&page_size=16`)
return res.data
}
const getNews = async () => {
if (loading.value || noMoreData.value) return;
loading.value = true;
try {
const data = await getList()
if (data.length === 0) {
noMoreData.value = true;
} else {
list.value.push(...data);
page.value++;
}
} catch (e) {
console.log(e);
} finally {
loading.value = false;
}
}
getNews()
return {
list,
loading,
noMoreData,
getNews,
containerRef
}
})

View File

@ -2,6 +2,7 @@ import type { Component } from 'vue'
import calendar from './calendar' import calendar from './calendar'
import weather from './weather' import weather from './weather'
import weApply from './weApply' import weApply from './weApply'
import gameNews from './gameNews'
export interface Widget { export interface Widget {
name: string // 小组件类型唯一标识 name: string // 小组件类型唯一标识
label: string // 小组件名称 label: string // 小组件名称
@ -16,4 +17,4 @@ export interface Widget {
}[] // 不同尺寸小组件块 }[] // 不同尺寸小组件块
} }
export default [calendar, weather, weApply] as Widget[] export default [calendar, weather, weApply, gameNews] as Widget[]

172
src/widgets/video/Large.tsx Normal file
View File

@ -0,0 +1,172 @@
import { defineComponent, ref, watch, type VNodeRef } from 'vue'
import { useWeApplyStore } from './useWeApplyStore'
import { addIcons, OhVueIcon } from 'oh-vue-icons'
import { HiChevronDown } from 'oh-vue-icons/icons'
import clsx from 'clsx'
addIcons(HiChevronDown)
export default defineComponent(() => {
const store = useWeApplyStore()
const gameRef = ref<VNodeRef | null>(null)
const isGameBottom = ref(false)
const isWorkBottom = ref(false)
const workRef = ref<VNodeRef | null>(null)
const computIsBottom = (ref: any) => {
if (ref) {
const { scrollTop, clientHeight, scrollHeight } = ref
return scrollTop + clientHeight >= scrollHeight
}
}
const handleGameScroll = () => {
const isAtBottom = computIsBottom(gameRef.value)
console.log(isAtBottom ? '已滚动到底部!' : '未滚动到底部。')
if (isAtBottom) {
isGameBottom.value = true
} else {
isGameBottom.value = false
}
}
const handleWorkScroll = () => {
const isAtBottom = computIsBottom(workRef.value)
console.log(isAtBottom ? '已滚动到底部!' : '未滚动到底部。')
if (isAtBottom) {
isWorkBottom.value = true
} else {
isWorkBottom.value = false
}
}
watch(gameRef, (val, _, onCleanup) => {
console.log(val)
if (!val) return
val.addEventListener('scroll', handleGameScroll)
// if (val + gameRef.value.clientHeight >= gameRef.value.scrollHeight) {
// isGameBottom.value = true
// } else {
// isGameBottom.value = false
// }
onCleanup(() => {
val.removeEventListener('scroll', handleGameScroll)
})
})
watch(workRef, (val, _, onCleanup) => {
console.log(val)
if (!val) return
val.addEventListener('scroll', handleWorkScroll)
// if (val + gameRef.value.clientHeight >= gameRef.value.scrollHeight) {
// isGameBottom.value = true
// } else {
// isGameBottom.value = false
// }
onCleanup(() => {
val.removeEventListener('scroll', handleWorkScroll)
})
})
return () => (
<div
class={'w-full h-full flex p-2 justify-between'}
style={{
background: 'rgba(23,33,45,.6)'
}}
>
<div
class={'w-[49.5%] h-full rounded-xl p-2 relative overflow-hidden'}
style={{
background: 'rgba(23,33,46,.8)'
}}
>
{!isGameBottom.value && (
<div
class={
'w-full absolute bottom-0 left-0 flex justify-center bg-[#17212e] h-[20px] items-center'
}
onClick={(e) => {
e.stopPropagation()
e.preventDefault()
if (gameRef.value) {
gameRef.value.scrollTop += 40
}
}}
>
<OhVueIcon name={HiChevronDown.name} fill="#ddd"></OhVueIcon>
</div>
)}
<div
ref={gameRef}
class={clsx('w-full h-full flex flex-col overflow-y-scroll gap-y-2 scrollbar-hide ')}
onWheel={() => {
console.log('wheel')
}}
>
{store.state.list
.filter((val) => val.type === 'game')
.map((item) => (
<div class={'flex gap-x-2 items-center'}>
<img src={item.icon} alt="game icon" class={'w-[37px] h-[37px] rounded'}></img>
<div class={'flex-1 flex flex-col overflow-hidden'}>
<span class={'text-white text-[14px]'}>{item.name}</span>
<span
class={
'text-[#fff9] text-[12px] whitespace-nowrap text-ellipsis overflow-hidden'
}
>
{item.des}
</span>
</div>
</div>
))}
</div>
</div>
<div
class={'w-[49.5%] h-full rounded-xl p-2 relative overflow-hidden'}
style={{
background: 'rgba(23,33,46,.8)'
}}
>
{!isWorkBottom.value && (
<div
class={
'w-full absolute bottom-0 left-0 flex justify-center bg-[#17212e] h-[20px] items-center'
}
onClick={(e) => {
e.stopPropagation()
e.preventDefault()
if (workRef.value) {
workRef.value.scrollTop += 20
}
}}
>
<OhVueIcon name={HiChevronDown.name} fill="#ddd"></OhVueIcon>
</div>
)}
<div
ref={workRef}
class={clsx('w-full h-full flex flex-col overflow-y-scroll gap-y-2 scrollbar-hide ')}
onWheel={() => {
console.log('wheel')
handleWorkScroll()
}}
>
{store.state.list
.filter((val) => val.type === 'work')
.map((item) => (
<div class={'flex gap-x-2 items-center'}>
<img src={item.icon} alt="game icon" class={'w-[37px] h-[37px] rounded'}></img>
<div class={'flex-1 flex flex-col overflow-hidden'}>
<span class={'text-white text-[14px]'}>{item.name}</span>
<span
class={
'text-[#fff9] text-[12px] whitespace-nowrap text-ellipsis overflow-hidden'
}
>
{item.des}
</span>
</div>
</div>
))}
</div>
</div>
</div>
)
})

View File

@ -0,0 +1,18 @@
import asyncLoader from '@/utils/asyncLoader'
import type { Widget } from '..'
export default {
name: 'video',
label: '视频',
description: '热门视频',
icon: '/icons/recommendedIcon.png',
modal: null,
list: [
{
w: 4,
h: 2,
label: '大',
component: asyncLoader(() => import('./Large'))
}
]
} as Widget

View File

@ -0,0 +1,29 @@
import request from "@/utils/request"
import { defineStore } from "pinia"
import { reactive } from "vue"
type WeApplyType = {
id: string;
name: string;
url: string;
icon: string;
des: string;
type: string;
}
export const useWeApplyStore = defineStore('weApply', () => {
const state = reactive({
list: [] as WeApplyType[]
})
const getWeApplyList = async () => {
return request<WeApplyType[]>('GET', '/api/app/weApplys').then(res => {
return res
})
}
const getList = async () => {
state.list = await getWeApplyList()
}
getList()
return {
state,
getList
}
})

View File

@ -13,12 +13,11 @@ export default defineComponent(() => {
const computIsBottom = (ref: any) => { const computIsBottom = (ref: any) => {
if (ref) { if (ref) {
const { scrollTop, clientHeight, scrollHeight } = ref const { scrollTop, clientHeight, scrollHeight } = ref
return scrollTop + clientHeight >= scrollHeight return scrollTop + clientHeight >= scrollHeight -8
} }
} }
const handleGameScroll = () => { const handleGameScroll = () => {
const isAtBottom = computIsBottom(gameRef.value) const isAtBottom = computIsBottom(gameRef.value)
console.log(isAtBottom ? '已滚动到底部!' : '未滚动到底部。')
if (isAtBottom) { if (isAtBottom) {
isGameBottom.value = true isGameBottom.value = true
} else { } else {
@ -27,40 +26,13 @@ export default defineComponent(() => {
} }
const handleWorkScroll = () => { const handleWorkScroll = () => {
const isAtBottom = computIsBottom(workRef.value) const isAtBottom = computIsBottom(workRef.value)
console.log(isAtBottom ? '已滚动到底部!' : '未滚动到底部。')
if (isAtBottom) { if (isAtBottom) {
isWorkBottom.value = true isWorkBottom.value = true
} else { } else {
isWorkBottom.value = false isWorkBottom.value = false
} }
} }
watch(gameRef, (val, _, onCleanup) => {
console.log(val)
if (!val) return
val.addEventListener('scroll', handleGameScroll)
// if (val + gameRef.value.clientHeight >= gameRef.value.scrollHeight) {
// isGameBottom.value = true
// } else {
// isGameBottom.value = false
// }
onCleanup(() => {
val.removeEventListener('scroll', handleGameScroll)
})
})
watch(workRef, (val, _, onCleanup) => {
console.log(val)
if (!val) return
val.addEventListener('scroll', handleWorkScroll)
// if (val + gameRef.value.clientHeight >= gameRef.value.scrollHeight) {
// isGameBottom.value = true
// } else {
// isGameBottom.value = false
// }
onCleanup(() => {
val.removeEventListener('scroll', handleWorkScroll)
})
})
return () => ( return () => (
<div <div
class={'w-full h-full flex p-2 justify-between'} class={'w-full h-full flex p-2 justify-between'}
@ -84,6 +56,7 @@ export default defineComponent(() => {
e.preventDefault() e.preventDefault()
if (gameRef.value) { if (gameRef.value) {
gameRef.value.scrollTop += 40 gameRef.value.scrollTop += 40
handleGameScroll()
} }
}} }}
> >
@ -95,7 +68,7 @@ export default defineComponent(() => {
ref={gameRef} ref={gameRef}
class={clsx('w-full h-full flex flex-col overflow-y-scroll gap-y-2 scrollbar-hide ')} class={clsx('w-full h-full flex flex-col overflow-y-scroll gap-y-2 scrollbar-hide ')}
onWheel={() => { onWheel={() => {
console.log('wheel') handleGameScroll()
}} }}
> >
{store.state.list {store.state.list
@ -133,6 +106,8 @@ export default defineComponent(() => {
e.preventDefault() e.preventDefault()
if (workRef.value) { if (workRef.value) {
workRef.value.scrollTop += 20 workRef.value.scrollTop += 20
handleWorkScroll()
} }
}} }}
> >
@ -144,7 +119,7 @@ export default defineComponent(() => {
ref={workRef} ref={workRef}
class={clsx('w-full h-full flex flex-col overflow-y-scroll gap-y-2 scrollbar-hide ')} class={clsx('w-full h-full flex flex-col overflow-y-scroll gap-y-2 scrollbar-hide ')}
onWheel={() => { onWheel={() => {
console.log('wheel') handleWorkScroll()
}} }}
> >
{store.state.list {store.state.list

View File

@ -4,7 +4,7 @@ import type { Widget } from '..'
export default { export default {
name: 'weApply', name: 'weApply',
label: '大家都在用', label: '大家都在用',
description: '大家都在用', description: '盘点近期大热的应用软件和热门游戏',
icon: '/icons/recommendedIcon.png', icon: '/icons/recommendedIcon.png',
modal: null, modal: null,
list: [ list: [