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

View File

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

View File

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

View File

@ -81,10 +81,11 @@ export default defineComponent(() => {
const isGame = computed(() => layout.state.current === 0) const isGame = computed(() => layout.state.current === 0)
const store = useAdderPageStore() const store = useAdderPageStore()
const addTo = ref(layout.state.currentPage) const addTo = ref(layout.state.currentPage)
provide(AddToToken, addTo) provide(AddToToken, addTo)
onUnmounted(() => { // onUnmounted(() => {
store.type = 1 // store.type = 1
}) // })
return () => ( return () => (
<div <div
class={clsx( class={clsx(
@ -144,9 +145,31 @@ export default defineComponent(() => {
}))} }))}
/> />
</Form.Item> </Form.Item>
<Form.Item> {store.type !== 2 && (
<Input.Search class="w-[200px]" placeholder="搜索组件或网站" /> <Form.Item>
</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> </Form>
<div class="w-full h-0 flex-grow p-6"> <div class="w-full h-0 flex-grow p-6">
<div class="w-full h-full relative"> <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 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 { OhVueIcon, addIcons } from 'oh-vue-icons'
import { MdUpload, MdImage, MdCheck } from 'oh-vue-icons/icons' import { MdUpload, MdImage, MdCheck } from 'oh-vue-icons/icons'
import useLink from '../../utils/useLink' import useLink from '../../utils/useLink'
import { CheckOutlined } from '@ant-design/icons-vue'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
import type { Block } from '../layout.types' import type { Block } from '../layout.types'
import { ColorPicker } from 'vue3-colorpicker'
import 'vue3-colorpicker/style.css' import 'vue3-colorpicker/style.css'
import { globalToast } from '@/main' import { globalToast } from '@/main'
import UploadAndCut from '@/utils/UploadAndCut' import UploadAndCut from '@/utils/UploadAndCut'
import { AddToToken } from './AdderPage' import { AddToToken } from './AdderPage'
import useAdderPageStore, { type EditBlockItemType } from './useAdderPageStore'
import useRouterStore from '@/useRouterStore'
import NativeColorPicker from '@/utils/NativeColorPicker'
addIcons(MdUpload, MdImage, MdCheck) addIcons(MdUpload, MdImage, MdCheck)
const TypeSelector = defineComponent({ const TypeSelector = defineComponent({
@ -47,6 +47,7 @@ const TypeSelector = defineComponent({
'update:icon': (() => true) as (val: string) => boolean 'update:icon': (() => true) as (val: string) => boolean
}, },
setup(props, ctx) { setup(props, ctx) {
const layout = useLayoutStore()
return () => ( return () => (
<div class="flex gap-4"> <div class="flex gap-4">
<div class={'flex justify-center flex-col items-center gap-y-1'}> <div class={'flex justify-center flex-col items-center gap-y-1'}>
@ -60,7 +61,7 @@ const TypeSelector = defineComponent({
ctx.emit('update:value', 0) 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 && ( {props.value === 0 && (
<div <div
class={ class={
@ -113,10 +114,11 @@ const TypeSelector = defineComponent({
ctx.emit('update:icon', e) ctx.emit('update:icon', e)
}} }}
></ImageUploader> */} ></ImageUploader> */}
<UploadAndCut onUpdate:value={(e)=> { <UploadAndCut
onUpdate:value={(e) => {
ctx.emit('update:icon', e) ctx.emit('update:icon', e)
}}
}}></UploadAndCut> ></UploadAndCut>
</div> </div>
<span class={'text-[12px]'}></span> <span class={'text-[12px]'}></span>
</div> </div>
@ -134,9 +136,21 @@ export default defineComponent(() => {
color: 'rgb(255,255,255)', color: 'rgb(255,255,255)',
icon: '', icon: '',
type: 0 // 0 默认1 文字 type: 0 // 0 默认1 文字
}) } as EditBlockItemType)
const isGame = computed(() => layout.state.current === 0) const isGame = computed(() => layout.state.current === 0)
const debounced = ref('') 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( watch(
() => form.link, () => form.link,
(val, _, onCleanup) => { (val, _, onCleanup) => {
@ -155,6 +169,20 @@ export default defineComponent(() => {
if (val.name) form.name = val.name if (val.name) form.name = val.name
if (val.icon) form.icon = val.icon 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) const addTo = inject(AddToToken)
return () => ( return () => (
<div <div
@ -163,19 +191,24 @@ export default defineComponent(() => {
(isGame.value ? 'bg-white/20' : 'bg-white/70') (isGame.value ? 'bg-white/20' : 'bg-white/70')
} }
> >
<Form labelCol={{ span: 4 }} labelAlign="left"> <Form>
<Form.Item label="地址"> <Form.Item label="地址" class={'relative'}>
<InputGroup compact style="display:flex"> <Input
<Input v-model:value={form.link}
v-model:value={form.link} placeholder="搜索想要添加的网址导航"
placeholder="搜索想要添加的网址导航" class={isGame?.value ? '' : ' bg-black/10 '}
class="w-0 flex-grow" />
/> <span
<Button></Button> class={'absolute right-[-70px] top-1/2 -translate-y-1/2 cursor-pointer'}
</InputGroup> onClick={() => {
debounced.value = debounced.value + ' '
}}
>
</span>
</Form.Item> </Form.Item>
<Form.Item label="名称"> <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>
<Form.Item label="图标"> <Form.Item label="图标">
<TypeSelector <TypeSelector
@ -189,70 +222,81 @@ export default defineComponent(() => {
</Form.Item> </Form.Item>
{form.type === 1 && ( {form.type === 1 && (
<> <>
<div class="flex"> <Form.Item label="图标背景">
<Form.Item <NativeColorPicker
label="文字颜色" size={30}
class="w-0 flex-grow" colorList={[
labelCol={{ 'rgb(227, 127, 53)',
span: 8 '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
}} }}
> ></NativeColorPicker>
<ColorPicker </Form.Item>
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>
<Form.Item label="图标文字"> <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>
</> </>
)} )}
<Form.Item label=" " colon={false}> <Form.Item label=" " colon={false}>
<Button <button
type="primary" class={
size="large" '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'
icon={<CheckOutlined />} }
style={{
background: 'linear-gradient(180deg,#ffaa4e 0%,#ff6227 100%)'
}}
onClick={() => { onClick={() => {
if (form.type === 1 && !form.text && !form.name) { if (form.type === 1 && !form.text && !form.name) {
globalToast.error('文字图标请至少填写文字或者名称') globalToast.error('文字图标请至少填写文字或者名称')
return return
} }
const id = uuid() if (!form.link) {
const data: Block = { globalToast.warning('请输入网站链接')
id, return
link: form.link, }
name: '', if (!form.name) {
icon: form.type === 0 ? form.icon || info.icon : '', globalToast.warning('请输入网站名称')
background: form.type === 0 ? '' : form.background, return
color: form.type === 0 ? '' : form.color, }
w: 1, if (store.editBlockItem !== null) {
h: 1, console.log(123)
text: layout.changeBlock(form, store.editBlockItem.id)
form.type === 0 ? '' : form.text || form.name.substring(0, 2).toLocaleUpperCase(), useRouterStore().back()
label: form.name 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.Item>
</Form> </Form>
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,11 +33,7 @@ export default defineComponent(() => {
<div <div
class={clsx( 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', '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' setting.state.showDock === 'auto' ? show.value ? 'bottom-4' : 'bottom-[-90px]' : 'bottom-4'
? show.value
? 'bottom-4'
: '-bottom-[90px]'
: 'bottom-4'
)} )}
ref={container} ref={container}
onMouseleave={() => { onMouseleave={() => {
@ -58,8 +54,8 @@ export default defineComponent(() => {
style={ style={
current.value >= 0 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 || ''} id={block?.id || ''}

View File

@ -7,6 +7,9 @@ import LinkBlock from './LinkBlock'
import DirBlock from './DirBlock' import DirBlock from './DirBlock'
import WidgetBlock from './WidgetBlock' import WidgetBlock from './WidgetBlock'
import useSettingsStore from '@/settings/useSettingsStore' import useSettingsStore from '@/settings/useSettingsStore'
import { useMenuStore } from '../GlobalMenu'
import { block } from '@milkdown/kit/plugin/block'
import clsx from 'clsx'
export default defineComponent({ export default defineComponent({
props: { props: {
@ -22,11 +25,15 @@ export default defineComponent({
setup(props) { setup(props) {
const settings = useSettingsStore() const settings = useSettingsStore()
const layout = useLayoutStore() const layout = useLayoutStore()
const menu = useMenuStore()
let it: any = 0 let it: any = 0
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={clsx(
'w-full h-full p-[var(--block-padding)] relative rounded-lg ',
menu.isEditPage && 'animate-wiggle'
)}
key={props.block.id} key={props.block.id}
id={props.block.id} id={props.block.id}
style={{ 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 <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={{ style={{
borderRadius: `calc(var(--block-radius) * var(--block-size))` borderRadius: `calc(var(--block-radius) * var(--block-size))`,
transition: 'transform 0.2s'
}} }}
onContextmenu={(e) => { onContextmenu={(e) => {
e.stopPropagation() e.stopPropagation()
@ -140,7 +175,7 @@ export default defineComponent({
</div> </div>
{settings.state.showBlockLabel && ( {settings.state.showBlockLabel && (
<div <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)" style="text-shadow: 0 0 4px rgba(0,0,0,.6)"
> >
{layout.getLabel(props.block)} {layout.getLabel(props.block)}

View File

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

View File

@ -1,23 +1,63 @@
import SettingItem from '@/settings/SettingItem' import SettingItem from '@/settings/SettingItem'
import useSettingsStore from '@/settings/useSettingsStore' import { Button } from 'ant-design-vue'
import { Button, Switch } from 'ant-design-vue'
import clsx from 'clsx' import clsx from 'clsx'
import { defineComponent } from 'vue' import { computed, defineComponent, ref } from 'vue'
import useLayoutStore from '../useLayoutStore'
export default defineComponent({ export default defineComponent({
setup() { setup() {
const settings = useSettingsStore() const open = ref(false)
const layout = useLayoutStore()
const isGame = computed(() => {
return layout.state.current === 0
})
return () => ( return () => (
<div class="p-4 flex flex-col "> <div class="p-4 flex flex-col ">
<SettingItem <SettingItem
v-slots={{ v-slots={{
label: () => <div></div> label: () => <div ></div>
}} }}
> >
<Button type="primary"></Button> <Button type="primary" onClick={() => {
open.value = true
}}></Button>
</SettingItem> </SettingItem>
<p class={'text-[#666] text-[12px]'}></p> <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 useLayoutStore from '../useLayoutStore'
import { OhVueIcon, addIcons } from 'oh-vue-icons' import { OhVueIcon, addIcons } from 'oh-vue-icons'
import { MdAdd } from 'oh-vue-icons/icons' import { MdAdd } from 'oh-vue-icons/icons'
@ -15,11 +15,21 @@ export default defineComponent(() => {
const layout = useLayoutStore() const layout = useLayoutStore()
const settings = useSettingsStore() const settings = useSettingsStore()
const router = useRouterStore() const router = useRouterStore()
const scrollRef = ref<any>()
const container = useSortable( const container = useSortable(
computed(() => layout.currentPage.list), computed(() => layout.currentPage.list),
ref('page') ref('page')
) )
watch(
() => layout.currentPage.list.length,
(cur, old) => {
if (cur > old) {
if (scrollRef.value) {
scrollRef.value.scrollTop = scrollRef.value.scrollHeight
}
}
}
)
listenParent('openSetting', (d) => { listenParent('openSetting', (d) => {
if (d === 'profile') { if (d === 'profile') {
router.go('settings-user') router.go('settings-user')
@ -30,6 +40,7 @@ export default defineComponent(() => {
}) })
return () => ( return () => (
<div <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)]" 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) => { onScroll={(e) => {
const h = (e.target as any).scrollTop 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 asyncLoader from '@/utils/asyncLoader'
import ThemeProvider from '@/utils/ThemeProvider' import ThemeProvider from '@/utils/ThemeProvider'
import { globalToast } from '@/main' import { globalToast } from '@/main'
import useUserStore from '@/user/useUserStore'
import useRouterStore from '@/useRouterStore'
const SearchAdder = asyncLoader(() => import('./SearchAdder')) const SearchAdder = asyncLoader(() => import('./SearchAdder'))
const SearchItem = defineComponent({ const SearchItem = defineComponent({
props: { props: {
@ -157,7 +159,12 @@ export default defineComponent(() => {
block block
icon={<PlusOutlined />} icon={<PlusOutlined />}
onClick={() => { 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 props.isMini
? {} ? {}
: { : {
top: layout.isCompact ? '40px' : layout.state.simple ? '230px' : '172px', top: layout.isCompact ? '40px' : layout.state.simple ? '230px' : '172px',
width: settings.state.searchWidth + 'rem' width: settings.state.searchWidth + 'rem'
} }
} }
> >
<div <div
class={clsx( class={clsx(
'w-full h-11 shadow-content overflow-hidden px-1 transition-all flex justify-between items-center gap-4 ', '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' props.isMini ? '' : 'max-w-[90vw] w-full'
)} )}
style={{ style={{
opacity: search.focus ? 1 : settings.state.searchOpacity,
borderRadius: settings.state.searchRadius + 'px' borderRadius: settings.state.searchRadius + 'px'
}} }}
> >
@ -58,7 +59,7 @@ export default defineComponent(
onContextmenu={(e) => e.stopPropagation()} onContextmenu={(e) => e.stopPropagation()}
onKeydown={(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" 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> </div>
<Transition name="searchContent">{search.showSearchConfig && <SearchConfig />}</Transition> <Transition name="searchContent">{search.showSearchConfig && <SearchConfig />}</Transition>

View File

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

View File

@ -8,6 +8,7 @@ import jump from '@/utils/jump'
import useSettingsStore from '@/settings/useSettingsStore' import useSettingsStore from '@/settings/useSettingsStore'
import useUserStore from '@/user/useUserStore' import useUserStore from '@/user/useUserStore'
import request from '@/utils/request' import request from '@/utils/request'
import type { EditBlockItemType } from './adder/useAdderPageStore'
const defaultLayout: Layout = { const defaultLayout: Layout = {
content: [ content: [
@ -26,7 +27,7 @@ const defaultLayout: Layout = {
export default defineStore('layout', () => { export default defineStore('layout', () => {
const settings = useSettingsStore() const settings = useSettingsStore()
const user = useUserStore() const user = useUserStore()
const state = reactive(defaultLayout) const state = reactive({ ...defaultLayout })
const ready = ref(false) const ready = ref(false)
db.getItem<Layout>('layout').then((res) => { db.getItem<Layout>('layout').then((res) => {
@ -78,6 +79,21 @@ export default defineStore('layout', () => {
pageList.push(block) pageList.push(block)
globalToast.success('添加成功') 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 openDir = ref('')
// 文件夹只有一个时,将当前界面的文件夹替换为图标 // 文件夹只有一个时,将当前界面的文件夹替换为图标
const checkDir = (id: string) => { const checkDir = (id: string) => {
@ -112,7 +128,17 @@ export default defineStore('layout', () => {
} }
return block.label || '' 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) => { const changeBackground = (url: string) => {
state.content[state.current].background = url state.content[state.current].background = url
} }
@ -161,6 +187,8 @@ export default defineStore('layout', () => {
openDir, openDir,
checkDir, checkDir,
getLabel, getLabel,
changeBackground changeBackground,
resetAll,
changeBlock
} }
}) })

View File

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

View File

@ -57,7 +57,7 @@ export default defineComponent(() => {
return layout.state.current === 0 return layout.state.current === 0
}) })
return () => ( 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 && ( {show.value && (
<div <div
@ -72,13 +72,20 @@ export default defineComponent(() => {
{show.value && ( {show.value && (
<div <div
class={clsx( 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' isGame.value ? 'bg-[#2c2e3e] text-white' : 'text-[#000] bg-white'
)} )}
style={
isGame.value &&
{
backgroundImage: `url('/tab/bg/gameModel.png')`,
backgroundSize: '100% 100%',
}
}
> >
<div <div
class={clsx( 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]' isGame.value ? 'bg-[#fff]/10 text-white ' : 'bg-[#ebebeb]'
)} )}
> >
@ -134,7 +141,10 @@ export default defineComponent(() => {
<SettingsTab label="问题反馈" path="settings-fallback" /> <SettingsTab label="问题反馈" path="settings-fallback" />
</div> </div>
</div> </div>
<Content /> <div class={"flex-1 w-0 h-full relative"}>
<Content />
</div>
</div> </div>
)} )}
</Transition> </Transition>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,141 +3,149 @@ import { defineComponent, ref, watch } from 'vue'
import { VueCropper } from 'vue-cropper' import { VueCropper } from 'vue-cropper'
import 'vue-cropper/dist/index.css' import 'vue-cropper/dist/index.css'
import upload from './upload' import upload from './upload'
import { v4 as uuid } from "uuid" import { v4 as uuid } from 'uuid'
import { MdCroprotateRound, BiPlusLg } from "oh-vue-icons/icons"; import { MdCroprotateRound, BiPlusLg } from 'oh-vue-icons/icons'
import { OhVueIcon, addIcons, } from 'oh-vue-icons' import { OhVueIcon, addIcons } from 'oh-vue-icons'
import NativeColorPicker from './NativeColorPicker' import NativeColorPicker from './NativeColorPicker'
import useUserStore from '@/user/useUserStore'
import { globalToast } from '@/main'
import useRouterStore from '@/useRouterStore'
addIcons(MdCroprotateRound, BiPlusLg) addIcons(MdCroprotateRound, BiPlusLg)
export default defineComponent({ 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: { const file = target.files?.[0]
'update:value': (() => true) as (val: string) => boolean target.value = ''
},
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] if (!file) return
target.value = '' // 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 // 使用 FileReader 对象读取 Blob 的前几个字节
// upload(file).then(res => { const reader = new FileReader()
const reader = new FileReader() reader.onloadend = function () {
reader.readAsDataURL(file) // 打印 File 对象以验证
reader.onload = (e: any) => {
originFile.value = e?.target.result // 显示原始图片
showCutModel.value = true
}
// emit('update:icon', res || '')
// })
}
const handleFinish = (blob: any) => {
// 根据前几个字节推测文件类型(这里只考虑常见的图片类型)
// 使用 FileReader 对象读取 Blob 的前几个字节 const fileData = new File([blob], `${uuid()}.png`, {
const reader = new FileReader() type: 'image/jpg',
reader.onloadend = function () { lastModified: Date.now()
// 打印 File 对象以验证 })
upload(fileData, 'customIcon').then((res) => {
showCutModel.value = false
const fileData = new File([blob], `${uuid()}.png`, { ctx.emit('update:value', res || '')
type: 'image/jpg', })
lastModified: Date.now() }
}) reader.readAsArrayBuffer(blob)
upload(fileData, 'customIcon').then((res) => { }
showCutModel.value = false watch(
fillColor,
ctx.emit('update:value', res || '') (e: string) => {
}) const elements = document.querySelectorAll('.cropper-crop-box')
} elements.forEach((element: any) => {
reader.readAsArrayBuffer(blob) element.style.backgroundColor = e // 修改颜色为蓝色
} })
watch( },
fillColor, {
(e: string) => { immediate: true
const elements = document.querySelectorAll('.cropper-crop-box') }
elements.forEach((element: any) => { )
element.style.backgroundColor = e // 修改颜色为蓝色 return () => (
}) <>
}, <Modal
{ open={showCutModel.value}
immediate: true onCancel={() => (showCutModel.value = false)}
} onOk={() => {
) cropper.value.getCropBlob((blob: any) => {
return () => ( handleFinish(blob)
<> })
<Modal }}
open={showCutModel.value} >
onCancel={() => showCutModel.value = false} <div class="w-full flex flex-col items-center gap-y-2">
onOk={() => { <div
cropper.value.getCropBlob((blob: any) => { class="w-[250px] h-[250px]"
handleFinish(blob) style={{
backgroundColor: fillColor.value || 'transparent'
}) }}
}} >
> <VueCropper
<div class="w-full flex flex-col items-center gap-y-2"> ref={cropper}
<div class="w-[250px] h-[250px]" style={{ autoCropWidth="250px"
backgroundColor: fillColor.value || 'transparent', autoCropHeight="250px"
}}> mode="contain"
<VueCropper outputType="png"
ref={cropper} img={originFile.value}
autoCropWidth="250px" autoCrop={true}
autoCropHeight="250px" fillColor={fillColor.value}
mode="contain" />
outputType="png" </div>
img={originFile.value} <div class="flex justify-between w-[250px]">
autoCrop={true} {/* <ArrowRotateClockwise24Regular
fillColor={fillColor.value}
/>
</div>
<div class="flex justify-between w-[250px]">
{/* <ArrowRotateClockwise24Regular
class="text-[10px] w-8 h-8 cursor-pointer" class="text-[10px] w-8 h-8 cursor-pointer"
onClick={() => ($refs.cropper as any).rotateRight()} onClick={() => ($refs.cropper as any).rotateRight()}
/> */} /> */}
<span class="flex items-center cursor-pointer" <span
onClick={() => { class="flex items-center cursor-pointer"
cropper.value.rotateRight() onClick={() => {
}}> cropper.value.rotateRight()
<OhVueIcon name="md-croprotate-round" scale={1} fill="#707070" ></OhVueIcon> }}
>
</span> <OhVueIcon name="md-croprotate-round" scale={1} fill="#707070"></OhVueIcon>
<span class="flex items-center gap-x-1"> </span>
<svg <span class="flex items-center gap-x-1">
onClick={() => cropper.value?.changeScale(1)} <svg
class="icon cursor-pointer" onClick={() => cropper.value?.changeScale(1)}
viewBox="0 0 1024 1024" class="icon cursor-pointer"
width="2rem" viewBox="0 0 1024 1024"
height="2rem" 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" <path
fill="#707070" 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 </svg>
onClick={() => cropper.value?.changeScale(-1)} <svg
class="icon cursor-pointer" onClick={() => cropper.value?.changeScale(-1)}
viewBox="0 0 1024 1024" class="icon cursor-pointer"
width="2rem" viewBox="0 0 1024 1024"
height="2rem" 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" <path
fill="#707070" 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> </svg>
</div> </span>
<NativeColorPicker value={fillColor.value} onUpdate:value={e => fillColor.value = e} transparent={true} /> </div>
{/* {props.colorList && ( <NativeColorPicker
value={fillColor.value}
onUpdate:value={(e) => (fillColor.value = e)}
transparent={true}
/>
{/* {props.colorList && (
<div class="w-[250px] flex justify-center"> <div class="w-[250px] flex justify-center">
<NativeColorPicker <NativeColorPicker
colorList={colorList} colorList={colorList}
@ -147,23 +155,32 @@ export default defineComponent({
/> />
</div> </div>
)} */} )} */}
</div>
</div> </Modal>
</Modal > <div
<div class="w-full h-full bg-white flex items-center justify-center" class="w-full h-full bg-white flex items-center justify-center"
onClick={() => { onClick={() => {
inputRef.value?.click?.() if (useUserStore().isLogin) {
}} inputRef.value?.click?.()
> } else {
<OhVueIcon name={BiPlusLg.name} fill='#666666' scale={2}></OhVueIcon> globalToast.warning('请先登录')
</div> useRouterStore().go('global-login')
<input // useAdderPageStore().type = 2
ref={inputRef} }
style={{ }}
display: 'none' >
}} accept=".jpg,.jpeg,.png,.svg" type="file" onChange={handleFile} /> <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>({ const info = reactive<LinkInfo>({
background: '', background: '',
desc: '', desc: '',
icon: '', icon: 'https://oss.goosetab.com/000/user_upload/1/resource/120be9d6-1c68-41ba-b539-570c39ce2421.png',
link: '', link: '',
name: '' 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 class={'text-[48px] font-extrabold pt-2'}>{store.state.select.format('D')}</span>
<span>{store.state.select.format('ddd')}</span> <span>{store.state.select.format('ddd')}</span>
<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')} {store.state.select.diff(store.state.select.set('date', 1).set('month', 0), 'day')}
</span> </span>
<div class={'w-[138px] h-[1px] flex-shrink-0 my-7 bg-black/10 relative'}></div> <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>
<div class={'flex flex-col text-[#666] text-[14px]'}> <div class={'flex-1 w-0 text-[#666] text-[14px]'}>
{lunar.value.getDayYi().map((item: any) => { {lunar.value.getDayYi().join(',')}
return <div class={'flex items-center'}>{item}</div>
})}
</div> </div>
</div> </div>
<div class={'flex w-full gap-x-5'}> <div class={'flex w-full gap-x-5'}>
@ -70,10 +70,8 @@ export default defineComponent(() => {
> >
</div> </div>
<div class={'flex flex-col text-[#666] text-[14px]'}> <div class={'flex-1 w-0 text-[#666] text-[14px]'}>
{lunar.value.getDayJi().map((item: any) => { {lunar.value.getDayJi().join(',')}
return <div class={'flex items-center'}>{item}</div>
})}
</div> </div>
</div> </div>
</div> </div>
@ -158,8 +156,10 @@ export default defineComponent(() => {
dayjs(), dayjs(),
'day' 'day'
), ),
'border-transparent border-solid': !el.day.isSame(dayjs(), 'day') && !el.day.isSame(store.state.select, 'day'), 'border-transparent border-solid':
'border-[#76d7f2] border-solid border-[1px]': !el.day.isSame(dayjs(), 'day') && el.day.isSame(store.state.select, 'day'), !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 { addIcons, OhVueIcon } from 'oh-vue-icons'
import { computed, defineComponent, onMounted, ref, watch, type CSSProperties } from 'vue' import { computed, defineComponent, onMounted, ref, watch, type CSSProperties } from 'vue'
import { FaChevronLeft } from 'oh-vue-icons/icons' 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 type { CarouselRef } from 'ant-design-vue/es/carousel'
import { randomNum } from '@/utils/tool' import { randomNum } from '@/utils/tool'
import jump from '@/utils/jump'
addIcons(FaChevronLeft) addIcons(FaChevronLeft)
interface Owner { interface Owner {
face: string face: string
@ -56,11 +57,19 @@ export default defineComponent(() => {
}) })
return () => ( return () => (
<div class="w-full h-full p-2 bg-[#17212d] relative "> <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 <div
class={'w-full h-full rounded-xl relative group'} class={'w-full h-full rounded-xl relative group'}
onClick={() => {
jump('https://www.bilibili.com/video/av' + current.value?.aid)
}}
style={{ style={{
backgroundImage: `url('${current.value?.pic}')`, backgroundImage: `url('${current.value?.pic}')`,
backgroundSize: 'cover', backgroundSize: 'cover',
@ -68,7 +77,6 @@ export default defineComponent(() => {
backgroundRepeat: 'no-repeat' backgroundRepeat: 'no-repeat'
}} }}
> >
<div <div
class={ class={
'absolute bottom-0 left-1/2 -translate-x-1/2 pb-2 w-[300px] flex flex-col text-white ' '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> <img class={'w-[48px] h-[48px]'} src={'/tab/icons/hot_information_icon.png'}></img>
<div class={'flex-1 flex justify-center'}> <div class={'flex-1 flex justify-center'}>
<div class="flex-col flex"> <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 class={'flex items-center text-[#fffc] text-[12px] '}>
<div> <div>

View File

@ -3,6 +3,7 @@ import { useWeApplyStore } from './useWeApplyStore'
import { addIcons, OhVueIcon } from 'oh-vue-icons' import { addIcons, OhVueIcon } from 'oh-vue-icons'
import { HiChevronDown } from 'oh-vue-icons/icons' import { HiChevronDown } from 'oh-vue-icons/icons'
import clsx from 'clsx' import clsx from 'clsx'
import jump from '@/utils/jump'
addIcons(HiChevronDown) addIcons(HiChevronDown)
export default defineComponent(() => { export default defineComponent(() => {
const store = useWeApplyStore() const store = useWeApplyStore()
@ -13,7 +14,7 @@ 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 -8 return scrollTop + clientHeight >= scrollHeight - 8
} }
} }
const handleGameScroll = () => { const handleGameScroll = () => {
@ -74,7 +75,12 @@ export default defineComponent(() => {
{store.state.list {store.state.list
.filter((val) => val.type === 'game') .filter((val) => val.type === 'game')
.map((item) => ( .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> <img src={item.icon} alt="game icon" class={'w-[37px] h-[37px] rounded'}></img>
<div class={'flex-1 flex flex-col overflow-hidden'}> <div class={'flex-1 flex flex-col overflow-hidden'}>
<span class={'text-white text-[14px]'}>{item.name}</span> <span class={'text-white text-[14px]'}>{item.name}</span>
@ -107,7 +113,6 @@ export default defineComponent(() => {
if (workRef.value) { if (workRef.value) {
workRef.value.scrollTop += 20 workRef.value.scrollTop += 20
handleWorkScroll() handleWorkScroll()
} }
}} }}
> >
@ -125,7 +130,12 @@ export default defineComponent(() => {
{store.state.list {store.state.list
.filter((val) => val.type === 'work') .filter((val) => val.type === 'work')
.map((item) => ( .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> <img src={item.icon} alt="game icon" class={'w-[37px] h-[37px] rounded'}></img>
<div class={'flex-1 flex flex-col overflow-hidden'}> <div class={'flex-1 flex flex-col overflow-hidden'}>
<span class={'text-white text-[14px]'}>{item.name}</span> <span class={'text-white text-[14px]'}>{item.name}</span>

View File

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

View File

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

View File

@ -66,7 +66,8 @@ export default defineComponent(() => {
</div> </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"} <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) const idx = store.state.list.findIndex(val => val.id === item.id)
if (idx !== -1) { if (idx !== -1) {
store.state.list.splice(0, 1) store.state.list.splice(0, 1)

View File

@ -5,8 +5,18 @@ module.exports = {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
darkMode: 'class', darkMode: 'class',
theme: { 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')] plugins: [require('@tailwindcss/typography')]
} }