完成今天吃什么小组件的开发

This commit is contained in:
expdsn 2024-10-17 18:33:05 +08:00
parent 91f8b1a229
commit f86c26b823
12 changed files with 234 additions and 202 deletions

BIN
public/bg/eat_bg@2x.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/bg/eat_top_bg.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

143
src/widgets/eat/Middle.tsx Normal file
View File

@ -0,0 +1,143 @@
import clsx from 'clsx'
import dayjs from 'dayjs'
import { defineComponent, ref } from 'vue'
export default defineComponent(() => {
const list = [
'猪脚饭',
'汉堡薯条',
'麻辣烫',
'烧腊饭',
'黄焖鸡米饭',
'煲仔饭 ',
'酸辣粉',
'肠粉',
'沙县小吃',
'热干面',
'重庆小面 ',
'兰州拉面',
'凉皮',
'生煎',
'锅贴',
'炒饭',
'冒菜',
'鸭血粉丝汤',
'胡辣汤',
'砂锅粥',
'螺蛳粉',
'水饺',
'茶餐厅',
'馄饨抄手',
'披萨',
'桂林米粉',
'川菜',
'湘菜',
'粤菜',
'日本料理',
'韩国料理',
'焗饭',
'泡面',
'麻辣香锅',
'沙拉轻食',
'馄饨',
'拉面',
'烩面',
'热干面',
'刀削面',
'油泼面',
'炸酱面',
'炒面',
'米线',
'酸辣粉',
'土豆粉',
'螺狮粉',
'凉皮儿',
'麻辣烫',
'肉夹馍',
'羊肉汤',
'炒饭',
'盖浇饭',
'卤肉饭',
'烤肉饭',
'黄焖鸡米饭',
'驴肉火烧',
'川菜',
'麻辣香锅',
'火锅',
'酸菜鱼',
'烤串',
'披萨',
'烤鸭',
'汉堡',
'炸鸡',
'寿司',
'蟹黄包',
'煎饼果子',
'生煎',
'炒年糕'
]
const text = ref('')
return () => (
<div
class="w-full h-full flex flex-col px-[14px] pt-[23px]"
style={{
backgroundImage: `url('/bg/eat_bg@2x.webp')`,
backgroundSize: '100% 100%'
}}
onClick={() => {
text.value = ''
}}
>
<div class={'w-full flex justify-center'}>
<img src="/bg/eat_top_bg.webp" alt="eat_bg"></img>
</div>
<div class={'flex flex-col w-full justify-center items-center'}>
<div
class={clsx(
'w-[130px] duration-150 bg-white relative',
text.value ? 'h-[120px]' : 'h-[17px]'
)}
>
<button
class={
'w-[48px] h-[19px] text-[12px] rounded left-1/2 bottom-[-10px] absolute -translate-x-1/2 rounded-br-[6px] rounded-bl-[6px] text-white'
}
style={{
background: 'linear-gradient(180deg,#ffa061 0%,#ffb62f 100%)',
boxShadow: 'inset 0 0 2px #ff640080'
}}
onClick={(e) => {
e.stopPropagation()
const index = Math.floor(Math.random() * list.length)
if (text.value) {
text.value = ''
setTimeout(() => {
text.value = list[index]
}, 400)
}else {
text.value = list[index]
}
}}
>
{text.value ? '换' : '开始'}
</button>
<span
class={
'text-[12px] text-[#333] absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'
}
>
{text.value}
</span>
</div>
{!text.value && (
<>
<span class={'text-[24px] mt-[24px] text-[#333]'}></span>
<span class={'mb-[2px] text-[12px] text-[#666]'}>{dayjs().format('MM.DD')}</span>
<span class="text-[12px] text-[#999]">{dayjs().format('ddd')}</span>
</>
)}
</div>
</div>
)
})

12
src/widgets/eat/Modal.tsx Normal file
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>
)
})

20
src/widgets/eat/index.ts Normal file
View File

@ -0,0 +1,20 @@
import asyncLoader from '@/utils/asyncLoader'
import type { Widget } from '..'
export default {
name: 'todayEat',
label: '今天吃什么',
description: '今天吃什么呢?',
icon: '/icons/today_eat.png',
modal: null,
list: [
{
w: 2,
h: 2,
label: '中',
component: asyncLoader(() => import('./Middle'))
},
]
} as Widget

View File

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

View File

@ -1,172 +1,9 @@
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)
import { defineComponent } from 'vue'
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 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>
)
})

View File

@ -2,12 +2,24 @@ import asyncLoader from '@/utils/asyncLoader'
import type { Widget } from '..'
export default {
name: 'video',
label: '视频',
description: '热门视频',
icon: '/icons/recommendedIcon.png',
modal: null,
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,

View File

@ -1,29 +0,0 @@
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
}
})