完成了大家都在用小组件的开发

This commit is contained in:
expdsn 2024-10-16 17:08:40 +08:00
parent 9cd6da6b82
commit ebb4bc8421
11 changed files with 229 additions and 5 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -26,7 +26,7 @@ export const WidgetItem = defineComponent({
key={props.content.name} key={props.content.name}
> >
<div class="flex"> <div class="flex">
<img src={props.content.icon} class="w-[48px] h-[48px] bg-cover" /> <img src={props.content.icon} class="w-[48px] h-[48px] bg-cover rounded-lg" />
<div class="px-2 w-0 flex-grow"> <div class="px-2 w-0 flex-grow">
<div <div
class={clsx('text text-sm', { class={clsx('text text-sm', {

View File

@ -26,13 +26,13 @@ export default defineComponent({
const hover = ref(false) const hover = ref(false)
return () => ( return () => (
<div <div
class="w-full h-full p-[var(--block-padding)] relative rounded-lg" class="w-full h-full p-[var(--block-padding)] relative rounded-lg hover-move"
key={props.block.id} key={props.block.id}
id={props.block.id} id={props.block.id}
style={{ style={{
gridColumn: `span ${props.block.w}`, gridColumn: `span ${props.block.w}`,
gridRow: `span ${props.block.h}`, gridRow: `span ${props.block.h}`,
transition: 'border .3s', transition: 'border .3s, transform .2s',
border: hover.value ? '2px solid rgba(255,255,255,.5)' : '2px solid rgba(255,255,255,0)' border: hover.value ? '2px solid rgba(255,255,255,.5)' : '2px solid rgba(255,255,255,0)'
}} }}
data-transportable={props.block.link && !props.block.link.startsWith('id:') ? '1' : ''} data-transportable={props.block.link && !props.block.link.startsWith('id:') ? '1' : ''}

View File

@ -32,6 +32,7 @@ export default defineComponent({
menu.open(props.block) menu.open(props.block)
}} }}
onClick={() => { onClick={() => {
if (!selected.modal) return
router.go(`widget-${props.block.name}`) router.go(`widget-${props.block.name}`)
}} }}
> >

View File

@ -165,6 +165,10 @@ body {
scrollbar-width: none; scrollbar-width: none;
-ms-overflow-style: none; -ms-overflow-style: none;
} }
.hover-move:hover {
transform: translateY(-4px);
}
@layer utilities { @layer utilities {
.scrollbar-transparent::-webkit-scrollbar { .scrollbar-transparent::-webkit-scrollbar {
width: 0; width: 0;

View File

@ -1,12 +1,13 @@
import type { Component } from 'vue' 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'
export interface Widget { export interface Widget {
name: string // 小组件类型唯一标识 name: string // 小组件类型唯一标识
label: string // 小组件名称 label: string // 小组件名称
description: string // 小组件描述 description: string // 小组件描述
icon: string // 小组件图标 icon: string // 小组件图标
modal: Component // 弹框组件 modal: Component | null // 弹框组件
list: { list: {
w: number w: number
h: number h: number
@ -15,4 +16,4 @@ export interface Widget {
}[] // 不同尺寸小组件块 }[] // 不同尺寸小组件块
} }
export default [calendar, weather] as Widget[] export default [calendar, weather, weApply] as Widget[]

View File

@ -0,0 +1,171 @@
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')
}}
>
{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: 'weApply',
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
}
})