Merge remote-tracking branch 'origin/tomato'

This commit is contained in:
plightfield 2024-11-15 21:04:26 +08:00
commit ddb834f0f7
40 changed files with 1054 additions and 646 deletions

BIN
public/bg/addBorder.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/bg/del_icon_img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

View File

@ -17,6 +17,8 @@ import WelcomePage from './layout/grid/WelcomePage'
import TomatoPage from './layout/grid/TomatoPage'
import useRouterStore from './useRouterStore'
import BackupRecovery from './user/BackupRecovery'
import { ConfigProvider } from 'ant-design-vue'
import zhCN from 'ant-design-vue/es/locale/zh_CN'
const Grid = asyncLoader(() => import('./layout/grid'))
const Fox = asyncLoader(() => import('./fox'))
const settings = useSettingsStore()
@ -28,51 +30,53 @@ const router = useRouterStore()
const layout = useLayoutStore()
</script>
<template>
<div class="fixed left-0 top-0 w-full h-screen style-root" @contextmenu.prevent>
<Header />
<Background
@dblclick="
() => {
layout.state.simple = !layout.state.simple
}
"
/>
<GLobalModal />
<SettingsOverlay />
<SettingsButton />
<ConfigProvider :locale="zhCN">
<div class="fixed left-0 top-0 w-full h-screen style-root" @contextmenu.prevent>
<Header />
<Background
@dblclick="
() => {
layout.state.simple = !layout.state.simple
}
"
/>
<GLobalModal />
<SettingsOverlay />
<SettingsButton />
<Sider
v-if="
!layout.state.simple ||
(layout.state.simple && settings.state.simpleModeShowString.includes('showSider'))
"
/>
<LoginModal v-if="router.path === 'global-login'" />
<Transition>
<Grid v-if="layout.ready && !layout.state.simple" />
</Transition>
<Dock
v-if="
!layout.state.simple ||
(layout.state.simple && settings.state.simpleModeShowString.includes('showDock'))
"
/>
<div
class="fixed z-40 right-[14%] top-8"
v-if="
!layout.state.simple ||
(layout.state.simple && settings.state.simpleModeShowString.includes('showPet'))
"
>
<Fox />
<Sider
v-if="
!layout.state.simple ||
(layout.state.simple && settings.state.simpleModeShowString.includes('showSider'))
"
/>
<LoginModal v-if="router.path === 'global-login'" />
<Transition>
<Grid v-if="layout.ready && !layout.state.simple" />
</Transition>
<Dock
v-if="
!layout.state.simple ||
(layout.state.simple && settings.state.simpleModeShowString.includes('showDock'))
"
/>
<div
class="fixed z-40 right-[14%] top-8"
v-if="
!layout.state.simple ||
(layout.state.simple && settings.state.simpleModeShowString.includes('showPet'))
"
>
<Fox />
</div>
<DirModal />
<GlobalMenu />
<WelcomePage></WelcomePage>
<TomatoPage></TomatoPage>
<BackupRecovery v-if="router.path === 'global-backup'"></BackupRecovery>
</div>
<DirModal />
<GlobalMenu />
<WelcomePage></WelcomePage>
<TomatoPage></TomatoPage>
<BackupRecovery v-if="router.path === 'global-backup'"></BackupRecovery>
</div>
</ConfigProvider>
</template>
<style lang="less">

View File

@ -1,5 +1,5 @@
import useRouterStore, { type RouteStr } from '@/useRouterStore'
import { computed, defineComponent, ref, Transition, watch } from 'vue'
import { computed, defineComponent, onMounted, onUnmounted, ref, Transition, watch } from 'vue'
import { OhVueIcon, addIcons } from 'oh-vue-icons'
import { MdClose, MdOpeninfull, MdClosefullscreen } from 'oh-vue-icons/icons'
import asyncLoader from './utils/asyncLoader'
@ -18,18 +18,31 @@ export default defineComponent(() => {
router.path.startsWith('widget-') ||
router.path === 'global-search' ||
router.path === 'global-adder' ||
router.path === 'global-background'
router.path === 'global-background'
)
const full = ref(false)
watch(router, () => {
full.value = false
})
onMounted(() => {
window.addEventListener('keydown', handleKeydown)
})
onUnmounted(() => {
// 清理事件监听
window.removeEventListener('keydown', handleKeydown)
})
function handleKeydown(e: any) {
if (e.key === 'Escape') {
router.back()
}
}
return () => (
<div
class="fixed left-0 top-0 z-50 w-full"
onContextmenu={(e) => e.stopPropagation()}
onKeydown={(e) => e.stopPropagation()}
onKeydown={(e) => {
e.stopPropagation()
}}
>
{/* 背景遮罩 */}
<Transition>
@ -43,7 +56,7 @@ export default defineComponent(() => {
)}
</Transition>
{/* 弹框主体 */}
<Transition >
<Transition>
{show.value && (
<div
class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 overflow-hidden "
@ -93,21 +106,20 @@ export default defineComponent(() => {
<AdderPage />
) : router.path === 'global-background' ? (
<BackgroundSwtich />
) :
router.path.startsWith('widget-') ? (
(() => {
const name = router.path.split('-')[1]
const selected = widgetList.find((el) => el.name === name)
if (!selected)
return (
<div class="w-full h-full flex justify-center items-center text-black/80">
</div>
)
const compo = selected.modal
return <compo />
})()
) : null}
) : router.path.startsWith('widget-') ? (
(() => {
const name = router.path.split('-')[1]
const selected = widgetList.find((el) => el.name === name)
if (!selected)
return (
<div class="w-full h-full flex justify-center items-center text-black/80">
</div>
)
const compo = selected.modal
return <compo />
})()
) : null}
</Transition>
</div>
</div>

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia'
import { computed, defineComponent, onUnmounted, reactive, ref } from 'vue'
import type { Block, LayoutPages } from './layout.types'
import type { Block } from './layout.types'
import clsx from 'clsx'
import useLayoutStore from './useLayoutStore'
import widgetList from '@/widgets'
@ -17,10 +17,11 @@ const defaultDisplay = {
export const useMenuStore = defineStore('menu', () => {
const display = reactive(defaultDisplay)
const selectPage = ref<{
id: string,
label: string,
name: string,
id: string
label: string
name: string
}>()
const isEditPage = ref(false)
const showEditPage = ref(false)
const mPos = {
x: 0,
@ -49,6 +50,7 @@ export const useMenuStore = defineStore('menu', () => {
display,
open,
dismiss,
isEditPage,
show,
selectPage,
showEditPage
@ -83,8 +85,8 @@ const Item = defineComponent({
style={
props.noStyle
? {
padding: '6px 10px'
}
padding: '6px 10px'
}
: {}
}
onClick={() => {
@ -155,6 +157,15 @@ export default defineComponent(() => {
>
</Item>
<Item
noStyle
onClick={() => {
menu.isEditPage = true
menu.dismiss()
}}
>
</Item>
<Item
noStyle
onClick={() => {
@ -164,6 +175,15 @@ export default defineComponent(() => {
>
</Item>
<Item
noStyle
onClick={() => {
window.location.reload()
menu.dismiss()
}}
>
</Item>
</>
)
}
@ -207,10 +227,18 @@ export default defineComponent(() => {
alert
onClick={() => {
// 删除链接
const idx = layout.currentPage.list.findIndex((el) => el.id === block.id)
if (idx < 0) return
layout.currentPage.list.splice(idx, 1)
console.log(menu.selectPage)
const idx = layout.state.content[layout.state.current].pages.findIndex(
(el) => el.id === menu.selectPage?.id
)
menu.dismiss()
if (idx < 0) return
if (idx === layout.state.currentPage) {
return
}
layout.state.content[layout.state.current].pages.splice(idx, 1)
}}
>
@ -309,7 +337,25 @@ export default defineComponent(() => {
// 链接
return (
<>
<Item></Item>
<Item
onClick={() => {
useAdderPageStore().editBlockItem = {
id: block.id,
name: block.name,
link: block.link,
icon: block.icon,
text: block.text,
background: block.background,
color: block.color,
type: block.color ? 1 : 0
}
router.go('global-adder')
useAdderPageStore().type = 2
menu.dismiss()
}}
>
</Item>
<Item
alert
onClick={() => {

View File

@ -81,10 +81,11 @@ export default defineComponent(() => {
const isGame = computed(() => layout.state.current === 0)
const store = useAdderPageStore()
const addTo = ref(layout.state.currentPage)
provide(AddToToken, addTo)
onUnmounted(() => {
store.type = 1
})
// onUnmounted(() => {
// store.type = 1
// })
return () => (
<div
class={clsx(
@ -144,9 +145,31 @@ export default defineComponent(() => {
}))}
/>
</Form.Item>
<Form.Item>
<Input.Search class="w-[200px]" placeholder="搜索组件或网站" />
</Form.Item>
{store.type !== 2 && (
<Form.Item>
<Input.Search
onSearch={(e) => {
if (store.type === 1) {
store.search(e)
} else if (store.type === 0) {
store.widgetSearchWords = e
} else if (store.type === 3) {
store.gameSearch = e
}
}}
class="w-[220px]"
placeholder={
store.type === 0
? '搜索想要添加的组件'
: store.type === 1
? '搜索想要添加的网址导航'
: store.type === 2
? ''
: '搜索想要添加的游戏'
}
/>
</Form.Item>
)}
</Form>
<div class="w-full h-0 flex-grow p-6">
<div class="w-full h-full relative">

View File

@ -1,18 +1,18 @@
import { computed, defineComponent, inject, reactive, ref, watch } from 'vue'
import { computed, defineComponent, inject, onMounted, reactive, ref, watch } from 'vue'
import useLayoutStore from '../useLayoutStore'
import { Button, Form, Input, InputGroup } from 'ant-design-vue'
import { Form, Input } from 'ant-design-vue'
import { OhVueIcon, addIcons } from 'oh-vue-icons'
import { MdUpload, MdImage, MdCheck } from 'oh-vue-icons/icons'
import useLink from '../../utils/useLink'
import { CheckOutlined } from '@ant-design/icons-vue'
import { v4 as uuid } from 'uuid'
import type { Block } from '../layout.types'
import { ColorPicker } from 'vue3-colorpicker'
import 'vue3-colorpicker/style.css'
import { globalToast } from '@/main'
import UploadAndCut from '@/utils/UploadAndCut'
import { AddToToken } from './AdderPage'
import useAdderPageStore, { type EditBlockItemType } from './useAdderPageStore'
import useRouterStore from '@/useRouterStore'
import NativeColorPicker from '@/utils/NativeColorPicker'
addIcons(MdUpload, MdImage, MdCheck)
const TypeSelector = defineComponent({
@ -47,6 +47,7 @@ const TypeSelector = defineComponent({
'update:icon': (() => true) as (val: string) => boolean
},
setup(props, ctx) {
const layout = useLayoutStore()
return () => (
<div class="flex gap-4">
<div class={'flex justify-center flex-col items-center gap-y-1'}>
@ -60,7 +61,7 @@ const TypeSelector = defineComponent({
ctx.emit('update:value', 0)
}}
>
{!props.icon && <OhVueIcon name="md-image" fill="white" scale={1.8} />}
{!props.icon && <img src={'/tab/icons/bgGameCloud.png'}></img>}
{props.value === 0 && (
<div
class={
@ -113,10 +114,11 @@ const TypeSelector = defineComponent({
ctx.emit('update:icon', e)
}}
></ImageUploader> */}
<UploadAndCut onUpdate:value={(e)=> {
<UploadAndCut
onUpdate:value={(e) => {
ctx.emit('update:icon', e)
}}></UploadAndCut>
}}
></UploadAndCut>
</div>
<span class={'text-[12px]'}></span>
</div>
@ -134,9 +136,21 @@ export default defineComponent(() => {
color: 'rgb(255,255,255)',
icon: '',
type: 0 // 0 默认1 文字
})
} as EditBlockItemType)
const isGame = computed(() => layout.state.current === 0)
const debounced = ref('')
const store = useAdderPageStore()
onMounted(() => {
if (store.editBlockItem !== null) {
form.link = store.editBlockItem.link
form.name = store.editBlockItem.name
form.icon = store.editBlockItem.icon
form.text = store.editBlockItem.text
form.background = store.editBlockItem.background
form.color = store.editBlockItem.color
form.type = store.editBlockItem.type
}
})
watch(
() => form.link,
(val, _, onCleanup) => {
@ -155,6 +169,20 @@ export default defineComponent(() => {
if (val.name) form.name = val.name
if (val.icon) form.icon = val.icon
})
watch(
() => form.type,
(cur, pre) => {
if (cur === 1) {
if (!form.name) {
globalToast.error('文字图标请至少填写文字或者名称')
form.type = pre
return
} else {
form.text = form.name.substring(0, 2).toLocaleUpperCase()
}
}
}
)
const addTo = inject(AddToToken)
return () => (
<div
@ -163,19 +191,24 @@ export default defineComponent(() => {
(isGame.value ? 'bg-white/20' : 'bg-white/70')
}
>
<Form labelCol={{ span: 4 }} labelAlign="left">
<Form.Item label="地址">
<InputGroup compact style="display:flex">
<Input
v-model:value={form.link}
placeholder="搜索想要添加的网址导航"
class="w-0 flex-grow"
/>
<Button></Button>
</InputGroup>
<Form>
<Form.Item label="地址" class={'relative'}>
<Input
v-model:value={form.link}
placeholder="搜索想要添加的网址导航"
class={isGame?.value ? '' : ' bg-black/10 '}
/>
<span
class={'absolute right-[-70px] top-1/2 -translate-y-1/2 cursor-pointer'}
onClick={() => {
debounced.value = debounced.value + ' '
}}
>
</span>
</Form.Item>
<Form.Item label="名称">
<Input v-model:value={form.name} />
<Input v-model:value={form.name} class={isGame?.value ? '' : ' bg-black/10 '} />
</Form.Item>
<Form.Item label="图标">
<TypeSelector
@ -189,70 +222,81 @@ export default defineComponent(() => {
</Form.Item>
{form.type === 1 && (
<>
<div class="flex">
<Form.Item
label="文字颜色"
class="w-0 flex-grow"
labelCol={{
span: 8
<Form.Item label="图标背景">
<NativeColorPicker
size={30}
colorList={[
'rgb(227, 127, 53)',
'rgb(239, 195, 57)',
'rgb(65, 201, 117)',
'rgb(67, 195, 195)',
'rgb(97, 182, 255)',
'rgb(153, 91, 179)'
]}
value={form.background}
onUpdate:value={(e) => {
form.background = e
}}
>
<ColorPicker
class="shadow-lg"
format="rgb"
shape="square"
v-model:pureColor={form.color}
/>
</Form.Item>
<Form.Item
label="图标背景"
class="w-0 flex-grow"
labelCol={{
span: 8
}}
>
<ColorPicker
class="shadow-lg"
format="rgb"
shape="square"
v-model:pureColor={form.background}
/>
</Form.Item>
</div>
></NativeColorPicker>
</Form.Item>
<Form.Item label="图标文字">
<Input v-model:value={form.text} maxlength={2} />
<Input
v-model:value={form.text}
maxlength={2}
class={isGame?.value ? '' : ' bg-black/10 '}
/>
</Form.Item>
</>
)}
<Form.Item label=" " colon={false}>
<Button
type="primary"
size="large"
icon={<CheckOutlined />}
<button
class={
'outline-none ml-[23px] mt-2 flex items-center hover:opacity-90 text-[16px] justify-center gap-x-2 text-white w-[94px] h-[40px] rounded-lg'
}
style={{
background: 'linear-gradient(180deg,#ffaa4e 0%,#ff6227 100%)'
}}
onClick={() => {
if (form.type === 1 && !form.text && !form.name) {
globalToast.error('文字图标请至少填写文字或者名称')
return
}
const id = uuid()
const data: Block = {
id,
link: form.link,
name: '',
icon: form.type === 0 ? form.icon || info.icon : '',
background: form.type === 0 ? '' : form.background,
color: form.type === 0 ? '' : form.color,
w: 1,
h: 1,
text:
form.type === 0 ? '' : form.text || form.name.substring(0, 2).toLocaleUpperCase(),
label: form.name
if (!form.link) {
globalToast.warning('请输入网站链接')
return
}
if (!form.name) {
globalToast.warning('请输入网站名称')
return
}
if (store.editBlockItem !== null) {
console.log(123)
layout.changeBlock(form, store.editBlockItem.id)
useRouterStore().back()
store.editBlockItem = null
} else {
const id = uuid()
const data: Block = {
id,
link: form.link,
name: '',
icon: form.type === 0 ? form.icon || info.icon : '',
background: form.type === 0 ? '' : form.background,
color: form.type === 0 ? '' : form.color,
w: 1,
h: 1,
text:
form.type === 0
? ''
: form.text || form.name.substring(0, 2).toLocaleUpperCase(),
label: form.name
}
layout.addBlock(data, addTo?.value)
}
layout.addBlock(data, addTo?.value)
}}
>
</Button>
</button>
</Form.Item>
</Form>
</div>

View File

@ -9,6 +9,7 @@ import { frontAddress, ossBase } from '@/config'
import dayjs from 'dayjs'
import { generateRandomString } from '@/utils/tool'
import MD5 from 'crypto-js/md5'
import useAdderPageStore from './useAdderPageStore'
export const SECRET = 'A1Cv12olxT12dOE3xA1vPA=='
export const URL_ADDRESS = 'http://newfatfox.oss-cn-beijing.aliyuncs.com'
export interface GameType {
@ -126,9 +127,9 @@ export const GameItem = defineComponent({
id: uuid(),
link: !props.content.rom.startsWith('http')
? `${frontAddress}/emu/#/home?params=${JSON.stringify({
...props.content,
rom: ossBase + '/' + props.content.rom
})}`
...props.content,
rom: ossBase + '/' + props.content.rom
})}`
: props.content.rom,
name: '',
label: props.content.name,
@ -159,6 +160,7 @@ export default defineComponent(() => {
const isGame = computed(() => layout.state.current === 0)
const appList = ref<GameType[]>([])
const selectType = ref('fc')
const store = useAdderPageStore()
const fetchGame = async (page: number) => {
const parems = `nonce=${generateRandomString(8)}&pid=PIDc8uT24mpo&timestamp=${dayjs().unix()}`
const sign = MD5(parems + SECRET).toString()
@ -169,11 +171,11 @@ export default defineComponent(() => {
return res.data.items
}
watch(
selectType,
[selectType, () => store.gameSearch],
(val) => {
loading.value = true
if (val !== 'yiqiyoo') {
request<GameType[]>('GET', `/api/games?type=${val}`)
if (val[0] !== 'yiqiyoo') {
request<GameType[]>('GET', `/api/games?type=${val[0]}&keyword=${val[1]}`)
.then((res) => {
appList.value = res
})
@ -212,41 +214,44 @@ export default defineComponent(() => {
)
return () => (
<div class={'w-full h-full flex flex-col gap-y-4'}>
<div class={'w-full '}>
<CategoryTab
list={DefautGameTypeList}
selectType={selectType.value}
onUpdate:type={(e) => {
selectType.value = e
}}
v-slots={{
select: (text: string) => (
<button
class={clsx(
'px-[20px] text-[14px] py-1 items-center justify-center duration-75 shrink-0 flex rounded-2xl whitespace-nowrap ',
isGame.value
? 'bg-white/30 text-white bg-gradient-to-b from-[#ffaa4e] to-[#ff6227]'
: 'text-white bg-[#D6D6D6] bg-gradient-to-b from-[#ffaa4e] to-[#ff6227]'
)}
>
{text}
</button>
),
unSelect: (text: string) => (
<button
class={clsx(
'px-[20px] text-[14px] py-1 items-center justify-center duration-75 shrink-0 flex rounded-2xl whitespace-nowrap',
isGame.value
? ' text-[#999] bg-white/10 hover:bg-white/20'
: 'text-[#666] hover:bg-black/10 bg-black/[0.05] hover:bg-[#f0ecec]'
)}
>
{text}
</button>
)
}}
></CategoryTab>
</div>
{!store.gameSearch && (
<div class={'w-full '}>
<CategoryTab
list={DefautGameTypeList}
selectType={selectType.value}
onUpdate:type={(e) => {
selectType.value = e
}}
v-slots={{
select: (text: string) => (
<button
class={clsx(
'px-[20px] text-[14px] py-1 items-center justify-center duration-75 shrink-0 flex rounded-2xl whitespace-nowrap ',
isGame.value
? 'bg-white/30 text-white bg-gradient-to-b from-[#ffaa4e] to-[#ff6227]'
: 'text-white bg-[#D6D6D6] bg-gradient-to-b from-[#ffaa4e] to-[#ff6227]'
)}
>
{text}
</button>
),
unSelect: (text: string) => (
<button
class={clsx(
'px-[20px] text-[14px] py-1 items-center justify-center duration-75 shrink-0 flex rounded-2xl whitespace-nowrap',
isGame.value
? ' text-[#999] bg-white/10 hover:bg-white/20'
: 'text-[#666] hover:bg-black/10 bg-black/[0.05] hover:bg-[#f0ecec]'
)}
>
{text}
</button>
)
}}
></CategoryTab>
</div>
)}
<div class={'h-0 flex-1 w-full'}>
<div class={'w-full h-full overflow-y-scroll scrollbar-hide'}>
{!loading.value ? (

View File

@ -1,26 +1,11 @@
import CategoryTab from '@/utils/CategoryTab'
import request from '@/utils/request'
import { computed, defineComponent, inject, onMounted, ref, watch } from 'vue'
import type { BackgroundType } from '../background/BackgroundSwtich'
import { computed, defineComponent, inject } from 'vue'
import clsx from 'clsx'
import useLayoutStore from '../useLayoutStore'
import { AddToToken } from './AdderPage'
import { v4 as uuid } from 'uuid'
import useAdderPageStore, { type HotAppType } from './useAdderPageStore'
interface HotAppCategoryType {
id: string
name: string
ordinal: number
}
interface HotAppType {
id: string
name: string
url: string
ordinal: number
desc: string
icon: string
background: string
}
export const LinkItem = defineComponent({
props: {
content: {
@ -34,10 +19,13 @@ export const LinkItem = defineComponent({
const addTo = inject(AddToToken)
return () => (
<div
class={clsx(' w-full h-full rounded-lg relative flex flex-col justify-between overflow-hidden shadow p-4', {
'bg-white/20': isGame.value,
'bg-white/80': !isGame.value
})}
class={clsx(
' w-full h-full rounded-lg relative flex flex-col justify-between overflow-hidden shadow p-4',
{
'bg-white/20': isGame.value,
'bg-white/80': !isGame.value
}
)}
key={props.content.name}
>
<div
@ -110,6 +98,9 @@ export const LinkItem = defineComponent({
},
addTo?.value
)
if (addTo?.value) {
layout.state.currentPage = addTo?.value
}
}}
>
@ -122,78 +113,55 @@ export const LinkItem = defineComponent({
export default defineComponent(() => {
const layout = useLayoutStore()
const loading = ref(false)
const isGame = computed(() => layout.state.current === 0)
const appList = ref<HotAppType[]>([])
const categoryList = ref<BackgroundType[]>([])
const selectType = ref('')
onMounted(() => {
request<HotAppCategoryType[]>('GET', '/api/app/hotAppTypes').then((res) => {
categoryList.value = res.map((el) => ({
id: el.id,
oridinal: el.ordinal,
type: el.name,
attr: 0
}))
selectType.value = res[0].id
})
})
watch(selectType, (val) => {
loading.value = true
const store = useAdderPageStore()
request<HotAppType[]>('GET', `/api/app/hotApps?hotAppsId=${val}`)
.then((res) => {
appList.value = res
})
.finally(() => {
setTimeout(() => {
loading.value = false
}, 200)
})
})
return () => (
<div class={'w-full h-full flex flex-col gap-y-4'}>
<div class={'w-full '}>
<CategoryTab
list={categoryList.value}
selectType={selectType.value}
onUpdate:type={(e) => {
selectType.value = e
}}
v-slots={{
select: (text: string) => (
<button
class={clsx(
'px-[20px] text-[14px] py-1 items-center justify-center duration-75 shrink-0 flex rounded-2xl whitespace-nowrap ',
isGame.value
? 'bg-white/30 text-white bg-gradient-to-b from-[#ffaa4e] to-[#ff6227]'
: 'text-white bg-[#D6D6D6] bg-gradient-to-b from-[#ffaa4e] to-[#ff6227]'
)}
>
{text}
</button>
),
unSelect: (text: string) => (
<button
class={clsx(
'px-[20px] text-[14px] py-1 items-center justify-center duration-75 shrink-0 flex rounded-2xl whitespace-nowrap',
isGame.value
? ' text-[#999] bg-white/10 hover:bg-white/20'
: 'text-[#666] hover:bg-black/10 bg-black/[0.05] hover:bg-[#f0ecec]'
)}
>
{text}
</button>
)
}}
></CategoryTab>
</div>
{!store.isSearch && (
<div class={'w-full '}>
<CategoryTab
list={store.categoryList}
selectType={store.selectType}
onUpdate:type={(e) => {
store.selectType = e
}}
v-slots={{
select: (text: string) => (
<button
class={clsx(
'px-[20px] text-[14px] py-1 items-center justify-center duration-75 shrink-0 flex rounded-2xl whitespace-nowrap ',
isGame.value
? 'bg-white/30 text-white bg-gradient-to-b from-[#ffaa4e] to-[#ff6227]'
: 'text-white bg-[#D6D6D6] bg-gradient-to-b from-[#ffaa4e] to-[#ff6227]'
)}
>
{text}
</button>
),
unSelect: (text: string) => (
<button
class={clsx(
'px-[20px] text-[14px] py-1 items-center justify-center duration-75 shrink-0 flex rounded-2xl whitespace-nowrap',
isGame.value
? ' text-[#999] bg-white/10 hover:bg-white/20'
: 'text-[#666] hover:bg-black/10 bg-black/[0.05] hover:bg-[#f0ecec]'
)}
>
{text}
</button>
)
}}
></CategoryTab>
</div>
)}
<div class={'h-0 flex-1 w-full'}>
<div class={'w-full h-full overflow-y-scroll scrollbar-hide'}>
{!loading.value ? (
{!store.loading ? (
<div class={'w-full grid grid-cols-3 gap-4 '} style="grid-auto-rows: 120px">
{appList.value.map((el) => (
{store.appList.map((el) => (
<LinkItem content={el}></LinkItem>
))}
</div>

View File

@ -4,6 +4,7 @@ import widgetList, { type Widget } from '@/widgets'
import clsx from 'clsx'
import { AddToToken } from './AdderPage'
import { v4 as uuid } from 'uuid'
import useAdderPageStore from './useAdderPageStore'
export const WidgetItem = defineComponent({
props: {
@ -32,10 +33,7 @@ export const WidgetItem = defineComponent({
key={props.content.name}
>
<div class="flex">
<img
src={props.content.icon}
class="w-[58px] h-[58px] bg-cover rounded-lg shadow-lg"
/>
<img src={props.content.icon} class="w-[58px] h-[58px] bg-cover rounded-lg shadow-lg" />
<div class="px-2 w-0 flex-grow ">
<div
class={clsx('text text-sm mb-[2px]', {
@ -120,18 +118,18 @@ export const WidgetItem = defineComponent({
export default defineComponent(() => {
const layout = useLayoutStore()
const store = useAdderPageStore()
return () => (
<div class="absolute left-0 top-0 w-full h-full overflow-y-auto scrollbar-hide gap-4">
<div class="w-full grid grid-cols-3 grid-flow-row-dense gap-4" style="grid-auto-rows: 120px">
{
layout.state.current !== 1 ?
widgetList.filter(val => val.name !== 'tomato_work').map((el) => (
<WidgetItem content={el} key={el.name} />
)) :
widgetList.map((el) => (
<WidgetItem content={el} key={el.name} />
))
}
{layout.state.current !== 1
? widgetList
.filter((val) => val.name !== 'tomato_work')
.filter((val) => val.label.includes(store.widgetSearchWords))
.map((el) => <WidgetItem content={el} key={el.name} />)
: widgetList
.filter((val) => val.label.includes(store.widgetSearchWords))
.map((el) => <WidgetItem content={el} key={el.name} />)}
</div>
</div>
)

View File

@ -1,9 +1,95 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import request from '@/utils/request'
import { defineStore } from 'pinia'
import { watch } from 'vue'
import { ref } from 'vue'
import type { BackgroundType } from '../background/BackgroundSwtich'
export interface HotAppType {
id: string
name: string
url: string
ordinal: number
desc: string
icon: string
background: string
}
export interface HotAppCategoryType {
id: string
name: string
ordinal: number
}
export type EditBlockItemType = {
id: string
link: string
name: string
text: string
background: string
color: string
icon: string
type: number // 0 默认1 文字
}
export default defineStore('adderPage', () => {
const type = ref(1)
const selectType = ref('')
const loading = ref(false)
const appList = ref<HotAppType[]>([])
const categoryList = ref<BackgroundType[]>([])
const isSearch = ref(false)
const widgetSearchWords = ref('')
const gameSearch = ref('')
const editBlockItem = ref<EditBlockItemType | null >(null)
request<HotAppCategoryType[]>('GET', '/api/app/hotAppTypes').then((res) => {
categoryList.value = res.map((el) => ({
id: el.id,
oridinal: el.ordinal,
type: el.name,
attr: 0
}))
export default defineStore("adderPage", () => {
const type = ref(1)
return {
type
selectType.value = res[0].id
})
watch(selectType, (val) => {
getApps(val)
})
const getApps = (_selectType: string) => {
loading.value = true
request<HotAppType[]>('GET', `/api/app/hotApps?hotAppsId=${_selectType}`)
.then((res) => {
appList.value = res
})
.finally(() => {
setTimeout(() => {
loading.value = false
}, 200)
})
}
const search = (keywords: string) => {
if (keywords === '') {
isSearch.value = false
getApps(selectType.value)
return
}
})
isSearch.value = true
request<HotAppType[]>('GET', `/api/app/hotApp/search?keyword=${keywords}`)
.then((res) => {
appList.value = res
})
.finally(() => {
setTimeout(() => {
loading.value = false
}, 200)
})
}
return {
type,
widgetSearchWords,
categoryList,
appList,
loading,
selectType,
search,
isSearch,
gameSearch,
editBlockItem
}
})

View File

@ -1,4 +1,4 @@
import { Button, Select, Slider } from 'ant-design-vue'
import { Button, Slider } from 'ant-design-vue'
import { defineComponent, ref, Transition, watch } from 'vue'
import useLayoutStore from '../useLayoutStore'
import { DownloadOutlined, EyeInvisibleOutlined, SwapOutlined } from '@ant-design/icons-vue'
@ -23,7 +23,7 @@ export default defineComponent(() => {
const settings = useSettingsStore()
return () => (
<div class="absolute left-0 top-0 w-full h-full p-4 overflow-y-auto scrollbar-hide">
<SettingItem
{/* <SettingItem
noBg
v-slots={{
label: () => <div></div>
@ -38,7 +38,7 @@ export default defineComponent(() => {
]}
v-model:value={selected.value}
/>
</SettingItem>
</SettingItem> */}
<div class="px-4">
<div class="h-[180px]">
{layout.background.video && layout.background.type !== 'own' ? (

View File

@ -154,7 +154,6 @@ export default defineComponent(() => {
fill={isGame.value ? 'white' : 'black'}
name={BiChevronDown.name}
class="group pointer-events-none absolute top-1/2 right-2.5 -translate-y-1/2 size-4 "
aria-hidden="true"
/>
</div>
</div>

View File

@ -33,11 +33,7 @@ export default defineComponent(() => {
<div
class={clsx(
'fixed bottom-4 left-1/2 duration-150 -translate-x-1/2 p-4 rounded-lg bg-white/60 backdrop-blur flex gap-4 shadow-lg',
setting.state.showDock === 'auto'
? show.value
? 'bottom-4'
: '-bottom-[90px]'
: 'bottom-4'
setting.state.showDock === 'auto' ? show.value ? 'bottom-4' : 'bottom-[-90px]' : 'bottom-4'
)}
ref={container}
onMouseleave={() => {
@ -58,8 +54,8 @@ export default defineComponent(() => {
style={
current.value >= 0
? {
transform: `translateY(${current.value === i - 1 || current.value === i + 1 ? '-5%' : current.value === i ? '-10%' : '0'}) scale(${current.value === i - 1 || current.value === i + 1 ? 1.1 : current.value === i ? 1.2 : 1})`
}
transform: `translateY(${current.value === i - 1 || current.value === i + 1 ? '-5%' : current.value === i ? '-10%' : '0'}) scale(${current.value === i - 1 || current.value === i + 1 ? 1.1 : current.value === i ? 1.2 : 1})`
}
: {}
}
id={block?.id || ''}

View File

@ -7,6 +7,9 @@ import LinkBlock from './LinkBlock'
import DirBlock from './DirBlock'
import WidgetBlock from './WidgetBlock'
import useSettingsStore from '@/settings/useSettingsStore'
import { useMenuStore } from '../GlobalMenu'
import { block } from '@milkdown/kit/plugin/block'
import clsx from 'clsx'
export default defineComponent({
props: {
@ -22,11 +25,15 @@ export default defineComponent({
setup(props) {
const settings = useSettingsStore()
const layout = useLayoutStore()
const menu = useMenuStore()
let it: any = 0
const hover = ref(false)
return () => (
<div
class="w-full h-full p-[var(--block-padding)] relative rounded-lg"
class={clsx(
'w-full h-full p-[var(--block-padding)] relative rounded-lg ',
menu.isEditPage && 'animate-wiggle'
)}
key={props.block.id}
id={props.block.id}
style={{
@ -115,10 +122,38 @@ export default defineComponent({
}
}}
>
{menu.isEditPage && (
<div
v-outside-click={() => {
menu.isEditPage = false
}}
onClick={(e) => {
e.stopPropagation()
const idx = layout.state.content[layout.state.current].pages[
layout.state.currentPage
].list.findIndex((val) => val.id === props.block.id)
if (idx < 0) return
layout.state.content[layout.state.current].pages[
layout.state.currentPage
].list.splice(idx, 1)
}}
class={
'rounded-full cursor-pointer backdrop-blur-md absolute w-[20px] h-[20px] top-[8px] right-[12px] z-10 '
}
style={{
backgroundImage: `url('/tab/bg/del_icon_img.png')`,
backgroundSize: 'cover'
}}
></div>
)}
<div
class="w-full h-full overflow-hidden relative cursor-pointer shadow-lg hover-move"
class="w-full h-full overflow-hidden relative cursor-pointer shadow-lg hover-move "
style={{
borderRadius: `calc(var(--block-radius) * var(--block-size))`
borderRadius: `calc(var(--block-radius) * var(--block-size))`,
transition: 'transform 0.2s'
}}
onContextmenu={(e) => {
e.stopPropagation()
@ -140,7 +175,7 @@ export default defineComponent({
</div>
{settings.state.showBlockLabel && (
<div
class="absolute left-1/2 -translate-x-1/2 -bottom-3 text-sm text-white text-center w-[172px] overflow-hidden text-ellipsis whitespace-nowrap break-all font-bold"
class="absolute left-1/2 -translate-x-1/2 -bottom-3 text-sm text-white text-center w-[172px] overflow-hidden text-ellipsis whitespace-nowrap break-all font-bold"
style="text-shadow: 0 0 4px rgba(0,0,0,.6)"
>
{layout.getLabel(props.block)}

View File

@ -31,7 +31,7 @@ export default defineComponent({
jump(props.block.link)
}}
style={{
backgroundColor: props.block.background || 'white',
backgroundColor: props.block.text ? props.block.background || 'white' : 'transparent',
color: props.block.color || 'black',
backgroundImage: props.block.icon ? `url('${props.block.icon}')` : '',
fontSize: props.dock ? '16px' : props.brief ? '12px' : 'calc(var(--block-size) / 5)'

View File

@ -1,23 +1,63 @@
import SettingItem from '@/settings/SettingItem'
import useSettingsStore from '@/settings/useSettingsStore'
import { Button, Switch } from 'ant-design-vue'
import { Button } from 'ant-design-vue'
import clsx from 'clsx'
import { defineComponent } from 'vue'
import { computed, defineComponent, ref } from 'vue'
import useLayoutStore from '../useLayoutStore'
export default defineComponent({
setup() {
const settings = useSettingsStore()
const open = ref(false)
const layout = useLayoutStore()
const isGame = computed(() => {
return layout.state.current === 0
})
return () => (
<div class="p-4 flex flex-col ">
<SettingItem
v-slots={{
label: () => <div></div>
label: () => <div ></div>
}}
>
<Button type="primary"></Button>
<Button type="primary" onClick={() => {
open.value = true
}}></Button>
</SettingItem>
<p class={'text-[#666] text-[12px]'}></p>
</div>
{
open.value &&
<div class={"w-[300px] h-[210px] absolute rounded-2xl right-[-310px] z-10 bg-[#2c2e3e]"} style={
isGame.value ?
{
backgroundImage: `url('/tab/bg/addBorder.png')`,
backgroundSize: '100% 100%',
backgroundColor: '#2c2e3e'
} : {}}>
<div class={"flex flex-col w-full h-full p-7 border-b-[1px] items-center justify-between"}>
<span class={isGame.value ? "" : ""}></span>
<div class={clsx("w-full h-[1px]", isGame.value ? " bg-white/20" : "bg-black/20")}></div>
<span class={clsx("text-[14px] leading-[20px] mb-2 text-center", isGame.value ? "text-[#fff9]" : "")}></span>
<div class={"flex justify-between w-full"}>
<button
onClick={() => {
layout.resetAll()
open.value = false
}}
class={clsx("w-[118px] rounded-lg py-1 flex justify-center", isGame.value ? "bg-white/20" : "")}></button>
<button
onClick={() => {
open.value = false
}}
class={clsx("w-[118px] rounded-lg py-1 flex justify-center", isGame.value ? "bg-[#ff7372]" : "")}></button>
</div>
</div>
</div>
}
</div >
)
}
})

View File

@ -1,4 +1,4 @@
import { computed, defineComponent, ref, toRaw } from 'vue'
import { computed, defineComponent, ref, toRaw, watch } from 'vue'
import useLayoutStore from '../useLayoutStore'
import { OhVueIcon, addIcons } from 'oh-vue-icons'
import { MdAdd } from 'oh-vue-icons/icons'
@ -15,11 +15,21 @@ export default defineComponent(() => {
const layout = useLayoutStore()
const settings = useSettingsStore()
const router = useRouterStore()
const scrollRef = ref<any>()
const container = useSortable(
computed(() => layout.currentPage.list),
ref('page')
)
watch(
() => layout.currentPage.list.length,
(cur, old) => {
if (cur > old) {
if (scrollRef.value) {
scrollRef.value.scrollTop = scrollRef.value.scrollHeight
}
}
}
)
listenParent('openSetting', (d) => {
if (d === 'profile') {
router.go('settings-user')
@ -30,6 +40,7 @@ export default defineComponent(() => {
})
return () => (
<div
ref={scrollRef}
class="absolute left-0 top-0 w-full h-screen overflow-y-auto no-scrollbar pt-[240px] px-[calc((100%_-_var(--main-width))_/_2)]"
onScroll={(e) => {
const h = (e.target as any).scrollTop

View File

@ -5,6 +5,8 @@ import { EditOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons-vu
import asyncLoader from '@/utils/asyncLoader'
import ThemeProvider from '@/utils/ThemeProvider'
import { globalToast } from '@/main'
import useUserStore from '@/user/useUserStore'
import useRouterStore from '@/useRouterStore'
const SearchAdder = asyncLoader(() => import('./SearchAdder'))
const SearchItem = defineComponent({
props: {
@ -157,7 +159,12 @@ export default defineComponent(() => {
block
icon={<PlusOutlined />}
onClick={() => {
showAdder.value = null
if (useUserStore().isLogin) {
showAdder.value = null
} else {
globalToast.warning('请先登录')
useRouterStore().go('global-login')
}
}}
>

View File

@ -23,18 +23,19 @@ export default defineComponent(
props.isMini
? {}
: {
top: layout.isCompact ? '40px' : layout.state.simple ? '230px' : '172px',
width: settings.state.searchWidth + 'rem'
}
top: layout.isCompact ? '40px' : layout.state.simple ? '230px' : '172px',
width: settings.state.searchWidth + 'rem'
}
}
>
<div
class={clsx(
'w-full h-11 shadow-content overflow-hidden px-1 transition-all flex justify-between items-center gap-4 ',
search.focus ? 'bg-white/60' : 'bg-white/40 hover:bg-white/60',
search.focus ? 'bg-white/60' : 'bg-white hover:bg-white',
props.isMini ? '' : 'max-w-[90vw] w-full'
)}
style={{
opacity: search.focus ? 1 : settings.state.searchOpacity,
borderRadius: settings.state.searchRadius + 'px'
}}
>
@ -58,7 +59,7 @@ export default defineComponent(
onContextmenu={(e) => e.stopPropagation()}
onKeydown={(e) => e.stopPropagation()}
class="flex-1 h-full outline-none bg-transparent placeholder:text-slate-600 placeholder:tracking-widest text-slate-800 pr-4"
placeholder={`输入搜索 ${searchConfig.current.name}`}
placeholder={`请输入搜索内容`}
/>
</div>
<Transition name="searchContent">{search.showSearchConfig && <SearchConfig />}</Transition>

View File

@ -42,11 +42,12 @@ const Item = defineComponent({
return () => (
<div
onContextmenu={(e) => {
if (!props.id) return
useMenuStore().open('page')
useMenuStore().selectPage = {
id: props.id,
label: props.label,
name: props.name,
name: props.name
}
e.stopPropagation()
e.preventDefault()
@ -106,6 +107,10 @@ export default defineComponent(() => {
<Transition>
{layout.ready && (
<div
onContextmenu={(e)=> {
e.stopPropagation()
e.preventDefault()
}}
class={clsx(
'w-[130px] min-h-[620px] hover:bg-red-10 z-20 fixed top-1/2 -translate-y-1/2 bottom-0 ',
settings.state.siderDirection === 'left' ? 'left-0' : 'right-0'
@ -194,6 +199,10 @@ export default defineComponent(() => {
{/* 添加页面 */}
{menu.showEditPage && (
<div
onContextmenu={(e) => {
e.stopPropagation()
e.preventDefault()
}}
class={clsx(
'absolute bottom-0 w-56 rounded-lg p-4 bg-white/40 backdrop-blur shadow-lg',
settings.state.siderDirection === 'left' ? 'left-[70px]' : 'right-[70px]'
@ -230,7 +239,16 @@ export default defineComponent(() => {
if (menu.selectPage) {
menu.selectPage.name = selected.value.name
menu.selectPage.label = label.value
if (!menu.selectPage?.id) return
const idx = layout.state.content[layout.state.current].pages.findIndex(
(val) => val.id === menu.selectPage?.id
)
if (idx !== -1) {
layout.state.content[layout.state.current].pages[idx].label = label.value
layout.state.content[layout.state.current].pages[idx].name =
selected.value.name
}
menu.selectPage = undefined
} else {
layout.currentMode.pages.push({
list: [],
@ -241,11 +259,10 @@ export default defineComponent(() => {
}
menu.showEditPage = false
}}
>
<OhVueIcon name="px-check" />
{menu.selectPage ? "修改页面" : "添加页面"}
{menu.selectPage ? '修改页面' : '添加页面'}
</div>
</div>
)}

View File

@ -8,6 +8,7 @@ import jump from '@/utils/jump'
import useSettingsStore from '@/settings/useSettingsStore'
import useUserStore from '@/user/useUserStore'
import request from '@/utils/request'
import type { EditBlockItemType } from './adder/useAdderPageStore'
const defaultLayout: Layout = {
content: [
@ -26,7 +27,7 @@ const defaultLayout: Layout = {
export default defineStore('layout', () => {
const settings = useSettingsStore()
const user = useUserStore()
const state = reactive(defaultLayout)
const state = reactive({ ...defaultLayout })
const ready = ref(false)
db.getItem<Layout>('layout').then((res) => {
@ -78,6 +79,21 @@ export default defineStore('layout', () => {
pageList.push(block)
globalToast.success('添加成功')
}
const changeBlock = (item: EditBlockItemType, target: string) => {
const idx = currentPage.value.list.findIndex((el) => el.id === target)
if (idx < 0) return
currentPage.value.list.splice(idx, 1, {
...currentPage.value.list[idx],
label: item.name,
color: item.color,
text: item.text,
link: item.link,
background: item.background,
icon: item.type === 0 ? item.icon : '',
})
}
const openDir = ref('')
// 文件夹只有一个时,将当前界面的文件夹替换为图标
const checkDir = (id: string) => {
@ -112,7 +128,17 @@ export default defineStore('layout', () => {
}
return block.label || ''
}
const resetAll = () => {
request('GET', '/api/app/desktop').then((res: any) => {
if (!res) return
state.dir = res.dir
state.content = res.content
}).catch(() => {
Object.assign(state, defaultLayout)
})
}
const changeBackground = (url: string) => {
state.content[state.current].background = url
}
@ -161,6 +187,8 @@ export default defineStore('layout', () => {
openDir,
checkDir,
getLabel,
changeBackground
changeBackground,
resetAll,
changeBlock
}
})

View File

@ -2,18 +2,17 @@ import './main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'
import App from './App.vue'
import getFp from './utils/getFp'
import vOutsideClick from './utils/vOutsideClick'
import dayjs from 'dayjs'
import Toast, { useToast, type PluginOptions } from 'vue-toastification'
import Toast, { useToast } from 'vue-toastification'
import customParseFormat from 'dayjs/plugin/customParseFormat'
import 'vue-toastification/dist/index.css'
import 'dayjs/locale/zh-cn'
dayjs.locale('zh-cn')
const app = createApp(App)
export const globalToast = useToast()
dayjs.extend(customParseFormat)

View File

@ -57,7 +57,7 @@ export default defineComponent(() => {
return layout.state.current === 0
})
return () => (
<div class="fixed left-0 bottom-0 z-40 w-full">
<div class="fixed left-0 bottom-0 z-40 w-full rounded-lg">
{/* 背景遮罩 */}
{show.value && (
<div
@ -72,13 +72,20 @@ export default defineComponent(() => {
{show.value && (
<div
class={clsx(
'absolute left-6 bottom-10 w-[660px] h-[580px] rounded-lg overflow-hidden shadow-2xl flex',
'absolute left-6 bottom-10 w-[660px] h-[580px] rounded-2xl shadow-2xl flex',
isGame.value ? 'bg-[#2c2e3e] text-white' : 'text-[#000] bg-white'
)}
style={
isGame.value &&
{
backgroundImage: `url('/tab/bg/gameModel.png')`,
backgroundSize: '100% 100%',
}
}
>
<div
class={clsx(
'w-[200px] p-4 h-full backdrop-blur flex flex-col',
'w-[200px] p-4 h-full flex flex-col rounded-lg',
isGame.value ? 'bg-[#fff]/10 text-white ' : 'bg-[#ebebeb]'
)}
>
@ -134,7 +141,10 @@ export default defineComponent(() => {
<SettingsTab label="问题反馈" path="settings-fallback" />
</div>
</div>
<Content />
<div class={"flex-1 w-0 h-full relative"}>
<Content />
</div>
</div>
)}
</Transition>

View File

@ -15,7 +15,7 @@ export default defineComponent(() => {
const router = useRouterStore()
return () => (
<div class="w-0 h-full flex-grow backdrop-blur">
<div class="w-full h-full rounded-xl">
<ThemeProvider>
{router.path === 'settings-user' ? (
<UserPage />

View File

@ -75,11 +75,13 @@ export default defineStore('user', () => {
token,
async (val) => {
localStorage.setItem('token', val)
if (!val) {
sendParent(['logout'])
return
}
sendParent(['login', val])
const res = await request<UserInfo>('GET', '/api/profile')
Object.assign(profile, res)
},

View File

@ -5,6 +5,8 @@ import upload from './upload'
import 'viewerjs/dist/viewer.css'
import { api as showViewer } from 'v-viewer'
import { globalToast } from '@/main'
import useUserStore from '@/user/useUserStore'
import useRouterStore from '@/useRouterStore'
addIcons(MdUpload, FaEye)
@ -33,6 +35,7 @@ export default defineComponent({
},
setup(props, ctx) {
let input: HTMLInputElement | null = null
return () => (
<div>
<input
@ -62,7 +65,12 @@ export default defineComponent({
backgroundImage: `url('${props.value}')`
}}
onClick={() => {
input?.click()
if (useUserStore().isLogin) {
input?.click()
} else {
globalToast.warning('请先登录')
useRouterStore().go('global-login')
}
}}
>
{props.value ? (

View File

@ -1,76 +1,82 @@
import clsx from "clsx";
import { computed } from "vue";
import { defineComponent } from "vue";
import { MdCheck } from "oh-vue-icons/icons";
import { addIcons, OhVueIcon } from "oh-vue-icons";
import clsx from 'clsx'
import { computed } from 'vue'
import { defineComponent } from 'vue'
import { MdCheck } from 'oh-vue-icons/icons'
import { addIcons, OhVueIcon } from 'oh-vue-icons'
addIcons(MdCheck)
export default defineComponent({
name: "NativeColorPicker",
props: {
value: {
type: String,
default: ''
},
colorList: {
type: Array<string>
},
class: {
type: String,
},
transparent: {
type: Boolean,
},
firstColor: {
type: String,
}
name: 'NativeColorPicker',
props: {
value: {
type: String,
default: ''
},
emits: {
"update:value": (_value: string) => true,
colorList: {
type: Array<string>
},
setup(props, context) {
const firstColor = props.value
const colorList = computed(() => {
let list = [
'#ffffff',
'#000000',
'#FA9B3F',
'#333333',
'#6D7278',
'#D8D8D8',
'#0091FF',
'#B620E0',
'#F31260'
]
if (props.colorList) {
list = props.colorList
}
if (props.transparent) {
list.push('transparent')
}
if (!list.includes(firstColor)) {
list.unshift(firstColor)
}
return list
})
return () => <div class={clsx("flex items-center gap-x-2 ", props.class)}>
{
colorList.value.map((item, index) => < div
key={index}
onClick={() => context.emit('update:value', item)}
class="text-[12px] cursor-pointer w-[20px] h-[20px] shadow-[0_0_2px_#999] rounded-full"
style={{
backgroundColor: item === 'transparent' ? '' : item,
backgroundImage:
item === 'transparent'
? `linear-gradient(45deg, rgba(0, 0, 0, 0.4) 25%, white 25%, transparent 75%,rgba(0, 0, 0, 0.4) 75%),
class: {
type: String
},
transparent: {
type: Boolean
},
firstColor: {
type: String
},
size: {
type: Number,
default: 20
}
},
emits: {
'update:value': (_value: string) => true
},
setup(props, context) {
const firstColor = props.value
const colorList = computed(() => {
let list = [
'#ffffff',
'#000000',
'#FA9B3F',
'#333333',
'#6D7278',
'#D8D8D8',
'#0091FF',
'#B620E0',
'#F31260'
]
if (props.colorList) {
list = props.colorList
}
if (props.transparent) {
list.push('transparent')
}
if (!list.includes(firstColor)) {
list.unshift(firstColor)
}
return list
})
return () => (
<div class={clsx('flex items-center gap-x-2 ', props.class)}>
{colorList.value.map((item, index) => (
<div
key={index}
onClick={() => context.emit('update:value', item)}
class="text-[12px] cursor-pointer w-[20px] h-[20px] flex items-center justify-center shadow-[0_0_2px_#999] rounded-full"
style={{
width: props.size + 'px',
height: props.size + 'px',
backgroundColor: item === 'transparent' ? '' : item,
backgroundImage:
item === 'transparent'
? `linear-gradient(45deg, rgba(0, 0, 0, 0.4) 25%, white 25%, transparent 75%,rgba(0, 0, 0, 0.4) 75%),
linear-gradient(45deg,rgba(0, 0, 0, 0.4) 25%, white 25%, transparent 75%, rgba(0, 0, 0, 0.4) 75%)`
: '',
backgroundPosition: item === 'transparent' ? '0 0, 2px 2px' : '',
backgroundSize: item === 'transparent' ? '4px 4px' : ''
}}
>
{/* <Checkmark16Filled
: '',
backgroundPosition: item === 'transparent' ? '0 0, 2px 2px' : '',
backgroundSize: item === 'transparent' ? '4px 4px' : ''
}}
>
{/* <Checkmark16Filled
class="text-[#888]"
: class="props.modelValue === item ? '' : 'hidden'"
: style="{
@ -78,32 +84,34 @@ export default defineComponent({
}"
>
</Checkmark16Filled> */}
{
props.value === item &&
<OhVueIcon class="text-[#888]" name={MdCheck.name} ></OhVueIcon>
{props.value === item && (
<OhVueIcon class="text-[#888]" name={MdCheck.name}></OhVueIcon>
)}
</div>
))}
}
</div >)
}
<div
class="text-[12px] cursor-pointer w-[20px] h-[20px] shadow-[0_0_2px_#999] rounded-full relative"
>
<div
class="w-full h-full rounded-full cursor-pointer"
style="background: conic-gradient(#dd0010, yellow, green, #0091ff, red)"
>
<input
onInput={(e: any)=> {
context.emit('update:value', e?.target.value)
}}
ref="inputRef"
type="color"
style="opacity: 0"
/>
</div>
</div>
</div >
}
})
<div
class="text-[12px] cursor-pointer shadow-[0_0_2px_#999] rounded-full relative"
style={{
width: props.size + 'px',
height: props.size + 'px'
}}
>
<div
class="w-full h-full rounded-full cursor-pointer"
style="background: conic-gradient(#dd0010, yellow, green, #0091ff, red)"
>
<input
onInput={(e: any) => {
context.emit('update:value', e?.target.value)
}}
ref="inputRef"
type="color"
style="opacity: 0"
/>
</div>
</div>
</div>
)
}
})

View File

@ -21,6 +21,7 @@ export default defineComponent({
},
components: {
Slider: {
railSize: 6
},

View File

@ -3,141 +3,149 @@ import { defineComponent, ref, watch } from 'vue'
import { VueCropper } from 'vue-cropper'
import 'vue-cropper/dist/index.css'
import upload from './upload'
import { v4 as uuid } from "uuid"
import { MdCroprotateRound, BiPlusLg } from "oh-vue-icons/icons";
import { OhVueIcon, addIcons, } from 'oh-vue-icons'
import { v4 as uuid } from 'uuid'
import { MdCroprotateRound, BiPlusLg } from 'oh-vue-icons/icons'
import { OhVueIcon, addIcons } from 'oh-vue-icons'
import NativeColorPicker from './NativeColorPicker'
import useUserStore from '@/user/useUserStore'
import { globalToast } from '@/main'
import useRouterStore from '@/useRouterStore'
addIcons(MdCroprotateRound, BiPlusLg)
export default defineComponent({
emits: {
'update:value': (() => true) as (val: string) => boolean
},
setup(props, ctx) {
const inputRef = ref<HTMLInputElement | null>(null)
const showCutModel = ref(false)
const originFile = ref('')
const fillColor = ref('transparent')
const cropper = ref<any>(null)
const handleFile = (e: any) => {
const target = e.target as HTMLInputElement
emits: {
'update:value': (() => true) as (val: string) => boolean
},
setup(props, ctx) {
const inputRef = ref<HTMLInputElement | null>(null)
const showCutModel = ref(false)
const originFile = ref('')
const fillColor = ref('transparent')
const cropper = ref<any>(null)
const handleFile = (e: any) => {
const target = e.target as HTMLInputElement
const file = target.files?.[0]
target.value = ''
const file = target.files?.[0]
target.value = ''
if (!file) return
// upload(file).then(res => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = (e: any) => {
originFile.value = e?.target.result // 显示原始图片
showCutModel.value = true
}
// emit('update:icon', res || '')
// })
}
const handleFinish = (blob: any) => {
// 根据前几个字节推测文件类型(这里只考虑常见的图片类型)
if (!file) return
// upload(file).then(res => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = (e: any) => {
originFile.value = e?.target.result // 显示原始图片
showCutModel.value = true
}
// emit('update:icon', res || '')
// })
}
const handleFinish = (blob: any) => {
// 根据前几个字节推测文件类型(这里只考虑常见的图片类型)
// 使用 FileReader 对象读取 Blob 的前几个字节
const reader = new FileReader()
reader.onloadend = function () {
// 打印 File 对象以验证
// 使用 FileReader 对象读取 Blob 的前几个字节
const reader = new FileReader()
reader.onloadend = function () {
// 打印 File 对象以验证
const fileData = new File([blob], `${uuid()}.png`, {
type: 'image/jpg',
lastModified: Date.now()
})
upload(fileData, 'customIcon').then((res) => {
showCutModel.value = false
const fileData = new File([blob], `${uuid()}.png`, {
type: 'image/jpg',
lastModified: Date.now()
})
upload(fileData, 'customIcon').then((res) => {
showCutModel.value = false
ctx.emit('update:value', res || '')
})
}
reader.readAsArrayBuffer(blob)
}
watch(
fillColor,
(e: string) => {
const elements = document.querySelectorAll('.cropper-crop-box')
elements.forEach((element: any) => {
element.style.backgroundColor = e // 修改颜色为蓝色
})
},
{
immediate: true
}
)
return () => (
<>
<Modal
open={showCutModel.value}
onCancel={() => showCutModel.value = false}
onOk={() => {
cropper.value.getCropBlob((blob: any) => {
handleFinish(blob)
})
}}
>
<div class="w-full flex flex-col items-center gap-y-2">
<div class="w-[250px] h-[250px]" style={{
backgroundColor: fillColor.value || 'transparent',
}}>
<VueCropper
ref={cropper}
autoCropWidth="250px"
autoCropHeight="250px"
mode="contain"
outputType="png"
img={originFile.value}
autoCrop={true}
fillColor={fillColor.value}
/>
</div>
<div class="flex justify-between w-[250px]">
{/* <ArrowRotateClockwise24Regular
ctx.emit('update:value', res || '')
})
}
reader.readAsArrayBuffer(blob)
}
watch(
fillColor,
(e: string) => {
const elements = document.querySelectorAll('.cropper-crop-box')
elements.forEach((element: any) => {
element.style.backgroundColor = e // 修改颜色为蓝色
})
},
{
immediate: true
}
)
return () => (
<>
<Modal
open={showCutModel.value}
onCancel={() => (showCutModel.value = false)}
onOk={() => {
cropper.value.getCropBlob((blob: any) => {
handleFinish(blob)
})
}}
>
<div class="w-full flex flex-col items-center gap-y-2">
<div
class="w-[250px] h-[250px]"
style={{
backgroundColor: fillColor.value || 'transparent'
}}
>
<VueCropper
ref={cropper}
autoCropWidth="250px"
autoCropHeight="250px"
mode="contain"
outputType="png"
img={originFile.value}
autoCrop={true}
fillColor={fillColor.value}
/>
</div>
<div class="flex justify-between w-[250px]">
{/* <ArrowRotateClockwise24Regular
class="text-[10px] w-8 h-8 cursor-pointer"
onClick={() => ($refs.cropper as any).rotateRight()}
/> */}
<span class="flex items-center cursor-pointer"
onClick={() => {
cropper.value.rotateRight()
}}>
<OhVueIcon name="md-croprotate-round" scale={1} fill="#707070" ></OhVueIcon>
</span>
<span class="flex items-center gap-x-1">
<svg
onClick={() => cropper.value?.changeScale(1)}
class="icon cursor-pointer"
viewBox="0 0 1024 1024"
width="2rem"
height="2rem"
>
<path
d="M886.51776 799.66208l-139.93984-139.93984c-10.50624-10.50624-23.90016-16.13824-37.60128-17.44896 42.16832-52.59264 67.54304-119.1936 67.54304-191.6928 0-169.39008-137.80992-307.2-307.2-307.2s-307.2 137.80992-307.2 307.2 137.80992 307.2 307.2 307.2c63.91808 0 123.31008-19.6608 172.52352-53.18656 0.34816 15.23712 6.22592 30.37184 17.85856 42.00448l139.93984 139.93984c11.9808 12.00128 27.72992 18.00192 43.43808 18.00192s31.45728-6.00064 43.43808-18.00192C910.52032 862.55616 910.52032 823.66464 886.51776 799.66208zM469.31968 655.38048c-112.92672 0-204.8-91.87328-204.8-204.8s91.87328-204.8 204.8-204.8 204.8 91.87328 204.8 204.8S582.2464 655.38048 469.31968 655.38048zM610.14016 450.58048c0 22.60992-18.35008 40.96-40.96 40.96l-56.32 0 0 56.32c0 22.60992-18.35008 40.96-40.96 40.96s-40.96-18.35008-40.96-40.96l0-56.32-56.32 0c-22.60992 0-40.96-18.35008-40.96-40.96s18.35008-40.96 40.96-40.96l56.32 0 0-56.32c0-22.60992 18.35008-40.96 40.96-40.96s40.96 18.35008 40.96 40.96l0 56.32 56.32 0C591.79008 409.62048 610.14016 427.95008 610.14016 450.58048z"
fill="#707070"
/>
</svg>
<svg
onClick={() => cropper.value?.changeScale(-1)}
class="icon cursor-pointer"
viewBox="0 0 1024 1024"
width="2rem"
height="2rem"
>
<path
d="M886.51776 799.66208l-139.93984-139.93984c-10.50624-10.50624-23.90016-16.13824-37.60128-17.44896 42.16832-52.59264 67.54304-119.1936 67.54304-191.6928 0-169.39008-137.80992-307.2-307.2-307.2s-307.2 137.80992-307.2 307.2 137.80992 307.2 307.2 307.2c63.91808 0 123.31008-19.6608 172.52352-53.18656 0.34816 15.23712 6.22592 30.37184 17.85856 42.00448l139.93984 139.93984c11.9808 12.00128 27.72992 18.00192 43.43808 18.00192s31.45728-6.00064 43.43808-18.00192C910.52032 862.55616 910.52032 823.66464 886.51776 799.66208zM469.31968 655.38048c-112.92672 0-204.8-91.87328-204.8-204.8s91.87328-204.8 204.8-204.8 204.8 91.87328 204.8 204.8S582.2464 655.38048 469.31968 655.38048zM610.14016 450.58048c0 22.60992-18.35008 40.96-40.96 40.96l-194.56 0c-22.60992 0-40.96-18.35008-40.96-40.96s18.35008-40.96 40.96-40.96l194.56 0C591.79008 409.62048 610.14016 427.95008 610.14016 450.58048z"
fill="#707070"
/>
</svg>
</span>
</div>
<NativeColorPicker value={fillColor.value} onUpdate:value={e => fillColor.value = e} transparent={true} />
{/* {props.colorList && (
<span
class="flex items-center cursor-pointer"
onClick={() => {
cropper.value.rotateRight()
}}
>
<OhVueIcon name="md-croprotate-round" scale={1} fill="#707070"></OhVueIcon>
</span>
<span class="flex items-center gap-x-1">
<svg
onClick={() => cropper.value?.changeScale(1)}
class="icon cursor-pointer"
viewBox="0 0 1024 1024"
width="2rem"
height="2rem"
>
<path
d="M886.51776 799.66208l-139.93984-139.93984c-10.50624-10.50624-23.90016-16.13824-37.60128-17.44896 42.16832-52.59264 67.54304-119.1936 67.54304-191.6928 0-169.39008-137.80992-307.2-307.2-307.2s-307.2 137.80992-307.2 307.2 137.80992 307.2 307.2 307.2c63.91808 0 123.31008-19.6608 172.52352-53.18656 0.34816 15.23712 6.22592 30.37184 17.85856 42.00448l139.93984 139.93984c11.9808 12.00128 27.72992 18.00192 43.43808 18.00192s31.45728-6.00064 43.43808-18.00192C910.52032 862.55616 910.52032 823.66464 886.51776 799.66208zM469.31968 655.38048c-112.92672 0-204.8-91.87328-204.8-204.8s91.87328-204.8 204.8-204.8 204.8 91.87328 204.8 204.8S582.2464 655.38048 469.31968 655.38048zM610.14016 450.58048c0 22.60992-18.35008 40.96-40.96 40.96l-56.32 0 0 56.32c0 22.60992-18.35008 40.96-40.96 40.96s-40.96-18.35008-40.96-40.96l0-56.32-56.32 0c-22.60992 0-40.96-18.35008-40.96-40.96s18.35008-40.96 40.96-40.96l56.32 0 0-56.32c0-22.60992 18.35008-40.96 40.96-40.96s40.96 18.35008 40.96 40.96l0 56.32 56.32 0C591.79008 409.62048 610.14016 427.95008 610.14016 450.58048z"
fill="#707070"
/>
</svg>
<svg
onClick={() => cropper.value?.changeScale(-1)}
class="icon cursor-pointer"
viewBox="0 0 1024 1024"
width="2rem"
height="2rem"
>
<path
d="M886.51776 799.66208l-139.93984-139.93984c-10.50624-10.50624-23.90016-16.13824-37.60128-17.44896 42.16832-52.59264 67.54304-119.1936 67.54304-191.6928 0-169.39008-137.80992-307.2-307.2-307.2s-307.2 137.80992-307.2 307.2 137.80992 307.2 307.2 307.2c63.91808 0 123.31008-19.6608 172.52352-53.18656 0.34816 15.23712 6.22592 30.37184 17.85856 42.00448l139.93984 139.93984c11.9808 12.00128 27.72992 18.00192 43.43808 18.00192s31.45728-6.00064 43.43808-18.00192C910.52032 862.55616 910.52032 823.66464 886.51776 799.66208zM469.31968 655.38048c-112.92672 0-204.8-91.87328-204.8-204.8s91.87328-204.8 204.8-204.8 204.8 91.87328 204.8 204.8S582.2464 655.38048 469.31968 655.38048zM610.14016 450.58048c0 22.60992-18.35008 40.96-40.96 40.96l-194.56 0c-22.60992 0-40.96-18.35008-40.96-40.96s18.35008-40.96 40.96-40.96l194.56 0C591.79008 409.62048 610.14016 427.95008 610.14016 450.58048z"
fill="#707070"
/>
</svg>
</span>
</div>
<NativeColorPicker
value={fillColor.value}
onUpdate:value={(e) => (fillColor.value = e)}
transparent={true}
/>
{/* {props.colorList && (
<div class="w-[250px] flex justify-center">
<NativeColorPicker
colorList={colorList}
@ -147,23 +155,32 @@ export default defineComponent({
/>
</div>
)} */}
</div>
</Modal >
<div class="w-full h-full bg-white flex items-center justify-center"
onClick={() => {
inputRef.value?.click?.()
}}
>
<OhVueIcon name={BiPlusLg.name} fill='#666666' scale={2}></OhVueIcon>
</div>
<input
ref={inputRef}
style={{
display: 'none'
}} accept=".jpg,.jpeg,.png,.svg" type="file" onChange={handleFile} />
</>
)
}
</div>
</Modal>
<div
class="w-full h-full bg-white flex items-center justify-center"
onClick={() => {
if (useUserStore().isLogin) {
inputRef.value?.click?.()
} else {
globalToast.warning('请先登录')
useRouterStore().go('global-login')
// useAdderPageStore().type = 2
}
}}
>
<OhVueIcon name={BiPlusLg.name} fill="#666666" scale={2}></OhVueIcon>
</div>
<input
ref={inputRef}
style={{
display: 'none'
}}
accept=".jpg,.jpeg,.png,.svg"
type="file"
onChange={handleFile}
/>
</>
)
}
})

View File

@ -13,7 +13,7 @@ export default function useLink(url: Ref<string>) {
const info = reactive<LinkInfo>({
background: '',
desc: '',
icon: '',
icon: 'https://oss.goosetab.com/000/user_upload/1/resource/120be9d6-1c68-41ba-b539-570c39ce2421.png',
link: '',
name: ''
})

View File

@ -33,7 +33,9 @@ export default defineComponent(() => {
<span class={'text-[48px] font-extrabold pt-2'}>{store.state.select.format('D')}</span>
<span>{store.state.select.format('ddd')}</span>
<span>
{store.state.select.diff(store.state.select.set('date', 1).set('months', 0), 'weeks')}
{store.state.select.diff(store.state.select.set('date', 1).set('months', 0), 'weeks')}
{store.state.select.diff(store.state.select.set('date', 1).set('month', 0), 'day')}
</span>
<div class={'w-[138px] h-[1px] flex-shrink-0 my-7 bg-black/10 relative'}></div>
@ -56,10 +58,8 @@ export default defineComponent(() => {
>
</div>
<div class={'flex flex-col text-[#666] text-[14px]'}>
{lunar.value.getDayYi().map((item: any) => {
return <div class={'flex items-center'}>{item}</div>
})}
<div class={'flex-1 w-0 text-[#666] text-[14px]'}>
{lunar.value.getDayYi().join(',')}
</div>
</div>
<div class={'flex w-full gap-x-5'}>
@ -70,10 +70,8 @@ export default defineComponent(() => {
>
</div>
<div class={'flex flex-col text-[#666] text-[14px]'}>
{lunar.value.getDayJi().map((item: any) => {
return <div class={'flex items-center'}>{item}</div>
})}
<div class={'flex-1 w-0 text-[#666] text-[14px]'}>
{lunar.value.getDayJi().join(',')}
</div>
</div>
</div>
@ -158,8 +156,10 @@ export default defineComponent(() => {
dayjs(),
'day'
),
'border-transparent border-solid': !el.day.isSame(dayjs(), 'day') && !el.day.isSame(store.state.select, 'day'),
'border-[#76d7f2] border-solid border-[1px]': !el.day.isSame(dayjs(), 'day') && el.day.isSame(store.state.select, 'day'),
'border-transparent border-solid':
!el.day.isSame(dayjs(), 'day') && !el.day.isSame(store.state.select, 'day'),
'border-[#76d7f2] border-solid border-[1px]':
!el.day.isSame(dayjs(), 'day') && el.day.isSame(store.state.select, 'day')
}
)}
>

View File

@ -3,9 +3,10 @@ import request from '@/utils/request'
import { addIcons, OhVueIcon } from 'oh-vue-icons'
import { computed, defineComponent, onMounted, ref, watch, type CSSProperties } from 'vue'
import { FaChevronLeft } from 'oh-vue-icons/icons'
import PlayImg from "~/icons/game_video_bg_play.png"
import PlayImg from '~/icons/game_video_bg_play.png'
import type { CarouselRef } from 'ant-design-vue/es/carousel'
import { randomNum } from '@/utils/tool'
import jump from '@/utils/jump'
addIcons(FaChevronLeft)
interface Owner {
face: string
@ -56,11 +57,19 @@ export default defineComponent(() => {
})
return () => (
<div class="w-full h-full p-2 bg-[#17212d] relative ">
<img src={PlayImg} class={"absolute z-10 w-[40px] bg-[#ffffff24] rounded-lg backdrop-blur-sm left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"}></img>
<img
src={PlayImg}
class={
'absolute z-10 w-[40px] bg-[#ffffff24] rounded-lg backdrop-blur-sm left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'
}
></img>
{
<div
class={'w-full h-full rounded-xl relative group'}
onClick={() => {
jump('https://www.bilibili.com/video/av' + current.value?.aid)
}}
style={{
backgroundImage: `url('${current.value?.pic}')`,
backgroundSize: 'cover',
@ -68,7 +77,6 @@ export default defineComponent(() => {
backgroundRepeat: 'no-repeat'
}}
>
<div
class={
'absolute bottom-0 left-1/2 -translate-x-1/2 pb-2 w-[300px] flex flex-col text-white '

View File

@ -10,7 +10,7 @@ export default defineComponent(() => {
<img class={'w-[48px] h-[48px]'} src={'/tab/icons/hot_information_icon.png'}></img>
<div class={'flex-1 flex justify-center'}>
<div class="flex-col flex">
<span class={'text-[16px] text-white'}></span>
<span class={'text-[16px] text-white'}></span>
<div class={'flex items-center text-[#fffc] text-[12px] '}>
<div>

View File

@ -3,6 +3,7 @@ import { useWeApplyStore } from './useWeApplyStore'
import { addIcons, OhVueIcon } from 'oh-vue-icons'
import { HiChevronDown } from 'oh-vue-icons/icons'
import clsx from 'clsx'
import jump from '@/utils/jump'
addIcons(HiChevronDown)
export default defineComponent(() => {
const store = useWeApplyStore()
@ -13,7 +14,7 @@ export default defineComponent(() => {
const computIsBottom = (ref: any) => {
if (ref) {
const { scrollTop, clientHeight, scrollHeight } = ref
return scrollTop + clientHeight >= scrollHeight -8
return scrollTop + clientHeight >= scrollHeight - 8
}
}
const handleGameScroll = () => {
@ -74,7 +75,12 @@ export default defineComponent(() => {
{store.state.list
.filter((val) => val.type === 'game')
.map((item) => (
<div class={'flex gap-x-2 items-center'}>
<div
class={'flex gap-x-2 items-center'}
onClick={() => {
jump(item.url)
}}
>
<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>
@ -107,7 +113,6 @@ export default defineComponent(() => {
if (workRef.value) {
workRef.value.scrollTop += 20
handleWorkScroll()
}
}}
>
@ -125,7 +130,12 @@ export default defineComponent(() => {
{store.state.list
.filter((val) => val.type === 'work')
.map((item) => (
<div class={'flex gap-x-2 items-center'}>
<div
class={'flex gap-x-2 items-center'}
onClick={() => {
jump(item.url)
}}
>
<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>

View File

@ -1,9 +1,9 @@
import { defineComponent } from 'vue'
import { formatSeconds } from '@/utils/tool'
import PlayStartImg from "~/icons/work/start.png"
import returnImg from "~/icons/work/return.png"
import endImg from "~/icons/work/tomotoIconEnd.png"
import PlusImg from "~/icons/work/tomatoIconAdd.png"
import PlayStartImg from '~/icons/work/start.png'
import returnImg from '~/icons/work/return.png'
import endImg from '~/icons/work/tomotoIconEnd.png'
import PlusImg from '~/icons/work/tomatoIconAdd.png'
import dayjs from 'dayjs'
import { Tooltip } from 'ant-design-vue'
import useTomatoStore from './useTomatoStore'
@ -11,41 +11,54 @@ import useTomatoStore from './useTomatoStore'
export default defineComponent(() => {
const store = useTomatoStore()
return () => (
<div class="w-full h-full flex relative p-6 justify-between" style={{
background: `url('https://newfatfox.oss-cn-beijing.aliyuncs.com/admin/tomotoback.png')`,
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat'
}}>
<div class={"bg-[#0000004d] absolute top-0 left-0 right-0 bottom-0 "}></div>
<div class={"w-[115px] h-full flex flex-col items-center z-10 text-white"}>
<div class={"w-full bg-white/20 text-center rounded text-[14px]"}></div>
<span class={"text-[42px] mb-1"}>
{
store.state.beginTime < 0 ? '15:00' : formatSeconds(store.remainingTime)
<div
class="w-full h-full flex relative p-6 justify-between"
style={{
background: `url('https://newfatfox.oss-cn-beijing.aliyuncs.com/admin/tomotoback.png')`,
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat'
}}
>
<div class={'bg-[#0000004d] absolute top-0 left-0 right-0 bottom-0 '}></div>
<div class={'w-[115px] h-full flex flex-col items-center z-10 text-white'}>
<div
class={
'w-full bg-white/20 text-center rounded text-[14px] overflow-hidden text-ellipsis whitespace-nowrap'
}
>
{store.state.list[0] ? store.state.list[0].title : '无目标'}
</div>
<span class={'text-[42px] mb-1'}>
{store.state.beginTime < 0 ? '15:00' : formatSeconds(store.remainingTime)}
</span>
<span class={"text-[14px]"}>
<span class={"text-[#76e6ff] mx-1"}>
{store.state.timeList.filter(val => dayjs(val).isSame(dayjs(), 'day')).length}</span>
<span class={'text-[14px]'}>
<span class={'text-[#76e6ff] mx-1'}>
{store.state.timeList.filter((val) => dayjs(val).isSame(dayjs(), 'day')).length}
</span>
</span>
<span class={"text-[14px]"}></span>
<span class={'text-[14px]'}></span>
</div>
<div class={"flex flex-col justify-end"}>
<div class={"flex gap-x-3 "}>
<Tooltip title={"沉浸模式"}>
<div class={'flex flex-col justify-end'}>
<div class={'flex gap-x-3 '}>
<Tooltip title={'沉浸模式'}>
<div
onClick={(e) => {
e.stopPropagation()
store.openFullscreen = true
}}
class={"w-[42px] h-[42px] bg-white/40 backdrop-blur-md flex items-center justify-center rounded-xl"}>
<img src={returnImg} alt="start" class={"w-[18px]"}></img>
class={
'w-[42px] h-[42px] bg-white/40 backdrop-blur-md flex items-center justify-center rounded-xl'
}
>
<img src={returnImg} alt="start" class={'w-[18px]'}></img>
</div>
</Tooltip>
<Tooltip title={"开始"}>
<div class={"w-[42px] h-[42px] bg-white/40 backdrop-blur-md flex items-center justify-center rounded-xl"}
<Tooltip title={'开始'}>
<div
class={
'w-[42px] h-[42px] bg-white/40 backdrop-blur-md flex items-center justify-center rounded-xl'
}
onClick={(e) => {
e.stopPropagation()
if (store.state.isStart) {
@ -53,30 +66,30 @@ export default defineComponent(() => {
} else {
store.beginTomatoTime()
store.openFullscreen = true
}
}}>
{
store.state.isStart ?
<img src={endImg} alt="start" class={"w-[18px]"}></img>
:
<img src={PlayStartImg} alt="start" class={"w-[18px]"}></img>
}
}}
>
{store.state.isStart ? (
<img src={endImg} alt="start" class={'w-[18px]'}></img>
) : (
<img src={PlayStartImg} alt="start" class={'w-[18px]'}></img>
)}
</div>
</Tooltip>
<Tooltip title={"添加目标"}>
<div class={"w-[42px] h-[42px] bg-white/40 backdrop-blur-md flex items-center justify-center rounded-xl"}
<Tooltip title={'添加目标'}>
<div
class={
'w-[42px] h-[42px] bg-white/40 backdrop-blur-md flex items-center justify-center rounded-xl'
}
onClick={() => {
setTimeout(() => {
store.openShowModel = null
}, 300)
}}>
<img src={PlusImg} alt="start" class={"w-[18px]"}></img>
}}
>
<img src={PlusImg} alt="start" class={'w-[18px]'}></img>
</div>
</Tooltip>
</div>
</div>
</div>

View File

@ -130,7 +130,8 @@ const EditContent = defineComponent(() => {
{!form.value.remindTime ? '选择时间' : dayjs(form.value.remindTime).format('HH:mm')}
<img src={DownImg} class={"w-[12px] object-cover "}></img>
<TimePicker
format={'HH:mm'}
showNow={false}
class={'absolute opacity-0 left-0 top-0 w-full h-full'}
onChange={(e) => {
form.value.remindTime = dayjs(e).valueOf()

View File

@ -66,7 +66,8 @@ export default defineComponent(() => {
</div>
<div class={"w-[16px] hidden group-hover:flex z-10 h-[16px] absolute -right-1 -top-1 bg-[#ddd] rounded-full items-center justify-center"}
onClick={() => {
onClick={(e) => {
e.stopPropagation()
const idx = store.state.list.findIndex(val => val.id === item.id)
if (idx !== -1) {
store.state.list.splice(0, 1)

View File

@ -5,8 +5,18 @@ module.exports = {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
darkMode: 'class',
theme: {
extend: {}
extend: {
animation: {
wiggle: 'wiggle .3s ease-in-out infinite'
},
keyframes: {
wiggle: {
'0%, 100%': { transform: 'rotate(-1deg)' },
'50%': { transform: 'rotate(1deg)' }
}
}
}
},
plugins: [require('@tailwindcss/typography')]
}